| /******************************************************************************* |
| * Copyright (c) 2002, 2013 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 java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import org.eclipse.rwt.RWT; |
| import org.eclipse.rwt.graphics.Graphics; |
| import org.eclipse.rwt.internal.textsize.TextSizeUtil; |
| import org.eclipse.rwt.internal.theme.IThemeAdapter; |
| import org.eclipse.rwt.lifecycle.WidgetUtil; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.events.TreeEvent; |
| import org.eclipse.swt.events.TreeListener; |
| 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.events.SetDataEvent; |
| import org.eclipse.swt.internal.widgets.ICellToolTipAdapter; |
| import org.eclipse.swt.internal.widgets.ICellToolTipProvider; |
| import org.eclipse.swt.internal.widgets.IItemHolderAdapter; |
| import org.eclipse.swt.internal.widgets.ITreeAdapter; |
| import org.eclipse.swt.internal.widgets.ItemHolder; |
| import org.eclipse.swt.internal.widgets.MarkupValidator; |
| import org.eclipse.swt.internal.widgets.WidgetTreeVisitor; |
| import org.eclipse.swt.internal.widgets.WidgetTreeVisitor.AllWidgetTreeVisitor; |
| import org.eclipse.swt.internal.widgets.treekit.TreeThemeAdapter; |
| |
| |
| /** |
| * Instances of this class provide a selectable user interface object that |
| * displays a hierarchy of items and issues notification when an item in the |
| * hierarchy is selected. |
| * <p> |
| * The item children that may be added to instances of this class must be of |
| * type <code>TreeItem</code>. |
| * </p> |
| * <p> |
| * Style <code>VIRTUAL</code> is used to create a <code>Tree</code> whose |
| * <code>TreeItem</code>s are to be populated by the client on an on-demand |
| * basis instead of up-front. This can provide significant performance |
| * improvements for trees that are very large or for which <code>TreeItem</code> |
| * population is expensive (for example, retrieving values from an external |
| * source). |
| * </p> |
| * <p> |
| * Here is an example of using a <code>Tree</code> with style |
| * <code>VIRTUAL</code>: <code><pre> |
| * final Tree tree = new Tree(parent, SWT.VIRTUAL | SWT.BORDER); |
| * tree.setItemCount(20); |
| * tree.addListener(SWT.SetData, new Listener() { |
| * public void handleEvent(Event event) { |
| * TreeItem item = (TreeItem)event.item; |
| * TreeItem parentItem = item.getParentItem(); |
| * String text = null; |
| * if (parentItem == null) { |
| * text = "node " + tree.indexOf(item); |
| * } else { |
| * text = parentItem.getText() + " - " + parentItem.indexOf(item); |
| * } |
| * item.setText(text); |
| * System.out.println(text); |
| * item.setItemCount(10); |
| * } |
| * }); |
| * </pre></code> |
| * </p> |
| * <p> |
| * Note that although this class is a subclass of <code>Composite</code>, it |
| * does not make sense to add <code>Control</code> children to it, or set a |
| * layout on it. |
| * </p> |
| * <p> |
| * <dl> |
| * <dt><b>Styles:</b></dt> |
| * <dd>SINGLE, MULTI, CHECK, FULL_SELECTION, VIRTUAL, NO_SCROLL</dd> |
| * <dt><b>Events:</b></dt> |
| * <dd>Selection, DefaultSelection, Collapse, Expand, SetData<!--, MeasureItem, |
| * EraseItem, PaintItem--></dd> |
| * </dl> |
| * </p> |
| * <p> |
| * Note: Only one of the styles SINGLE and MULTI may be specified. |
| * </p> |
| * <p> |
| * IMPORTANT: This class is <em>not</em> intended to be subclassed. |
| * </p> |
| * |
| * @since 1.0 |
| */ |
| public class Tree extends Composite { |
| |
| private static final TreeItem[] EMPTY_SELECTION = new TreeItem[ 0 ]; |
| // This values must be kept in sync with appearance of list items |
| private static final int MIN_ITEM_HEIGHT = 16; |
| private static final int GRID_WIDTH = 1; |
| |
| private static final Rectangle TEXT_MARGIN = new Rectangle( 3, 0, 8, 0 ); |
| |
| private int itemCount; |
| private int customItemHeight; |
| private TreeItem[] items; |
| final ItemHolder<TreeColumn> columnHolder; |
| private TreeItem[] selection; |
| private boolean linesVisible; |
| private int[] columnOrder; |
| private int itemImageCount; |
| private TreeColumn sortColumn; |
| private int sortDirection; |
| private boolean headerVisible; |
| private final ITreeAdapter treeAdapter; |
| private int scrollLeft; |
| private int topItemIndex; |
| private boolean hasVScrollBar; |
| private boolean hasHScrollBar; |
| private Point itemImageSize; |
| LayoutCache layoutCache; |
| boolean isFlatIndexValid; |
| private int visibleItemsCount; |
| boolean markupEnabled; |
| boolean markupValidationDisabled; |
| |
| /** |
| * Constructs a new instance of this class given its parent and a style value |
| * describing its behavior and appearance. |
| * <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 composite 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#SINGLE |
| * @see SWT#MULTI |
| * @see SWT#CHECK |
| * @see SWT#FULL_SELECTION |
| * @see SWT#NO_SCROLL |
| * @see Widget#checkSubclass |
| * @see Widget#getStyle |
| */ |
| public Tree( Composite parent, int style ) { |
| super( parent, checkStyle( style ) ); |
| columnHolder = new ItemHolder<TreeColumn>( TreeColumn.class ); |
| treeAdapter = new InternalTreeAdapter(); |
| setTreeEmpty(); |
| sortDirection = SWT.NONE; |
| selection = EMPTY_SELECTION; |
| customItemHeight = -1; |
| layoutCache = new LayoutCache(); |
| } |
| |
| TreeItem[] getCreatedItems() { |
| TreeItem[] result; |
| if( isVirtual() ) { |
| int count = 0; |
| for( int i = 0; i < itemCount; i++ ) { |
| if( items[ i ] != null && items[ i ].isCached() ) { |
| count++; |
| } |
| } |
| result = new TreeItem[ count ]; |
| count = 0; |
| for( int i = 0; i < itemCount; i++ ) { |
| if( items[ i ] != null && items[ i ].isCached() ) { |
| result[ count ] = items[ i ]; |
| count++; |
| } |
| } |
| } else { |
| result = new TreeItem[ itemCount ]; |
| System.arraycopy( items, 0, result, 0, itemCount ); |
| } |
| return result; |
| } |
| |
| private void setTreeEmpty() { |
| items = new TreeItem[ 4 ]; |
| // TODO: Not sure if we have to clear the image size???!!! |
| // clearItemImageSize(); |
| } |
| |
| @Override |
| void initState() { |
| state &= ~( /* CANVAS | */THEME_BACKGROUND ); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> T getAdapter( Class<T> adapter ) { |
| T result; |
| if( adapter == IItemHolderAdapter.class ) { |
| result = ( T )new CompositeItemHolder(); |
| } else if( adapter == ITreeAdapter.class ) { |
| result = ( T )treeAdapter; |
| } else if( adapter == ICellToolTipAdapter.class ) { |
| result = ( T )treeAdapter; |
| } else { |
| result = super.getAdapter( adapter ); |
| } |
| return result; |
| } |
| |
| @Override |
| public void setFont( Font font ) { |
| super.setFont( font ); |
| for( int i = 0; i < itemCount; i++ ) { |
| if( items[ i ] != null ) { |
| items[ i ].clearPreferredWidthBuffers( true ); |
| } |
| } |
| layoutCache.invalidateItemHeight(); |
| updateScrollBars(); |
| } |
| |
| // ///////////////////////// |
| // Methods to manage items |
| /** |
| * Sets the number of root-level 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 deleteIndex = oldItemCount - 1; |
| while( deleteIndex >= newItemCount ) { |
| TreeItem item = items[ deleteIndex ]; |
| if( item != null && !item.isDisposed() ) { |
| item.dispose(); |
| } else { |
| destroyItem( null, deleteIndex ); |
| } |
| deleteIndex--; |
| } |
| 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( !isVirtual() ) { |
| for( int i = itemCount; i < newItemCount; i++ ) { |
| items[ i ] = new TreeItem( this, SWT.NONE, i ); |
| } |
| } |
| itemCount = newItemCount; |
| isFlatIndexValid = false; |
| updateScrollBars(); |
| redraw(); |
| } |
| } |
| |
| /** |
| * Returns the number of items contained in the receiver that are direct item |
| * children of the receiver. The number that is returned is the number of |
| * roots in the tree. |
| * |
| * @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(); |
| return itemCount; |
| } |
| |
| /** |
| * Returns a (possibly empty) array of items contained in the receiver that |
| * are direct item children of the receiver. These are the roots of the tree. |
| * <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 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( isVirtual() ) { |
| for( int i = 0; i < itemCount; i++ ) { |
| result[ i ] = _getItem( i ); |
| } |
| } else { |
| System.arraycopy( items, 0, result, 0, itemCount ); |
| } |
| return result; |
| } |
| |
| private TreeItem _getItem( int index ) { |
| if( isVirtual() && items[ index ] == null ) { |
| items[ index ] = new TreeItem( this, null, SWT.NONE, index, false ); |
| } |
| return items[ index ]; |
| } |
| |
| /** |
| * 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 ); |
| } |
| |
| /** |
| * 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.parent == this ? item.index : -1; |
| } |
| |
| /** |
| * 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 null; |
| } |
| |
| /** |
| * Removes all of the items from 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 void removeAll() { |
| checkWidget(); |
| for( int i = itemCount - 1; i >= 0; i-- ) { |
| if( items[ i ] != null ) { |
| items[ i ].dispose(); |
| } else { |
| itemCount--; |
| } |
| } |
| setTreeEmpty(); |
| selection = EMPTY_SELECTION; |
| } |
| |
| /** |
| * Shows the item. If the item is already showing in the receiver, this method |
| * simply returns. Otherwise, the items are scrolled and expanded until the |
| * item is visible. |
| * |
| * @param item the item to be shown |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the item is null</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the 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> |
| * @see Tree#showSelection() |
| */ |
| public void showItem( TreeItem item ) { |
| checkWidget(); |
| if( item == null ) { |
| error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| if( item.isDisposed() ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| if( item.getParent() == this ) { |
| TreeItem parent = item.getParentItem(); |
| while( parent != null ) { |
| parent.setExpanded( true ); |
| parent = parent.getParentItem(); |
| } |
| int flatIndex = item.getFlatIndex(); |
| if( flatIndex <= topItemIndex ) { |
| setTopItemIndex( flatIndex ); |
| } else { |
| int itemsAreaHeight = getClientArea().height - getHeaderHeight(); |
| int rows = ( int )Math.floor( itemsAreaHeight / getItemHeight() ); |
| if( flatIndex >= topItemIndex + rows ) { |
| setTopItemIndex( flatIndex - rows + 1 ); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sets the item which is currently at the top of the receiver. |
| * This item can change when items are expanded, collapsed, scrolled |
| * or new items are added or removed. |
| * |
| * @param item the item to be shown |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the item is null</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the 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> |
| * |
| * @see Tree#getTopItem() |
| * |
| * @since 1.4 |
| */ |
| public void setTopItem( TreeItem item ) { |
| checkWidget(); |
| if( item == null ) { |
| error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| if( item.isDisposed() ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| if( item.getParent() == this ) { |
| TreeItem parent = item.getParentItem(); |
| while( parent != null ) { |
| parent.setExpanded( true ); |
| parent = parent.getParentItem(); |
| } |
| int itemsAreaHeight = getClientArea().height - getHeaderHeight(); |
| int rows = ( int )Math.floor( itemsAreaHeight / getItemHeight() ); |
| int flatIndex = item.getFlatIndex(); |
| if( flatIndex <= topItemIndex || flatIndex + rows <= visibleItemsCount ) { |
| setTopItemIndex( flatIndex ); |
| } else { |
| int index = Math.max( 0, visibleItemsCount - rows ); |
| setTopItemIndex( index ); |
| } |
| } |
| } |
| |
| /** |
| * Returns the item which is currently at the top of the receiver. |
| * This item can change when items are expanded, collapsed, scrolled |
| * or new items are added or removed. |
| * |
| * @return the item at the top of 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> |
| * |
| * @since 1.4 |
| */ |
| public TreeItem getTopItem() { |
| checkWidget(); |
| TreeItem result = null; |
| if( itemCount > 0 ) { |
| List visibleItems = collectVisibleItems( null ); |
| result = ( TreeItem )visibleItems.get( topItemIndex ); |
| } |
| return result; |
| } |
| |
| private void setTopItemIndex( int index ) { |
| if( index != topItemIndex ) { |
| topItemIndex = index; |
| adjustTopItemIndex(); |
| updateAllItems(); |
| } |
| } |
| |
| int getTopItemIndex() { |
| return topItemIndex; |
| } |
| |
| /** |
| * Shows the column. If the column is already showing in the receiver, |
| * this method simply returns. Otherwise, the columns are scrolled until |
| * the column is visible. |
| * |
| * @param column the column to be shown |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the column is null</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the column 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> |
| * |
| * @since 1.3 |
| */ |
| public void showColumn( TreeColumn column ) { |
| checkWidget(); |
| if( column == null ) { |
| error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| if( column.isDisposed() ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| if( column.getParent() == this ) { |
| int index = indexOf( column ); |
| if( 0 <= index && index < getColumnCount() ) { |
| int leftColumnsWidth = 0; |
| int rightColumnsWidth = 0; |
| int columnWidth = column.getWidth(); |
| int clientWidth = getClientArea().width; |
| int[] columnOrder = getColumnOrder(); |
| boolean found = false; |
| for( int i = 0; i < columnOrder.length; i++ ) { |
| if( index != columnOrder[ i ] ) { |
| int currentColumnWidth = getColumn( columnOrder[ i ] ).getWidth(); |
| if( found ) { |
| rightColumnsWidth += currentColumnWidth; |
| } else { |
| if( isFixedColumn( columnOrder[ i ] ) ) { |
| clientWidth -= currentColumnWidth; |
| } else { |
| leftColumnsWidth += currentColumnWidth; |
| } |
| } |
| } else { |
| found = true; |
| } |
| } |
| if( getColumnLeftOffset( index ) > leftColumnsWidth ) { |
| scrollLeft = leftColumnsWidth; |
| } else if( scrollLeft < leftColumnsWidth + columnWidth - clientWidth ) { |
| if( columnWidth + rightColumnsWidth < clientWidth ) { |
| scrollLeft = leftColumnsWidth + columnWidth + rightColumnsWidth - clientWidth; |
| } else { |
| scrollLeft = leftColumnsWidth; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Shows the selection. If the selection is already showing in the receiver, |
| * this method simply returns. Otherwise, the items are scrolled until the |
| * selection is visible. |
| * |
| * @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 Tree#showItem(TreeItem) |
| */ |
| public void showSelection() { |
| checkWidget(); |
| if( selection.length == 0 ) { |
| return; |
| } |
| showItem( selection[ 0 ] ); |
| } |
| |
| // /////////////////////////////////// |
| // Methods to get/set/clear selection |
| /** |
| * Returns an array of <code>TreeItem</code>s that are currently selected in |
| * the receiver. The order of the items is unspecified. An empty array |
| * indicates that no items are selected. |
| * <p> |
| * Note: This is not the actual structure used by the receiver to maintain its |
| * selection, so modifying the array will not affect the receiver. |
| * </p> |
| * |
| * @return an array representing the selection |
| * @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[] getSelection() { |
| checkWidget(); |
| TreeItem[] result = new TreeItem[ selection.length ]; |
| System.arraycopy( selection, 0, result, 0, selection.length ); |
| return result; |
| } |
| |
| /** |
| * Returns the number of selected items contained in the receiver. |
| * |
| * @return the number of selected 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 getSelectionCount() { |
| checkWidget(); |
| return selection.length; |
| } |
| |
| /** |
| * Sets the receiver's selection to the given item. The current selection is |
| * cleared before the new item is selected. |
| * <p> |
| * If the item is not in the receiver, then it is ignored. |
| * </p> |
| * |
| * @param selection the item to select |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the item is null</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the 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 void setSelection( TreeItem selection ) { |
| checkWidget(); |
| if( selection == null ) { |
| SWT.error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| setSelection( new TreeItem[]{ |
| selection |
| } ); |
| } |
| |
| /** |
| * Sets the receiver's selection to be the given array of items. The current |
| * selection is cleared before the new items are selected. |
| * <p> |
| * Items that are not in the receiver are ignored. If the receiver is |
| * single-select and multiple items are specified, then all items are ignored. |
| * </p> |
| * |
| * @param selection the array of items |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the array of items is null</li> |
| * <li>ERROR_INVALID_ARGUMENT - if one of the items 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> |
| * @see Tree#deselectAll() |
| */ |
| public void setSelection( TreeItem[] selection ) { |
| checkWidget(); |
| if( selection == null ) { |
| SWT.error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| int length = selection.length; |
| if( ( style & SWT.SINGLE ) != 0 ) { |
| if( length == 0 || length > 1 ) { |
| deselectAll(); |
| } else { |
| TreeItem item = selection[ 0 ]; |
| if( item != null ) { |
| if( item.isDisposed() ) { |
| SWT.error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| this.selection = new TreeItem[]{ |
| item |
| }; |
| } |
| } |
| } else { |
| if( length == 0 ) { |
| deselectAll(); |
| } else { |
| // Construct an array that contains all non-null items to be selected |
| TreeItem[] validSelection = new TreeItem[ length ]; |
| int validLength = 0; |
| for( int i = 0; i < length; i++ ) { |
| if( selection[ i ] != null ) { |
| if( selection[ i ].isDisposed() ) { |
| SWT.error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| validSelection[ validLength ] = selection[ i ]; |
| validLength++; |
| } |
| } |
| if( validLength > 0 ) { |
| // Copy the above created array to its 'final destination' |
| this.selection = new TreeItem[ validLength ]; |
| System.arraycopy( validSelection, 0, this.selection, 0, validLength ); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Selects an item in the receiver. If the item was already |
| * selected, it remains selected. |
| * |
| * @param item the item to be selected |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the item is null</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the 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> |
| * |
| * @since 1.3 |
| */ |
| public void select( TreeItem item ) { |
| checkWidget(); |
| if( item == null ) { |
| error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| if( item.isDisposed() ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| if( ( style & SWT.SINGLE ) != 0 ) { |
| setSelection( item ); |
| } else { |
| List<TreeItem> selItems = new ArrayList<TreeItem>( Arrays.asList( selection ) ); |
| if( !selItems.contains( item ) ) { |
| selItems.add( item ); |
| selection = new TreeItem[ selItems.size() ]; |
| selItems.toArray( selection ); |
| } |
| } |
| } |
| |
| /** |
| * Selects all of the items in the receiver. |
| * <p> |
| * If the receiver is single-select, do nothing. |
| * </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 selectAll() { |
| checkWidget(); |
| if( ( style & SWT.MULTI ) != 0 ) { |
| final java.util.List<TreeItem> allItems = new ArrayList<TreeItem>(); |
| WidgetTreeVisitor.accept( this, new AllWidgetTreeVisitor() { |
| @Override |
| public boolean doVisit( Widget widget ) { |
| if( widget instanceof TreeItem ) { |
| allItems.add( ( TreeItem )widget ); |
| } |
| return true; |
| } |
| } ); |
| selection = new TreeItem[ allItems.size() ]; |
| allItems.toArray( selection ); |
| } |
| } |
| |
| /** |
| * Deselects an item in the receiver. If the item was already |
| * deselected, it remains deselected. |
| * |
| * @param item the item to be deselected |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the item is null</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the 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> |
| * |
| * @since 1.3 |
| */ |
| public void deselect( TreeItem item ) { |
| checkWidget(); |
| if( item == null ) { |
| error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| if( item.isDisposed() ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| List<TreeItem> selItems = new ArrayList<TreeItem>( Arrays.asList( selection ) ); |
| if( selItems.contains( item ) ) { |
| selItems.remove( item ); |
| selection = new TreeItem[ selItems.size() ]; |
| selItems.toArray( selection ); |
| } |
| } |
| |
| /** |
| * Deselects all selected items 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 void deselectAll() { |
| checkWidget(); |
| selection = EMPTY_SELECTION; |
| } |
| |
| /** |
| * Marks the receiver's lines as visible if the argument is <code>true</code>, |
| * and marks it invisible otherwise. |
| * <p> |
| * If one of the receiver's ancestors is not visible or some other condition |
| * makes the receiver not visible, marking it visible may not actually cause |
| * it to be displayed. |
| * </p> |
| * |
| * @param value the new visibility 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 setLinesVisible( boolean value ) { |
| checkWidget(); |
| if( linesVisible == value ) { |
| return; /* no change */ |
| } |
| linesVisible = value; |
| } |
| |
| /** |
| * Returns the width in pixels of a grid line. |
| * |
| * @return the width of a grid line in pixels |
| * |
| * @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 |
| */ |
| public int getGridLineWidth() { |
| checkWidget(); |
| return GRID_WIDTH; |
| } |
| |
| /** |
| * Returns <code>true</code> if the receiver's lines are visible, and |
| * <code>false</code> otherwise. |
| * <p> |
| * If one of the receiver's ancestors is not visible or some other condition |
| * makes the receiver not visible, this method may still indicate that it is |
| * considered visible even though it may not actually be showing. |
| * </p> |
| * |
| * @return the visibility state of the lines |
| * @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 getLinesVisible() { |
| checkWidget(); |
| return linesVisible; |
| } |
| |
| /** |
| * 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 ); |
| } |
| } |
| } |
| |
| /** |
| * Returns the item at the given point in the receiver or null if no such item |
| * exists. The point is in the coordinate system of the receiver. |
| * <p> |
| * The item that is returned represents an item that could be selected by the |
| * user. For example, if selection only occurs in items in the first column, |
| * then null is returned if the point is outside of the item. Note that the |
| * SWT.FULL_SELECTION style hint, which specifies the selection policy, |
| * determines the extent of the selection. |
| * </p> |
| * |
| * @param point the point used to locate the item |
| * @return the item at the given point, or null if the point is not in a |
| * selectable item |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the point 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 TreeItem getItem( Point point ) { |
| checkWidget(); |
| if( point == null ) { |
| error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| TreeItem result = null; |
| int index = ( point.y - getHeaderHeight() ) / getItemHeight() + topItemIndex; |
| List visibleItems = collectVisibleItems( null ); |
| if( 0 <= index && index < visibleItems.size() ) { |
| result = ( TreeItem )visibleItems.get( index ); |
| } |
| return result; |
| } |
| |
| private List<TreeItem> collectVisibleItems( TreeItem parentItem ) { |
| List<TreeItem> result = new ArrayList<TreeItem>(); |
| TreeItem[] items = parentItem == null ? this.items : parentItem.items; |
| int itemCount = parentItem == null ? this.itemCount : parentItem.itemCount; |
| for( int i = 0; i < itemCount; i++ ) { |
| TreeItem item = items[ i ]; |
| result.add( item ); |
| if( item != null && item.getExpanded() ) { |
| result.addAll( collectVisibleItems( item ) ); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Returns the height of the area which would be used to display <em>one</em> |
| * of the items in the tree. |
| * |
| * @return the height of one 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> |
| * |
| * @since 1.3 |
| */ |
| public int getItemHeight() { |
| checkWidget(); |
| int result = customItemHeight; |
| if( result == -1 ) { |
| if( !layoutCache.hasItemHeight() ) { |
| layoutCache.itemHeight = computeItemHeight(); |
| } |
| result = layoutCache.itemHeight; |
| } |
| return result; |
| } |
| |
| /** |
| * 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 ) { |
| checkWidget(); |
| for( int i = 0; i < itemCount; i++ ) { |
| TreeItem item = items[ i ]; |
| if( item != null ) { |
| item.clear(); |
| if( recursive ) { |
| item.clearAll( true, false ); |
| } |
| } |
| } |
| if( isVirtual() && itemCount != 0 ) { |
| updateAllItems(); |
| } |
| } |
| |
| @Override |
| public void changed( Control[] changed ) { |
| clearItemsPreferredWidthBuffer(); |
| super.changed( changed ); |
| } |
| |
| private void clearItemsPreferredWidthBuffer() { |
| for( int i = 0; i < itemCount; i++ ) { |
| TreeItem item = items[ i ]; |
| if( item != null ) { |
| item.clearPreferredWidthBuffers( true ); |
| } |
| } |
| } |
| |
| /** |
| * Returns the number of columns contained in the receiver. If no |
| * <code>TreeColumn</code>s were created by the programmer, this value is |
| * zero, despite the fact that visually, one column of items may be visible. |
| * This occurs when the programmer uses the tree like a list, adding items but |
| * never creating a column. |
| * |
| * @return the number of columns |
| * @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 getColumnCount() { |
| checkWidget(); |
| return columnHolder.size(); |
| } |
| |
| void createColumn( TreeColumn column, int index ) { |
| columnHolder.insert( column, index ); |
| if( columnOrder == null ) { |
| columnOrder = new int[]{ |
| index |
| }; |
| } else { |
| int length = columnOrder.length; |
| for( int i = index; i < length; i++ ) { |
| columnOrder[ i ]++; |
| } |
| int[] newColumnOrder = new int[ length + 1 ]; |
| System.arraycopy( columnOrder, 0, newColumnOrder, 0, index ); |
| System.arraycopy( columnOrder, index, newColumnOrder, index + 1, length - index ); |
| columnOrder = newColumnOrder; |
| columnOrder[ index ] = index; |
| } |
| for( int i = 0; i < itemCount; i++ ) { |
| if( items[ i ] != null ) { |
| items[ i ].shiftData( index ); |
| } |
| } |
| updateScrollBars(); |
| } |
| |
| final void destroyColumn( TreeColumn column ) { |
| if( !isInDispose() ) { |
| int index = indexOf( column ); |
| // Remove data from TreeItems |
| for( int i = 0; i < itemCount; i++ ) { |
| if( items[ i ] != null ) { |
| items[ i ].removeData( index ); |
| } |
| } |
| // Reset sort column if necessary |
| if( column == sortColumn ) { |
| sortColumn = null; |
| } |
| // Remove from column holder |
| columnHolder.remove( column ); |
| // Remove from column order |
| int length = columnOrder.length; |
| int[] newColumnOrder = new int[ length - 1 ]; |
| int count = 0; |
| for( int i = 0; i < length; i++ ) { |
| if( columnOrder[ i ] != index ) { |
| int newOrder = columnOrder[ i ]; |
| if( index < newOrder ) { |
| newOrder--; |
| } |
| newColumnOrder[ count ] = newOrder; |
| count++; |
| } |
| } |
| columnOrder = newColumnOrder; |
| updateScrollBars(); |
| } |
| } |
| |
| /** |
| * Returns the height of the receiver's header |
| * |
| * @return the height of the header or zero if the header is not visible |
| * @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 getHeaderHeight() { |
| checkWidget(); |
| if( !layoutCache.hasHeaderHeight() ) { |
| layoutCache.headerHeight = computeHeaderHeight(); |
| } |
| return layoutCache.headerHeight; |
| } |
| |
| /** |
| * Marks the receiver's header as visible if the argument is <code>true</code> |
| * , and marks it invisible otherwise. |
| * <p> |
| * If one of the receiver's ancestors is not visible or some other condition |
| * makes the receiver not visible, marking it visible may not actually cause |
| * it to be displayed. |
| * </p> |
| * |
| * @param value the new visibility 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 setHeaderVisible( boolean value ) { |
| checkWidget(); |
| if( headerVisible != value ) { |
| headerVisible = value; |
| layoutCache.invalidateHeaderHeight(); |
| updateScrollBars(); |
| } |
| } |
| |
| /** |
| * Returns <code>true</code> if the receiver's header is visible, and |
| * <code>false</code> otherwise. |
| * <p> |
| * If one of the receiver's ancestors is not visible or some other condition |
| * makes the receiver not visible, this method may still indicate that it is |
| * considered visible even though it may not actually be showing. |
| * </p> |
| * |
| * @return the receiver's header's visibility 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 getHeaderVisible() { |
| checkWidget(); |
| return headerVisible; |
| } |
| |
| /** |
| * Searches the receiver's list starting at the first column (index 0) until a |
| * column is found that is equal to the argument, and returns the index of |
| * that column. If no column is found, returns -1. |
| * |
| * @param column the search column |
| * @return the index of the column |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the column 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 int indexOf( TreeColumn column ) { |
| checkWidget(); |
| if( column == null ) { |
| SWT.error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| if( column.isDisposed() ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| return columnHolder.indexOf( column ); |
| } |
| |
| /** |
| * Returns the column at the given, zero-relative index in the receiver. |
| * Throws an exception if the index is out of range. Columns are returned in |
| * the order that they were created. If no <code>TreeColumn</code>s were |
| * created by the programmer, this method will throw |
| * <code>ERROR_INVALID_RANGE</code> despite the fact that a single column of |
| * data may be visible in the tree. This occurs when the programmer uses the |
| * tree like a list, adding items but never creating a column. |
| * |
| * @param index the index of the column to return |
| * @return the column 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> |
| * @see Tree#getColumnOrder() |
| * @see Tree#setColumnOrder(int[]) |
| * @see TreeColumn#getMoveable() |
| * @see TreeColumn#setMoveable(boolean) |
| * @see SWT#Move |
| */ |
| public TreeColumn getColumn( int index ) { |
| checkWidget(); |
| if( !( 0 <= index && index < columnHolder.size() ) ) { |
| error( SWT.ERROR_INVALID_RANGE ); |
| } |
| return columnHolder.getItem( index ); |
| } |
| |
| /** |
| * Returns an array of <code>TreeColumn</code>s which are the columns in the |
| * receiver. Columns are returned in the order that they were created. If no |
| * <code>TreeColumn</code>s were created by the programmer, the array is |
| * empty, despite the fact that visually, one column of items may be visible. |
| * This occurs when the programmer uses the tree like a list, adding items but |
| * never creating a column. |
| * <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 items 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> |
| * @see Tree#getColumnOrder() |
| * @see Tree#setColumnOrder(int[]) |
| * @see TreeColumn#getMoveable() |
| * @see TreeColumn#setMoveable(boolean) |
| * @see SWT#Move |
| */ |
| public TreeColumn[] getColumns() { |
| checkWidget(); |
| return columnHolder.getItems(); |
| } |
| |
| /** |
| * Sets the order that the items in the receiver should be displayed in to the |
| * given argument which is described in terms of the zero-relative ordering of |
| * when the items were added. |
| * |
| * @param order the new order to display the 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> |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the item order is null</li> |
| * <li>ERROR_INVALID_ARGUMENT - if the item order is not the same |
| * length as the number of items</li> |
| * </ul> |
| * @see Tree#getColumnOrder() |
| * @see TreeColumn#getMoveable() |
| * @see TreeColumn#setMoveable(boolean) |
| * @see SWT#Move |
| */ |
| public void setColumnOrder( int[] order ) { |
| checkWidget(); |
| if( order == null ) { |
| error( SWT.ERROR_NULL_ARGUMENT ); |
| } |
| int columnCount = getColumnCount(); |
| if( order.length != columnCount ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| if( columnCount > 0 ) { |
| int[] oldOrder = new int[ columnCount ]; |
| System.arraycopy( columnOrder, 0, oldOrder, 0, columnOrder.length ); |
| boolean reorder = false; |
| boolean[] seen = new boolean[ columnCount ]; |
| for( int i = 0; i < order.length; i++ ) { |
| int index = order[ i ]; |
| if( index < 0 || index >= columnCount ) { |
| error( SWT.ERROR_INVALID_RANGE ); |
| } |
| if( seen[ index ] ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| seen[ index ] = true; |
| if( index != oldOrder[ i ] ) { |
| reorder = true; |
| } |
| } |
| if( reorder ) { |
| System.arraycopy( order, 0, columnOrder, 0, columnOrder.length ); |
| for( int i = 0; i < seen.length; i++ ) { |
| if( oldOrder[ i ] != columnOrder[ i ] ) { |
| TreeColumn column = getColumn( columnOrder[ i ] ); |
| int controlMoved = ControlEvent.CONTROL_MOVED; |
| ControlEvent controlEvent = new ControlEvent( column, controlMoved ); |
| controlEvent.processEvent(); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sets the column used by the sort indicator for the receiver. A null value |
| * will clear the sort indicator. The current sort column is cleared before |
| * the new column is set. |
| * |
| * @param column the column used by the sort indicator or <code>null</code> |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_INVALID_ARGUMENT - if the column is 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 setSortColumn( TreeColumn column ) { |
| checkWidget(); |
| if( column != null && column.isDisposed() ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| if( column == sortColumn ) { |
| return; |
| } |
| if( sortColumn != null && !sortColumn.isDisposed() ) { |
| sortColumn.setSortDirection( SWT.NONE ); |
| } |
| sortColumn = column; |
| if( sortColumn != null ) { |
| sortColumn.setSortDirection( sortDirection ); |
| } |
| } |
| |
| /** |
| * Sets the direction of the sort indicator for the receiver. The value can be |
| * one of <code>UP</code>, <code>DOWN</code> or <code>NONE</code>. |
| * |
| * @param direction the direction of the sort indicator |
| * @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 setSortDirection( int direction ) { |
| checkWidget(); |
| if( direction != SWT.UP && direction != SWT.DOWN && direction != SWT.NONE ) |
| { |
| return; |
| } |
| sortDirection = direction; |
| if( sortColumn == null || sortColumn.isDisposed() ) { |
| return; |
| } |
| sortColumn.setSortDirection( sortDirection ); |
| } |
| |
| /** |
| * Returns the column which shows the sort indicator for the receiver. The |
| * value may be null if no column shows the sort indicator. |
| * |
| * @return the sort indicator |
| * @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 #setSortColumn(TreeColumn) |
| */ |
| public TreeColumn getSortColumn() { |
| checkWidget(); |
| return sortColumn; |
| } |
| |
| /** |
| * Returns the direction of the sort indicator for the receiver. The value |
| * will be one of <code>UP</code>, <code>DOWN</code> or <code>NONE</code>. |
| * |
| * @return the sort direction |
| * @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 #setSortDirection(int) |
| */ |
| public int getSortDirection() { |
| checkWidget(); |
| return sortDirection; |
| } |
| |
| /** |
| * Returns an array of zero-relative integers that map the creation order of |
| * the receiver's items to the order in which they are currently being |
| * displayed. |
| * <p> |
| * Specifically, the indices of the returned array represent the current |
| * visual order of the items, and the contents of the array represent the |
| * creation order of the items. |
| * </p> |
| * <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 current visual order of 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> |
| * @see Tree#setColumnOrder(int[]) |
| * @see TreeColumn#getMoveable() |
| * @see TreeColumn#setMoveable(boolean) |
| * @see SWT#Move |
| */ |
| public int[] getColumnOrder() { |
| checkWidget(); |
| int[] result; |
| if( columnHolder.size() == 0 ) { |
| result = new int[ 0 ]; |
| } else { |
| result = new int[ columnOrder.length ]; |
| System.arraycopy( columnOrder, 0, result, 0, columnOrder.length ); |
| } |
| return result; |
| } |
| |
| /////////////////////////////////////// |
| // Listener registration/deregistration |
| |
| /** |
| * Adds the listener to the collection of listeners who will be notified when |
| * the receiver's selection changes, by sending it one of the messages defined |
| * in the <code>SelectionListener</code> interface. |
| * <p> |
| * When <code>widgetSelected</code> is called, the item field of the event |
| * object is valid. If the receiver has <code>SWT.CHECK</code> style set and |
| * the check selection changes, the event object detail field contains the |
| * value <code>SWT.CHECK</code>. <code>widgetDefaultSelected</code> is |
| * typically called when an item is double-clicked. The item field of the |
| * event object is valid for default selection, but the detail field is not |
| * used. |
| * </p> |
| * |
| * @param listener the listener which should be notified |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the listener 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> |
| * @see SelectionListener |
| * @see #removeSelectionListener |
| * @see SelectionEvent |
| */ |
| public void addSelectionListener( SelectionListener listener ) { |
| checkWidget(); |
| SelectionEvent.addListener( this, listener ); |
| } |
| |
| /** |
| * Removes the listener from the collection of listeners who will be notified |
| * when the receiver's selection changes. |
| * |
| * @param listener the listener which should no longer be notified |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the listener 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> |
| * @see SelectionListener |
| * @see #addSelectionListener |
| */ |
| public void removeSelectionListener( SelectionListener listener ) { |
| checkWidget(); |
| SelectionEvent.removeListener( this, listener ); |
| } |
| |
| /** |
| * Adds the listener to the collection of listeners who will be notified when |
| * an item in the receiver is expanded or collapsed by sending it one of the |
| * messages defined in the <code>TreeListener</code> interface. |
| * |
| * @param listener the listener which should be notified |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the listener 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> |
| * @see TreeListener |
| * @see #removeTreeListener |
| */ |
| public void addTreeListener( TreeListener listener ) { |
| checkWidget(); |
| TreeEvent.addListener( this, listener ); |
| } |
| |
| /** |
| * Removes the listener from the collection of listeners who will be notified |
| * when items in the receiver are expanded or collapsed. |
| * |
| * @param listener the listener which should no longer be notified |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the listener 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> |
| * @see TreeListener |
| * @see #addTreeListener |
| */ |
| public void removeTreeListener( TreeListener listener ) { |
| checkWidget(); |
| TreeEvent.removeListener( this, listener ); |
| } |
| |
| @Override |
| public void setData( String key, Object value ) { |
| if( WidgetUtil.CUSTOM_VARIANT.equals( key ) ) { |
| layoutCache.invalidateAll(); |
| } else if( RWT.CUSTOM_ITEM_HEIGHT.equals( key ) ) { |
| setCustomItemHeight( value ); |
| } else if( RWT.MARKUP_ENABLED.equals( key ) && !markupEnabled ) { |
| markupEnabled = Boolean.TRUE.equals( value ); |
| } else if( MarkupValidator.MARKUP_VALIDATION_DISABLED.equals( key ) ) { |
| markupValidationDisabled = Boolean.TRUE.equals( value ); |
| } |
| super.setData( key, value ); |
| } |
| |
| ///////////////////////////////// |
| // Methods to cleanup on dispose |
| |
| @Override |
| void releaseChildren() { |
| for( int i = items.length - 1; i >= 0; i-- ) { |
| if( items[ i ] != null ) { |
| items[ i ].dispose(); |
| } |
| } |
| TreeColumn[] cols = columnHolder.getItems(); |
| for( int c = 0; c < cols.length; c++ ) { |
| cols[ c ].dispose(); |
| columnHolder.remove( cols[ c ] ); |
| } |
| super.releaseChildren(); |
| } |
| |
| void removeFromSelection( TreeItem item ) { |
| int index = -1; |
| for( int i = 0; index == -1 && i < selection.length; i++ ) { |
| if( selection[ i ] == item ) { |
| index = i; |
| } |
| } |
| if( index != -1 ) { |
| TreeItem[] newSelection = new TreeItem[ selection.length - 1 ]; |
| System.arraycopy( selection, 0, newSelection, 0, index ); |
| if( index < selection.length - 1 ) { |
| int length = selection.length - index - 1; |
| System.arraycopy( selection, index + 1, newSelection, index, length ); |
| } |
| selection = newSelection; |
| } |
| } |
| |
| ///////////////////// |
| // Widget dimensions |
| |
| @Override |
| public Point computeSize( int wHint, int hHint, boolean changed ) { |
| checkWidget(); |
| int width = 0; |
| int height = 0; |
| if( getColumnCount() > 0 ) { |
| for( int i = 0; i < getColumnCount(); i++ ) { |
| width += getColumn( i ).getWidth(); |
| } |
| } else { |
| for( int i = 0; i < itemCount; i++ ) { |
| TreeItem item = items[ i ]; |
| if( item != null && item.isCached() ) { |
| int itemWidth = item.getPreferredWidth( 0, false ); |
| width = Math.max( width, itemWidth ); |
| if( item.getExpanded() ) { |
| int innerWidth = getMaxInnerWidth( item.items, 0, 1, false ); |
| width = Math.max( width, innerWidth ); |
| } |
| } |
| } |
| } |
| height += getHeaderHeight(); |
| height += itemCount * getItemHeight(); |
| for( int i = 0; i < itemCount; i++ ) { |
| TreeItem item = items[ i ]; |
| if( item != null && !item.isInDispose() && item.getExpanded() ) { |
| height += item.getInnerHeight(); |
| } |
| } |
| if( width == 0 ) { |
| width = DEFAULT_WIDTH; |
| } |
| if( height == 0 ) { |
| height = DEFAULT_HEIGHT; |
| } |
| if( wHint != SWT.DEFAULT ) { |
| width = wHint; |
| } |
| if( hHint != SWT.DEFAULT ) { |
| height = hHint; |
| } |
| int border = getBorderWidth(); |
| width += border * 2; |
| height += border * 2; |
| if( ( style & SWT.V_SCROLL ) != 0 ) { |
| width += getVerticalBar().getSize().x; |
| } |
| if( ( style & SWT.H_SCROLL ) != 0 ) { |
| height += getHorizontalBar().getSize().y; |
| } |
| return new Point( width, height ); |
| } |
| |
| private void setCustomItemHeight( Object value ) { |
| if( value == null ) { |
| customItemHeight = -1; |
| } else { |
| if( !( value instanceof Integer ) ) { |
| error( SWT.ERROR_INVALID_ARGUMENT ); |
| } |
| int itemHeight = ( ( Integer )value ).intValue(); |
| if( itemHeight < 0 ) { |
| error( SWT.ERROR_INVALID_RANGE ); |
| } |
| customItemHeight = itemHeight; |
| } |
| } |
| |
| ///////////////////// |
| // item layout helper |
| |
| int getMaxContentWidth( TreeColumn column ) { |
| return getMaxInnerWidth( items, indexOf( column ), 1, true ); |
| } |
| |
| private int getMaxInnerWidth( TreeItem[] items, int columnIndex, int level, boolean clearBuffer ) |
| { |
| int maxInnerWidth = 0; |
| for( int i = 0; i < items.length; i++ ) { |
| TreeItem item = items[ i ]; |
| if( item != null && item.isCached() ) { |
| int indention = columnIndex == 0 ? level * getIndentionWidth() : 0; // TODO [tb] : test |
| if( clearBuffer ) { |
| item.clearPreferredWidthBuffers( false ); |
| } |
| int itemWidth = item.getPreferredWidth( columnIndex, false ) + indention; |
| maxInnerWidth = Math.max( maxInnerWidth, itemWidth ); |
| if( item.getExpanded() ) { |
| int innerWidth = getMaxInnerWidth( item.items, columnIndex, level + 1, clearBuffer ); |
| maxInnerWidth = Math.max( maxInnerWidth, innerWidth ); |
| } |
| } |
| } |
| return maxInnerWidth; |
| } |
| |
| int getCellLeft( int index ) { |
| return getColumnCount() == 0 ? 0 : getColumn( index ).getLeft(); |
| } |
| |
| private int getCellWidth( int index ) { |
| return getColumnCount() == 0 && index == 0 |
| ? getMaxInnerWidth( items, 0, 1, false ) |
| : getColumn( index ).getWidth(); |
| } |
| |
| int getImageOffset( int index ) { |
| // Note: The left cell-padding is visually ignored for the tree-column |
| int result = isTreeColumn( index ) ? 0 : getCellPadding().x; |
| if( hasCheckBoxes( index ) ) { |
| result += getCheckImageOuterSize().x; |
| } |
| return result; |
| } |
| |
| private int getTextOffset( int index ) { |
| int result = getImageOffset( index ); |
| result += getItemImageOuterWidth( index ); |
| if( isTreeColumn( index ) ) { |
| result += TEXT_MARGIN.x; |
| } |
| return result; |
| } |
| |
| int getTextWidth( int index ) { |
| int result = getCellWidth( index ) - getTextOffset( index ) - getCellPadding().width; |
| if( isTreeColumn( index ) ) { |
| result -= ( TEXT_MARGIN.width - TEXT_MARGIN.x ); |
| } |
| return Math.max( 0, result ); |
| } |
| |
| int getIndentionOffset( TreeItem item ) { |
| return getIndentionWidth() * ( item.depth + 1); |
| } |
| |
| int getVisualCellLeft( int index, TreeItem item ) { |
| int result = getCellLeft( index ) - getColumnLeftOffset( index ); |
| if( isTreeColumn( index ) ) { |
| result += getIndentionOffset( item ); |
| } |
| if( hasCheckBoxes( index ) ) { |
| result += getCheckImageOuterSize().x; |
| } |
| return result; |
| } |
| |
| int getVisualCellWidth( int index, TreeItem item ) { |
| int result; |
| if( getColumnCount() == 0 && index == 0 ) { |
| String text = item.getText( 0 ); |
| int textWidth = Graphics.stringExtent( item.getFont(), text ).x; |
| result = getCellPadding().width |
| + getItemImageOuterWidth( index ) |
| + textWidth |
| + TEXT_MARGIN.width; |
| } else { |
| result = getColumn( index ).getWidth(); |
| if( isTreeColumn( index ) ) { |
| result -= getIndentionOffset( item ); |
| } |
| if( hasCheckBoxes( index ) ) { |
| result -= getCheckImageOuterSize().x; |
| } |
| result = Math.max( 0, result ); |
| } |
| return result; |
| } |
| |
| int getVisualTextLeft( int index, TreeItem item ) { |
| return getVisualCellLeft( index, item ) + getCellPadding().x + getItemImageOuterWidth( index ); |
| } |
| |
| int getVisualTextWidth( int index, TreeItem item ) { |
| int result = 0; |
| if( index == 0 && getColumnCount() == 0 ) { |
| result = Graphics.stringExtent( item.getFont(), item.getText( 0 ) ).x; |
| result += TEXT_MARGIN.width; |
| } else if( index >= 0 && index < getColumnCount() ) { |
| result = getTextWidth( index ) - getIndentionOffset( item ); |
| result = Math.max( 0, result ); |
| } |
| return result; |
| } |
| |
| int getPreferredCellWidth( TreeItem item, int columnIndex, boolean checkData ) { |
| int result = item.getPreferredWidthBuffer( columnIndex ); |
| if( !item.hasPreferredWidthBuffer( columnIndex ) ) { |
| result = getTextOffset( columnIndex ) ; |
| Rectangle padding = getCellPadding(); |
| result += Graphics.stringExtent( getFont(), item.getTextWithoutMaterialize( columnIndex ) ).x; |
| result += padding.width - padding.x; |
| if( isTreeColumn( columnIndex ) ) { |
| result += TEXT_MARGIN.width - TEXT_MARGIN.x; |
| } |
| item.setPreferredWidthBuffer( columnIndex, result ); |
| } |
| return result; |
| } |
| |
| boolean isTreeColumn( int index ) { |
| return index == 0 && getColumnCount() == 0 |
| || getColumnCount() > 0 && getColumnOrder()[ 0 ] == index; |
| } |
| |
| /** |
| * Returns the scroll-offset of the column, which is the leftOffset unless it is a fixed column. |
| */ |
| final int getColumnLeftOffset( int columnIndex ) { |
| int result = scrollLeft; |
| if( columnIndex >= 0 ) { |
| result = isFixedColumn( columnIndex ) ? 0 : scrollLeft; |
| } |
| return result; |
| } |
| |
| private boolean isFixedColumn( int index ) { |
| int[] columnOrder = getColumnOrder(); |
| int visualIndex = -1; |
| for( int i = 0; i < columnOrder.length && visualIndex == -1; i++ ) { |
| if( index == columnOrder[ i ] ) { |
| visualIndex = i; |
| } |
| } |
| return visualIndex < getFixedColumns(); |
| } |
| |
| private int getFixedColumns() { |
| int result = -1; |
| try { |
| Integer data = ( Integer )getData( RWT.FIXED_COLUMNS ); |
| if( data != null ) { |
| result = data.intValue(); |
| } |
| } catch( ClassCastException ex ) { |
| // not a valid fixedColumns value |
| } |
| return result; |
| } |
| |
| private boolean hasCheckBoxes( int index ) { |
| return ( style & SWT.CHECK ) != 0 && isTreeColumn( index ); |
| } |
| |
| private boolean hasColumnImages( int columnIndex ) { |
| int count = columnIndex == 0 ? itemImageCount : getColumn( columnIndex ).itemImageCount; |
| return count > 0; |
| } |
| |
| void updateColumnImageCount( int columnIndex, Image oldImage, Image newImage ) { |
| int delta = 0; |
| if( oldImage == null && newImage != null ) { |
| delta = +1; |
| } else if( oldImage != null && newImage == null ) { |
| delta = -1; |
| } |
| if( delta != 0 ) { |
| if( columnIndex == 0 ) { |
| itemImageCount += delta; |
| } else { |
| TreeColumn column = getColumn( columnIndex ); |
| column.itemImageCount += delta; |
| } |
| } |
| } |
| |
| void updateItemImageSize( Image image ) { |
| if( image != null && itemImageSize == null ) { |
| Rectangle imageBounds = image.getBounds(); |
| itemImageSize = new Point( imageBounds.width, imageBounds.height ); |
| layoutCache.invalidateItemHeight(); |
| } |
| } |
| |
| Point getItemImageSize( int index ) { |
| Point result; |
| if( hasColumnImages( index ) ) { |
| result = getItemImageSize(); |
| if( getColumnCount() > 0 ) { |
| int availWidth = getColumn( index ).getWidth(); |
| availWidth -= getCellPadding().x; |
| availWidth = Math.max( 0, availWidth ); |
| result.x = Math.min( result.x, availWidth ); |
| } |
| } else { |
| result = new Point( 0, 0 ); |
| } |
| return result; |
| } |
| |
| private int getItemImageOuterWidth( int index ) { |
| int result = 0; |
| if( hasColumnImages( index ) ) { |
| result += getItemImageSize( index ).x; |
| result += getCellSpacing(); |
| } |
| return result; |
| } |
| |
| private Point getItemImageSize() { |
| Point result = new Point( 0, 0 ); |
| if( itemImageSize != null ) { |
| result.x = itemImageSize.x; |
| result.y = itemImageSize.y; |
| } |
| return result; |
| } |
| |
| private Point getCheckImageSize() { |
| return getThemeAdapter().getCheckBoxImageSize( this ); |
| } |
| |
| private TreeThemeAdapter getThemeAdapter() { |
| return ( TreeThemeAdapter )getAdapter( IThemeAdapter.class ); |
| } |
| |
| private Point getCheckImageOuterSize() { |
| Point result = getCheckImageSize(); |
| Rectangle margin = getCheckBoxMargin(); |
| result.x += margin.width; |
| result.y += margin.height; |
| return result; |
| } |
| |
| private Rectangle getCheckBoxMargin() { |
| if( !layoutCache.hasCheckBoxMargin() ) { |
| layoutCache.checkBoxMargin = getThemeAdapter().getCheckBoxMargin( this ); |
| } |
| return layoutCache.checkBoxMargin; |
| } |
| |
| private int getIndentionWidth() { |
| return getThemeAdapter().getIndentionWidth( this ); |
| } |
| |
| private int computeHeaderHeight() { |
| int result = 0; |
| if( headerVisible ) { |
| TreeThemeAdapter themeAdapter = getThemeAdapter(); |
| Font headerFont = themeAdapter.getHeaderFont( this ); |
| int textHeight = Graphics.getCharHeight( headerFont ); |
| int imageHeight = 0; |
| for( int i = 0; i < getColumnCount(); i++ ) { |
| TreeColumn column = columnHolder.getItem( i ); |
| if( column.getText().contains( "\n" ) ) { |
| int columnTextHeight = Graphics.textExtent( headerFont, column.getText(), 0 ).y; |
| textHeight = Math.max( textHeight, columnTextHeight ); |
| } |
| Image image = getColumn( i ).getImage(); |
| int height = image == null ? 0 : image.getBounds().height; |
| if( height > imageHeight ) { |
| imageHeight = height; |
| } |
| } |
| result = Math.max( textHeight, imageHeight ); |
| result += themeAdapter.getHeaderBorderBottomWidth( this ); |
| result += themeAdapter.getHeaderPadding( this ).height; |
| } |
| return result; |
| } |
| |
| private int computeItemHeight() { |
| Rectangle padding = getCellPadding(); |
| int textHeight = Graphics.getCharHeight( getFont() ); |
| textHeight += TEXT_MARGIN.height + padding.height; |
| int itemImageHeight = getItemImageSize().y + padding.height; |
| int result = Math.max( itemImageHeight, textHeight ); |
| if( hasCheckBoxes( 0 ) ) { |
| result = Math.max( getCheckImageOuterSize().y, result ); |
| } |
| result += 1; // The space needed for horizontal gridline is always added, even if not visible |
| result = Math.max( result, MIN_ITEM_HEIGHT ); |
| return result; |
| } |
| |
| /////////////////// |
| // Helping methods |
| |
| @Override |
| void notifyResize( Point oldSize ) { |
| if( !oldSize.equals( getSize() ) && !TextSizeUtil.isTemporaryResize() ) { |
| updateAllItems(); |
| updateScrollBars(); |
| adjustTopItemIndex(); |
| } |
| super.notifyResize( oldSize ); |
| } |
| |
| private void adjustTopItemIndex() { |
| int visibleRowCount = getVisibleRowCount( false ); |
| int correction = visibleRowCount == 0 ? 1 : 0; |
| if( topItemIndex > visibleItemsCount - visibleRowCount - correction ) { |
| topItemIndex = Math.max( 0, visibleItemsCount - visibleRowCount - correction ); |
| } |
| } |
| |
| private int getVisibleRowCount( boolean includePartlyVisible ) { |
| int clientHeight = getBounds().height - getHeaderHeight() - getHScrollBarHeight(); |
| int result = 0; |
| if( clientHeight >= 0 ) { |
| int itemHeight = getItemHeight(); |
| result = clientHeight / itemHeight; |
| if( includePartlyVisible && clientHeight % itemHeight != 0 ) { |
| result++; |
| } |
| } |
| return result; |
| } |
| |
| void updateAllItems() { |
| int flatIndex = 0; |
| for( int index = 0; index < itemCount; index++ ) { |
| flatIndex = updateAllItemsRecursively( null, index, flatIndex ); |
| } |
| isFlatIndexValid = true; |
| visibleItemsCount = flatIndex; |
| } |
| |
| private int updateAllItemsRecursively( TreeItem parent, int index, int flatIndex ) { |
| int newFlatIndex = flatIndex; |
| TreeItem item = parent == null ? items[ index ] : parent.items[ index ]; |
| if( isVirtual() && isItemVisible( flatIndex ) ) { |
| if( item == null ) { |
| item = parent == null ? _getItem( index ) : parent._getItem( index ); |
| } |
| checkData( item, index ); |
| } |
| if( item != null ) { |
| item.setFlatIndex( newFlatIndex ); |
| } |
| newFlatIndex++; |
| if( item != null && item.isCached() && item.getExpanded() ) { |
| for( int i = 0; i < item.itemCount; i++ ) { |
| newFlatIndex = updateAllItemsRecursively( item, i, newFlatIndex ); |
| } |
| } |
| return newFlatIndex; |
| } |
| |
| private boolean isItemVisible( int flatIndex ) { |
| boolean result = false; |
| int headerHeight = getHeaderHeight(); |
| int itemHeight = getItemHeight(); |
| int itemPosition = headerHeight + ( flatIndex - getTopItemIndex() ) * itemHeight; |
| // TODO shouldn't we call getClientArea() instead? |
| if( itemPosition >= 0 && itemPosition <= getSize().y ) { |
| result = true; |
| } |
| return result; |
| } |
| |
| final boolean checkData( TreeItem item, int index ) { |
| boolean result = true; |
| if( isVirtual() && !item.isCached() ) { |
| item.markCached(); |
| SetDataEvent event = new SetDataEvent( Tree.this, item, index ); |
| event.processEvent(); |
| // widget could be disposed at this point |
| if( isDisposed() || item.isDisposed() ) { |
| result = false; |
| } |
| } |
| return result; |
| } |
| |
| private static int checkStyle( int style ) { |
| int result = style; |
| if( ( style & SWT.NO_SCROLL ) == 0 ) { |
| result |= SWT.H_SCROLL | SWT.V_SCROLL; |
| } |
| return checkBits( result, SWT.SINGLE, SWT.MULTI, 0, 0, 0, 0 ); |
| } |
| |
| Rectangle getCellPadding() { |
| if( !layoutCache.hasCellPadding() ) { |
| layoutCache.cellPadding = getThemeAdapter().getCellPadding( this ); |
| } |
| return layoutCache.cellPadding; |
| } |
| |
| int getCellSpacing() { |
| if( !layoutCache.hasCellSpacing() ) { |
| layoutCache.cellSpacing = getThemeAdapter().getCellSpacing( this ); |
| } |
| return layoutCache.cellSpacing; |
| } |
| |
| /////////////////////////////////////// |
| // Helping methods - dynamic scrollbars |
| |
| boolean hasVScrollBar() { |
| return hasVScrollBar; |
| } |
| |
| boolean hasHScrollBar() { |
| return hasHScrollBar; |
| } |
| |
| @Override |
| int getVScrollBarWidth() { |
| int result = 0; |
| if( hasVScrollBar() ) { |
| result = getVerticalBar().getSize().x; |
| } |
| return result; |
| } |
| |
| @Override |
| int getHScrollBarHeight() { |
| int result = 0; |
| if( hasHScrollBar() ) { |
| result = getHorizontalBar().getSize().y; |
| } |
| return result; |
| } |
| |
| boolean needsVScrollBar() { |
| int availableHeight = getClientArea().height; |
| int height = getHeaderHeight(); |
| height += itemCount * getItemHeight(); |
| for( int i = 0; i < itemCount; i++ ) { |
| TreeItem item = items[ i ]; |
| if( item != null && item.getExpanded() ) { |
| height += item.getInnerHeight(); |
| } |
| } |
| return height > availableHeight; |
| } |
| |
| boolean needsHScrollBar() { |
| boolean result = false; |
| int availableWidth = getClientArea().width; |
| int columnCount = getColumnCount(); |
| if( columnCount > 0 ) { |
| int totalWidth = 0; |
| for( int i = 0; i < columnCount; i++ ) { |
| TreeColumn column = getColumn( i ); |
| totalWidth += column.getWidth(); |
| } |
| result = totalWidth > availableWidth; |
| } else { |
| int maxWidth = 0; |
| for( int i = 0; i < itemCount; i++ ) { |
| TreeItem item = items[ i ]; |
| if( item != null && !item.isInDispose() && item.isCached() ) { |
| int itemWidth = item.getPreferredWidth( 0, false ); |
| maxWidth = Math.max( maxWidth, itemWidth ); |
| if( item.getExpanded() ) { |
| int innerWidth = getMaxInnerWidth( item.items, 0, 1, false ); |
| maxWidth = Math.max( maxWidth, innerWidth ); |
| } |
| } |
| } |
| result = maxWidth > availableWidth; |
| } |
| return result; |
| } |
| |
| void updateScrollBars() { |
| if( ( style & SWT.NO_SCROLL ) == 0 ) { |
| hasVScrollBar = false; |
| hasHScrollBar = needsHScrollBar(); |
| if( needsVScrollBar() ) { |
| hasVScrollBar = true; |
| hasHScrollBar = needsHScrollBar(); |
| } |
| getHorizontalBar().setVisible( hasHScrollBar ); |
| getVerticalBar().setVisible( hasVScrollBar ); |
| } |
| } |
| |
| boolean isVirtual() { |
| return ( style & SWT.VIRTUAL ) != 0; |
| } |
| |
| 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 ); |
| } |
| |
| void destroyItem( TreeItem treeItem, int index ) { |
| itemCount--; |
| if( itemCount == 0 ) { |
| setTreeEmpty(); |
| } 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; |
| } |
| } |
| } |
| |
| /////////////////// |
| // Skinning support |
| |
| @Override |
| void reskinChildren( int flags ) { |
| for( int i = 0; i < itemCount; i++ ) { |
| if( items[ i ] != null ) { |
| items[ i ].reskinChildren( flags ); |
| } |
| } |
| TreeColumn[] columns = getColumns(); |
| if( columns != null ) { |
| for( int i = 0; i < columns.length; i++ ) { |
| TreeColumn column = columns[ i ]; |
| if( !column.isDisposed() ) { |
| column.reskinChildren( flags ); |
| } |
| } |
| } |
| super.reskinChildren( flags ); |
| } |
| |
| //////////////// |
| // Inner classes |
| |
| private final class CompositeItemHolder implements IItemHolderAdapter { |
| public void add( Item item ) { |
| if( item instanceof TreeColumn ) { |
| columnHolder.add( ( TreeColumn )item ); |
| } else { |
| String msg = "Only TreeColumns may be added to CompositeItemHolder"; |
| throw new IllegalArgumentException( msg ); |
| } |
| } |
| public void insert( Item item, int index ) { |
| if( item instanceof TreeColumn ) { |
| columnHolder.insert( ( TreeColumn )item, index ); |
| } else { |
| String msg = "Only TreeColumns may be inserted to CompositeItemHolder"; |
| throw new IllegalArgumentException( msg ); |
| } |
| } |
| public void remove( Item item ) { |
| if( item instanceof TreeColumn ) { |
| columnHolder.remove( ( TreeColumn )item ); |
| } else { |
| String msg = "Only TreeColumns may be removed from CompositeItemHolder"; |
| throw new IllegalArgumentException( msg ); |
| } |
| } |
| public Item[] getItems() { |
| TreeItem[] items = getCreatedItems(); |
| Item[] columns = columnHolder.getItems(); |
| Item[] result = new Item[ columns.length + items.length ]; |
| System.arraycopy( columns, 0, result, 0, columns.length ); |
| System.arraycopy( items, 0, result, columns.length, items.length ); |
| return result; |
| } |
| } |
| |
| private final class InternalTreeAdapter |
| implements ITreeAdapter, ICellToolTipAdapter, SerializableCompatibility |
| { |
| private String toolTipText; |
| private ICellToolTipProvider provider; |
| |
| public void checkData() { |
| Tree.this.updateAllItems(); |
| } |
| |
| public void setScrollLeft( int left ) { |
| Tree.this.scrollLeft = left; |
| } |
| |
| public int getScrollLeft() { |
| return Tree.this.scrollLeft; |
| } |
| |
| public boolean isCached( TreeItem item ) { |
| return item.isCached(); |
| } |
| |
| public boolean hasHScrollBar() { |
| return Tree.this.hasHScrollBar(); |
| } |
| |
| public boolean hasVScrollBar() { |
| return Tree.this.hasVScrollBar(); |
| } |
| |
| public Point getItemImageSize( int index ) { |
| return Tree.this.getItemImageSize( index ); |
| } |
| |
| public int getCellLeft( int index ) { |
| return Tree.this.getCellLeft( index ); |
| } |
| |
| public int getCellWidth( int index ) { |
| return Tree.this.getCellWidth( index ); |
| } |
| |
| public int getTextOffset( int index ) { |
| return Tree.this.getTextOffset( index ); |
| } |
| |
| public int getTextMaxWidth( int index ) { |
| return getTextWidth( index ); |
| } |
| |
| public int getCheckWidth() { |
| return getCheckImageSize().x; |
| } |
| |
| public int getImageOffset( int index ) { |
| return Tree.this.getImageOffset( index ); |
| } |
| |
| public int getIndentionWidth() { |
| return Tree.this.getIndentionWidth(); |
| } |
| |
| public int getCheckLeft() { |
| return getCheckBoxMargin().x; |
| } |
| |
| public Rectangle getTextMargin() { |
| return TEXT_MARGIN; |
| } |
| |
| public int getTopItemIndex() { |
| return Tree.this.getTopItemIndex(); |
| } |
| |
| public void setTopItemIndex( int index ) { |
| Tree.this.setTopItemIndex( index ); |
| } |
| |
| public int getColumnLeft( TreeColumn column ) { |
| int index = Tree.this.indexOf( column ); |
| return getColumn( index ).getLeft(); |
| } |
| |
| public ICellToolTipProvider getCellToolTipProvider() { |
| return provider; |
| } |
| |
| public void setCellToolTipProvider( ICellToolTipProvider provider ) { |
| this.provider = provider; |
| } |
| |
| public String getCellToolTipText() { |
| return toolTipText; |
| } |
| |
| public void setCellToolTipText( String toolTipText ) { |
| this.toolTipText = toolTipText; |
| } |
| |
| public int getFixedColumns() { |
| return Tree.this.getFixedColumns(); |
| } |
| |
| public boolean isFixedColumn( TreeColumn column ) { |
| return Tree.this.isFixedColumn( Tree.this.indexOf( column ) ); |
| } |
| |
| } |
| |
| static final class LayoutCache implements SerializableCompatibility { |
| private static final int UNKNOWN = -1; |
| |
| int headerHeight = UNKNOWN; |
| int itemHeight = UNKNOWN; |
| int cellSpacing = UNKNOWN; |
| Rectangle cellPadding; |
| Rectangle checkBoxMargin; |
| |
| public boolean hasHeaderHeight() { |
| return headerHeight != UNKNOWN; |
| } |
| |
| public void invalidateHeaderHeight() { |
| headerHeight = UNKNOWN; |
| } |
| |
| public boolean hasItemHeight() { |
| return itemHeight != UNKNOWN; |
| } |
| |
| public void invalidateItemHeight() { |
| itemHeight = UNKNOWN; |
| } |
| |
| public boolean hasCellSpacing() { |
| return cellSpacing != UNKNOWN; |
| } |
| |
| public void invalidateCellSpacing() { |
| cellSpacing = UNKNOWN; |
| } |
| |
| public boolean hasCellPadding() { |
| return cellPadding != null; |
| } |
| |
| public void invalidateCellPadding() { |
| cellPadding = null; |
| } |
| |
| public boolean hasCheckBoxMargin() { |
| return checkBoxMargin != null; |
| } |
| |
| public void invalidateCheckBoxMargin() { |
| checkBoxMargin = null; |
| } |
| |
| public void invalidateAll() { |
| invalidateHeaderHeight(); |
| invalidateItemHeight(); |
| invalidateCellSpacing(); |
| invalidateCellPadding(); |
| invalidateCheckBoxMargin(); |
| } |
| } |
| } |