/******************************************************************************* * Copyright (c) 2002, 2015 Innoopract Informationssysteme GmbH and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Innoopract Informationssysteme GmbH - initial API and implementation * EclipseSource - ongoing development ******************************************************************************/ package org.eclipse.swt.widgets; import static org.eclipse.swt.internal.widgets.MarkupUtil.isMarkupEnabledFor; import static org.eclipse.swt.internal.widgets.MarkupValidator.isValidationDisabledFor; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.rap.rwt.internal.lifecycle.WidgetLCA; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.internal.SerializableCompatibility; import org.eclipse.swt.internal.widgets.IItemHolderAdapter; import org.eclipse.swt.internal.widgets.ITreeItemAdapter; import org.eclipse.swt.internal.widgets.IWidgetColorAdapter; import org.eclipse.swt.internal.widgets.IWidgetFontAdapter; import org.eclipse.swt.internal.widgets.MarkupValidator; import org.eclipse.swt.internal.widgets.treeitemkit.TreeItemLCA; /** * Instances of this class represent a selectable user interface object that * represents a hierarchy of tree items in a tree widget. * <dl> * <dt><b>Styles:</b></dt> * <dd>(none)</dd> * <dt><b>Events:</b></dt> * <dd>(none)</dd> * </dl> * <p> * IMPORTANT: This class is <em>not</em> intended to be subclassed. * </p> * * @since 1.0 */ public class TreeItem extends Item { private final TreeItem parentItem; final Tree parent; TreeItem[] items; int itemCount; private transient ITreeItemAdapter treeItemAdapter; int index; private Data[] data; private Font font; private boolean expanded; private boolean checked; private Color background; private Color foreground; private boolean grayed; int depth; private boolean cached; private int flatIndex; /** * Constructs a new instance of this class given its parent (which must be a * <code>Tree</code> or a <code>TreeItem</code>) and a style value describing * its behavior and appearance. The item is added to the end of the items * maintained by its parent. * <p> * The style value is either one of the style constants defined in class * <code>SWT</code> which is applicable to instances of this class, or must be * built by <em>bitwise OR</em>'ing together (that is, using the * <code>int</code> "|" operator) two or more of those <code>SWT</code> style * constants. The class description lists the style constants that are * applicable to the class. Style bits are also inherited from superclasses. * </p> * * @param parent a tree control which will be the parent of the new instance * (cannot be null) * @param style the style of control to construct * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the parent</li> * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed * subclass</li> * </ul> * @see SWT * @see Widget#checkSubclass * @see Widget#getStyle */ public TreeItem( Tree parent, int style ) { this( parent, null, style, parent == null ? 0 : parent.getItemCount(), true ); } /** * Constructs a new instance of this class given its parent (which must be a * <code>Tree</code> or a <code>TreeItem</code>), a style value describing its * behavior and appearance, and the index at which to place it in the items * maintained by its parent. * <p> * The style value is either one of the style constants defined in class * <code>SWT</code> which is applicable to instances of this class, or must be * built by <em>bitwise OR</em>'ing together (that is, using the * <code>int</code> "|" operator) two or more of those <code>SWT</code> style * constants. The class description lists the style constants that are * applicable to the class. Style bits are also inherited from superclasses. * </p> * * @param parent a tree control which will be the parent of the new instance * (cannot be null) * @param style the style of control to construct * @param index the zero-relative index to store the receiver in its parent * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> * <li>ERROR_INVALID_RANGE - if the index is not between 0 and * the number of elements in the parent (inclusive)</li> * </ul> * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the parent</li> * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed * subclass</li> * </ul> * @see SWT * @see Widget#checkSubclass * @see Widget#getStyle */ public TreeItem( Tree parent, int style, int index ) { this( parent, null, style, index, true ); } /** * Constructs a new instance of this class given its parent (which must be a * <code>Tree</code> or a <code>TreeItem</code>) and a style value describing * its behavior and appearance. The item is added to the end of the items * maintained by its parent. * <p> * The style value is either one of the style constants defined in class * <code>SWT</code> which is applicable to instances of this class, or must be * built by <em>bitwise OR</em>'ing together (that is, using the * <code>int</code> "|" operator) two or more of those <code>SWT</code> style * constants. The class description lists the style constants that are * applicable to the class. Style bits are also inherited from superclasses. * </p> * * @param parentItem a tree control which will be the parent of the new * instance (cannot be null) * @param style the style of control to construct * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the parent</li> * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed * subclass</li> * </ul> * @see SWT * @see Widget#checkSubclass * @see Widget#getStyle */ public TreeItem( TreeItem parentItem, int style ) { this( parentItem == null ? null : parentItem.parent, parentItem, style, parentItem == null ? 0 : parentItem.itemCount, true ); } /** * Constructs a new instance of this class given its parent (which must be a * <code>Tree</code> or a <code>TreeItem</code>), a style value describing its * behavior and appearance, and the index at which to place it in the items * maintained by its parent. * <p> * The style value is either one of the style constants defined in class * <code>SWT</code> which is applicable to instances of this class, or must be * built by <em>bitwise OR</em>'ing together (that is, using the * <code>int</code> "|" operator) two or more of those <code>SWT</code> style * constants. The class description lists the style constants that are * applicable to the class. Style bits are also inherited from superclasses. * </p> * * @param parentItem a tree control which will be the parent of the new * instance (cannot be null) * @param style the style of control to construct * @param index the zero-relative index to store the receiver in its parent * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> * <li>ERROR_INVALID_RANGE - if the index is not between 0 and * the number of elements in the parent (inclusive)</li> * </ul> * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the parent</li> * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed * subclass</li> * </ul> * @see SWT * @see Widget#checkSubclass * @see Widget#getStyle */ public TreeItem( TreeItem parentItem, int style, int index ) { this( parentItem == null ? null : parentItem.parent, parentItem, style, index, true ); } TreeItem( Tree parent, TreeItem parentItem, int style, int index, boolean create ) { super( parent, style ); this.parent = parent; this.parentItem = parentItem; this.index = index; if( parentItem != null ) { depth = parentItem.depth + 1; } parent.invalidateFlatIndex(); setEmpty(); if( create ) { int numberOfItems; if( parentItem != null ) { numberOfItems = parentItem.itemCount; } else { // If there is no parent item, get the next index of the tree numberOfItems = parent.getItemCount(); } // check range if( index < 0 || index > numberOfItems ) { error( SWT.ERROR_INVALID_RANGE ); } if( parentItem != null ) { parentItem.createItem( this, index ); } else { parent.createItem( this, index ); } parent.updateScrollBars(); } } private void setEmpty() { items = new TreeItem[ 4 ]; } private void createItem( TreeItem item, int index ) { if( itemCount == items.length ) { /* * Grow the array faster when redraw is off or the table is not visible. * When the table is painted, the items array is resized to be smaller to * reduce memory usage. */ boolean small = /* drawCount == 0 && */isVisible(); int length = small ? items.length + 4 : Math.max( 4, items.length * 3 / 2 ); TreeItem[] newItems = new TreeItem[ length ]; System.arraycopy( items, 0, newItems, 0, items.length ); items = newItems; } System.arraycopy( items, index, items, index + 1, itemCount - index ); items[ index ] = item; itemCount++; adjustItemIndices( index ); } private void destroyItem( int index ) { itemCount--; if( itemCount == 0 ) { setEmpty(); } else { System.arraycopy( items, index + 1, items, index, itemCount - index ); items[ itemCount ] = null; } adjustItemIndices( index ); } private void adjustItemIndices( int start ) { for( int i = start; i < itemCount; i++ ) { if( items[ i ] != null ) { items[ i ].index = i; } } } @Override @SuppressWarnings("unchecked") public <T> T getAdapter( Class<T> adapter ) { if( adapter == IItemHolderAdapter.class ) { return ( T )new CompositeItemHolder(); } if( adapter == IWidgetFontAdapter.class || adapter == IWidgetColorAdapter.class || adapter == ITreeItemAdapter.class ) { if( treeItemAdapter == null ) { treeItemAdapter = new TreeItemAdapter(); } return ( T )treeItemAdapter; } if( adapter == WidgetLCA.class ) { return ( T )TreeItemLCA.INSTANCE; } return super.getAdapter( adapter ); } ////////////////////////// // Parent/child relations /** * Returns the receiver's parent, which must be a <code>Tree</code>. * * @return the receiver's parent * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public Tree getParent() { checkWidget(); return parent; } /** * Returns the receiver's parent item, which must be a <code>TreeItem</code> * or null when the receiver is a root. * * @return the receiver's parent item * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public TreeItem getParentItem() { checkWidget(); return parentItem; } ///////////////// // Getter/Setter /** * Sets the expanded state of the receiver. * <p> * * @param expanded the new expanded state * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setExpanded( boolean expanded ) { checkWidget(); if( this.expanded != expanded && ( !expanded || itemCount > 0 ) ) { this.expanded = expanded; if( !expanded ) { updateSelection(); } markCached(); parent.invalidateFlatIndex(); parent.updateScrollBars(); parent.updateAllItems(); } } /** * Returns <code>true</code> if the receiver is expanded, and false otherwise. * <p> * * @return the expanded state * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public boolean getExpanded() { checkWidget(); return expanded; } /** * Returns a rectangle describing the receiver's size and location relative to * its parent. * * @return the receiver's bounding rectangle * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public Rectangle getBounds() { return getBounds( 0 ); } /** * Returns a rectangle describing the receiver's size and location relative to * its parent at a column in the tree. * * @param index the index that specifies the column * @return the receiver's bounding column rectangle * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public Rectangle getBounds( int index ) { checkWidget(); if( !parent.checkData( this, this.index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Rectangle result = new Rectangle( 0, 0, 0, 0 ); if( isVisible() && isValidColumn( index ) ) { int left = parent.getVisualCellLeft( this, index ); int width = parent.getVisualCellWidth( this, index ); result = new Rectangle( left, getItemTop(), width, parent.getItemHeight() ); } return result; } /** * Returns the background color at the given column index in the receiver. * * @param index the column index * @return the background color * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public Color getBackground( int index ) { checkWidget(); if( !parent.checkData( this, this.index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Color result; if( hasData( index ) && data[ index ].background != null ) { result = data[ index ].background; } else if( background == null ) { result = parent.getBackground(); } else { result = background; } return result; } /** * Returns the font that the receiver will use to paint textual information * for the specified cell in this item. * * @param index the column index * @return the receiver's font * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public Font getFont( int index ) { checkWidget(); if( !parent.checkData( this, this.index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Font result; if( hasData( index ) && data[ index ].font != null ) { result = data[ index ].font; } else if( font == null ) { result = parent.getFont(); } else { result = font; } return result; } /** * Returns the foreground color at the given column index in the receiver. * * @param index the column index * @return the foreground color * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public Color getForeground( int index ) { checkWidget(); if( !parent.checkData( this, this.index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Color result; if( hasData( index ) && data[ index ].foreground != null ) { result = data[ index ].foreground; } else if( foreground == null ) { result = parent.getForeground(); } else { result = foreground; } return result; } /** * Sets the background color at the given column index in the receiver to the * color specified by the argument, or to the default system color for the * item if the argument is null. * * @param index the column index * @param color the new color (or null) * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed * </li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setBackground( int index, Color color ) { checkWidget(); if( color != null && color.isDisposed() ) { error( SWT.ERROR_INVALID_ARGUMENT ); } int count = Math.max( 1, parent.getColumnCount() ); if( index >= 0 && index < count ) { ensureData( index, count ); if( !equals( data[ index ].background, color ) ) { data[ index ].background = color; markCached(); parent.redraw(); } } } /** * Sets the font that the receiver will use to paint textual information for * the specified cell in this item to the font specified by the argument, or * to the default font for that kind of control if the argument is null. * * @param index the column index * @param font the new font (or null) * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed * </li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setFont( int index, Font font ) { checkWidget(); if( font != null && font.isDisposed() ) { error( SWT.ERROR_INVALID_ARGUMENT ); } int count = Math.max( 1, parent.getColumnCount() ); if( index >= 0 && index < count ) { ensureData( index, count ); if( !equals( font, data[ index ].font ) ) { data[ index ].font = font; data[ index ].preferredWidthBuffer = Data.UNKNOWN_WIDTH; markCached(); parent.redraw(); } } } /** * Sets the foreground color at the given column index in the receiver to the * color specified by the argument, or to the default system color for the * item if the argument is null. * * @param index the column index * @param color the new color (or null) * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed * </li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setForeground( int index, Color color ) { checkWidget(); if( color != null && color.isDisposed() ) { error( SWT.ERROR_INVALID_ARGUMENT ); } int count = Math.max( 1, parent.getColumnCount() ); if( index >= 0 && index < count ) { ensureData( index, count ); if( !equals( data[ index ].foreground, color ) ) { data[ index ].foreground = color; markCached(); parent.redraw(); } } } /** * Sets the font that the receiver will use to paint textual information for * this item to the font specified by the argument, or to the default font for * that kind of control if the argument is null. * * @param font the new font (or null) * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed * </li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setFont( Font font ) { checkWidget(); if( font != null && font.isDisposed() ) { error( SWT.ERROR_INVALID_ARGUMENT ); } if( !equals( this.font, font ) ) { this.font = font; markCached(); if( parent.getColumnCount() == 0 ) { parent.updateScrollBars(); } parent.redraw(); } } /** * Returns the font that the receiver will use to paint textual information * for this item. * * @return the receiver's font * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public Font getFont() { checkWidget(); if( !parent.checkData( this, index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Font result; if( font == null ) { result = parent.getFont(); } else { result = font; } return result; } /** * Sets the receiver's background color to the color specified by the * argument, or to the default system color for the item if the argument is * null. * * @param value the new color (or null) * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed * </li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setBackground( Color value ) { checkWidget(); if( value != null && value.isDisposed() ) { error( SWT.ERROR_INVALID_ARGUMENT ); } if( !equals( background, value ) ) { background = value; markCached(); } } /** * Returns the receiver's background color. * * @return the background color * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public Color getBackground() { checkWidget(); if( !parent.checkData( this, index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Color result; if( background == null ) { result = parent.getBackground(); } else { result = background; } return result; } /** * Returns the foreground color that the receiver will use to draw. * * @return the receiver's foreground color * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public Color getForeground() { checkWidget(); if( !parent.checkData( this, index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Color result; if( foreground == null ) { result = parent.getForeground(); } else { result = foreground; } return result; } /** * Sets the receiver's foreground color to the color specified by the * argument, or to the default system color for the item if the argument is * null. * * @param value the new color (or null) * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed * </li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setForeground( Color value ) { checkWidget(); if( value != null && value.isDisposed() ) { error( SWT.ERROR_INVALID_ARGUMENT ); } if( !equals( foreground, value ) ) { foreground = value; markCached(); } } /** * Sets the checked state of the receiver. * <p> * * @param checked the new checked state * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setChecked( boolean checked ) { checkWidget(); if( ( parent.getStyle() & SWT.CHECK ) != 0 ) { if( this.checked != checked ) { this.checked = checked; markCached(); } } } /** * Returns <code>true</code> if the receiver is checked, and false otherwise. * When the parent does not have the <code>CHECK style, return false. * <p> * * @return the checked state * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ public boolean getChecked() { checkWidget(); if( !parent.checkData( this, index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } return checked; } /** * Sets the grayed state of the checkbox for this item. This state change only * applies if the Tree was created with the SWT.CHECK style. * * @param grayed the new grayed state of the checkbox * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ public void setGrayed( boolean grayed ) { checkWidget(); if( ( parent.getStyle() & SWT.CHECK ) != 0 ) { if( this.grayed != grayed ) { this.grayed = grayed; markCached(); } } } /** * Returns <code>true</code> if the receiver is grayed, and false otherwise. * When the parent does not have the <code>CHECK style, return false. * <p> * * @return the grayed state of the checkbox * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ public boolean getGrayed() { checkWidget(); if( !parent.checkData( this, index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } return grayed; } /** * Returns the receiver's text, which will be an empty string if it has never * been set. * * @return the receiver's text * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ @Override public String getText() { checkWidget(); return getText( 0 ); } /** * Returns the text stored at the given column index in the receiver, or empty * string if the text has not been set. * * @param index the column index * @return the text stored at the given column index in the receiver * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ public String getText( int index ) { checkWidget(); if( !parent.checkData( this, this.index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } return getTextWithoutMaterialize( index ); } String getTextWithoutMaterialize( int index ) { String result = ""; if( hasData( index ) ) { result = data[ index ].text; } return result; } /** * Returns a rectangle describing the size and location * relative to its parent of the text at a column in the * tree. * * @param index the index that specifies the column * @return the receiver's bounding text rectangle * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 1.3 */ public Rectangle getTextBounds( int index ) { checkWidget(); if( !parent.checkData( this, this.index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Rectangle result = new Rectangle( 0, 0, 0, 0 ); if( isVisible() && isValidColumn( index ) ) { result.x = parent.getVisualTextLeft( this, index ); result.y = getItemTop(); result.width = parent.getVisualTextWidth( this, index ); result.height = parent.getItemHeight(); } return result; } /** * Sets the text for multiple columns in the tree. * * @param value the array of new strings * @exception IllegalArgumentException <ul> <li>ERROR_NULL_ARGUMENT - if the * text is null</li> </ul> * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ public void setText( String[] value ) { checkWidget(); if( value == null ) { error( SWT.ERROR_NULL_ARGUMENT ); } for( int i = 0; i < value.length; i++ ) { if( value[ i ] != null ) { setText( i, value[ i ] ); } } } /** * Sets the receiver's text. * * @param text the new text * @exception IllegalArgumentException <ul> <li>ERROR_NULL_ARGUMENT - if the * text is null</li> </ul> * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ @Override public void setText( String text ) { checkWidget(); setText( 0, text ); } /** * Sets the receiver's text at a column * * @param index the column index * @param text the new text * @exception IllegalArgumentException <ul> <li>ERROR_NULL_ARGUMENT - if the * text is null</li> </ul> * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ public void setText( int index, String text ) { checkWidget(); if( text == null ) { error( SWT.ERROR_NULL_ARGUMENT ); } if( isMarkupEnabledFor( parent ) && !isValidationDisabledFor( parent ) ) { MarkupValidator.getInstance().validate( text ); } int count = Math.max( 1, parent.getColumnCount() ); if( index >= 0 && index < count ) { ensureData( index, count ); if( !text.equals( data[ index ].text ) ) { data[ index ].text = text; data[ index ].preferredWidthBuffer = Data.UNKNOWN_WIDTH; markCached(); if( parent.getColumnCount() == 0 ) { parent.updateScrollBars(); } parent.redraw(); } } } /** * Returns the receiver's image if it has one, or null * if it does not. * * @return the receiver's image * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 1.4 */ @Override public Image getImage() { checkWidget(); return getImage( 0 ); } /** * Returns the image stored at the given column index in the receiver, or null * if the image has not been set or if the column does not exist. * * @param index the column index * @return the image stored at the given column index in the receiver * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ public Image getImage( int index ) { checkWidget(); if( !parent.checkData( this, this.index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Image result = null; if( hasData( index ) ) { result = data[ index ].image; } return result; } /** * Returns a rectangle describing the size and location relative to its parent * of an image at a column in the tree. * * @param index the index that specifies the column * @return the receiver's bounding image rectangle * @exception SWTException <ul> <li>ERROR_WIDGET_DISPOSED - if the receiver * has been disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if * not called from the thread that created the receiver</li> * </ul> */ public Rectangle getImageBounds( int index ) { checkWidget(); if( !parent.checkData( this, this.index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } Rectangle result = null; int validColumnCount = Math.max( 1, parent.columnHolder.size() ); if( ( 0 <= index && index < validColumnCount ) ) { result = new Rectangle( 0, 0, 0, 0 ); Point size = parent.getItemImageSize( index ); result.width = size.x; result.height = size.y; result.x = parent.getVisualCellLeft( this, index ); // Note: The left cell-padding is visually ignored for the tree-column if( !parent.isTreeColumn( index ) ) { result.x += parent.getCellPadding().left; } // SWT behavior on windows gives the correct y value // On Gtk the y value is always the same (eg. 1) // we emulate the default windows behavior here result.y = getItemTop(); } else { result = new Rectangle( 0, 0, 0, 0 ); } return result; } void clear() { data = null; checked = false; grayed = false; foreground = null; background = null; font = null; clearCached(); parent.updateScrollBars(); } /** * Clears the item at the given zero-relative index in the receiver. The text, * icon and other attributes of the item are set to the default value. If the * tree was created with the <code>SWT.VIRTUAL</code> style, these attributes * are requested again as needed. * * @param index the index of the item to clear * @param recursive <code>true</code> if all child items of the indexed item * should be cleared recursively, and <code>false</code> otherwise * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE - if the index is not between 0 and * the number of elements in the list minus 1 (inclusive)</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> * @see SWT#VIRTUAL * @see SWT#SetData */ public void clear( int index, boolean recursive ) { checkWidget(); if( index < 0 || index >= itemCount ) { error( SWT.ERROR_INVALID_RANGE ); } TreeItem item = items[ index ]; if( item != null ) { item.clear(); if( recursive ) { item.clearAll( true, false ); } if( parent.isVirtual() ) { parent.redraw(); } } } @Override public void setImage( Image image ) { checkWidget(); setImage( 0, image ); } /** * Sets the receiver's image at a column. * * @param index the column index * @param image the new image * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the image has been disposed * </li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setImage( int index, Image image ) { checkWidget(); if( image != null && image.isDisposed() ) { error( SWT.ERROR_INVALID_ARGUMENT ); } int count = Math.max( 1, parent.getColumnCount() ); if( index >= 0 && index < count ) { ensureData( index, count ); if( !equals( data[ index ].image, image ) ) { parent.updateColumnImageCount( index, data[ index ].image, image ); data[ index ].image = image; data[ index ].preferredWidthBuffer = Data.UNKNOWN_WIDTH; parent.updateItemImageSize( image ); markCached(); if( parent.getColumnCount() == 0 ) { parent.updateScrollBars(); } parent.redraw(); } } } /** * Sets the image for multiple columns in the tree. * * @param value the array of new images * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the array of images is null</li> * <li>ERROR_INVALID_ARGUMENT - if one of the images has been * disposed</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setImage( Image[] value ) { checkWidget(); if( value == null ) { error( SWT.ERROR_NULL_ARGUMENT ); } for( int i = 0; i < value.length; i++ ) { if( value[ i ] != null && value[ i ].isDisposed() ) { error( SWT.ERROR_INVALID_ARGUMENT ); } } for( int i = 0; i < value.length; i++ ) { if( value[ i ] != null ) { setImage( i, value[ i ] ); } } } /** * Clears all the items in the receiver. The text, icon and other attributes * of the items are set to their default values. If the tree was created with * the <code>SWT.VIRTUAL</code> style, these attributes are requested again as * needed. * * @param recursive <code>true</code> if all child items should be cleared * recursively, and <code>false</code> otherwise * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> * @see SWT#VIRTUAL * @see SWT#SetData */ public void clearAll( boolean recursive ) { clearAll( recursive, true ); } void clearAll( boolean recursive, boolean doVisualUpdate ) { checkWidget(); for( int i = 0; i < itemCount; i++ ) { TreeItem item = items[ i ]; if( item != null ) { item.clear(); if( recursive ) { item.clearAll( true, false ); } } } if( parent.isVirtual() && doVisualUpdate ) { parent.redraw(); } } //////////////////////////////////////// // Methods to maintain (sub-) TreeItems /** * Returns a (possibly empty) array of <code>TreeItem</code>s which are the * direct item children of the receiver. * <p> * Note: This is not the actual structure used by the receiver to maintain its * list of items, so modifying the array will not affect the receiver. * </p> * * @return the receiver's items * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public TreeItem[] getItems() { checkWidget(); TreeItem[] result = new TreeItem[ itemCount ]; if( parent.isVirtual() ) { for( int i = 0; i < itemCount; i++ ) { result[ i ] = _getItem( i ); } } else { System.arraycopy( items, 0, result, 0, itemCount ); } return result; } TreeItem _getItem( int index ) { if( parent.isVirtual() && items[ index ] == null ) { items[ index ] = new TreeItem( parent, this, SWT.NONE, index, false ); } return items[ index ]; } TreeItem[] getCreatedItems() { TreeItem[] result; if( parent.isVirtual() ) { int count = 0; for( int i = 0; i < itemCount; i++ ) { if( items[ i ] != null ) { count++; } } result = new TreeItem[ count ]; count = 0; for( int i = 0; i < itemCount; i++ ) { if( items[ i ] != null ) { result[ count ] = items[ i ]; count++; } } } else { result = new TreeItem[ itemCount ]; System.arraycopy( items, 0, result, 0, itemCount ); } return result; } /** * Returns the item at the given, zero-relative index in the receiver. Throws * an exception if the index is out of range. * * @param index the index of the item to return * @return the item at the given index * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE - if the index is not between 0 and * the number of elements in the list minus 1 (inclusive)</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public TreeItem getItem( int index ) { checkWidget(); if( index < 0 || index >= itemCount ) { SWT.error( SWT.ERROR_INVALID_RANGE ); } return _getItem( index ); } /** * Returns the number of items contained in the receiver that are direct item * children of the receiver. * * @return the number of items * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public int getItemCount() { checkWidget(); if( !parent.checkData( this, index ) ) { error( SWT.ERROR_WIDGET_DISPOSED ); } return itemCount; } /** * Searches the receiver's list starting at the first item (index 0) until an * item is found that is equal to the argument, and returns the index of that * item. If no item is found, returns -1. * * @param item the search item * @return the index of the item * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the tool item is null</li> * <li>ERROR_INVALID_ARGUMENT - if the tool item has been * disposed</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public int indexOf( TreeItem item ) { checkWidget(); if( item == null ) { SWT.error( SWT.ERROR_NULL_ARGUMENT ); } if( item.isDisposed() ) { SWT.error( SWT.ERROR_INVALID_ARGUMENT ); } return item.parentItem == this ? item.index : -1; } /** * Removes all of the items from the receiver. * <p> * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void removeAll() { checkWidget(); for( int i = itemCount - 1; i >= 0; i-- ) { if( items[ i ] != null ) { items[ i ].dispose(); } else { itemCount--; } } setEmpty(); } /** * Sets the number of child items contained in the receiver. * * @param count the number of items * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed * </li> <li>ERROR_THREAD_INVALID_ACCESS - if not called from the * thread that created the receiver</li> * </ul> */ public void setItemCount( int count ) { checkWidget(); int oldItemCount = itemCount; int newItemCount = Math.max( 0, count ); if( newItemCount != oldItemCount ) { int index = oldItemCount - 1; while( index >= newItemCount ) { TreeItem item = items[ index ]; if( item != null && !item.isDisposed() ) { item.dispose(); } index--; } int length = Math.max( 4, ( newItemCount + 3 ) / 4 * 4 ); TreeItem[] newItems = new TreeItem[ length ]; System.arraycopy( items, 0, newItems, 0, Math.min( newItemCount, itemCount ) ); items = newItems; if( !parent.isVirtual() ) { for( int i = oldItemCount; i < newItemCount; i++ ) { new TreeItem( this, SWT.NONE, i ); } } itemCount = newItemCount; parent.invalidateFlatIndex(); parent.updateScrollBars(); parent.redraw(); } } ///////////////////////////////// // Methods to dispose of the item @Override final void releaseChildren() { for( int i = items.length - 1; i >= 0; i-- ) { if( items[ i ] != null ) { items[ i ].dispose(); } } } @Override final void releaseParent() { if( parentItem != null ) { parentItem.destroyItem( index ); } else { parent.destroyItem( index ); } if( !parent.isInDispose() ) { parent.invalidateFlatIndex(); parent.removeFromSelection( this ); parent.updateScrollBars(); } super.releaseParent(); } ////////////////// // helping methods private boolean isValidColumn( int index ) { int columnCount = parent.getColumnCount(); return ( columnCount == 0 && index == 0 ) || ( index >= 0 && index < columnCount ); } private boolean isVisible() { return getParentItem() == null || getParentItem().getExpanded(); } int getItemTop() { int headerHeight = parent.getHeaderHeight(); int itemHeight = parent.getItemHeight(); return headerHeight + ( getFlatIndex() - parent.getTopItemIndex() ) * itemHeight; } int getFlatIndex() { if( !parent.isFlatIndexValid ) { parent.updateAllItems(); } return flatIndex; } void setFlatIndex( int flatIndex ) { this.flatIndex = flatIndex; } boolean hasPreferredWidthBuffer( int index ) { return getPreferredWidthBuffer( index ) != Data.UNKNOWN_WIDTH; } int getPreferredWidthBuffer( int index ) { int result = Data.UNKNOWN_WIDTH; if( hasData( index ) ) { result = data[ index ].preferredWidthBuffer; } return result; } void setPreferredWidthBuffer( int index, int preferredWidthBuffer ) { int count = Math.max( 1, parent.getColumnCount() ); ensureData( index, count ); data[ index ].preferredWidthBuffer = preferredWidthBuffer; } void clearPreferredWidthBuffers( boolean recursive ) { int count = Math.max( 1, parent.getColumnCount() ); for( int i = 0; i < count; i++ ) { if( hasData( i ) ) { data[ i ].preferredWidthBuffer = Data.UNKNOWN_WIDTH; } } if( recursive && expanded ) { for( int i = 0; i < itemCount; i++ ) { TreeItem item = items[ i ]; if( item != null ) { item.clearPreferredWidthBuffers( recursive ); } } } } int getInnerHeight() { int innerHeight = itemCount * parent.getItemHeight(); for( int i = 0; i < itemCount; i++ ) { TreeItem item = items[ i ]; if( item != null && item.getExpanded() ) { innerHeight += item.getInnerHeight(); } } return innerHeight; } void markCached() { if( parent.isVirtual() ) { cached = true; } } private void clearCached() { if( parent.isVirtual() ) { cached = false; } } boolean isCached() { return parent.isVirtual() ? cached : true; } private static boolean equals( Object object1, Object object2 ) { boolean result; if( object1 == object2 ) { result = true; } else if( object1 == null ) { result = false; } else { result = object1.equals( object2 ); } return result; } //////////////////////////////////////// // Manage item data (texts, images, etc) private void ensureData( int index, int columnCount ) { if( data == null ) { data = new Data[ columnCount ]; } else if( data.length < columnCount ) { Data[] newData = new Data[ columnCount ]; System.arraycopy( data, 0, newData, 0, data.length ); data = newData; } if( data[ index ] == null ) { data[ index ] = new Data(); } } private boolean hasData( int index ) { return data != null && index >= 0 && index < data.length && data[ index ] != null; } final void shiftData( int index ) { if( data != null && data.length > index && parent.getColumnCount() > 1 ) { Data[] newData = new Data[ data.length + 1 ]; System.arraycopy( data, 0, newData, 0, index ); int offSet = data.length - index; System.arraycopy( data, index, newData, index + 1, offSet ); data = newData; } for( int i = 0; i < itemCount; i++ ) { if( items[ i ] != null ) { items[ i ].shiftData( index ); } } } final void removeData( int index ) { if( data != null && data.length > index && parent.getColumnCount() > 1 ) { Data[] newData = new Data[ data.length - 1 ]; System.arraycopy( data, 0, newData, 0, index ); int offSet = data.length - index - 1; System.arraycopy( data, index + 1, newData, index, offSet ); data = newData; } for( int i = 0; i < itemCount; i++ ) { if( items[ i ] != null ) { items[ i ].removeData( index ); } } } private void updateSelection() { TreeItem[] selection = parent.getSelection(); List<TreeItem> selectedItems = new ArrayList<>( Arrays.asList( selection ) ); if( deselectChildren( selectedItems ) ) { if( ( parent.getStyle() & SWT.SINGLE ) != 0 ) { selectedItems.add( this ); } parent.setSelection( selectedItems.toArray( new TreeItem[ 0 ] ) ); Event event = new Event(); event.item = this; parent.notifyListeners( SWT.Selection, event ); } } boolean deselectChildren( List<TreeItem> selectedItems ) { boolean result = false; for( int i = 0; i < itemCount; i++ ) { TreeItem item = items[ i ]; if( item != null ) { if( selectedItems.contains( item ) ) { selectedItems.remove( item ); result = true; } if( item.deselectChildren( selectedItems ) ) { result = true; } } } return result; } //////////////// // Inner classes private final class TreeItemAdapter implements ITreeItemAdapter, IWidgetFontAdapter, IWidgetColorAdapter { @Override public boolean isParentDisposed() { Widget itemParent = parentItem == null ? parent : parentItem; return itemParent.isDisposed(); } @Override public Color getUserBackground() { return background; } @Override public Color getUserForeground() { return foreground; } @Override public Font getUserFont() { return font; } @Override public String[] getTexts() { int columnCount = Math.max( 1, getParent().getColumnCount() ); String[] result = null; if( data != null ) { for( int i = 0; i < data.length; i++ ) { String text = data[ i ] == null ? "" : data[ i ].text; if( !"".equals( text ) ) { if( result == null ) { result = new String[ columnCount ]; Arrays.fill( result, "" ); } result[ i ] = text; } } } return result; } @Override public Image[] getImages() { int columnCount = Math.max( 1, getParent().getColumnCount() ); Image[] result = null; if( data != null ) { for( int i = 0; i < data.length; i++ ) { Image image = data[ i ] == null ? null : data[ i ].image; if( image != null ) { if( result == null ) { result = new Image[ columnCount ]; } result[ i ] = image; } } } return result; } @Override public Color[] getCellBackgrounds() { int columnCount = Math.max( 1, getParent().getColumnCount() ); Color[] result = null; if( data != null ) { for( int i = 0; i < data.length; i++ ) { Color background = data[ i ] == null ? null : data[ i ].background; if( background != null ) { if( result == null ) { result = new Color[ columnCount ]; } result[ i ] = background; } } } return result; } @Override public Color[] getCellForegrounds() { int columnCount = Math.max( 1, getParent().getColumnCount() ); Color[] result = null; if( data != null ) { for( int i = 0; i < data.length; i++ ) { Color foreground = data[ i ] == null ? null : data[ i ].foreground; if( foreground != null ) { if( result == null ) { result = new Color[ columnCount ]; } result[ i ] = foreground; } } } return result; } @Override public Font[] getCellFonts() { int columnCount = Math.max( 1, getParent().getColumnCount() ); Font[] result = null; if( data != null ) { for( int i = 0; i < data.length; i++ ) { Font font = data[ i ] == null ? null : data[ i ].font; if( font != null ) { if( result == null ) { result = new Font[ columnCount ]; } result[ i ] = font; } } } return result; } } private final class CompositeItemHolder implements IItemHolderAdapter<Item> { @Override public void add( Item item ) { throw new UnsupportedOperationException(); } @Override public void insert( Item item, int index ) { throw new UnsupportedOperationException(); } @Override public void remove( Item item ) { throw new UnsupportedOperationException(); } @Override public Item[] getItems() { TreeItem[] items = getCreatedItems(); Item[] result = new Item[ items.length ]; System.arraycopy( items, 0, result, 0, items.length ); return result; } } private static final class Data implements SerializableCompatibility { static final int UNKNOWN_WIDTH = -1; String text = ""; // Note [fappel]: Yourkit analysis with the UI workbench testsuite showed an extensive // appearance of preferred width calculations. Buffering the preferred width // speeds up the suite on my machine to 1/4th of the time needed without buffering. int preferredWidthBuffer = UNKNOWN_WIDTH; Image image; Font font; Color background; Color foreground; } }