/*
* File : TableRowImpl.java
* Originally TorrentRow.java, and changed to be more generic by TuxPaper
*
* Copyright (C) 2004, 2005, 2006 Aelitis SAS, 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.
*
* 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
*
* AELITIS, SAS au capital de 46,603.30 euros,
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*/
package org.gudy.azureus2.ui.swt.views.table.impl;
import java.util.*;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Rectangle;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.ui.tables.*;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.components.BufferedTableRow;
import org.gudy.azureus2.ui.swt.mainwindow.Colors;
import org.gudy.azureus2.ui.swt.views.table.*;
import com.aelitis.azureus.ui.common.table.*;
import com.aelitis.azureus.ui.common.table.impl.TableViewImpl;
/** Represents an entire row in a table. Stores each cell belonging to the
* row and handles refreshing them.
*
* @see TableCellImpl
*
* @author TuxPaper
* 2005/Oct/07: Moved TableItem.SetData("TableRow", ..) to
* BufferedTableRow
* 2005/Oct/07: Removed all calls to BufferedTableRoe.getItem()
*/
public class TableRowImpl<COREDATASOURCE>
extends BufferedTableRow
implements TableRowSWT
{
/**
* List of cells in this row.
* They are not stored in display order.
* Not written to after initializer
*/
private Map<String, TableCellCore> mTableCells;
private Object coreDataSource;
private Object pluginDataSource;
private boolean bDisposed;
private boolean bSetNotUpToDateLastRefresh = false;
private TableView<COREDATASOURCE> tableView;
private static AEMonitor this_mon = new AEMonitor("TableRowImpl");
private ArrayList<TableRowMouseListener> mouseListeners;
private boolean wasShown = false;
private Map<String, Object> dataList;
private int lastIndex = -1;
private int fontStyle;
private int alpha = 255;
private TableRowCore parentRow;
private TableRowImpl<Object>[] subRows;
private AEMonitor mon_SubRows = new AEMonitor("subRows");
private TableColumnCore[] columnsSorted;
private boolean bSkipFirstColumn;
// XXX add rowVisuallyupdated bool like in ListRow
public TableRowImpl(TableRowCore parentRow, TableView<COREDATASOURCE> tv,
TableOrTreeSWT table, TableColumnCore[] columnsSorted, String sTableID,
Object dataSource, int index, boolean bSkipFirstColumn) {
super(table);
this.parentRow = parentRow;
this.tableView = tv;
coreDataSource = dataSource;
bDisposed = false;
lastIndex = index;
mTableCells = new LightHashMap<String, TableCellCore>(columnsSorted.length,
1);
// create all the cells for the column
for (int i = 0; i < columnsSorted.length; i++) {
if (columnsSorted[i] == null) {
continue;
}
if (!columnsSorted[i].handlesDataSourceType(getDataSource(false).getClass())) {
mTableCells.put(columnsSorted[i].getName(), null);
continue;
}
//System.out.println(dataSource + ": " + tableColumns[i].getName() + ": " + tableColumns[i].getPosition());
TableCellImpl cell = new TableCellImpl(TableRowImpl.this,
columnsSorted[i], bSkipFirstColumn ? i + 1 : i);
mTableCells.put(columnsSorted[i].getName(), cell);
//if (i == 10) cell.bDebug = true;
}
}
/**
* Default constructor
*
* @param table
* @param sTableID
* @param columnsSorted
* @param dataSource
* @param bSkipFirstColumn
*/
public TableRowImpl(TableView<COREDATASOURCE> tv, TableOrTreeSWT table,
TableColumnCore[] columnsSorted, Object dataSource,
boolean bSkipFirstColumn) {
super(table);
this.tableView = tv;
this.columnsSorted = columnsSorted;
coreDataSource = dataSource;
this.bSkipFirstColumn = bSkipFirstColumn;
bDisposed = false;
mTableCells = new LightHashMap<String, TableCellCore>(columnsSorted.length,
1);
// create all the cells for the column
for (int i = 0; i < columnsSorted.length; i++) {
if (columnsSorted[i] == null) {
continue;
}
//System.out.println(dataSource + ": " + tableColumns[i].getName() + ": " + tableColumns[i].getPosition());
TableCellImpl cell = new TableCellImpl(TableRowImpl.this,
columnsSorted[i], bSkipFirstColumn ? i + 1 : i);
mTableCells.put(columnsSorted[i].getName(), cell);
//if (i == 10) cell.bDebug = true;
}
}
public boolean isValid() {
if (bDisposed || mTableCells == null) {
return true;
}
boolean valid = true;
for (TableCell cell : mTableCells.values()) {
if (cell != null && cell.isValid()) {
return false;
}
}
return valid;
}
/** TableRow Implementation which returns the
* associated plugin object for the row. Core Column Object who wish to get
* core data source must re-class TableRow as TableRowCore and use
* getDataSource(boolean)
*
* @see TableRowCore.getDataSource()
*/
public Object getDataSource() {
return getDataSource(false);
}
public String getTableID() {
return tableView.getTableID();
}
public TableCell getTableCell(String field) {
if (bDisposed || mTableCells == null) {
return null;
}
return mTableCells.get(field);
}
public void addMouseListener(TableRowMouseListener listener) {
try {
this_mon.enter();
if (mouseListeners == null) {
mouseListeners = new ArrayList<TableRowMouseListener>(1);
}
mouseListeners.add(listener);
} finally {
this_mon.exit();
}
}
public void removeMouseListener(TableRowMouseListener listener) {
try {
this_mon.enter();
if (mouseListeners == null) {
return;
}
mouseListeners.remove(listener);
} finally {
this_mon.exit();
}
}
public void invokeMouseListeners(TableRowMouseEvent event) {
ArrayList<TableRowMouseListener> listeners = mouseListeners;
if (listeners == null) {
return;
}
for (int i = 0; i < listeners.size(); i++) {
try {
TableRowMouseListener l = listeners.get(i);
l.rowMouseTrigger(event);
} catch (Throwable e) {
Debug.printStackTrace(e);
}
}
}
/* Start Core-Only functions */
///////////////////////////////
public void delete() {
this_mon.enter();
try {
if (bDisposed) {
return;
}
if (TableViewImpl.DEBUGADDREMOVE) {
System.out.println((table.isDisposed() ? "" : table.getData("Name"))
+ " row delete; index=" + getIndex());
}
for (TableCellCore cell : mTableCells.values()) {
try {
if (cell != null) {
cell.dispose();
}
} catch (Exception e) {
Debug.out(e);
}
}
//setForeground((Color) null);
bDisposed = true;
} finally {
this_mon.exit();
}
}
public List<TableCellCore> refresh(boolean bDoGraphics) {
if (bDisposed) {
return Collections.EMPTY_LIST;
}
boolean bVisible = isVisible();
return refresh(bDoGraphics, bVisible);
}
public List<TableCellCore> refresh(boolean bDoGraphics, boolean bVisible) {
// If this were called from a plugin, we'd have to refresh the sorted column
// even if we weren't visible
List<TableCellCore> list = Collections.EMPTY_LIST;
if (bDisposed) {
return list;
}
if (!bVisible) {
if (!bSetNotUpToDateLastRefresh) {
setUpToDate(false);
bSetNotUpToDateLastRefresh = true;
}
return list;
}
bSetNotUpToDateLastRefresh = false;
//System.out.println(SystemTime.getCurrentTime() + "refresh " + getIndex() + ";vis=" + bVisible);
((TableViewSWTImpl<COREDATASOURCE>) tableView).invokeRefreshListeners(this);
for (TableCellCore cell : mTableCells.values()) {
if (cell == null || cell.isDisposed()) {
continue;
}
TableColumn column = cell.getTableColumn();
//System.out.println(column);
if (column != tableView.getSortColumn()
&& !tableView.isColumnVisible(column)) {
//System.out.println("skip " + column);
continue;
}
boolean changed = cell.refresh(bDoGraphics, bVisible);
if (changed) {
if (list == Collections.EMPTY_LIST) {
list = new ArrayList<TableCellCore>(mTableCells.size());
}
list.add(cell);
}
}
//System.out.println();
return list;
}
public void locationChanged(int iStartColumn) {
if (bDisposed || !isVisible()) {
return;
}
for (TableCellCore cell : mTableCells.values()) {
if (cell != null && cell.getTableColumn().getPosition() > iStartColumn) {
cell.locationChanged();
}
}
}
public TableCellCore getTableCellCore(String name) {
if (bDisposed || mTableCells == null) {
return null;
}
return mTableCells.get(name);
}
/**
* @param name
* @return
*/
public TableCellSWT getTableCellSWT(String name) {
if (bDisposed || mTableCells == null) {
return null;
}
TableCellCore cell = mTableCells.get(name);
if (cell instanceof TableCellSWT) {
return (TableCellSWT) cell;
}
return null;
}
public Object getDataSource(boolean bCoreObject) {
if (bDisposed) {
return null;
}
if (bCoreObject) {
return coreDataSource;
}
if (pluginDataSource != null) {
return pluginDataSource;
}
pluginDataSource = PluginCoreUtils.convert(coreDataSource, bCoreObject);
return pluginDataSource;
}
public boolean isRowDisposed() {
return bDisposed;
}
/* (non-Javadoc)
* @see org.gudy.azureus2.ui.swt.components.BufferedTableRow#getIndex()
*/
public int getIndex() {
if (bDisposed) {
return -1;
}
if (lastIndex >= 0) {
if (parentRow != null) {
return lastIndex;
}
TableRowCore row = ((TableViewSWTImpl<COREDATASOURCE>) tableView).getRowQuick(lastIndex);
if (row == this) {
return lastIndex;
}
}
// don't set directly to lastIndex, so setTableItem will eventually do
// its job
return tableView.indexOf(this);
//return super.getIndex();
}
public int getRealIndex() {
return super.getIndex();
}
public boolean setTableItem(int newIndex, boolean isVisible) {
if (bDisposed) {
System.out.println("XXX setTI: bDisposed from "
+ Debug.getCompressedStackTrace());
return false;
}
int maxItemShown = tableView.getMaxItemShown();
if (newIndex > maxItemShown) {
//System.out.println((item == null ? null : "" + table.indexOf(item)) + ":" + newIndex + ":" + isVisible + ":" + tableView.getMaxItemShown());
tableView.setMaxItemShown(newIndex);
if (!isVisible) {
return false;
}
}
boolean changedIndex = lastIndex != newIndex || item == null;
//if (getRealIndex() != newIndex) {
// ((TableViewSWTImpl)tableView).debug("sTI " + newIndex + "; via " + Debug.getCompressedStackTrace(4));
//}
boolean changedSWTRow = !changedIndex ? false : super.setTableItem(newIndex, isVisible);
//if (changedSWTRow) {
// System.out.println((item == null ? null : "" + table.indexOf(item)) + ":" + newIndex + ":" + isVisible + ":" + tableView.getMaxItemShown());
//}
if (changedIndex) {
//System.out.println("row " + newIndex + " from " + lastIndex + ";" + tableView.isRowVisible(this) + ";" + changedSWTRow);
lastIndex = newIndex;
}
//boolean rowVisible = tableView.isRowVisible(this);
setShown(isVisible, changedSWTRow);
if (changedSWTRow && isVisible) {
redraw();
//invalidate();
//refresh(true, true);
setUpToDate(false);
}
return changedSWTRow;
}
public boolean setTableItem(int newIndex) {
if (!Utils.isThisThreadSWT()) {
return false;
}
return setTableItem(newIndex, true);
}
private static final boolean DEBUG_SET_FOREGROUND = System.getProperty("debug.setforeground") != null;
private Object[] subDataSources;
private static void setForegroundDebug(String method_sig, Color c) {
if (DEBUG_SET_FOREGROUND && c != null) {
Debug.out("BufferedTableRow " + method_sig + " -> " + c);
}
}
private static void setForegroundDebug(String method_sig, int r, int g, int b) {
if (DEBUG_SET_FOREGROUND && (!(r == 0 && g == 0 && b == 0))) {
Debug.out("BufferedTableRow " + method_sig + " -> " + r + "," + g + ","
+ b);
}
}
// @see org.gudy.azureus2.ui.swt.components.BufferedTableRow#setForeground(int, int, int)
public void setForeground(int r, int g, int b) {
setForegroundDebug("setForeground(r, g, b)", r, g, b);
// Don't need to set when not visible
if (!isVisible()) {
return;
}
super.setForeground(r, g, b);
}
// @see org.gudy.azureus2.ui.swt.components.BufferedTableRow#setForeground(org.eclipse.swt.graphics.Color)
public boolean setForeground(final Color c) {
setForegroundDebug("setForeground(Color)", c);
// Don't need to set when not visible
if (!isVisible()) {
return true;
}
Utils.execSWTThread(new AERunnable() {
public void runSupport() {
TableRowImpl.this.setForegroundInSWTThread(c);
}
});
return true;
}
private void setForegroundInSWTThread(Color c) {
setForegroundDebug("setForegroundInSWTThread(Color)", c);
if (!isVisible()) {
return;
}
super.setForeground(c);
}
// @see org.gudy.azureus2.plugins.ui.tables.TableRow#setForeground(int[])
public void setForeground(int[] rgb) {
if (rgb == null || rgb.length < 3) {
setForeground((Color) null);
return;
}
setForeground(rgb[0], rgb[1], rgb[2]);
}
public void setForegroundToErrorColor() {
this.setForeground(Colors.colorError);
}
/* (non-Javadoc)
* @see org.gudy.azureus2.ui.swt.components.BufferedTableRow#invalidate()
*/
public void invalidate() {
invalidate(false);
}
public void invalidate(boolean mustRefresh) {
super.invalidate();
if (bDisposed) {
return;
}
for (TableCellCore cell : mTableCells.values()) {
if (cell != null) {
cell.invalidate(mustRefresh);
}
}
}
public void setUpToDate(boolean upToDate) {
if (bDisposed) {
return;
}
for (TableCellCore cell : mTableCells.values()) {
if (cell != null) {
cell.setUpToDate(upToDate);
}
}
}
public void redraw() {
redraw(false);
}
// @see com.aelitis.azureus.ui.common.table.TableRowCore#redraw()
public void redraw(boolean doChildren) {
// this will call paintItem which may call refresh
Rectangle bounds = getBounds();
table.redraw(bounds.x, bounds.y, bounds.width, bounds.height, false);
}
public String toString() {
String result = "TableRowImpl@" + Integer.toHexString(hashCode()) + "/#"
+ lastIndex;
return result;
}
// @see com.aelitis.azureus.ui.common.table.TableRowCore#getView()
public TableView<COREDATASOURCE> getView() {
return tableView;
}
public boolean isShown() {
return wasShown;
}
/**
* @param b
*
* @since 3.0.4.3
*/
public boolean setShown(boolean b, boolean force) {
if (bDisposed) {
return false;
}
if (b == wasShown && !force) {
return false;
}
wasShown = b;
for (TableCellCore cell : mTableCells.values()) {
if (cell != null) {
cell.invokeVisibilityListeners(b
? TableCellVisibilityListener.VISIBILITY_SHOWN
: TableCellVisibilityListener.VISIBILITY_HIDDEN, true);
}
}
return true;
/* Don't need to refresh; paintItem will trigger a refresh on
* !cell.isUpToDate()
*
if (b) {
refresh(b, true);
}
/**/
}
public boolean isMouseOver() {
return tableView.getTableRowWithCursor() == this;
}
public void setData(String id, Object data) {
synchronized (this) {
if (dataList == null) {
dataList = new HashMap<String, Object>(1);
}
if (data == null) {
dataList.remove(id);
} else {
dataList.put(id, data);
}
}
}
public Object getData(String id) {
synchronized (this) {
return dataList == null ? null : dataList.get(id);
}
}
// @see org.gudy.azureus2.ui.swt.views.table.TableRowSWT#getBounds()
public Rectangle getBounds() {
Rectangle bounds = getBounds(1);
if (bounds == null) {
return new Rectangle(0, 0, 0, 0);
}
Rectangle tableBounds = table.getClientArea();
bounds.x = tableBounds.x;
bounds.width = tableBounds.width;
return bounds;
}
// @see org.gudy.azureus2.ui.swt.views.table.TableRowSWT#setFontStyle(int)
public boolean setFontStyle(int style) {
if (fontStyle == style) {
return false;
}
fontStyle = style;
invalidate();
return true;
}
// @see com.aelitis.azureus.ui.common.table.TableRowCore#setAlpha(int)
public boolean setAlpha(int alpha) {
if (this.alpha == alpha) {
return false;
}
this.alpha = alpha;
invalidate();
return true;
}
// @see org.gudy.azureus2.ui.swt.views.table.TableRowSWT#getAlpha()
public int getAlpha() {
return alpha;
}
// @see org.gudy.azureus2.ui.swt.views.table.TableRowSWT#getFontStyle()
public int getFontStyle() {
return fontStyle;
}
// @see org.gudy.azureus2.ui.swt.components.BufferedTableRow#isVisible()
public boolean isVisible() {
return tableView.isRowVisible(this);
//return Utils.execSWTThreadWithBool("isVisible", new AERunnableBoolean() {
// public boolean runSupport() {
// return TableRowImpl.super.isVisible();
// }
//}, 1000);
}
public boolean isVisibleNoSWT() {
return tableView.isRowVisible(this);
}
// @see org.gudy.azureus2.ui.swt.components.BufferedTableRow#setSelected(boolean)
public void setSelected(boolean selected) {
if (tableView instanceof TableViewSWT) {
((TableViewSWT<COREDATASOURCE>) tableView).setRowSelected(this, selected, true);
}
}
public void setWidgetSelected(boolean selected) {
super.setSelected(selected);
}
// @see org.gudy.azureus2.ui.swt.components.BufferedTableRow#isSelected()
public boolean isSelected() {
return tableView.isSelected(this);
/*
return Utils.execSWTThreadWithBool("isSelected", new AERunnableBoolean() {
public boolean runSupport() {
return TableRowImpl.super.isSelected();
}
}, 1000);
*/
}
// @see org.gudy.azureus2.ui.swt.components.BufferedTableRow#setSubItemCount(int)
@SuppressWarnings("rawtypes")
public void setSubItemCount(final int count) {
super.setSubItemCount(count);
if (count == getSubItemCount()) {
if (count == 0 || (subRows != null && subRows[0] == null)) {
return;
}
}
mon_SubRows.enter();
try {
subRows = new TableRowImpl[count];
for (int i = 0; i < count; i++) {
//subRows[i] = new TableRowImpl(this, tableView, table, columnsSorted,
// getTableID(), null, i, bSkipFirstColumn);
subRows[i] = null;
}
} finally {
mon_SubRows.exit();
}
}
@SuppressWarnings("rawtypes")
public void setSubItems(Object[] datasources) {
this.subDataSources = datasources;
super.setSubItemCount(datasources.length);
mon_SubRows.enter();
try {
subRows = new TableRowImpl[datasources.length];
for (int i = 0; i < datasources.length; i++) {
//subRows[i] = new TableRowImpl(this, tableView, table, columnsSorted,
// getTableID(), datasources[i], i, bSkipFirstColumn);
subRows[i] = null;
}
} finally {
mon_SubRows.exit();
}
}
public TableRowCore linkSubItem(int indexOf) {
mon_SubRows.enter();
try {
if (indexOf >= subRows.length) {
return null;
}
TableRowImpl<Object> subRow = subRows[indexOf];
if (subRow == null) {
subRows[indexOf] = subRow = new TableRowImpl(this, tableView, table,
columnsSorted, getTableID(), subDataSources[indexOf], indexOf,
bSkipFirstColumn);
}
TableItemOrTreeItem subItem = item.getItem(indexOf);
subRow.setTableItem(subItem, true);
return subRow;
} finally {
mon_SubRows.exit();
}
}
public TableRowCore[] getSubRowsWithNull() {
mon_SubRows.enter();
try {
TableRowCore[] copyOf = new TableRowCore[subRows.length];
System.arraycopy(subRows, 0, copyOf, 0, subRows.length);
return copyOf;
} finally {
mon_SubRows.exit();
}
}
public TableRowCore getSubRow(int pos) {
mon_SubRows.enter();
try {
int i = -1;
for (TableRowCore row : subRows) {
if (row != null) {
i++;
if (i == pos) {
return row;
}
}
}
return null;
} finally {
mon_SubRows.exit();
}
}
public void removeSubRow(final Object datasource) {
Utils.execSWTThreadLater(0, new AERunnable() {
public void runSupport() {
swt_removeSubRow(datasource);
}
});
}
public void swt_removeSubRow(Object datasource) {
if (datasource instanceof TableRowImpl) {
removeSubRow(((TableRowImpl) datasource).getDataSource());
}
mon_SubRows.enter();
try {
if (subDataSources == null || subDataSources.length == 0
|| subDataSources.length != subRows.length) {
return;
}
for (int i = 0; i < subDataSources.length; i++) {
Object ds = subDataSources[i];
if (ds == datasource) { // use .equals instead?
TableRowImpl rowToDel = subRows[i];
TableRowImpl[] newSubRows = new TableRowImpl[subRows.length - 1];
System.arraycopy(subRows, 0, newSubRows, 0, i);
System.arraycopy(subRows, i + 1, newSubRows, i, subRows.length - i
- 1);
subRows = newSubRows;
Object[] newDatasources = new Object[subRows.length];
System.arraycopy(subDataSources, 0, newDatasources, 0, i);
System.arraycopy(subDataSources, i + 1, newDatasources, i,
subDataSources.length - i - 1);
subDataSources = newDatasources;
rowToDel.dispose();
rowToDel.delete();
super.setSubItemCount(subRows.length);
break;
}
}
} finally {
mon_SubRows.exit();
}
}
// @see com.aelitis.azureus.ui.common.table.TableRowCore#isInPaintItem()
public boolean isInPaintItem() {
return super.inPaintItem();
}
// @see com.aelitis.azureus.ui.common.table.TableRowCore#getParentRowCore()
public TableRowCore getParentRowCore() {
return parentRow;
}
public TableItemOrTreeItem getItem() {
return super.item;
}
public int getFullHeight() {
// TODO: should include subitems
return getBounds(1).height;
}
public void setSortColumn(String columnID) {
// ignored
}
public TableCellCore getSortColumnCell(String hint) {
if (bDisposed || mTableCells == null) {
return null;
}
return mTableCells.get(hint);
}
}