/*
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.gudy.azureus2.ui.swt.views.table.painted;

import java.util.*;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.internat.MessageText.MessageTextListener;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.ui.tables.*;
import org.gudy.azureus2.plugins.ui.tables.TableColumn;
import org.gudy.azureus2.ui.swt.MenuBuildUtils;
import org.gudy.azureus2.ui.swt.SimpleTextEntryWindow;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.debug.ObfusticateImage;
import org.gudy.azureus2.ui.swt.debug.UIDebugGenerator;
import org.gudy.azureus2.ui.swt.mainwindow.Colors;
import org.gudy.azureus2.ui.swt.mainwindow.HSLColor;
import org.gudy.azureus2.ui.swt.plugins.UISWTView;
import org.gudy.azureus2.ui.swt.plugins.UISWTViewEvent;
import org.gudy.azureus2.ui.swt.pluginsimpl.UISWTViewCore;
import org.gudy.azureus2.ui.swt.shells.GCStringPrinter;
import org.gudy.azureus2.ui.swt.views.table.*;
import org.gudy.azureus2.ui.swt.views.table.impl.*;

import com.aelitis.azureus.ui.common.table.*;
import com.aelitis.azureus.ui.common.table.TableViewFilterCheck;
import com.aelitis.azureus.ui.common.table.impl.TableColumnManager;
import com.aelitis.azureus.ui.common.table.impl.TableRowCoreSorter;
import com.aelitis.azureus.ui.common.table.impl.TableViewImpl;
import com.aelitis.azureus.ui.selectedcontent.ISelectedContent;
import com.aelitis.azureus.ui.selectedcontent.SelectedContentListener;
import com.aelitis.azureus.ui.selectedcontent.SelectedContentManager;
import com.aelitis.azureus.ui.swt.imageloader.ImageLoader;
import com.aelitis.azureus.ui.swt.utils.FontUtils;

/**
 * A TableView implemented by painting on a canvas
 * 
 * TODO: 
 * Keyboard Selection
 * Cursor
 * Column move and resize past bounds
 */
