/* * File : BufferedTableItem.java * Created : 24-Nov-2003 * By : parg * * Azureus - a Java Bittorrent client * * 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 */ package org.gudy.azureus2.ui.swt.components; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.*; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.ui.swt.Utils; import org.gudy.azureus2.ui.swt.views.table.TableItemOrTreeItem; import org.gudy.azureus2.ui.swt.views.table.TableOrTreeSWT; /** * A buffered Table Row. *<p> * We buffer certain properties of TableRow to save CPU cycles. For example, * foreground_colors is cached because TableItem.getForegroundColor is expensive * when there is no color set, and setForegroundColor is always expensive. *<p> * Text is not buffered as SWT does a good job of buffering it already. *<p> * * @note For Virtual tables, we can not set any visual properties until * SWT.SetData has been called. getData("SD") is set after SetData * call, and the row is invalidated. Thus, there is no need to set * any visual properties until the row #isVisible() * * @author parg<br> * @author TuxPaper (SWT.Virtual Stuff) */ public class BufferedTableRow { private static final int VALUE_SIZE_INC = 8; // for checkWidget(int) public final static int REQUIRE_TABLEITEM = 0; public final static int REQUIRE_TABLEITEM_INITIALIZED = 1; public final static int REQUIRE_VISIBILITY = 2; protected TableOrTreeSWT table; protected TableItemOrTreeItem item; protected Image[] image_values = new Image[0]; protected Color[] foreground_colors = new Color[0]; protected Color foreground; protected Color ourForeground; private Point ptIconSize = null; private Image imageBG; private int numSubItems; private boolean expanded; /** * Default constructor * * @param _table */ public BufferedTableRow(TableOrTreeSWT _table) { table = _table; item = null; } /** * Disposes of underlying SWT TableItem. If no TableItem has been * assigned to the row yet, an unused TableItem will be disposed of, if * available. * <p> * Disposing of fonts, colors and other resources are the responsibilty of * the caller. */ public void dispose() { if (table != null && !table.isDisposed() && Utils.isThisThreadSWT()) { if (!checkWidget(REQUIRE_TABLEITEM)) { // No assigned spot yet, or not our spot: // find a row with no TableRow data TableItemOrTreeItem[] items = table.getItems(); for (int i = items.length - 1; i >= 0; i--) { TableItemOrTreeItem item = items[i]; if (!item.isDisposed()) { Object itemRow = item.getData("TableRow"); if (itemRow == null || itemRow == this) { this.item = item; break; } } } } boolean itemNeedsDisposal = item != null && !item.isDisposed(); if (this.ourForeground != null && !this.ourForeground.isDisposed()) { // Even though we are going to dispose soon, set foreground to null // in case for some reason the OS gets a paint event in between // the color disposal and the item disposal if (itemNeedsDisposal) { item.setForeground(null); } this.ourForeground.dispose(); } if (itemNeedsDisposal) { item.dispose(); } else if (table.getItemCount() > 0) { System.err.println("No table row was found to dispose"); } } else { if (!Utils.isThisThreadSWT()) { System.err.println("Calling BufferedTableRow.dispose on non-SWT thread!"); System.err.println(Debug.getStackTrace(false, false)); } } item = null; } /** * Sets the receiver's image at a column. * * @param index the column index * @param new_image the new image */ public void setImage( int index, Image new_image ) { if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return; if ( index >= image_values.length ){ int new_size = Math.max( index+1, image_values.length+VALUE_SIZE_INC ); Image[] new_images = new Image[new_size]; System.arraycopy( image_values, 0, new_images, 0, image_values.length ); image_values = new_images; } Image image = image_values[index]; if ( new_image == image ){ return; } image_values[index] = new_image; item.setImage( index, new_image ); } public Image getImage(int index) { if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return null; return item.getImage(index); } /** * Checks if the widget is valid * * @param checkFlags REQUIRE_* flags (OR'd) * * @return True: Ok; False: Not ok */ public boolean checkWidget(int checkFlags) { boolean bWidgetOk = !table.isDisposed() && item != null && !item.isDisposed() && item.getData("TableRow") == this; final boolean bCheckVisibility = (checkFlags & REQUIRE_VISIBILITY) > 0; final boolean bCheckInitialized = (checkFlags & REQUIRE_TABLEITEM_INITIALIZED) > 0; if (bWidgetOk && bCheckInitialized) { bWidgetOk = (table.getStyle() & SWT.VIRTUAL) == 0 || item.getData("SD") != null; } if (bWidgetOk && bCheckVisibility) { if (_isVisible()) { // Caller assumes that a visible item can be modified, so // make sure we initialize it. if (!bCheckInitialized && (table.getStyle() & SWT.VIRTUAL) != 0 && item.getData("SD") == null) { // This is catch is temporary for SWT 3212, because there are cases where // it says it isn't disposed, when it really almost is try { item.setData("SD", "1"); } catch (NullPointerException badSWT) { } setIconSize(ptIconSize); invalidate(); } } else { bWidgetOk = false; } } return bWidgetOk; } private boolean _isVisible() { // Least time consuming checks first if (inPaintItem()) { return true; } if (!table.isVisible()) { return false; } //if (table.getData("inPaintItem") != null) { // System.out.println(table.indexOf((Widget) table.getData("inPaintItem")) + ";" + table.indexOf(item)); // System.out.println(Debug.getCompressedStackTrace()); //} Rectangle bounds = getBounds(0); if (bounds == null) { return false; } return table.getClientArea().contains(bounds.x, bounds.y); /* int index = table.indexOf(item); if (index == -1) return false; int iTopIndex = table.getTopIndex(); if (index < iTopIndex) return false; int iBottomIndex = Utils.getTableBottomIndex(table, iTopIndex); if (index > iBottomIndex) return false; //System.out.println("i-" + index + ";top=" + iTopIndex + ";b=" + iBottomIndex); return true; */ } public Color getForeground() { if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return null; if (ourForeground == null && isSelected()) { return table.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT); } return( item.getForeground()); } private static final boolean DEBUG_SET_FOREGROUND = System.getProperty("debug.setforeground") != null; 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); } } public void setForeground( Color c ) { setForegroundDebug("setForeground(Color)", c); if (foreground == null && c == null) {return;} if (foreground != null && foreground.equals(c)) return; foreground = c; if (this.ourForeground != null) { if (!this.ourForeground.isDisposed()) { this.ourForeground.dispose(); } this.ourForeground = null; } if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return; item.setForeground(foreground); } public void setForeground(int red, int green, int blue) { setForegroundDebug("setForeground(r,g,b)", red, green, blue); if (red == -1 && green == -1 && blue == -1) { this.setForeground(null); return; } RGB newRGB = new RGB(red, green, blue); if (this.foreground != null && this.foreground.getRGB().equals(newRGB)) { return; } // Hopefully it is OK to just assume it is safe to dispose of the colour, // since we're expecting it to match this.foreground. Color newColor = new Color(getTable().getDisplay(), newRGB); if (checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) { item.setForeground(newColor); } if (ourForeground != null && !ourForeground.isDisposed()) { ourForeground.dispose(); } this.foreground = newColor; this.ourForeground = newColor; } public boolean setForeground( int index, Color new_color ) { setForegroundDebug("setForeground(int,Color)", new_color); if ( index >= foreground_colors.length ){ int new_size = Math.max( index+1, foreground_colors.length+VALUE_SIZE_INC ); Color[] new_colors = new Color[new_size]; System.arraycopy( foreground_colors, 0, new_colors, 0, foreground_colors.length ); foreground_colors = new_colors; } Color value = foreground_colors[index]; if ( new_color == value ){ return false; } if ( new_color != null && value != null && new_color.equals( value )){ return false; } foreground_colors[index] = new_color; if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) { return true; } try { item.setForeground(index, new_color); } catch (NoSuchMethodError e) { /* Ignore for Pre 3.0 SWT.. */ } return true; } public Color getForeground(int index) { if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return null; if (index >= foreground_colors.length) { return getForeground(); } if (foreground_colors[index] == null && isSelected()) { Color systemColor = table.getDisplay().getSystemColor( table.isFocusControl() ? SWT.COLOR_LIST_SELECTION_TEXT : SWT.COLOR_WIDGET_FOREGROUND); return systemColor; } return foreground_colors[index]; } protected String getText( int index ) { if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return ""; // SWT >= 3041(Win),3014(GTK),3002(Carbon) and returns "" if range check // fails return item.getText(index); } /** * @param index * @param new_value * @return true if the item has been updated */ public boolean setText( int index, String new_value ) { if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return false; if (index < 0 || index >= table.getColumnCount()) return false; if (new_value == null) new_value = ""; if (item.getText(index).equals(new_value)) return false; item.setText( index, new_value ); return true; } public Rectangle getBounds(int index) { if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return null; Rectangle r = item.getBounds(index); if (r == null || r.width == 0 || r.height == 0) return null; return r; } protected TableOrTreeSWT getTable() { return table; } public Color getBackground() { if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return null; if (isSelected()) { // XXX This isn't the right color on Cocoa when in focus return table.getDisplay().getSystemColor( table.isFocusControl() ? SWT.COLOR_LIST_SELECTION : SWT.COLOR_WIDGET_BACKGROUND); } return item.getBackground(); } /** * The Index is this item's the position in list. * * @return Item's Position */ public int getIndex() { if (!checkWidget(REQUIRE_TABLEITEM)) return -1; return table.indexOf(item); } public boolean isSelected() { if (!checkWidget(REQUIRE_TABLEITEM)) return false; return table.isSelected(item); } public void setSelected(final boolean bSelected) { Utils.execSWTThread(new AERunnable() { public void runSupport() { swt_setSelected(bSelected); } }); } private void swt_setSelected(boolean bSelected) { if (!checkWidget(REQUIRE_TABLEITEM)) return; if (bSelected) table.select(item); else table.deselect(item); } public boolean setTableItem(int newIndex, boolean isVisible) { TableItemOrTreeItem newRow; try { newRow = table.getItem(newIndex); } catch (IllegalArgumentException er) { if (item == null || item.isDisposed()) { return false; } item = null; return true; } catch (Throwable e) { System.out.println("setTableItem(" + newIndex + ", " + isVisible + ")"); e.printStackTrace(); return false; } return setTableItem(newRow, isVisible); } public boolean setTableItem(TableItemOrTreeItem newRow, boolean isVisible) { if (newRow.isDisposed()) { Debug.out("newRow disposed from " + Debug.getCompressedStackTrace()); return false; } if (newRow.equals(item)) { if (newRow.getData("TableRow") == this) { return false; } } //if (!newRow.getParent().equalsTableOrTree(table)) // return false; if (!isVisible) { // Q&D, clear out.. we'll fill it correctly when it's visible if (newRow.getData("TableRow") != null) { newRow.setData("TableRow", null); table.deselect(newRow); return true; } //System.out.println("quickclear " + table.indexOf(newRow)); return false; } //System.out.println("slowset " + table.indexOf(newRow)); boolean lastItemExisted = item != null && !item.isDisposed(); // can't base newRowHadItem on "TableRow" as we clear it in "unlinking" stage //boolean newRowHadItem = newRow.getData("TableRow") != null; if (newRow.getData("SD") != null) { } else { newRow.setData("SD", "1"); setIconSize(ptIconSize); } newRow.setForeground(foreground); int numColumns = table.getColumnCount(); for (int i = 0; i < numColumns; i++) { try { //newRow.setImage(i, null); newRow.setForeground(i, i < foreground_colors.length ? foreground_colors[i] : null); } catch (NoSuchMethodError e) { /* Ignore for Pre 3.0 SWT.. */ } } try { newRow.setData("TableRow", this); } catch (Exception e) { e.printStackTrace(); System.out.println("Disposed? " + newRow.isDisposed()); if (!newRow.isDisposed()) { System.out.println("TR? " + newRow.getData("TableRow")); System.out.println("SD? " + newRow.getData("SD")); } } image_values = new Image[0]; //foreground_colors = new Color[0]; //foreground = null; // unlink old item from tablerow if (lastItemExisted && item.getData("TableRow") == this && !newRow.equals(item)) { item.setData("TableRow", null); table.deselect(item); /* int numColumns = table.getColumnCount(); for (int i = 0; i < numColumns; i++) { try { //item.setImage(i, null); item.setForeground(i, null); } catch (NoSuchMethodError e) { } } */ } item = newRow; setSubItemCount(numSubItems); item.setExpanded(expanded); // Need to execute (de)select later, because if we are in a paint event // that paint event may be reporting a row selected before the selection // event fired (Cocoa 3650 fires paint before select yet the row is marked // selected) Utils.execSWTThreadLater(0, new AERunnable() { public void runSupport() { if (table.isDisposed() || item == null) { return; } if (item.isDisposed() || item.getData("TableRow") != BufferedTableRow.this) { return; } // select/deselect takes less time than a table.isSelected (tree) if (isSelected()) { table.select(item); } else { table.deselect(item); } } }); if (isVisible && !inPaintItem()) { //if (newRowHadItem && isVisible && !inPaintItem()) { //invalidate(); // skip the visibility check and SWT thread wrapping of invalidate Rectangle r = item.getBounds(0); table.redraw(0, r.y, table.getClientArea().width, r.height, true); } return true; } public boolean setHeight(int iHeight) { return setIconSize(new Point(1, iHeight)); } public boolean setIconSize(Point pt) { ptIconSize = pt; if (pt == null) return false; if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED)) return false; Image oldImage = item.getImage(0); if (oldImage != null) { Rectangle r = oldImage.getBounds(); if (r.width == pt.x && r.height == pt.y) return false; } // set row height by setting image Image image = new Image(item.getDisplay(), pt.x, pt.y); item.setImage(0, image); item.setImage(0, null); image.dispose(); return true; } /** * Whether the row is currently visible to the user * * @return visibility */ public boolean isVisible() { return checkWidget(REQUIRE_VISIBILITY); } /** * Overridable function that is called when row needs invalidation. * */ public void invalidate() { Utils.execSWTThread(new AERunnable() { public void runSupport() { if (!checkWidget(REQUIRE_TABLEITEM_INITIALIZED | REQUIRE_VISIBILITY)) return; Rectangle r = item.getBounds(0); table.redraw(0, r.y, table.getClientArea().width, r.height, true); } }); } public void setBackgroundImage(Image image) { if (imageBG != null && !imageBG.isDisposed()) { imageBG.dispose(); } imageBG = image; } public Image getBackgroundImage() { return imageBG; } public void setSubItemCount(int i) { numSubItems = i; if (item != null) { Utils.execSWTThread(new AERunnable() { public void runSupport() { if (item.isDisposed()) { return; } if (item.getItemCount() != numSubItems) { item.setItemCount(numSubItems); Rectangle r = item.getBounds(0); if (r != null) { table.redraw(0, r.y, table.getClientArea().width, r.height, true); } } TableItemOrTreeItem[] items = item.getItems(); for (TableItemOrTreeItem subItem : items) { subItem.setData("TableRow", null); } } }); } } public int getSubItemCount() { return numSubItems; } public TableItemOrTreeItem[] getSubItems() { return table.getItems(); } public void setExpanded(boolean b) { expanded = b; if (item != null) { item.setExpanded(b); } } public boolean isExpanded() { return expanded; } /** * @return * * @since 4.4.0.5 */ public boolean inPaintItem() { if (item != null && !item.isDisposed() && item.equals(table.getData("inPaintItem"))) { return true; } return false; } public boolean isVisibleNoSWT() { return true; // assume the worst } }