public class TableViewPainted
	extends TableViewImpl<Object>
	implements ParameterListener, TableViewSWT<Object>, ObfusticateImage,
	MessageTextListener
{
	private static final boolean hasGetScrollBarMode = SWT.getVersion() >= 3821;

	private static final boolean DEBUG_ROWCHANGE = false;

	private static final boolean DEBUG_WITH_SHELL = false;

	private static final int DEFAULT_HEADER_HEIGHT = 27;

	private Composite cTable;

	private int loopFactor;

	/** How often graphic cells get updated
	 */
	protected int graphicsUpdate = configMan.getIntParameter("Graphics Update");

	protected int reOrderDelay = configMan.getIntParameter("ReOrder Delay");
	
	protected boolean extendedErase = configMan.getBooleanParameter("Table.extendedErase");

	private int defaultRowHeight = 17;

	/**
	 * Rows visible to user.  We assume this list is always up to date
	 */
	LinkedHashSet<TableRowPainted> visibleRows = new LinkedHashSet<TableRowPainted>();
	
	Object visibleRows_sync = new Object();

	Object lock = new Object();

	/**
	 * Up to date table client area.  So far, the best places to refresh
	 * this variable are in the PaintItem event and the scrollbar's events.
	 * Typically table.getClientArea() is time consuming 
	 */
	protected Rectangle clientArea;

	private boolean isVisible;

	private Shell shell;

	private Color colorLine;

	private int headerHeight;

	private Canvas cHeaderArea;

	private Image canvasImage;

	private final String sDefaultSortOn;

	private TableViewSWT_Common tvSWTCommon;

	private TableViewSWT_TabsCommon tvTabsCommon;

	private TableViewSWTPanelCreator mainPanelCreator;

	private boolean isMultiSelect;

	private int columnsWidth;

	private Menu menu;

	protected boolean isHeaderDragging;

	private TableRowPainted focusedRow;

	private boolean 	enableTabViews;
	private String[]	tabViewRestriction;
	private boolean 	tabViewsExpandedByDefault = true;

	protected boolean isDragging;

	private Composite mainComposite;

	private Object heightChangeSync = new Object();
	private int totalHeight = 0;

	private boolean redrawTableScheduled;

	private Font fontHeaderSmall;
	private Font fontHeader;

	private ScrollBar hBar;

	private ScrollBar vBar;
	
	private Canvas sCanvasImage;

	class RefreshTableRunnable extends AERunnable {
		private boolean forceSort;
		public void runSupport() {
			__refreshTable(isForceSort());
		}
		public boolean isForceSort() {
			return forceSort;
		}
		public void setForceSort(boolean forceSort) {
			this.forceSort = forceSort;
		}
	}
	
	private RefreshTableRunnable refreshTableRunnable = new RefreshTableRunnable();

	protected boolean isFocused;

	/**
	 * Main Initializer
	 * @param _sTableID Which table to handle (see 
	 *                   {@link org.gudy.azureus2.plugins.ui.tables.TableManager}).
	 *                   Config settings are stored with the prefix of  
	 *                   "Table.<i>TableID</i>"
	 * @param _sPropertiesPrefix Prefix for retrieving text from the properties
	 *                            file (MessageText).  Typically 
	 *                            <i>TableID</i> + "View"
	 * @param _basicItems Column Definitions
	 * @param _sDefaultSortOn Column name to sort on if user hasn't chosen one yet
	 * @param _iTableStyle SWT style constants used when creating the table
	 */
	public TableViewPainted(Class<?> pluginDataSourceType, String _sTableID,
			String _sPropertiesPrefix, TableColumnCore[] _basicItems,
			String _sDefaultSortOn, int _iTableStyle) {
		super(pluginDataSourceType, _sTableID, _sPropertiesPrefix, _basicItems);
		setRowsSync(lock);
		//		boolean wantTree = (_iTableStyle & SWT.CASCADE) != 0;
		//		_iTableStyle &= ~SWT.CASCADE;
		//		if (wantTree) {
		//			useTree = COConfigurationManager.getBooleanParameter("Table.useTree")
		//					&& !Utils.isCarbon;
		//		}
		//		basicItems = _basicItems;
		//		sDefaultSortOn = _sDefaultSortOn;
		//		iTableStyle = _iTableStyle | SWT.V_SCROLL | SWT.DOUBLE_BUFFERED;
		this.sDefaultSortOn = _sDefaultSortOn;
		this.isMultiSelect = (_iTableStyle & SWT.MULTI) != 0;
		
		// Deselect rows if user clicks on a blank spot (a spot with no row)
		tvSWTCommon = new TableViewSWT_Common(this) {
			public void widgetSelected(SelectionEvent event) {
				//updateSelectedRows(table.getSelection(), true);
			}

			@Override
			public void mouseUp(TableRowCore clickedRow, TableCellCore cell, int button,
					int stateMask) {
				super.mouseUp(clickedRow, cell, button, stateMask);
				
				if (clickedRow == null) {
					return;
				}
				if (button == 1) {
  				int keyboardModifier = (stateMask & SWT.MODIFIER_MASK);
  				if ((keyboardModifier & SWT.SHIFT) != 0) {
  					// select from focus to row
  					selectRowsTo(clickedRow);
  					return;
  				} else if (keyboardModifier == 0) {
  					setSelectedRows(new TableRowCore[] {
  						clickedRow
  					});
  					return;
  				}
				}
			}

			@Override
			public void mouseDown(TableRowSWT clickedRow, TableCellCore cell, int button,
					int stateMask) {
				if (clickedRow == null) {
					return;
				}
				int keyboardModifier = (stateMask & SWT.MODIFIER_MASK);
				if (button == 1) {
  				if ((keyboardModifier & SWT.MOD1) != 0) {
  					// control (win), alt (mac)
  					setRowSelected(clickedRow, !clickedRow.isSelected(), true);
  					return;
  				} 
				} else if (button == 3) {
					if (!isSelected(clickedRow) && keyboardModifier == 0) {
						setSelectedRows(new TableRowCore[] {
							clickedRow
						});
					}
				}
				if (getSelectedRowsSize() == 0) {
					setSelectedRows(new TableRowCore[] {
						clickedRow
					});
				}
			}

			@Override
			public void keyPressed(KeyEvent event) {
				if (getComposite() != event.widget) {
					super.keyPressed(event);	
					return;
				}
				boolean updateTable = false;
				if (event.keyCode == SWT.ARROW_UP) {
					TableRowCore rowToSelect = getPreviousRow(focusedRow);
					if ((event.stateMask & SWT.SHIFT) != 0) {
						if (rowToSelect != null) {
							TableRowCore[] selectedRows = getSelectedRows();
							Arrays.sort(selectedRows, new TableRowCoreSorter());
							boolean select = selectedRows.length == 0
									|| selectedRows[0] == focusedRow;
//							System.out.println("i=" + selectedRows[0].getIndex() + ";"
//									+ select + ";" + focusedRow.getIndex());
							if (select) {
								rowToSelect.setSelected(select);
							} else {
								focusedRow.setSelected(false);
								setFocusedRow(rowToSelect);
							}
							updateTable = true;
						}
					} else if ((event.stateMask & SWT.CONTROL) != 0) {
						// show one more topRow
						TableRowPainted firstRow = visibleRows.iterator().next();
						if (firstRow != null) {
							int hChange = 0;
							if (isRowPartiallyVisible(firstRow)) {
								hChange =  firstRow.getDrawOffset().y  - clientArea.y;
							} else {
  							TableRowCore prevRow = getPreviousRow(firstRow);
  							if (prevRow != firstRow && prevRow != null) {
  								hChange = -prevRow.getHeight();
  							}
							}
							vBar.setSelection(vBar.getSelection() + hChange);
							swt_vBarChanged();
						}
					} else {
						setSelectedRows(new TableRowCore[] {
							rowToSelect
						});
						updateTable = true;
					}
				} else if (event.keyCode == SWT.PAGE_UP) {
					TableRowCore row = focusedRow;
					TableRowPainted lastRow = getLastVisibleRow();
					int y = lastRow == null ? 0 : (clientArea.y + clientArea.height) - lastRow.getDrawOffset().y;
					while (row != null && y < clientArea.height) {
						y += row.getHeight();
						row = getPreviousRow(row);
					}
					if (row == null) {
						row = getRow(0);
					}
					if ((event.stateMask & SWT.SHIFT) != 0) {
						if ( row != null ){
							selectRowsTo(row);
						}
					} else if (event.stateMask == 0) {
  					setSelectedRows(new TableRowCore[] {
  						row
  					});
					}
					updateTable = true;
				} else if (event.keyCode == SWT.HOME) {
					if ((event.stateMask & SWT.SHIFT) != 0) {
						selectRowsTo(getRow(0));
					} else if (event.stateMask == 0) {
  					setSelectedRows(new TableRowCore[] {
  						getRow(0)
  					});
					}
					updateTable = true;
				} else if (event.keyCode == SWT.ARROW_DOWN) {
					if ((event.stateMask & SWT.CONTROL) != 0) {
						// show one less topRow 
						TableRowPainted firstRow = visibleRows.iterator().next();
						if (firstRow != null) {
							int hChange = 0;
							if (isRowPartiallyVisible(firstRow)) {
								hChange = firstRow.getHeight() + (firstRow.getDrawOffset().y - clientArea.y);
							} else {
								hChange = firstRow.getHeight();
							}
							vBar.setSelection(vBar.getSelection() + hChange);
							swt_vBarChanged();
						}
					} else {
						TableRowCore rowToSelect = getNextRow(focusedRow);
  					if (rowToSelect != null) {
  						if ((event.stateMask & SWT.SHIFT) != 0) {
  							TableRowCore[] selectedRows = getSelectedRows();
  							Arrays.sort(selectedRows, new TableRowCoreSorter());
  							boolean select = selectedRows.length == 0
  									|| selectedRows[selectedRows.length - 1] == focusedRow;
  							if (select) {
  								rowToSelect.setSelected(select);
  							} else {
  								focusedRow.setSelected(false);
  								setFocusedRow(rowToSelect);
  							}
  						} else {
  							setSelectedRows(new TableRowCore[] {
  								rowToSelect
  							});
  						}
							updateTable = true;
  					}
					}
				} else if (event.keyCode == SWT.PAGE_DOWN) {
					TableRowCore row = focusedRow;
					TableRowPainted firstRow = visibleRows.size() == 0 ? null : visibleRows.iterator().next();

					int y = firstRow == null ? 0 : firstRow.getHeight() - (clientArea.y - firstRow.getDrawOffset().y);
					while (row != null && y < clientArea.height) {
						y += row.getHeight();
						TableRowCore nextRow = getNextRow(row);
						if (nextRow == null) {
							break;
						}
						row = nextRow;
					}
					if ((event.stateMask & SWT.SHIFT) != 0) {
						selectRowsTo(row);
					} else if (event.stateMask == 0) {
  					setSelectedRows(new TableRowCore[] {
  						row
  					});
					}
					updateTable = true;
				} else if (event.keyCode == SWT.END) {
					TableRowCore lastRow = getRow(getRowCount() - 1);
					if ((event.stateMask & SWT.SHIFT) != 0) {
						selectRowsTo(lastRow);
					} else if (event.stateMask == 0) {
  					setSelectedRows(new TableRowCore[] {
  						lastRow
  					});
					}
					updateTable = true;
				} else if (event.keyCode == SWT.ARROW_RIGHT && event.stateMask == 0) {
					if (event.stateMask == 0 && focusedRow != null && !focusedRow.isExpanded() && canHaveSubItems()) {
						focusedRow.setExpanded(true);
					} else {
						if (hBar.isEnabled()) {
							hBar.setSelection(hBar.getSelection() + 50);
							cTable.redraw();
							updateTable = true;
						}
					}
				} else if (event.keyCode == SWT.ARROW_LEFT && event.stateMask == 0) {
					if (event.stateMask == 0 && focusedRow != null && focusedRow.isExpanded() && canHaveSubItems()) {
						focusedRow.setExpanded(false);
					} else {
						if (hBar.isEnabled()) {
							hBar.setSelection(hBar.getSelection() - 50);
							cTable.redraw();
							updateTable = true;
						}
					}
				}
				
				if (updateTable) {
					cTable.update();
				}
				super.keyPressed(event);
			}

			@Override
			public void keyReleased(KeyEvent e) {
				swt_calculateClientArea();
				visibleRowsChanged();

				super.keyReleased(e);
			}
		};
	}

	protected boolean isRowPartiallyVisible(TableRowPainted row) {
		if (row == null) {
			return false;
		}
		Point drawOffset = row.getDrawOffset();
		int height = row.getHeight();
		return (drawOffset.y < clientArea.y && drawOffset.y + height > clientArea.y)
				|| (drawOffset.y < clientArea.y + clientArea.height && drawOffset.y
						+ height > clientArea.y + clientArea.height);
	}

	protected void selectRowsTo(TableRowCore clickedRow) {
		if (!isMultiSelect) {
			setSelectedRows(new TableRowCore[] {
				clickedRow
			});
			return;
		}
		TableRowCore[] selectedRows = getSelectedRows();
		TableRowCore firstRow = selectedRows.length > 0 ? selectedRows[0]
				: getRow(0);
		TableRowCore parentFirstRow = firstRow;
		while (parentFirstRow.getParentRowCore() != null) {
			parentFirstRow = parentFirstRow.getParentRowCore();
		}
		TableRowCore parentClickedRow = clickedRow;
		while (parentClickedRow.getParentRowCore() != null) {
			parentClickedRow = parentClickedRow.getParentRowCore();
		}
		int startPos;
		int endPos;
		if (parentFirstRow == parentClickedRow) {
			startPos = parentFirstRow == firstRow ? -1 : firstRow.getIndex();
			endPos = parentClickedRow == clickedRow ? -1 : clickedRow.getIndex();
		} else {
			startPos = indexOf(parentFirstRow);
			endPos = indexOf(parentClickedRow);
			if (endPos == -1 || startPos == -1) {
				return;
			}
		}
		ArrayList<TableRowCore> rowsToSelect = new ArrayList<TableRowCore>(Arrays.asList(selectedRows));
		TableRowCore curRow = firstRow;
		do {
			if (!rowsToSelect.contains(curRow)) {
				rowsToSelect.add(curRow);
			}
			TableRowCore newRow = (startPos < endPos) ? getNextRow(curRow) : getPreviousRow(curRow);
			
				// prevent infinite loop if things go wonky (which they have been soon to do!)
			if ( newRow == curRow ){
				break;
			}else{
				curRow = newRow;
			}
			
		} while (curRow != clickedRow && curRow != null);
		if (curRow != null && !rowsToSelect.contains(curRow)) {
			rowsToSelect.add(curRow);
		}
		setSelectedRows(rowsToSelect.toArray(new TableRowCore[0]));
		setFocusedRow(clickedRow);
	}

	protected TableRowCore getPreviousRow(TableRowCore relativeToRow) {
		TableRowCore rowToSelect = null;
		if (relativeToRow != null) {
			TableRowCore parentRow = relativeToRow.getParentRowCore();
			if (parentRow == null) {
				TableRowCore row = getRow(indexOf(relativeToRow) - 1);
				if (row != null && row.isExpanded() && row.getSubItemCount() > 0) {
					rowToSelect = row.getSubRow(row.getSubItemCount() - 1);
				} else {
					rowToSelect = row;
				}
			} else {
				int index = relativeToRow.getIndex();
				if (index > 0) {
					rowToSelect = parentRow.getSubRow(index - 1);
				} else {
					rowToSelect = parentRow;
				}
			}
		}
		if (rowToSelect == null) {
			rowToSelect = getRow(0);
		}
		return rowToSelect;
	}

	protected TableRowCore getNextRow(TableRowCore relativeToRow) {
		TableRowCore rowToSelect = null;
		if (relativeToRow == null) {
			rowToSelect = getRow(0);
		} else {
			if (relativeToRow.isExpanded() && relativeToRow.getSubItemCount() > 0) {
				TableRowCore[] subRowsWithNull = relativeToRow.getSubRowsWithNull();
				for (TableRowCore row : subRowsWithNull) {
					if (row != null) {
						rowToSelect = row;
						break;
					}
				}
				if (rowToSelect == null) {
					rowToSelect = getRow(relativeToRow.getIndex() + 1);
				}
			} else {
				TableRowCore parentRow = relativeToRow.getParentRowCore();
				if (parentRow != null) {
					rowToSelect = parentRow.getSubRow(relativeToRow.getIndex() + 1);

					if (rowToSelect == null) {
						rowToSelect = getRow(parentRow.getIndex() + 1);
					}
				} else {
					rowToSelect = getRow(relativeToRow.getIndex() + 1);
				}
			}
		}
		return rowToSelect;
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#clipboardSelected()
	 */
	public void clipboardSelected() {
		String sToClipboard = "";
		TableColumnCore[] visibleColumns = getVisibleColumns();
		for (int j = 0; j < visibleColumns.length; j++) {
			if (j != 0) {
				sToClipboard += "\t";
			}
			String title = MessageText.getString(visibleColumns[j].getTitleLanguageKey());
			sToClipboard += title;
		}

		TableRowCore[] rows = getSelectedRows();
		for (TableRowCore row : rows) {
			sToClipboard += "\n";
			for (int j = 0; j < visibleColumns.length; j++) {
				TableColumnCore column = visibleColumns[j];
				if (j != 0) {
					sToClipboard += "\t";
				}
				TableCellCore cell = row.getTableCellCore(column.getName());
				if (cell != null) {
					sToClipboard += cell.getClipboardText();
				}
			}
		}
		new Clipboard(getComposite().getDisplay()).setContents(new Object[] {
			sToClipboard
		}, new Transfer[] {
			TextTransfer.getInstance()
		});
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#isDisposed()
	 */
	public boolean isDisposed() {
		return cTable == null || cTable.isDisposed();
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#refreshTable(boolean)
	 */
	public void refreshTable(final boolean bForceSort) {
		//__refreshTable(bForceSort);
		refreshTableRunnable.setForceSort(bForceSort);
		Utils.getOffOfSWTThread(refreshTableRunnable);
	}

	public void __refreshTable(boolean bForceSort) {
		long lStart = SystemTime.getCurrentTime();
		super.refreshTable(bForceSort);

		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				// call to trigger invalidation if visibility changes
				isVisible();
			}
		});
		final boolean bDoGraphics = (loopFactor % graphicsUpdate) == 0;
		final boolean bWillSort = bForceSort || (reOrderDelay != 0)
				&& ((loopFactor % reOrderDelay) == 0);
		//System.out.println("Refresh.. WillSort? " + bWillSort);

		if (bWillSort) {
			TableColumnCore sortColumn = getSortColumn();
			if (bForceSort && sortColumn != null) {
				resetLastSortedOn();
				sortColumn.setLastSortValueChange(SystemTime.getCurrentTime());
			}
			_sortColumn(true, false, false);
		}

		runForAllRows(new TableGroupRowVisibilityRunner() {
			public void run(TableRowCore row, boolean bVisible) {
				row.refresh(bDoGraphics, bVisible);
			}
		});
		loopFactor++;

		long diff = SystemTime.getCurrentTime() - lStart;
		if (diff > 0) {
			//debug("refreshTable took " + diff);
		}

		if (tvTabsCommon != null) {
			Utils.execSWTThread(new AERunnable() {
				public void runSupport() {
					if (tvTabsCommon != null) {
						tvTabsCommon.swt_refresh();
					}
				}
			});
		}
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#setEnableTabViews(boolean)
	 */
	public void setEnableTabViews(boolean enableTabViews, boolean expandByDefault, String[] restrictedToIDs  ){
		this.enableTabViews = enableTabViews;
		tabViewRestriction = restrictedToIDs;
		tabViewsExpandedByDefault = expandByDefault;
	}

	public boolean isTabViewsEnabled() {
		return enableTabViews;
	}

	public String[] getTabViewsRestrictedTo() {
		return( tabViewRestriction );
	}
	public boolean 
	getTabViewsExpandedByDefault()
	{
		return( tabViewsExpandedByDefault );
	}
	
	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#setFocus()
	 */
	public void setFocus() {
		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				if (isDisposed()) {
					return;
				}
				cTable.setFocus();
			}
		});
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#setRowDefaultHeight(int)
	 */
	public void setRowDefaultHeight(int iHeight) {
		if (iHeight > defaultRowHeight) {
			defaultRowHeight = iHeight;
			
			Utils.execSWTThread(new AERunnable() {
				public void runSupport() {
					if (vBar != null && !vBar.isDisposed()) {
						vBar.setIncrement(defaultRowHeight);
					}
				}
			});
		}
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#getRow(int, int)
	 */
	public TableRowCore getRow(int x, int y) {
		Set<TableRowPainted> visibleRows = this.visibleRows;
		if (visibleRows.size() == 0) {
			return null;
		}
		boolean firstRow = true;
		int curY = 0;
		for (TableRowPainted row : visibleRows) {
			if (firstRow) {
				curY = row.getDrawOffset().y;
			}
			int h = row.getHeight();
			if (y >= curY && y < curY + h) {
				return row;
			}
			curY += h;
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#isRowVisible(com.aelitis.azureus.ui.common.table.TableRowCore)
	 */
	public boolean isRowVisible(TableRowCore row) {
		if (row == null) {
			return false;
		}
		synchronized (visibleRows_sync) {
			return visibleRows.contains(row);
		}
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#getTableCellWithCursor()
	 */
	public TableCellCore getTableCellWithCursor() {
		// TODO: Make work outside SWT?
		Point pt = cTable.getDisplay().getCursorLocation();
		pt = cTable.toControl(pt);
		return getTableCell(pt.x, clientArea.y + pt.y);
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#getTableRowWithCursor()
	 */
	public TableRowCore getTableRowWithCursor() {
		// TODO: Make work outside SWT?
		Point pt = cTable.getDisplay().getCursorLocation();
		pt = cTable.toControl(pt);
		return getTableRow(pt.x, pt.y, true);
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#getRowDefaultHeight()
	 */
	public int getRowDefaultHeight() {
		return defaultRowHeight;
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#setEnabled(boolean)
	 */
	public void setEnabled(final boolean enable) {
		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				if (!isDisposed()) {
					cTable.setEnabled(enable);
				}
			}
		});
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#canHaveSubItems()
	 */
	public boolean canHaveSubItems() {
		return true;
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#setHeaderVisible(boolean)
	 */
	public void setHeaderVisible(final boolean visible) {
		super.setHeaderVisible(visible);

		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				if (cHeaderArea != null && !cHeaderArea.isDisposed()) {
					cHeaderArea.setVisible(visible);
					FormData fd = Utils.getFilledFormData();
					fd.height = visible ? headerHeight : 1;
					fd.bottom = null;
					cHeaderArea.setLayoutData(fd);
					cHeaderArea.getParent().layout(true);
				}
			}
		});
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#getMaxItemShown()
	 */
	public int getMaxItemShown() {
		// NOT USED
		return 0;
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableView#setMaxItemShown(int)
	 */
	public void setMaxItemShown(int newIndex) {
		// NOT USED
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.core3.internat.MessageText.MessageTextListener#localeChanged(java.util.Locale, java.util.Locale)
	 */
	public void localeChanged(Locale old_locale, Locale new_locale) {
		Utils.execSWTThreadLater(0, new AERunnable() {
			public void runSupport() {
				if (tvTabsCommon != null) {
					tvTabsCommon.localeChanged();
				}

				tableInvalidate();
				refreshTable(true);
				cHeaderArea.redraw();
			}
		});
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableStructureModificationListener#columnOrderChanged(int[])
	 */
	public void columnOrderChanged(int[] iPositions) {
		//TODO
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.TableStructureModificationListener#columnSizeChanged(com.aelitis.azureus.ui.common.table.TableColumnCore, int)
	 */
	public void columnSizeChanged(TableColumnCore tableColumn, int diff) {
		columnsWidth += diff;
		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				if (cHeaderArea != null && !cHeaderArea.isDisposed()) {
					cHeaderArea.redraw();
				}
				swt_fixupSize();
				redrawTable();
			}
		});
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#addKeyListener(org.eclipse.swt.events.KeyListener)
	 */
	public void addKeyListener(KeyListener listener) {
		if (tvSWTCommon == null) {
			return;
		}
		tvSWTCommon.addKeyListener(listener);
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#removeKeyListener(org.eclipse.swt.events.KeyListener)
	 */
	public void removeKeyListener(KeyListener listener) {
		if (tvSWTCommon == null) {
			return;
		}
		tvSWTCommon.removeKeyListener(listener);
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#getKeyListeners()
	 */
	public KeyListener[] getKeyListeners() {
		if (tvSWTCommon == null) {
			return new KeyListener[0];
		}
		return tvSWTCommon.getKeyListeners();
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#addMenuFillListener(org.gudy.azureus2.ui.swt.views.table.TableViewSWTMenuFillListener)
	 */
	public void addMenuFillListener(TableViewSWTMenuFillListener l) {
		if (tvSWTCommon == null) {
			return;
		}
		tvSWTCommon.addMenuFillListener(l);
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#createDragSource(int)
	 */
	public DragSource createDragSource(int style) {
		final DragSource dragSource = new DragSource(cTable, style);
		dragSource.addDragListener(new DragSourceAdapter() {
			public void dragStart(DragSourceEvent event) {
				cTable.setCursor(null);
				TableRowCore row = getTableRow(event.x, event.y, true);
				if (row != null && !row.isSelected()) {
					setSelectedRows(new TableRowCore[] { row });
				}
				isDragging = true;
			}

			public void dragFinished(DragSourceEvent event) {
				isDragging = false;
			}
		});
		cTable.addDisposeListener(new DisposeListener() {
			// @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
			public void widgetDisposed(DisposeEvent e) {
				if (!dragSource.isDisposed()) {
					dragSource.dispose();
				}
			}
		});
		return dragSource;
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#createDropTarget(int)
	 */
	public DropTarget createDropTarget(int style) {
		final DropTarget dropTarget = new DropTarget(cTable, style);
		cTable.addDisposeListener(new DisposeListener() {
			// @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
			public void widgetDisposed(DisposeEvent e) {
				if (!dropTarget.isDisposed()) {
					dropTarget.dispose();
				}
			}
		});
		return dropTarget;
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#getComposite()
	 */
	public Composite getComposite() {
		return cTable;
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#getRow(org.eclipse.swt.dnd.DropTargetEvent)
	 */
	public TableRowCore getRow(DropTargetEvent event) {
		//TODO
		// maybe
		Point pt = cTable.toControl(event.x, event.y);
		return getRow(pt.x, clientArea.y + pt.y);
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#getRowSWT(java.lang.Object)
	 */
	public TableRowSWT getRowSWT(Object dataSource) {
		return (TableRowSWT) getRow(dataSource);
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#getTableComposite()
	 */
	public Composite getTableComposite() {
		return cTable;
	}

	/** Creates a composite within the specified composite and sets its layout
	 * to a default FillLayout().
	 *
	 * @param composite to create your Composite under
	 * @return The newly created composite
	 */
	public Composite createMainPanel(Composite composite) {
		TableViewSWTPanelCreator mainPanelCreator = getMainPanelCreator();
		if (mainPanelCreator != null) {
			return mainPanelCreator.createTableViewPanel(composite);
		}
		Composite panel = new Composite(composite, SWT.NO_FOCUS);
		composite.getLayout();
		GridLayout layout = new GridLayout();
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		panel.setLayout(layout);

		Object parentLayout = composite.getLayout();
		if (parentLayout == null || (parentLayout instanceof GridLayout)) {
			panel.setLayoutData(new GridData(GridData.FILL_BOTH));
		}

		return panel;
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#initialize(org.eclipse.swt.widgets.Composite)
	 */
	public void initialize(Composite parent) {
		initialize( null, parent );
	}
	public void initialize(UISWTView parentView, Composite parent) {
		tvTabsCommon = new TableViewSWT_TabsCommon(parentView,this);

		shell = parent.getShell();
		mainComposite = tvTabsCommon.createSashForm(parent);
		mainComposite.setData("Name", tableID);
		mainComposite.setData("ObfusticateImage", this);
		Composite cTableComposite = tvTabsCommon.tableComposite;

		cTableComposite.setLayout(new FormLayout());
		Layout layout = parent.getLayout();
		if (layout instanceof FormLayout) {
			FormData fd = Utils.getFilledFormData();
			cTableComposite.setLayoutData(fd);
		}

		cHeaderArea = new Canvas(cTableComposite, SWT.DOUBLE_BUFFERED);

		fontHeader = FontUtils.getFontWithHeight(cHeaderArea.getFont(), null, 12);
		fontHeaderSmall = FontUtils.getFontPercentOf(fontHeader, 0.8f);
		cHeaderArea.setFont(fontHeader);

		cTable = new Canvas(cTableComposite, SWT.NO_BACKGROUND | SWT.H_SCROLL | SWT.V_SCROLL);
		
		// good test
		//cTable.setFont(FontUtils.getFontPercentOf(cTable.getFont(), 1.50f));
		int minRowHeight = FontUtils.getFontHeightInPX(cTable.getFont());
		minRowHeight += Math.ceil(minRowHeight * 2.0 / 16.0);
		if (defaultRowHeight < minRowHeight) {
			defaultRowHeight = minRowHeight;
		}

		cTable.setBackground(parent.getDisplay().getSystemColor(
				SWT.COLOR_LIST_BACKGROUND));

		headerHeight = configMan.getIntParameter("Table.headerHeight");
		if (headerHeight <= 0) {
			headerHeight = DEFAULT_HEADER_HEIGHT;
		}

		FormData fd = Utils.getFilledFormData();
		fd.height = headerHeight;
		fd.bottom = null;
		cHeaderArea.setLayoutData(fd);
		fd = Utils.getFilledFormData();
		fd.top = new FormAttachment(cHeaderArea);
		cTable.setLayoutData(fd);

		clientArea = cTable.getClientArea();

		TableColumnCore[] tableColumns = getAllColumns();
		TableColumnCore[] tmpColumnsOrdered = new TableColumnCore[tableColumns.length];
		//Create all columns
		int columnOrderPos = 0;
		Arrays.sort(tableColumns,
				TableColumnManager.getTableColumnOrderComparator());
		for (int i = 0; i < tableColumns.length; i++) {
			int position = tableColumns[i].getPosition();
			if (position != -1 && tableColumns[i].isVisible()) {
				//table.createNewColumn(SWT.NULL);
				//System.out.println(i + "] " + tableColumns[i].getName() + ";" + position);
				tmpColumnsOrdered[columnOrderPos++] = tableColumns[i];
			}
		}
		//int numSWTColumns = table.getColumnCount();
		//int iNewLength = numSWTColumns - (bSkipFirstColumn ? 1 : 0);
		TableColumnCore[] columnsOrdered = new TableColumnCore[columnOrderPos];
		System.arraycopy(tmpColumnsOrdered, 0, columnsOrdered, 0, columnOrderPos);
		setColumnsOrdered(columnsOrdered);

		cTable.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				swt_paintComposite(e);
			}
		});

		menu = createMenu();
		cTable.setMenu(menu);
		cHeaderArea.setMenu(menu);

		setupHeaderArea(cHeaderArea);

		cTable.addControlListener(new ControlListener() {

			public void controlResized(ControlEvent e) {
				swt_calculateClientArea();
				swt_fixupSize();
			}
			
			public void controlMoved(ControlEvent e) {
			}
		});
		
		hBar = cTable.getHorizontalBar();
		if (hBar != null) {
			hBar.setValues(0, 0, 0, 10, 10, 100);
			hBar.addSelectionListener(new SelectionListener() {
				
				public void widgetSelected(SelectionEvent e) {
					//swt_calculateClientArea();
					cTable.redraw();
				}
				
				public void widgetDefaultSelected(SelectionEvent e) {
				}
			});
		}
		vBar = cTable.getVerticalBar();
		if (vBar != null) {
			vBar.setValues(0, 0, 0, 50, getRowDefaultHeight(), 50);
			vBar.addSelectionListener(new SelectionListener() {
				public void widgetSelected(SelectionEvent e) {
					swt_vBarChanged();
				}
				
				public void widgetDefaultSelected(SelectionEvent e) {
				}
			});
		}
		
		if (DEBUG_WITH_SHELL) {
	  		Shell shell = new Shell();
	  		sCanvasImage = new Canvas(shell, SWT.DOUBLE_BUFFERED);
	  		shell.setLayout(new FillLayout());
	  		sCanvasImage.addPaintListener(new PaintListener() {
	  			public void paintControl(PaintEvent e) {
	  				if (canvasImage == null) {
	  					return;
	  				}
	  				e.gc.drawImage(canvasImage, 0, 0);
	  				//System.out.println(System.currentTimeMillis() + "] Paint " + e.x + "x" + e.y + " " + e.width + "x" + e.height);
	  
	  			}
	  		});
	  		shell.addDisposeListener(new DisposeListener() {
	  			public void widgetDisposed(DisposeEvent e) {
	  				sCanvasImage = null;
	  			}
	  		});
	  		shell.setVisible(true);
		}


		cTable.addMouseListener(tvSWTCommon);
		cTable.addMouseMoveListener(tvSWTCommon);
		cTable.addKeyListener(tvSWTCommon);
		//composite.addSelectionListener(tvSWTCommon);
		
		cTable.addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent e) {
				e.doit = true;
			}
		});
		
		
		SelectedContentManager.addCurrentlySelectedContentListener(new SelectedContentListener() {
			public void currentlySelectedContentChanged(
					ISelectedContent[] currentContent, String viewID) {
				if ( cTable == null || cTable.isDisposed()){
					SelectedContentManager.removeCurrentlySelectedContentListener( this );
				}else{
					redrawTable();
				}
			}
		});
		
		cTable.addFocusListener(new FocusListener() {
			public void focusLost(FocusEvent e) {
				isFocused = false;
				redrawTable();
			}
			
			public void focusGained(FocusEvent e) {
				isFocused = true;
				redrawTable();
			}
		});
		isFocused = cTable.isFocusControl();

		new TableTooltips(this, cTable);

		TableColumnManager tcManager = TableColumnManager.getInstance();

		String sSortColumn = tcManager.getDefaultSortColumnName(tableID);
		if (sSortColumn == null || sSortColumn.length() == 0) {
			sSortColumn = sDefaultSortOn;
		}

		TableColumnCore tc = tcManager.getTableColumnCore(tableID, sSortColumn);
		if (tc == null && tableColumns.length > 0) {
			tc = tableColumns[0];
		}
		setSortColumn(tc, false);

		triggerLifeCycleListener(TableLifeCycleListener.EVENT_INITIALIZED);

		configMan.addParameterListener("Graphics Update", this);
		configMan.addParameterListener("ReOrder Delay", this);
		configMan.addParameterListener("Table.extendedErase", this);
		configMan.addParameterListener("Table.headerHeight", this);
		Colors.getInstance().addColorsChangedListener(this);

		// So all TableView objects of the same TableID have the same columns,
		// and column widths, etc
		TableStructureEventDispatcher.getInstance(tableID).addListener(this);

	}

	protected void swt_vBarChanged() {
		if (DEBUG_SELECTION) {
			debug("vBar changed " + vBar.getSelection() + " via " + Debug.getCompressedStackTrace());
		}
		swt_calculateClientArea();
		cTable.update();
	}

	private void setupHeaderArea(final Canvas cHeaderArea) {

		cHeaderArea.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				paintHeader(e);
			}
		});

		Listener l = new Listener() {
			boolean mouseDown = false;

			TableColumnCore columnSizing;

			int columnSizingStart = 0;

			public void handleEvent(Event e) {
				switch (e.type) {
					case SWT.MouseDown: {
						if (e.button != 1) {
							return;
						}
						mouseDown = true;

						columnSizing = null;
						int x = -clientArea.x;
						TableColumnCore[] visibleColumns = getVisibleColumns();
						for (TableColumnCore column : visibleColumns) {
							int w = column.getWidth();
							x += w;

							if (e.x >= x - 3 && e.x <= x + 3) {
								columnSizing = column;
								columnSizingStart = e.x;
								break;
							}
						}

						break;
					}

					case SWT.MouseUp: {
						if (e.button != 1) {
							return;
						}
						if (mouseDown && columnSizing == null) {
							TableColumnCore column = getTableColumnByOffset(e.x);
							if (column != null) {
								setSortColumn(column, true);
							}
						}
						columnSizing = null;
						mouseDown = false;
						break;
					}

					case SWT.MouseMove: {
						if (columnSizing != null) {
							int diff = (e.x - columnSizingStart);
							columnSizing.setWidth(columnSizing.getWidth() + diff);
							columnSizingStart = e.x;
						} else {
							int cursorID = SWT.CURSOR_HAND;
							int x = -clientArea.x;
							TableColumnCore[] visibleColumns = getVisibleColumns();
							for (TableColumnCore column : visibleColumns) {
								int w = column.getWidth();
								x += w;

								if (e.x >= x - 3 && e.x <= x + 3) {
									cursorID = SWT.CURSOR_SIZEWE;
									break;
								}
							}
							cHeaderArea.setCursor(e.display.getSystemCursor(cursorID));
							TableColumnCore column = getTableColumnByOffset(e.x);

							if (column == null) {
								cHeaderArea.setToolTipText(null);
							} else {
								String info = MessageText.getString(
										column.getTitleLanguageKey() + ".info", (String) null);
								if (column.showOnlyImage()) {
									String tt = MessageText.getString(
											column.getTitleLanguageKey());
									if (info != null) {
										tt += "\n" + info;
									}
									cHeaderArea.setToolTipText(tt);
								} else {
									cHeaderArea.setToolTipText(info);
								}
							}
						}
					}
					
				}
			}
		};

		cHeaderArea.addListener(SWT.MouseDown, l);
		cHeaderArea.addListener(SWT.MouseUp, l);
		cHeaderArea.addListener(SWT.MouseMove, l);

		Transfer[] types = new Transfer[] {
			TextTransfer.getInstance()
		};

		final DragSource ds = new DragSource(cHeaderArea, DND.DROP_MOVE);
		ds.setTransfer(types);
		ds.addDragListener(new DragSourceListener() {
			private String eventData;

			public void dragStart(DragSourceEvent event) {
				Cursor cursor = cHeaderArea.getCursor();
				if (cursor != null
						&& cursor.equals(event.display.getSystemCursor(SWT.CURSOR_SIZEWE))) {
					event.doit = false;
					return;
				}

				cHeaderArea.setCursor(null);
				TableColumnCore tc = getTableColumnByOffset(event.x);
				isHeaderDragging = tc != null;
				if (isHeaderDragging) {
					eventData = tc.getName();
				}
				//System.out.println("drag " + eventData);
			}

			public void dragSetData(DragSourceEvent event) {
				event.data = eventData;
			}

			public void dragFinished(DragSourceEvent event) {
				isHeaderDragging = false;
				eventData = null;
			}
		});

		final DropTarget dt = new DropTarget(cHeaderArea, DND.DROP_MOVE);
		dt.setTransfer(types);
		dt.addDropListener(new DropTargetListener() {

			public void dropAccept(DropTargetEvent event) {
			}

			public void drop(final DropTargetEvent event) {
				if (event.data instanceof String) {
					TableColumn tcOrig = getTableColumn((String) event.data);
					Point pt = cTable.toControl(event.x, event.y);
					TableColumn tcDest = getTableColumnByOffset(pt.x);
					if (tcDest == null) {
						TableColumnCore[] visibleColumns = getVisibleColumns();
						if (visibleColumns != null && visibleColumns.length > 0) {
							tcDest = visibleColumns[visibleColumns.length - 1];
						}
					}
					if (tcOrig != null && tcDest != null) {
						int destPos = tcDest.getPosition();
						int origPos = tcOrig.getPosition();
						final boolean moveRight = destPos > origPos;
						TableColumnCore[] visibleColumns = getVisibleColumns();
						((TableColumnCore) tcOrig).setPositionNoShift(destPos);

						//System.out.println("Move " + origPos + " Right? " + moveRight + " of " + destPos);
						Arrays.sort(visibleColumns, new Comparator<TableColumnCore>() {
							public int compare(TableColumnCore o1, TableColumnCore o2) {
								if (o1 == o2) {
									return 0;
								}
								int diff = o1.getPosition() - o2.getPosition();
								if (diff == 0) {
									int i = o1.getName().equals(event.data) ? -1 : 1;
									if (moveRight) {
										i *= -1;
									}
									return i;
								}
								return diff;
							}
						});

						for (int i = 0; i < visibleColumns.length; i++) {
							TableColumnCore tc = visibleColumns[i];
							tc.setPositionNoShift(i);
						}
						setColumnsOrdered(visibleColumns);

						TableStructureEventDispatcher.getInstance(tableID).tableStructureChanged(
								false, getDataSourceType());
					}
				}
			}

			public void dragOver(DropTargetEvent event) {
			}

			public void dragOperationChanged(DropTargetEvent event) {
			}

			public void dragLeave(DropTargetEvent event) {
			}

			public void dragEnter(DropTargetEvent event) {
			}
		});
		cHeaderArea.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				Utils.disposeSWTObjects(new Object[] {
					ds,
					dt,
					fontHeader,
					fontHeaderSmall
				});
			}
		});
	}

	@Override
	public void tableStructureChanged(final boolean columnAddedOrRemoved,
			final Class forPluginDataSourceType) {

		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				TableViewPainted.super.tableStructureChanged(columnAddedOrRemoved, forPluginDataSourceType);
				if (cHeaderArea != null && !cHeaderArea.isDisposed()) {
					cHeaderArea.redraw();
				}

				redrawTable();
			}
		});
	}
	
	protected void swt_paintComposite(PaintEvent e) {
		swt_calculateClientArea();
		if (canvasImage == null) {
			return;
		}
		
		//System.out.println(e.count + " paint " + e.gc.getClipping() + ";" + e.x + "," + e.y + "," + e.width + "," + e.height + " via " + Debug.getCompressedStackTrace());

		e.gc.drawImage(canvasImage, -clientArea.x, 0);
		
		// test line
		//e.gc.drawLine(0, 0, cTable.getSize().x, canvasImage.getBounds().height);
	}

	protected void swt_paintCanvasImage(GC gc, Rectangle drawBounds) {
		if (cTable == null || cTable.isDisposed()) {
			return;
		}
		
		int end = drawBounds.y + drawBounds.height;

		gc.setFont(cTable.getFont());
		gc.setClipping(drawBounds);
		TableRowCore oldRow = null;
		int pos = -1;
		Set<TableRowPainted> visibleRows = this.visibleRows;
		
		for (TableRowPainted row : visibleRows) {
			TableRowPainted paintedRow = row;
			if (pos == -1) {
				pos = row.getIndex();
			} else {
				pos++;
			}
			Point drawOffset = paintedRow.getDrawOffset();
			int rowStartX = 0;
			int rowStartY = drawOffset.y - clientArea.y;
			int rowHeight = paintedRow.getHeight();
			//debug("Paint " + drawBounds.x + "x" + drawBounds.y + " " + drawBounds.width + "x" + drawBounds.height + "; Row=" +row.getIndex() + ";clip=" + gc.getClipping() +";drawOffset=" + drawOffset);
			if (drawBounds.intersects(rowStartX, rowStartY, 9999, rowHeight)) {
				// ensure full row height
				int diffY2 = (rowStartY + rowHeight) - (drawBounds.y + drawBounds.height); 
				if (diffY2 > 0 ) {
					drawBounds.height += diffY2; 
					gc.setClipping(drawBounds);
				}
				paintedRow.swt_paintGC(gc, drawBounds, rowStartX, rowStartY, pos);
			}
			oldRow = row;
		}

		int h;
		int yDirty;
		if (oldRow == null) {
			yDirty = drawBounds.y;
			h = drawBounds.height;
		} else {
			yDirty = ((TableRowPainted) oldRow).getDrawOffset().y
					+ ((TableRowPainted) oldRow).getFullHeight();
			h = (drawBounds.y + drawBounds.height) - yDirty;
		}
		if (h > 0) {
			int rowHeight = getRowDefaultHeight();
			if (extendedErase) {
				while (yDirty < end) {
					pos++;
					Color color = TableRowPainted.alternatingColors[pos % 2];
					if (color != null) {
						gc.setBackground(color);
					}
					if (color == null) {
						gc.setBackground(gc.getDevice().getSystemColor(
								SWT.COLOR_LIST_BACKGROUND));
					}
					gc.fillRectangle(drawBounds.x, yDirty, drawBounds.width, rowHeight);
					yDirty += rowHeight;
				}
			} else {
				gc.setBackground(gc.getDevice().getSystemColor(
						SWT.COLOR_LIST_BACKGROUND));
				gc.fillRectangle(drawBounds.x, yDirty, drawBounds.width, h);
			}
		}

		//gc.setForeground(getColorLine());
		TableColumnCore[] visibleColumns = getVisibleColumns();
		int x = 0;
		gc.setAlpha(20);
		for (TableColumnCore column : visibleColumns) {
			x += column.getWidth();

			// Vertical lines between columns
			gc.drawLine(x - 1, drawBounds.y, x - 1, drawBounds.y + drawBounds.height);
		}
		gc.setAlpha(255);
	}
	
	private Color getColorLine() {
		if (colorLine == null) {
			colorLine = cTable.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
			HSLColor hslColor = new HSLColor();
			hslColor.initHSLbyRGB(colorLine.getRed(), colorLine.getGreen(),
					colorLine.getBlue());

			int lum = hslColor.getLuminence();
			if (lum > 127)
				lum -= 25;
			else
				lum += 40;
			hslColor.setLuminence(lum);

			colorLine = new Color(cTable.getDisplay(), hslColor.getRed(),
					hslColor.getGreen(), hslColor.getBlue());
		}

		return colorLine;
	}

	private void paintHeader(PaintEvent e) {

		Rectangle ca = cHeaderArea.getClientArea();
		Color c1 = e.display.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
		Color c2 = e.display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
		Color line = c2;
		Color fg = e.display.getSystemColor(SWT.COLOR_LIST_FOREGROUND);

		
		Pattern patternUp = new Pattern(e.display, 0, 0, 0, ca.height, c1, c2);
		Pattern patternDown = new Pattern(e.display, 0, -ca.height , 0, 0, c2, c1);
		//e.gc.setBackgroundPattern(patternUp);
		//e.gc.fillRectangle(ca);

		e.gc.setForeground(line);
		//e.gc.drawLine(0, 0, clientArea.width, 0);
		e.gc.drawLine(0, headerHeight - 1, clientArea.width, headerHeight - 1);

		TableColumnCore[] visibleColumns = getVisibleColumns();
		GCStringPrinter sp;
		TableColumnCore sortColumn = getSortColumn();
		int x = -clientArea.x;
		for (TableColumnCore column : visibleColumns) {
			int w = column.getWidth();

			//squeeze last column's text into available visible space
			if (x + w > ca.width) {
				w = ca.width - x;
				if (w <= 16) {
					break;
				}
			}

			
			boolean isSortColumn = column.equals(sortColumn);

			e.gc.setBackgroundPattern(isSortColumn ? patternDown : patternUp);
			e.gc.fillRectangle(x, 1, w, headerHeight - 2);
			e.gc.setForeground(line);
			e.gc.drawLine(x + w - 1, 0, x + w - 1, headerHeight - 1);

			e.gc.setForeground(fg);
			int yOfs = 0;
			int wText = w;
/* Top Center
			if (isSortColumn) {
				int arrowY = 2;
				int arrowHeight = 6;
				yOfs = 8;
				// draw sort indicator
				int middle = w / 2;
				int y1, y2;
				int arrowHalfW = 4;
				if (column.isSortAscending()) {
					y2 = arrowY;
					y1 = y2 + arrowHeight;
				} else {
					y1 = arrowY;
					y2 = y1 + arrowHeight;
				}
				e.gc.setAntialias(SWT.ON);
				e.gc.setBackground(ColorCache.getColor(e.display, 0, 0, 0));
				e.gc.fillPolygon(new int[] {
					x + middle - arrowHalfW,
					y1,
					x + middle + arrowHalfW,
					y1,
					x + middle,
					y2
				});
			}
*/
			if (isSortColumn) {
				// draw sort indicator
				int arrowHeight = 6;
				int arrowY = (headerHeight / 2) - (arrowHeight / 2);
				int arrowHalfW = 4;
				int middle = w - arrowHalfW - 4;
				wText = w - (arrowHalfW * 2) - 5;
				int y1, y2;
				if (column.isSortAscending()) {
					y2 = arrowY;
					y1 = y2 + arrowHeight;
				} else {
					y1 = arrowY;
					y2 = y1 + arrowHeight;
				}
				e.gc.setAntialias(SWT.ON);
				e.gc.setBackground(fg);
				e.gc.fillPolygon(new int[] {
					x + middle - arrowHalfW,
					y1,
					x + middle + arrowHalfW,
					y1,
					x + middle,
					y2
				});
			}

			int xOfs = x + 2;
			
			boolean onlyShowImage = column.showOnlyImage();
			String text = "";
			if (!onlyShowImage) {
				text = MessageText.getString(column.getTitleLanguageKey());
			}

			int style = SWT.WRAP | SWT.CENTER;
			Image image = null;
			String imageID = column.getIconReference();
			if (imageID != null) {
				image = ImageLoader.getInstance().getImage(imageID);
				if (ImageLoader.isRealImage(image)) {
					if (onlyShowImage) {
						text = null;
						Rectangle imageBounds = image.getBounds();
						e.gc.drawImage(image, (int) (x + (w / 2.0) - (imageBounds.width / 2.0) + 0.5),
								(headerHeight / 2) - (imageBounds.height / 2));
					} else {
						text = "%0 " + text;
					}
				} else {
					image = null;
				}
			}

			if (text != null) {
  			sp = new GCStringPrinter(e.gc, text, new Rectangle(xOfs, yOfs - 1,
  					wText - 4, headerHeight - yOfs + 2), true, false,style);
  			if (image != null) {
  				sp.setImages(new Image[] { image } );
  			}
  			sp.calculateMetrics();
  			if (sp.isWordCut() || sp.isCutoff()) {
  				Font font = e.gc.getFont();
  				e.gc.setFont(fontHeaderSmall);
  				sp.printString();
  				e.gc.setFont(font);
  			} else {
  				sp.printString();
  			}
			}
			
			if (imageID != null) {
				ImageLoader.getInstance().releaseImage(imageID);
			}

			x += w;
		}

		e.gc.setBackgroundPattern(patternUp);
		e.gc.fillRectangle(x, 1, clientArea.width - x, headerHeight - 2);

		patternUp.dispose();
		patternDown.dispose();
		e.gc.setBackgroundPattern(null);
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#obfusticatedImage(org.eclipse.swt.graphics.Image)
	 */
	public Image obfusticatedImage(Image image) {
		TableColumnCore[] visibleColumns = getVisibleColumns();
		TableRowPainted[] visibleRows = this.visibleRows.toArray(new TableRowPainted[0]);
		
		for (TableRowPainted row : visibleRows) {
			if (row == null || row.isRowDisposed()) {
				continue;
			}

			for (TableColumnCore tc : visibleColumns) {
				if (tc == null || !tc.isObfusticated()) {
					continue;
				}

				TableCellPainted cell = (TableCellPainted) row.getTableCell(tc.getName());
				if (cell == null) {
					continue;
				}

				String text = cell.getObfusticatedText();

				if (text != null) {

					final Rectangle cellBounds = cell.getBoundsOnDisplay();
					Point ptDisplay = cTable.getShell().getLocation();
					cellBounds.x -= ptDisplay.x;
					cellBounds.y -= ptDisplay.y;
					Rectangle boundsRaw = cell.getBoundsRaw();
					if (boundsRaw.y + cellBounds.height > clientArea.y
							+ clientArea.height) {
						cellBounds.height -= (boundsRaw.y + cellBounds.height)
								- (clientArea.y + clientArea.height);
					}
					int tableWidth = cTable.getClientArea().width;
					if (boundsRaw.x + cellBounds.width > clientArea.x
							+ tableWidth) {
						cellBounds.width -= (boundsRaw.x + cellBounds.width)
								- (clientArea.x + tableWidth);
					}

					UIDebugGenerator.obfusticateArea(image, cellBounds, text);
				}

			}
		}

		UISWTViewCore view = tvTabsCommon == null ? null
				: tvTabsCommon.getActiveSubView();
		if (view instanceof ObfusticateImage) {
			try {
				((ObfusticateImage) view).obfusticatedImage(image);
			} catch (Exception e) {
				Debug.out("Obfuscating " + view, e);
			}
		}
		return image;
	}

	protected TableViewSWTPanelCreator getMainPanelCreator() {
		return mainPanelCreator;
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#setMainPanelCreator(org.gudy.azureus2.ui.swt.views.table.TableViewSWTPanelCreator)
	 */
	public void setMainPanelCreator(TableViewSWTPanelCreator mainPanelCreator) {
		this.mainPanelCreator = mainPanelCreator;
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#setRowDefaultIconSize(org.eclipse.swt.graphics.Point)
	 */
	public void setRowDefaultIconSize(Point size) {
		setRowDefaultHeight(size.y);
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#getTableCell(int, int)
	 */
	public TableCellCore getTableCell(int x, int y) {
		TableRowSWT row = getTableRow(x, y, true);
		if (row == null) {
			return null;
		}

		TableColumnCore column = getTableColumnByOffset(x);
		if (column == null) {
			return null;
		}

		return row.getTableCellCore(column.getName());
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#getTableCellMouseOffset(org.gudy.azureus2.ui.swt.views.table.TableCellSWT)
	 */
	public Point getTableCellMouseOffset(TableCellSWT tableCell) {
		if (tableCell == null) {
			return null;
		}
		Point pt = cTable.getDisplay().getCursorLocation();
		pt = cTable.toControl(pt);

		Rectangle bounds = tableCell.getBounds();
		int x = pt.x - bounds.x;
		if (x < 0 || x > bounds.width) {
			return null;
		}
		int y = pt.y - bounds.y;
		if (y < 0 || y > bounds.height) {
			return null;
		}
		return new Point(x, y);
	}

	// @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#enableFilterCheck(org.eclipse.swt.widgets.Text, com.aelitis.azureus.ui.common.table.TableViewFilterCheck)
	public void enableFilterCheck(Text txtFilter,
			TableViewFilterCheck<Object> filterCheck) {
		TableViewSWTFilter<?> filter = getSWTFilter();
		if (filter != null) {
			if (filter.widget != null && !filter.widget.isDisposed()) {
				filter.widget.removeKeyListener(tvSWTCommon);
				filter.widget.removeModifyListener(filter.widgetModifyListener);
			}
		} else {
			this.filter = filter = new TableViewSWTFilter();
		}
		filter.widget = txtFilter;
		if (txtFilter != null) {
			txtFilter.addKeyListener(tvSWTCommon);

			filter.widgetModifyListener = new ModifyListener() {
				public void modifyText(ModifyEvent e) {
					setFilterText(((Text) e.widget).getText());
				}
			};
			txtFilter.addModifyListener(filter.widgetModifyListener);

			if (txtFilter.getText().length() == 0) {
				txtFilter.setText(filter.text);
			} else {
				filter.text = filter.nextText = txtFilter.getText();
			}
		} else {
			filter.text = filter.nextText = "";
		}

		filter.checker = filterCheck;

		filter.checker.filterSet(filter.text);
		refilter();
	}
	
	public Text 
	getFilterControl()
	{
		TableViewSWTFilter<?> filter = getSWTFilter();
		
		return( filter==null?null:filter.widget );
	}
	
	public void disableFilterCheck() {
		TableViewSWTFilter<?> filter = getSWTFilter();
		if ( filter == null ){
			return;
		}
		
		if (filter.widget != null && !filter.widget.isDisposed()) {
			filter.widget.removeKeyListener(tvSWTCommon);
			filter.widget.removeModifyListener(filter.widgetModifyListener);
		}
		filter = null;
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#setFilterText(java.lang.String)
	 */
	public void setFilterText(String s) {
		if (tvSWTCommon != null) {
			tvSWTCommon.setFilterText(s);
		}
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#enableSizeSlider(org.eclipse.swt.widgets.Composite, int, int)
	 */
	public boolean enableSizeSlider(Composite composite, int min, int max) {
		// TODO
		return false;
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#disableSizeSlider()
	 */
	public void disableSizeSlider() {
		// TODO
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#addRowPaintListener(org.gudy.azureus2.ui.swt.views.table.TableRowSWTPaintListener)
	 */
	public void addRowPaintListener(TableRowSWTPaintListener listener) {
		if (tvSWTCommon != null) {
			tvSWTCommon.addRowPaintListener(listener);
		}
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#removeRowPaintListener(org.gudy.azureus2.ui.swt.views.table.TableRowSWTPaintListener)
	 */
	public void removeRowPaintListener(TableRowSWTPaintListener listener) {
		if (tvSWTCommon != null) {
			tvSWTCommon.removeRowPaintListener(listener);
		}
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#invokePaintListeners(org.eclipse.swt.graphics.GC, com.aelitis.azureus.ui.common.table.TableRowCore, com.aelitis.azureus.ui.common.table.TableColumnCore, org.eclipse.swt.graphics.Rectangle)
	 */
	public void invokePaintListeners(GC gc, TableRowCore row,
			TableColumnCore column, Rectangle cellArea) {
		if (tvSWTCommon != null) {
			tvSWTCommon.invokePaintListeners(gc, row, column, cellArea);
		}
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#addRowMouseListener(org.gudy.azureus2.plugins.ui.tables.TableRowMouseListener)
	 */
	public void addRowMouseListener(TableRowMouseListener listener) {
		if (tvSWTCommon != null) {
			tvSWTCommon.addRowMouseListener(listener);
		}
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#removeRowMouseListener(org.gudy.azureus2.plugins.ui.tables.TableRowMouseListener)
	 */
	public void removeRowMouseListener(TableRowMouseListener listener) {
		if (tvSWTCommon != null) {
			tvSWTCommon.removeRowMouseListener(listener);
		}
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#invokeRowMouseListener(org.gudy.azureus2.plugins.ui.tables.TableRowMouseEvent)
	 */
	public void invokeRowMouseListener(TableRowMouseEvent event) {
		if (tvSWTCommon != null) {
			tvSWTCommon.invokeRowMouseListener(event);
		}
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#getTableOrTreeSWT()
	 */
	public TableOrTreeSWT getTableOrTreeSWT() {
		return null;
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#packColumns()
	 */
	public void packColumns() {
		// TODO
	}

	/* (non-Javadoc)
	 * @see org.gudy.azureus2.core3.config.ParameterListener#parameterChanged(java.lang.String)
	 */
	public void parameterChanged(String parameterName) {
		boolean invalidate = parameterName == null;
		if (parameterName == null || parameterName.equals("Graphics Update")) {
			graphicsUpdate = configMan.getIntParameter("Graphics Update");
		}
		if (parameterName == null || parameterName.equals("ReOrder Delay")) {
			reOrderDelay = configMan.getIntParameter("ReOrder Delay");
		}
		if (parameterName == null || parameterName.equals("Table.extendedErase")) {
			extendedErase = configMan.getBooleanParameter("Table.extendedErase");
			invalidate = true;
		}
		if (parameterName == null || parameterName.equals("Table.headerHeight")) {
			headerHeight = configMan.getIntParameter("Table.headerHeight");
			if (headerHeight == 0) {
				headerHeight = DEFAULT_HEADER_HEIGHT;
			}
			setHeaderVisible(getHeaderVisible());
		}
		
		if (parameterName == null || parameterName.startsWith("Color")) {
			tableInvalidate();
		}
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.impl.TableViewImpl#createNewRow(java.lang.Object)
	 */
	@Override
	public TableRowCore createNewRow(Object object) {
		return new TableRowPainted(null, this, object, true);
	}

	/* (non-Javadoc)
	 * @see com.aelitis.azureus.ui.common.table.impl.TableViewImpl#visibleRowsChanged()
	 */
	@Override
	public void visibleRowsChanged() {
		swt_visibleRowsChanged();
	}

	private void swt_visibleRowsChanged() {
		final List<TableRowSWT> newlyVisibleRows = new ArrayList<TableRowSWT>();
		final List<TableRowSWT> nowInVisibleRows;
		final ArrayList<TableRowSWT> rowsStayedVisibleButMoved = new ArrayList<TableRowSWT>();
		List<TableRowSWT> newVisibleRows;
		if (isVisible()) {
			// this makes a copy.. slower
			TableRowCore[] rows = getRows();
			newVisibleRows = new ArrayList<TableRowSWT>();
			recalculateVisibleRows(rows, 0, newVisibleRows,
					rowsStayedVisibleButMoved);

		} else {
			newVisibleRows = Collections.emptyList();
		}
		nowInVisibleRows = new ArrayList<TableRowSWT>(0);
		synchronized (visibleRows_sync) {
			if (visibleRows != null) {
				nowInVisibleRows.addAll(visibleRows);
			}
		}

		LinkedHashSet<TableRowPainted> rows = new LinkedHashSet<TableRowPainted>(newVisibleRows.size());
		for (TableRowSWT row : newVisibleRows) {
			rows.add((TableRowPainted) row);
			boolean removed = nowInVisibleRows.remove(row);
			if (!removed) {
				newlyVisibleRows.add(row);
			}
		}

		synchronized (visibleRows_sync) {
			visibleRows = rows;
		}

		if (DEBUG_ROWCHANGE) {
			debug("visRowsChanged; shown=" + visibleRows.size() + "; +"
					+ newlyVisibleRows.size() + "/-" + nowInVisibleRows.size() + "/"
					+ rowsStayedVisibleButMoved.size() + " via "
					+ Debug.getCompressedStackTrace(8));
		}
		Utils.getOffOfSWTThread(new AERunnable() {

			public void runSupport() {
				boolean bTableUpdate = false;

				for (TableRowSWT row : newlyVisibleRows) {
					// no need to refres, the redraw will do it
					//row.refresh(true, true);
 					row.setShown(true, false);
					rowsStayedVisibleButMoved.remove(row);
					if (Constants.isOSX) {
						bTableUpdate = true;
					}
				}

				for (TableRowSWT row : rowsStayedVisibleButMoved) {
					row.invalidate();
					redrawRow((TableRowPainted) row, false);
				}

				for (TableRowSWT row : nowInVisibleRows) {
					row.setShown(false, false);
				}

				if (bTableUpdate) {
					Utils.execSWTThread(new AERunnable() {
						public void runSupport() {
							if (cTable != null && !cTable.isDisposed()) {
								cTable.update();
							}
						}
					});
				}

			}
		});
	}

	private void recalculateVisibleRows(TableRowCore[] rows, int yStart,
			List<TableRowSWT> newVisibleRows,
			List<TableRowSWT> rowsStayedVisibleButMoved) {
		Rectangle bounds = clientArea;

		int y = yStart;
		String sDebug;
		if (DEBUG_ROWCHANGE) {
			sDebug = "Visible Rows: ";
		}
		for (TableRowCore row : rows) {
			if (row == null) {
				continue;
			}
			TableRowPainted rowSWT = ((TableRowPainted) row);
			int rowHeight = rowSWT.getHeight();
			int rowFullHeight = rowSWT.getFullHeight();

			if ((y < bounds.y + bounds.height) && (y + rowFullHeight > bounds.y)) {
				// this row or subrows are visible

				boolean offsetChanged = rowSWT.setDrawOffset(new Point(bounds.x, y));

				// check if this row
				if (y + rowHeight > bounds.y) {
					if (DEBUG_ROWCHANGE) {
						sDebug += (rowSWT.getParentRowCore() == null ? ""
								: rowSWT.getParentRowCore().getIndex() + ".")
								+ rowSWT.getIndex()
								+ "(ofs=" + (offsetChanged ? "*" : "")
								+ y
								+ ";rh="
								+ rowHeight + "/" + rowFullHeight + ")" + ", ";
					}

					if (offsetChanged) {
						rowsStayedVisibleButMoved.add(rowSWT);
					}
					newVisibleRows.add(rowSWT);
				}

				// check if subrows
				if (row.isExpanded()) {
					TableRowCore[] subRowsWithNull = row.getSubRowsWithNull();
					if (subRowsWithNull.length > 0) {
						recalculateVisibleRows(subRowsWithNull, y + rowHeight,
								newVisibleRows, rowsStayedVisibleButMoved);
					}
				}
			} else if (newVisibleRows.size() > 0) {
				if (DEBUG_ROWCHANGE) {
					sDebug += "break(ofs=" + y + ";bounds=" + bounds + ";rh=" + rowFullHeight + ")";
				}
				break;
			}
			y += rowFullHeight;
		}
		if (DEBUG_ROWCHANGE) {
			if (yStart == 0) {
				debug(sDebug);
			}
		}
	}
	
	@Override
	public int uiGuessMaxVisibleRows() {
		return (clientArea.height / defaultRowHeight) + 1;
	}

	@Override
	public void uiRemoveRows(TableRowCore[] rows, Integer[] rowIndexes) {
		if (focusedRow != null) {
  		for (TableRowCore row : rows) {
  			if (row == focusedRow) {
  				setFocusedRow(null);
  				break;
  			}
  		}
  	}
		int bottomIndex = getRowCount() - 1;
		if (bottomIndex < 0) {
			redrawTable();
		} else {
			TableRowCore rowBottom = getLastVisibleRow();
			if (rowBottom != null) {
				while (rowBottom.getParentRowCore() != null) {
					rowBottom = rowBottom.getParentRowCore();
				}
				
				if (indexOf(rowBottom) < 0) {
					redrawTable();
				}
			}
		}
	}
	
	private TableRowPainted getLastVisibleRow() {
		synchronized (visibleRows_sync) {
			if (visibleRows == null || visibleRows.size() == 0) {
				return null;
			}
			TableRowPainted rowBottom = null;
			for (TableRowPainted row : visibleRows) {
				rowBottom = row;
			}
			return rowBottom;
		}
	}


	@Override
	public void getOffUIThread(AERunnable runnable) {
		Utils.getOffOfSWTThread(runnable);
	}

	protected void swt_calculateClientArea() {
		if (cTable == null || cTable.isDisposed()) {
			return;
		}
		Rectangle oldClientArea = clientArea;
		Rectangle newClientArea = cTable.getClientArea();
		newClientArea.x = hBar.getSelection();
		newClientArea.y = vBar.getSelection();

		int w = 0;
		TableColumnCore[] visibleColumns = getVisibleColumns();
		for (TableColumnCore column : visibleColumns) {
			w += column.getWidth();
		}
		columnsWidth = w;
		w = newClientArea.width = Math.max(newClientArea.width, w);

		boolean refreshTable = false;
		boolean changedX;
		boolean changedY;
		//boolean changedW;
		boolean changedH;
		if (oldClientArea != null) {
			changedX = oldClientArea.x != newClientArea.x;
			changedY = oldClientArea.y != newClientArea.y;
			//changedW = oldClientArea.width != newClientArea.width;
			changedH = oldClientArea.height != newClientArea.height;
		} else {
			changedX = changedY = changedH = true;
			//changedX = changedY = changedW = changedH = true;
		}
		
		clientArea = newClientArea;
		if (tvSWTCommon != null) {
			tvSWTCommon.xAdj = -clientArea.x;
		}
		
		//System.out.println("CA=" + clientArea + " via " + Debug.getCompressedStackTrace());

		boolean needRedraw = false;
		if (changedY || changedH) {
			visibleRowsChanged();
			if (changedY && oldClientArea != null) {
				Set<TableRowPainted> visibleRows = this.visibleRows;
				if (visibleRows.size() > 0) {
					if (canvasImage != null && !canvasImage.isDisposed() && !changedH) {

						int yDiff = oldClientArea.y - newClientArea.y;
						if (Math.abs(yDiff) < clientArea.height) {
							boolean wasIn = in_swt_updateCanvasImage;
							in_swt_updateCanvasImage = true;
							try{
								GC gc = new GC(canvasImage);
								try{
									Rectangle bounds = canvasImage.getBounds();
									//System.out.println("moving y " + yDiff + ";cah=" + clientArea.height);
									if (yDiff > 0) {
										gc.copyArea(0, 0, bounds.width, bounds.height, 0, yDiff, false);
										swt_paintCanvasImage(gc, new Rectangle(0, 0, 9999, yDiff));
										gc.setClipping((Rectangle) null);
									} else {
										gc.copyArea(0, -yDiff, bounds.width, bounds.height , 0, 0, false);
										int h = -yDiff;
										TableRowPainted row = getLastVisibleRow();
										if (row != null) {
											//row.invalidate();
											h += row.getHeight();
										}
										swt_paintCanvasImage(gc, new Rectangle(0, bounds.height - h, 9999, h));
										gc.setClipping((Rectangle) null);
									}
								}finally{
									gc.dispose();
								}
							}catch( Throwable e ){
								
									// seen an exception here caused, I think, by canvasImage already being
									// selected into a GC by code 'further down the stack'...
								
								refreshTable = true;
								
							}finally{
								
								in_swt_updateCanvasImage = wasIn;
							}
							
							needRedraw = true;
						} else {
							refreshTable = true;
						}
					}

				}
			}
		}

		if (changedX) {
			cHeaderArea.redraw();
		}

		Image newImage = canvasImage;

		//List<TableRowSWT> visibleRows = getVisibleRows();
		int h = 0;
		synchronized (visibleRows_sync) {
			TableRowPainted lastRow = getLastVisibleRow();
			if (lastRow != null) {
				h = lastRow.getDrawOffset().y - clientArea.y + lastRow.getHeight();
				if (h < clientArea.height && lastRow.isExpanded()) {
					TableRowCore[] subRows = lastRow.getSubRowsWithNull();
					for (TableRowCore subRow : subRows) {
						if (subRow == null) {
							continue;
						}
						TableRowPainted subRowP = (TableRowPainted) subRow;

						h += subRowP.getFullHeight();
						if (h >= clientArea.height) {
							break;
						}
					}
				}
			}
		}
		if (h < clientArea.height) {
			h = clientArea.height;
		}

		int oldH = canvasImage == null || canvasImage.isDisposed() ? 0
				: canvasImage.getBounds().height;
		int oldW = canvasImage == null || canvasImage.isDisposed() ? 0
				: canvasImage.getBounds().width;

		if (canvasImage == null || oldW != w || h > oldH) {
			//System.out.println("oldW=" + oldW + ";" + w+ ";h=" + h + ";" + oldH);
			if (h <= 0 || clientArea.width <= 0) {
				newImage = null;
			} else {
				newImage = new Image(shell.getDisplay(), w, h);
			}
		}
		boolean canvasChanged = (canvasImage != newImage);
		if (canvasChanged) {
			Image oldImage = canvasImage;
			canvasImage = newImage;
			
			if (oldImage != null && !oldImage.isDisposed()) {
				oldImage.dispose();
			}
		}
		
		

		// paint event will handle any changedX or changedW
		if (changedH || canvasChanged || refreshTable) {
			//System.out.println(changedX + ";" + changedY + ";" + changedH + ";" + canvasChanged);
			//System.out.println("Redraw " + Debug.getCompressedStackTrace());

			// run refreshTable on SWT (this) thread to ensure rows have been
			// refreshed for the updateCanvasImage call immediately after it
			__refreshTable(false);
			// refreshtable will call swt_updateCanvasImage for each visible row
			if (canvasChanged) {
				swt_updateCanvasImage(false);
			}
		}
		
		//		System.out.println("imgBounds = " + canvasImage.getBounds() + ";ca="
		//				+ clientArea + ";" + composite.getClientArea() + ";h=" + h + ";oh="
		//				+ oldH + " via " + Debug.getCompressedStackTrace(3));

		if (needRedraw) {
			cTable.redraw();
		}
	}

	public void swt_updateCanvasImage(boolean immediateRedraw) {
		if (canvasImage != null && !canvasImage.isDisposed()) {
			swt_updateCanvasImage(canvasImage.getBounds(), immediateRedraw);
		}
	}

	private boolean in_swt_updateCanvasImage = false;
	protected void swt_updateCanvasImage(final Rectangle bounds, final boolean immediateRedraw) {
		// no need to sync around in_swt_updateCanvasImage, we are assumed to always
		// be on SWT thread and in_swt_updateCanvasImage is only used here
		if (in_swt_updateCanvasImage) {
			Utils.execSWTThreadLater(0, new AERunnable() {
				public void runSupport() {
					swt_updateCanvasImage(bounds, immediateRedraw);
				}
			});
			return;
		}
		in_swt_updateCanvasImage = true;
		try {
  		if (canvasImage == null || canvasImage.isDisposed() || bounds == null) {
  			return;
  		}
  		//System.out.println("UpdateCanvasImage " + bounds + "; via " + Debug.getCompressedStackTrace());
  		GC gc = new GC(canvasImage);
  		swt_paintCanvasImage(gc, bounds);
  		gc.dispose();
  		if (cTable != null && !cTable.isDisposed()) {
  			cTable.redraw(bounds.x - clientArea.x, bounds.y, bounds.width, bounds.height, false);
  			if (immediateRedraw) {
  				cTable.update();
  			}
  		}
  		if ( DEBUG_WITH_SHELL ){
	  		if (sCanvasImage != null) {
	  			sCanvasImage.getShell().setSize(canvasImage.getBounds().width, canvasImage.getBounds().height);
	  			sCanvasImage.redraw(bounds.x, bounds.y, bounds.width, bounds.height, true);
	  			sCanvasImage.update();
	  		}
  		}
		} finally {
			in_swt_updateCanvasImage = false;
		}
	}

	public Rectangle getClientArea() {
		return clientArea;
	}

	public boolean isVisible() {
		if (!Utils.isThisThreadSWT()) {
			return isVisible;
		}
		boolean wasVisible = isVisible;
		isVisible = cTable != null && !cTable.isDisposed() && cTable.isVisible()
				&& !shell.getMinimized();
		if (isVisible != wasVisible) {
			visibleRowsChanged();
			UISWTViewCore view = tvTabsCommon == null ? null
					: tvTabsCommon.getActiveSubView();
			if (isVisible) {
				loopFactor = 0;

				if (view != null) {
					view.triggerEvent(UISWTViewEvent.TYPE_FOCUSGAINED, null);
				}
			} else {
				if (view != null) {
					view.triggerEvent(UISWTViewEvent.TYPE_FOCUSLOST, null);
				}
			}
		}
		return isVisible;
	}

	public void removeAllTableRows() {
		if (DEBUG_ROWCHANGE) {
			debug("RemoveAlLRows");
		}
		super.removeAllTableRows();
		synchronized (visibleRows_sync) {
			visibleRows = new LinkedHashSet<TableRowPainted>();
		}
		setFocusedRow(null);
		totalHeight = 0;
		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				if (cTable == null || cTable.isDisposed()) {
					return;
				}
				swt_fixupSize();
				swt_updateCanvasImage(false);
				if (DEBUG_ROWCHANGE) {
					debug("RemoveAllRows done");
				}
			}
		});
	}

	protected void swt_fixupSize() {
		//debug("Set minSize to " + columnsWidth + "x" + totalHeight + ";ca=" + clientArea + ";" + Debug.getCompressedStackTrace());
		boolean vBarValid = vBar != null && !vBar.isDisposed();
		if (vBarValid) {
			int tableSize = clientArea.height;
			int max = totalHeight;
			if (max < tableSize) {
				vBar.setSelection(0);
				vBar.setEnabled(false);
				vBar.setVisible(false);
			} else {
				if (!vBar.isVisible()) {
					vBar.setVisible(true);
					vBar.setEnabled(true);
				}
				if (vBar.getMaximum() != max) {
					vBar.setMaximum(max);
					swt_vBarChanged();
				}
				vBar.setThumb(tableSize);
				vBar.setPageIncrement(tableSize);
			}
		}
		if (hBar != null && !hBar.isDisposed()) {
			int tableSize = cTable.getSize().x;
			int max = columnsWidth;
			if (vBarValid && vBar.isVisible() && getScrollbarsMode() == SWT.NONE) {
				int vBarW = vBar.getSize().x;

				max += vBarW;
			}
			if (max < tableSize) {
				hBar.setSelection(0);
				hBar.setEnabled(false);
				hBar.setVisible(false);
			} else {
				if (!hBar.isVisible()) {
					hBar.setVisible(true);
					hBar.setEnabled(true);
				}
				hBar.setValues(hBar.getSelection(), 0, max, tableSize, 50, tableSize);
			}
			if (vBarValid && hBar.isVisible()) {
				int hBarW = getScrollbarsMode() == SWT.NONE ? hBar.getSize().y : 0;

				vBar.setThumb(clientArea.height - hBarW);
				vBar.setMaximum(totalHeight - hBarW);
				vBar.setPageIncrement(vBar.getPageIncrement() - hBarW);
			}

		}
	}

	private int getScrollbarsMode() {
		if (hasGetScrollBarMode) {
			return cTable.getScrollbarsMode();
		}
		return SWT.NONE;
	}

	@Override
	protected void uiChangeColumnIndicator() {
		Utils.execSWTThread(new AERunnable() {

			@Override
			public void runSupport() {
				if (cHeaderArea != null && !cHeaderArea.isDisposed()) {
					cHeaderArea.redraw();
				}
			}
		});
	}

	public TableColumnCore getTableColumnByOffset(int mouseX) {
		int x = -clientArea.x;
		TableColumnCore[] visibleColumns = getVisibleColumns();
		for (TableColumnCore column : visibleColumns) {
			int w = column.getWidth();

			if (mouseX >= x && mouseX < x + w) {
				return column;
			}

			x += w;
		}
		return null;
	}

	// @see org.gudy.azureus2.ui.swt.views.table.TableViewSWT#getTableRow(int, int, boolean)
	public TableRowSWT getTableRow(int x, int y, boolean anyX) {
		return (TableRowSWT) getRow(anyX ? 2 : x, clientArea.y + y);
	}

	@Override
	public void setSelectedRows(TableRowCore[] newSelectionArray, boolean trigger) {
		super.setSelectedRows(newSelectionArray, trigger);

		boolean focusInSelection = false;
		for (TableRowCore row : newSelectionArray) {
			if (row == null) {
				continue;
			}
			if (row.equals(focusedRow)) {
				focusInSelection = true;
				break;
			}
		}
		if (!focusInSelection) {
			setFocusedRow(newSelectionArray.length == 0 ? null : newSelectionArray[0]);
		}
	}

	public void setRowSelected(final TableRowCore row, boolean selected,
			boolean trigger) {
		if (selected && !isSelected(row)) {
			setFocusedRow(row);
		}
		super.setRowSelected(row, selected, trigger);

		if (row instanceof TableRowSWT) {
			((TableRowSWT) row).setWidgetSelected(selected);
		}
	}

	public void editCell(TableColumnCore column, int row) {
		//TODO
	}

	public boolean isDragging() {
		return isDragging;
	}

	public TableViewSWTFilter<?> getSWTFilter() {
		return (TableViewSWTFilter<?>) filter;
	}

	public void openFilterDialog() {
		if (filter == null) {
			return;
		}
		SimpleTextEntryWindow entryWindow = new SimpleTextEntryWindow();
		entryWindow.initTexts("MyTorrentsView.dialog.setFilter.title", null,
				"MyTorrentsView.dialog.setFilter.text", new String[] {
					MessageText.getString(getTableID() + "View" + ".header")
				});
		entryWindow.setPreenteredText(filter.text, false);
		entryWindow.prompt();
		if (!entryWindow.hasSubmittedInput()) {
			return;
		}
		String message = entryWindow.getSubmittedInput();

		if (message == null) {
			message = "";
		}

		setFilterText(message);
	}

	public boolean isSingleSelection() {
		return !isMultiSelect;
	}

	public void expandColumns() {
		//TODO
	}

	@Override
	public void triggerTabViewsDataSourceChanged(boolean sendParent) {
		if (tvTabsCommon != null) {
			tvTabsCommon.triggerTabViewsDataSourceChanged(sendParent);
		}
	}
	
	public TableViewSWT_TabsCommon getTabsCommon() {
		return( tvTabsCommon );
	}
	
	@Override
	public void uiSelectionChanged(final TableRowCore[] newlySelectedRows,
			final TableRowCore[] deselectedRows) {
		//System.out.println("Redraw " + Debug.getCompressedStackTrace());
		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				for (TableRowCore row : deselectedRows) {
					row.invalidate();
					redrawRow((TableRowPainted) row, false);
				}
				for (TableRowCore row : newlySelectedRows) {
					row.invalidate();
					redrawRow((TableRowPainted) row, false);
				}
			}
		});
	}

	public void delete() {
		triggerLifeCycleListener(TableLifeCycleListener.EVENT_DESTROYED);

		if (tvTabsCommon != null) {
			tvTabsCommon.delete();
			tvTabsCommon = null;
		}

		TableStructureEventDispatcher.getInstance(tableID).removeListener(this);
		TableColumnManager tcManager = TableColumnManager.getInstance();
		if (tcManager != null) {
			tcManager.saveTableColumns(getDataSourceType(), tableID);
		}

		Utils.disposeSWTObjects(new Object[] {
			cTable
		});
		cTable = null;

		removeAllTableRows();
		configMan.removeParameterListener("ReOrder Delay", this);
		configMan.removeParameterListener("Graphics Update", this);
		configMan.removeParameterListener("Table.extendedErase", this);
		configMan.removeParameterListener("Table.headerHeight", this);
		Colors.getInstance().removeColorsChangedListener(this);

		super.delete();

		MessageText.removeListener(this);
	}

	@Override
	public void generate(IndentWriter writer) {
		super.generate(writer);

		if (tvTabsCommon != null) {
			tvTabsCommon.generate(writer);
		}
	}

	private Menu createMenu() {
		if (!isMenuEnabled()) {
			return null;
		}

		final Menu menu = new Menu(shell, SWT.POP_UP);
		cTable.addListener(SWT.MenuDetect, new Listener() {
			public void handleEvent(Event event) {
				if (event.widget == cHeaderArea) {
					menu.setData("inBlankArea", false);
					menu.setData("isHeader", true);

				} else {
					TableRowCore row = getTableRowWithCursor();
					boolean noRow = row == null;

					// If shell is not active, right clicking on a row will
					// result in a MenuDetect, but not a MouseDown or MouseUp
					if (!isSelected(row)) {
						setSelectedRows(new TableRowCore[] { row });
					}

					menu.setData("inBlankArea", noRow);
					menu.setData("isHeader", false);
				}
				Point pt = cHeaderArea.toControl(event.x, event.y);
				menu.setData("column", getTableColumnByOffset(pt.x));
			}
		});
		cHeaderArea.addListener(SWT.MenuDetect, new Listener() {
			public void handleEvent(Event event) {
				menu.setData("inBlankArea", false);
				menu.setData("isHeader", true);
				Point pt = cHeaderArea.toControl(event.x, event.y);
				menu.setData("column", getTableColumnByOffset(pt.x));
			}
		});
		MenuBuildUtils.addMaintenanceListenerForMenu(menu,
				new MenuBuildUtils.MenuBuilder() {
					public void buildMenu(Menu menu, MenuEvent menuEvent) {
						Object oIsHeader = menu.getData("isHeader");
						boolean isHeader = (oIsHeader instanceof Boolean)
								? ((Boolean) oIsHeader).booleanValue() : false;

						Object oInBlankArea = menu.getData("inBlankArea");
						boolean inBlankArea = (oInBlankArea instanceof Boolean)
								? ((Boolean) oInBlankArea).booleanValue() : false;

						TableColumnCore column = (TableColumnCore) menu.getData("column");

						if (isHeader) {
							tvSWTCommon.fillColumnMenu(menu, column, false);
						} else if (inBlankArea) {
							tvSWTCommon.fillColumnMenu(menu, column, true);
						} else {
							tvSWTCommon.fillMenu(menu, column);
						}

					}
				});

		return menu;
	}

	public void showColumnEditor() {
		if (tvSWTCommon != null) {
			tvSWTCommon.showColumnEditor();
		}
	}

	@Override
	public TableRowCore getFocusedRow() {
		return focusedRow;
	}

	public void setFocusedRow(TableRowCore row) {
		TableRowPainted oldFocusedRow = focusedRow;
		if (!(row instanceof TableRowPainted)) {
			row = null;
		}
		focusedRow = (TableRowPainted) row;
		if (focusedRow != null) {
			if (focusedRow.isVisible()
					&& focusedRow.getDrawOffset().y + focusedRow.getHeight() <= clientArea.y + clientArea.height
					&& focusedRow.getDrawOffset().y >= clientArea.y) {
				// redraw for BG color change
				redrawRow(focusedRow, false);
			} else {

				showRow(focusedRow);
			}
		}
		if (oldFocusedRow != null) {
			redrawRow(oldFocusedRow, false);
		}
	}

	public void showRow(final TableRowCore rowToShow) {
		// scrollto
		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				if (isDisposed()) {
					return;
				}
				
				if (rowToShow.isVisible()) {
					// draw offset is valid, use that to scroll
					int y = ((TableRowPainted) rowToShow).getDrawOffset().y;
					if (y + rowToShow.getHeight() > clientArea.y + clientArea.height) {
						y -= (clientArea.height - rowToShow.getHeight());
					}
					vBar.setSelection(y);
					swt_vBarChanged();
				} else {
					TableRowCore parentFocusedRow = rowToShow;
					while (parentFocusedRow.getParentRowCore() != null) {
						parentFocusedRow = parentFocusedRow.getParentRowCore();
					}
					TableRowCore[] rows = getRows();
					int y = 0;
					for (TableRowCore row : rows) {
						if (row == parentFocusedRow) {
							if (parentFocusedRow != rowToShow) {
								y += row.getHeight();
								TableRowCore[] subRowsWithNull = parentFocusedRow.getSubRowsWithNull();
								for (TableRowCore subrow : subRowsWithNull) {
									if (subrow == rowToShow) {
										break;
									}
									y += ((TableRowPainted) subrow).getFullHeight();
								}
							}
							break;
						}
						y += ((TableRowPainted) row).getFullHeight();
					}

					if (y + rowToShow.getHeight() > clientArea.y + clientArea.height) {
						y -= (clientArea.height - rowToShow.getHeight());
					}
					// y now at top of focused row
					vBar.setSelection(y);
					swt_vBarChanged();
				}
			}
		});
	}

	boolean qdRowHeightChanged = false;
	public void rowHeightChanged(final TableRowCore row, int oldHeight,
			int newHeight) {

		synchronized (heightChangeSync) {
  		totalHeight += (newHeight - oldHeight);
  		//System.out.println("Height delta: " + (newHeight - oldHeight) + ";ttl=" + totalHeight);
  
  		if (qdRowHeightChanged) {
  			return;
  		}
  		qdRowHeightChanged = true;
		}
		Utils.execSWTThreadLater(0, new AERunnable() {
			public void runSupport() {
				synchronized (heightChangeSync) {
					qdRowHeightChanged = false;
				}
				// if moving visibleRowsChanged(), make sure subrows being resized on
				// add trigger work properly
				visibleRowsChanged();
				swt_fixupSize();
			}
		});
	}

	public void redrawTable() {
		synchronized (TableViewPainted.this) {
			if (redrawTableScheduled) {
				return;
			}
			redrawTableScheduled = true;
		}

		visibleRowsChanged();
		Utils.execSWTThreadLater(0, new AERunnable() {
			public void runSupport() {
				synchronized (TableViewPainted.this) {
					redrawTableScheduled = false;
				}

				if (canvasImage != null && !canvasImage.isDisposed()) {
					canvasImage.dispose();
					canvasImage = null;
				}
				swt_calculateClientArea();
			}
		});
	}
	
	private String prettyIndex(TableRowCore row) {
		String s = "" + row.getIndex();
		if (row.getParentRowCore() != null) {
			s = row.getParentRowCore().getIndex() + "." + s;
		}
		return s;
	}

	public void redrawRow(final TableRowPainted row, final boolean immediateRedraw) {
		if (row == null) {
			return;
		}
		if (TableRowPainted.DEBUG_ROW_PAINT) {
			System.out.println(SystemTime.getCurrentTime() + "} redraw "
					+ prettyIndex(row) + " scheduled via " + Debug.getCompressedStackTrace());
		}
		Utils.execSWTThread(new AERunnable() {

			public void runSupport() {
				if (!isVisible || !row.isVisible()) {
					return;
				}
				Rectangle bounds = row.getDrawBounds();
				if (TableRowPainted.DEBUG_ROW_PAINT) {
					System.out.println(SystemTime.getCurrentTime() + "] redraw "
							+ prettyIndex(row) + " @ " + bounds);
				}
				if (bounds != null) {
					Composite composite = getComposite();
					if (composite != null && !composite.isDisposed()) {
						int h = isLastRow(row) ? composite.getSize().y - bounds.y
								: bounds.height;
						//row.debug("isLastRow?" + isLastRow(row) + ";" + bounds + ";" + h);
						swt_updateCanvasImage(new Rectangle(bounds.x, bounds.y, bounds.width, h), immediateRedraw);
					}
				}
			}
		});
	}

	public Object getSyncObject() {
		return lock;
	}
	
	@Override
	public boolean isTableSelected() {
		TableView tv = SelectedContentManager.getCurrentlySelectedTableView();
		return tv == this || (tv == null && isFocused);
	}
}
