diff options
author | Silenio Quarti <silenio> | 2009-07-01 14:50:54 +0000 |
---|---|---|
committer | Silenio Quarti <silenio> | 2009-07-01 14:50:54 +0000 |
commit | 093c579a4ffd9551acb901bba9617e7aa776989d (patch) | |
tree | 71cf23798b651ef92f188390841a8d130908fb11 /bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt | |
parent | f664d297f7bb009784868bf3fcf0b3e3bb9a646b (diff) | |
download | eclipse.platform.swt-093c579a4ffd9551acb901bba9617e7aa776989d.tar.gz eclipse.platform.swt-093c579a4ffd9551acb901bba9617e7aa776989d.tar.xz eclipse.platform.swt-093c579a4ffd9551acb901bba9617e7aa776989d.zip |
restore HEAD after accidental deletion by error in automated build script
Diffstat (limited to 'bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt')
6 files changed, 14952 insertions, 0 deletions
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/Table.java b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/Table.java new file mode 100644 index 0000000000..af445fdd61 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/Table.java @@ -0,0 +1,4166 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.*; + +/** + * Instances of this class implement a selectable user interface + * object that displays a list of images and strings and issues + * notification when selected. + * <p> + * The item children that may be added to instances of this class + * must be of type <code>TableItem</code>. + * </p><p> + * Style <code>VIRTUAL</code> is used to create a <code>Table</code> whose + * <code>TableItem</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 + * tables that are very large or for which <code>TableItem</code> population is + * expensive (for example, retrieving values from an external source). + * </p><p> + * Here is an example of using a <code>Table</code> with style <code>VIRTUAL</code>: + * <code><pre> + * final Table table = new Table (parent, SWT.VIRTUAL | SWT.BORDER); + * table.setItemCount (1000000); + * table.addListener (SWT.SetData, new Listener () { + * public void handleEvent (Event event) { + * TableItem item = (TableItem) event.item; + * int index = table.indexOf (item); + * item.setText ("Item " + index); + * System.out.println (item.getText ()); + * } + * }); + * </pre></code> + * </p><p> + * Note that although this class is a subclass of <code>Composite</code>, + * it does not normally make sense to add <code>Control</code> children to + * it, or set a layout on it, unless implementing something like a cell + * editor. + * </p><p> + * <dl> + * <dt><b>Styles:</b></dt> + * <dd>SINGLE, MULTI, CHECK, FULL_SELECTION, HIDE_SELECTION, VIRTUAL, NO_SCROLL</dd> + * <dt><b>Events:</b></dt> + * <dd>Selection, DefaultSelection, 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> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#table">Table, TableItem, TableColumn snippets</a> + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ +public class Table extends Composite { + Canvas header; + TableColumn[] columns = new TableColumn [0]; + TableColumn[] orderedColumns; + TableItem[] items = new TableItem [0]; + TableItem[] selectedItems = new TableItem [0]; + TableItem focusItem, anchorItem, lastClickedItem; + Event lastSelectionEvent; + boolean linesVisible, ignoreKey, ignoreDispose, customHeightSet; + int itemsCount = 0; + int topIndex = 0, horizontalOffset = 0; + int fontHeight = 0, imageHeight = 0, itemHeight = 0; + int col0ImageWidth = 0; + int headerImageHeight = 0; + TableColumn resizeColumn; + int resizeColumnX = -1; + int drawCount = 0; + TableColumn sortColumn; + int sortDirection = SWT.NONE; + + /* column header tooltip */ + Listener toolTipListener; + Shell toolTipShell; + Label toolTipLabel; + + Rectangle arrowBounds, checkboxBounds, clientArea; + + static final int MARGIN_IMAGE = 3; + static final int MARGIN_CELL = 1; + static final int SIZE_HORIZONTALSCROLL = 5; + static final int TOLLERANCE_COLUMNRESIZE = 2; + static final int WIDTH_HEADER_SHADOW = 2; + static final int WIDTH_CELL_HIGHLIGHT = 1; + static final int [] toolTipEvents = new int[] {SWT.MouseExit, SWT.MouseHover, SWT.MouseMove, SWT.MouseDown}; + static final String ELLIPSIS = "..."; //$NON-NLS-1$ + static final String ID_UNCHECKED = "UNCHECKED"; //$NON-NLS-1$ + static final String ID_GRAYUNCHECKED = "GRAYUNCHECKED"; //$NON-NLS-1$ + static final String ID_CHECKMARK = "CHECKMARK"; //$NON-NLS-1$ + static final String ID_ARROWUP = "ARROWUP"; //$NON-NLS-1$ + static final String ID_ARROWDOWN = "ARROWDOWN"; //$NON-NLS-1$ + +//TEMPORARY CODE +boolean hasFocus; +public boolean isFocusControl() { + return hasFocus; +} + +/** + * 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#HIDE_SELECTION + * @see SWT#VIRTUAL + * @see SWT#NO_SCROLL + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public Table (Composite parent, int style) { + super (parent, checkStyle (style)); + setForeground (null); /* set foreground and background to chosen default colors */ + setBackground (null); + GC gc = new GC (this); + fontHeight = gc.getFontMetrics ().getHeight (); + gc.dispose (); + itemHeight = fontHeight + (2 * getCellPadding ()); + initImages (display); + checkboxBounds = getUncheckedImage ().getBounds (); + arrowBounds = getArrowDownImage ().getBounds (); + clientArea = getClientArea (); + + Listener listener = new Listener () { + public void handleEvent (Event event) { + handleEvents (event); + } + }; + addListener (SWT.Paint, listener); + addListener (SWT.MouseDown, listener); + addListener (SWT.MouseUp, listener); + addListener (SWT.MouseDoubleClick, listener); + addListener (SWT.Dispose, listener); + addListener (SWT.Resize, listener); + addListener (SWT.KeyDown, listener); + addListener (SWT.FocusOut, listener); + addListener (SWT.FocusIn, listener); + addListener (SWT.Traverse, listener); + + header = new Canvas (this, SWT.NO_REDRAW_RESIZE | SWT.NO_FOCUS); + header.setVisible (false); + header.setBounds (0, 0, 0, fontHeight + 2 * getHeaderPadding ()); + header.addListener (SWT.Paint, listener); + header.addListener (SWT.MouseDown, listener); + header.addListener (SWT.MouseUp, listener); + header.addListener (SWT.MouseHover, listener); + header.addListener (SWT.MouseDoubleClick, listener); + header.addListener (SWT.MouseMove, listener); + header.addListener (SWT.MouseExit, listener); + header.addListener (SWT.MenuDetect, listener); + + toolTipListener = new Listener () { + public void handleEvent (Event event) { + switch (event.type) { + case SWT.MouseHover: + case SWT.MouseMove: + if (headerUpdateToolTip (event.x)) break; + // FALL THROUGH + case SWT.MouseExit: + case SWT.MouseDown: + headerHideToolTip (); + break; + } + } + }; + + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setValues (0, 0, 1, 1, 1, 1); + hBar.setVisible (false); + hBar.addListener (SWT.Selection, listener); + } + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + vBar.setValues (0, 0, 1, 1, 1, 1); + vBar.setVisible (false); + vBar.addListener (SWT.Selection, listener); + } +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the user changes the receiver's selection, 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 the <code>SWT.CHECK</code> style 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 when the user changes the receiver's selection + * + * @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 (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Selection, typedListener); + addListener (SWT.DefaultSelection, typedListener); +} +boolean checkData (TableItem item, boolean redraw) { + if (item.cached) return true; + if ((style & SWT.VIRTUAL) != 0) { + item.cached = true; + Event event = new Event (); + event.item = item; + event.index = indexOf (item); + sendEvent (SWT.SetData, event); + if (isDisposed () || item.isDisposed ()) return false; + if (redraw) redrawItem (item.index, false); + } + return true; +} +static int checkStyle (int style) { + /* + * Feature in Windows. Even when WS_HSCROLL or + * WS_VSCROLL is not specified, Windows creates + * trees and tables with scroll bars. The fix + * is to set H_SCROLL and V_SCROLL. + * + * NOTE: This code appears on all platforms so that + * applications have consistent scroll bar behavior. + */ + if ((style & SWT.NO_SCROLL) == 0) { + style |= SWT.H_SCROLL | SWT.V_SCROLL; + } + style |= SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED; + //TEMPORARY CODE + style |= SWT.FULL_SELECTION; + return checkBits (style, SWT.SINGLE, SWT.MULTI, 0, 0, 0, 0); +} +protected void checkSubclass () { + if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS); +} +/** + * 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 table 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 + * + * @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 + * + * @since 3.0 + */ +public void clear (int index) { + checkWidget (); + if (!(0 <= index && index < itemsCount)) error (SWT.ERROR_INVALID_RANGE); + Rectangle bounds = items [index].getBounds (false); + int oldRightX = bounds.x + bounds.width; + items [index].clear (); + if (columns.length == 0) updateHorizontalBar (0, -oldRightX); + redrawItem (index, false); +} +/** + * Removes the items from the receiver which are between the given + * zero-relative start and end indices (inclusive). The text, icon + * and other attributes of the items are set to their default values. + * If the table was created with the <code>SWT.VIRTUAL</code> style, + * these attributes are requested again as needed. + * + * @param start the start index of the item to clear + * @param end the end index of the item to clear + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if either the start or end are 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 + * + * @since 3.0 + */ +public void clear (int start, int end) { + checkWidget (); + if (start > end) return; + if (!(0 <= start && start <= end && end < itemsCount)) { + error (SWT.ERROR_INVALID_RANGE); + } + for (int i = start; i <= end; i++) { + items [i].clear (); + } + updateHorizontalBar (); + redrawItems (start, end, false); +} +/** + * Clears the items at the given zero-relative indices in the receiver. + * The text, icon and other attributes of the items are set to their default + * values. If the table was created with the <code>SWT.VIRTUAL</code> style, + * these attributes are requested again as needed. + * + * @param indices the array of indices of the items + * + * @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> + * <li>ERROR_NULL_ARGUMENT - if the indices array 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 SWT#VIRTUAL + * @see SWT#SetData + * + * @since 3.0 + */ +public void clear (int [] indices) { + checkWidget (); + if (indices == null) error (SWT.ERROR_NULL_ARGUMENT); + if (indices.length == 0) return; + for (int i = 0; i < indices.length; i++) { + if (!(0 <= indices [i] && indices [i] < itemsCount)) { + error (SWT.ERROR_INVALID_RANGE); + } + } + + for (int i = 0; i < indices.length; i++) { + items [indices [i]].clear (); + } + updateHorizontalBar (); + for (int i = 0; i < indices.length; i++) { + redrawItem (indices [i], false); + } +} +/** + * Clears all the items in the receiver. The text, icon and other + * attributes of the items are set to their default values. If the + * table was created with the <code>SWT.VIRTUAL</code> style, these + * attributes are requested again as needed. + * + * @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 + * + * @since 3.0 + */ +public void clearAll () { + checkWidget (); + clear (0, itemsCount - 1); +} +/* + * Returns the ORDERED index of the column that the specified x falls within, + * or -1 if the x lies to the right of the last column. + */ +int computeColumnIntersect (int x, int startColumn) { + TableColumn[] orderedColumns = getOrderedColumns (); + if (orderedColumns.length - 1 < startColumn) return -1; + int rightX = orderedColumns [startColumn].getX (); + for (int i = startColumn; i < orderedColumns.length; i++) { + rightX += orderedColumns [i].width; + if (x < rightX) return i; + } + return -1; +} +public Point computeSize (int wHint, int hHint, boolean changed) { + checkWidget (); + int width = 0, height = 0; + if (wHint != SWT.DEFAULT) { + width = wHint; + } else { + if (columns.length == 0) { + for (int i = 0; i < itemsCount; i++) { + Rectangle itemBounds = items [i].getBounds (false); + width = Math.max (width, itemBounds.x + itemBounds.width); + } + } else { + TableColumn[] orderedColumns = getOrderedColumns (); + TableColumn lastColumn = orderedColumns [orderedColumns.length - 1]; + width = lastColumn.getX () + lastColumn.width; + } + } + if (hHint != SWT.DEFAULT) { + height = hHint; + } else { + height = getHeaderHeight () + itemsCount * itemHeight; + } + Rectangle result = computeTrim (0, 0, width, height); + return new Point (result.width, result.height); +} +void createItem (TableColumn column, int index) { + TableColumn[] newColumns = new TableColumn [columns.length + 1]; + System.arraycopy (columns, 0, newColumns, 0, index); + newColumns [index] = column; + System.arraycopy (columns, index, newColumns, index + 1, columns.length - index); + columns = newColumns; + + if (orderedColumns != null) { + int insertIndex = 0; + if (index > 0) { + insertIndex = columns [index - 1].getOrderIndex () + 1; + } + TableColumn[] newOrderedColumns = new TableColumn [orderedColumns.length + 1]; + System.arraycopy (orderedColumns, 0, newOrderedColumns, 0, insertIndex); + newOrderedColumns [insertIndex] = column; + System.arraycopy ( + orderedColumns, + insertIndex, + newOrderedColumns, + insertIndex + 1, + orderedColumns.length - insertIndex); + orderedColumns = newOrderedColumns; + } + + /* allow all items to update their internal structures accordingly */ + for (int i = 0; i < itemsCount; i++) { + items [i].addColumn (column); + } + + /* existing items become hidden when going from 0 to 1 column (0 width) */ + if (columns.length == 1 && itemsCount > 0) { + redrawFromItemDownwards (topIndex); + } else { + /* checkboxes become hidden when creating a column with index == orderedIndex == 0 (0 width) */ + if (itemsCount > 0 && (style & SWT.CHECK) != 0 && index == 0 && column.getOrderIndex () == 0) { + redrawFromItemDownwards (topIndex); + } + } +} +void createItem (TableItem item) { + int index = item.index; + if (itemsCount == items.length) { + int grow = drawCount <= 0 ? 4 : Math.max (4, items.length * 3 / 2); + TableItem[] newItems = new TableItem [items.length + grow]; + System.arraycopy (items, 0, newItems, 0, items.length); + items = newItems; + } + if (index != itemsCount) { + /* new item is not at end of list, so shift other items right to create space for it */ + System.arraycopy (items, index, items, index + 1, itemsCount - index); + } + items [index] = item; + itemsCount++; + + /* update the index for items bumped down by this new item */ + for (int i = index + 1; i < itemsCount; i++) { + items [i].index = i; + } + + /* update scrollbars */ + updateVerticalBar (); + Rectangle bounds = item.getBounds (false); + int rightX = bounds.x + bounds.width; + updateHorizontalBar (rightX, rightX); + /* + * If new item is above viewport then adjust topIndex and the vertical + * scrollbar so that the current viewport items will not change. + */ + if (item.index < topIndex) { + topIndex++; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + return; + } + /* + * If this is the first item and the receiver has focus then its boundary + * focus ring must be removed. + */ + if (itemsCount == 1 && isFocusControl ()) { + focusItem = item; + redraw (); + return; + } + if (item.isInViewport ()) { + redrawFromItemDownwards (index); + } +} +/** + * Deselects the item at the given zero-relative index in the receiver. + * If the item at the index was already deselected, it remains + * deselected. Indices that are out of range are ignored. + * + * @param index the index of the item to deselect + * + * @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 deselect (int index) { + checkWidget (); + if (!(0 <= index && index < itemsCount)) return; + TableItem item = items [index]; + int selectIndex = getSelectionIndex (item); + if (selectIndex == -1) return; + + TableItem[] newSelectedItems = new TableItem [selectedItems.length - 1]; + System.arraycopy (selectedItems, 0, newSelectedItems, 0, selectIndex); + System.arraycopy (selectedItems, selectIndex + 1, newSelectedItems, selectIndex, newSelectedItems.length - selectIndex); + selectedItems = newSelectedItems; + + if (hasFocus () || (style & SWT.HIDE_SELECTION) == 0) { + redrawItem (item.index, false); + } +} +/** + * Deselects the items at the given zero-relative indices in the receiver. + * If the item at the given zero-relative index in the receiver + * is selected, it is deselected. If the item at the index + * was not selected, it remains deselected. The range of the + * indices is inclusive. Indices that are out of range are ignored. + * + * @param start the start index of the items to deselect + * @param end the end index of the items to deselect + * + * @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 deselect (int start, int end) { + checkWidget (); + if (start == 0 && end == itemsCount - 1) { + deselectAll (); + } else { + start = Math.max (start, 0); + end = Math.min (end, itemsCount - 1); + for (int i = start; i <= end; i++) { + deselect (i); + } + } +} +/** + * Deselects the items at the given zero-relative indices in the receiver. + * If the item at the given zero-relative index in the receiver + * is selected, it is deselected. If the item at the index + * was not selected, it remains deselected. Indices that are out + * of range and duplicate indices are ignored. + * + * @param indices the array of indices for the items to deselect + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the set of indices 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 deselect (int [] indices) { + checkWidget (); + if (indices == null) error (SWT.ERROR_NULL_ARGUMENT); + if (indices.length == 0) return; + for (int i = 0; i < indices.length; i++) { + deselect (indices [i]); + } +} +/** + * 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 (); + TableItem[] oldSelection = selectedItems; + selectedItems = new TableItem [0]; + if (hasFocus () || (style & SWT.HIDE_SELECTION) == 0) { + for (int i = 0; i < oldSelection.length; i++) { + redrawItem (oldSelection [i].index, true); + } + } +} +void deselectItem (TableItem item) { + int index = getSelectionIndex (item); + if (index == -1) return; + TableItem[] newSelectedItems = new TableItem [selectedItems.length - 1]; + System.arraycopy (selectedItems, 0, newSelectedItems, 0, index); + System.arraycopy ( + selectedItems, + index + 1, + newSelectedItems, + index, + newSelectedItems.length - index); + selectedItems = newSelectedItems; +} +void destroyItem (TableColumn column) { + headerHideToolTip (); + int index = column.getIndex (); + int orderedIndex = column.getOrderIndex (); + + TableColumn[] newColumns = new TableColumn [columns.length - 1]; + System.arraycopy (columns, 0, newColumns, 0, index); + System.arraycopy (columns, index + 1, newColumns, index, newColumns.length - index); + columns = newColumns; + + if (orderedColumns != null) { + if (columns.length < 2) { + orderedColumns = null; + } else { + int removeIndex = column.getOrderIndex (); + TableColumn[] newOrderedColumns = new TableColumn [orderedColumns.length - 1]; + System.arraycopy (orderedColumns, 0, newOrderedColumns, 0, removeIndex); + System.arraycopy ( + orderedColumns, + removeIndex + 1, + newOrderedColumns, + removeIndex, + newOrderedColumns.length - removeIndex); + orderedColumns = newOrderedColumns; + } + } + + /* ensure that column 0 always has left-alignment */ + if (index == 0 && columns.length > 0) { + columns [0].style |= SWT.LEFT; + columns [0].style &= ~(SWT.CENTER | SWT.RIGHT); + } + + /* allow all items to update their internal structures accordingly */ + for (int i = 0; i < itemsCount; i++) { + items [i].removeColumn (column, index); + } + + /* update horizontal scrollbar */ + int lastColumnIndex = columns.length - 1; + if (lastColumnIndex < 0) { /* no more columns */ + updateHorizontalBar (); + } else { + int newWidth = 0; + for (int i = 0; i < columns.length; i++) { + newWidth += columns [i].width; + } + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setMaximum (newWidth); + hBar.setVisible (clientArea.width < newWidth); + } + int selection = hBar.getSelection (); + if (selection != horizontalOffset) { + horizontalOffset = selection; + redraw (); + if (header.isVisible () && drawCount <= 0) header.redraw (); + } + } + TableColumn[] orderedColumns = getOrderedColumns (); + for (int i = orderedIndex; i < orderedColumns.length; i++) { + if (!orderedColumns [i].isDisposed ()) { + orderedColumns [i].sendEvent (SWT.Move); + } + } + + if (sortColumn == column) { + sortColumn = null; + } +} +/* + * Allows the Table to update internal structures it has that may contain the + * item being destroyed. + */ +void destroyItem (TableItem item) { + if (item == focusItem) reassignFocus (); + + int index = item.index; + Rectangle bounds = item.getBounds (false); + int rightX = bounds.x + bounds.width; + + if (index != itemsCount - 1) { + /* item is not at end of items list, so must shift items left to reclaim its slot */ + System.arraycopy (items, index + 1, items, index, itemsCount - index - 1); + items [itemsCount - 1] = null; + } else { + items [index] = null; /* last item, so no array copy needed */ + } + itemsCount--; + + if (drawCount <= 0 && items.length - itemsCount == 4) { + /* shrink the items array */ + TableItem[] newItems = new TableItem [itemsCount]; + System.arraycopy (items, 0, newItems, 0, newItems.length); + items = newItems; + } + + /* update the index on affected items */ + for (int i = index; i < itemsCount; i++) { + items [i].index = i; + } + item.index = -1; + + int oldTopIndex = topIndex; + updateVerticalBar (); + updateHorizontalBar (0, -rightX); + /* + * If destroyed item is above viewport then adjust topIndex and the vertical + * scrollbar so that the current viewport items will not change. + */ + if (index < topIndex) { + topIndex = oldTopIndex - 1; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + } + + /* selectedItems array */ + if (item.isSelected ()) { + int selectionIndex = getSelectionIndex (item); + TableItem[] newSelectedItems = new TableItem [selectedItems.length - 1]; + System.arraycopy (selectedItems, 0, newSelectedItems, 0, selectionIndex); + System.arraycopy ( + selectedItems, + selectionIndex + 1, + newSelectedItems, + selectionIndex, + newSelectedItems.length - selectionIndex); + selectedItems = newSelectedItems; + } + if (item == anchorItem) anchorItem = null; + if (item == lastClickedItem) lastClickedItem = null; + /* + * If this was the last item and the receiver has focus then its boundary + * focus ring must be redrawn. + */ + if (itemsCount == 0 && isFocusControl ()) { + redraw (); + return; + } +} +Image getArrowDownImage () { + return (Image) display.getData (ID_ARROWDOWN); +} +Image getArrowUpImage () { + return (Image) display.getData (ID_ARROWUP); +} +int getCellPadding () { + return MARGIN_CELL + WIDTH_CELL_HIGHLIGHT; +} +Image getCheckmarkImage () { + return (Image) display.getData (ID_CHECKMARK); +} +public Control[] getChildren () { + checkWidget (); + Control[] controls = _getChildren (); + if (header == null) return controls; + Control[] result = new Control [controls.length - 1]; + /* remove the Header from the returned set of children */ + int index = 0; + for (int i = 0; i < controls.length; i++) { + if (controls [i] != header) { + result [index++] = controls [i]; + } + } + return result; +} +/** + * 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>TableColumn</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 table. + * This occurs when the programmer uses the table 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 Table#getColumnOrder() + * @see Table#setColumnOrder(int[]) + * @see TableColumn#getMoveable() + * @see TableColumn#setMoveable(boolean) + * @see SWT#Move + */ +public TableColumn getColumn (int index) { + checkWidget (); + if (!(0 <= index && index < columns.length)) error (SWT.ERROR_INVALID_RANGE); + return columns [index]; +} +/** + * Returns the number of columns contained in the receiver. + * If no <code>TableColumn</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 table 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 columns.length; +} +/** + * 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 Table#setColumnOrder(int[]) + * @see TableColumn#getMoveable() + * @see TableColumn#setMoveable(boolean) + * @see SWT#Move + * + * @since 3.1 + */ +public int[] getColumnOrder () { + checkWidget (); + int[] result = new int [columns.length]; + if (orderedColumns != null) { + for (int i = 0; i < result.length; i++) { + result [i] = orderedColumns [i].getIndex (); + } + } else { + for (int i = 0; i < columns.length; i++) { + result [i] = i; + } + } + return result; +} +/** + * Returns an array of <code>TableColumn</code>s which are the + * columns in the receiver. Columns are returned in the order + * that they were created. If no <code>TableColumn</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 table 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 Table#getColumnOrder() + * @see Table#setColumnOrder(int[]) + * @see TableColumn#getMoveable() + * @see TableColumn#setMoveable(boolean) + * @see SWT#Move + */ +public TableColumn[] getColumns () { + checkWidget (); + TableColumn[] result = new TableColumn [columns.length]; + System.arraycopy (columns, 0, result, 0, columns.length); + return result; +} +Image getGrayUncheckedImage () { + return (Image) display.getData (ID_GRAYUNCHECKED); +} +/** + * 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> + */ +public int getGridLineWidth () { + checkWidget (); + return 1; +} +/** + * 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> + * + * @since 2.0 + */ +public int getHeaderHeight () { + checkWidget (); + if (!header.getVisible ()) return 0; + return header.getSize ().y; +} +int getHeaderPadding () { + return MARGIN_CELL + WIDTH_HEADER_SHADOW; +} +/** + * 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 header.getVisible (); +} +/** + * 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 TableItem getItem (int index) { + checkWidget (); + if (!(0 <= index && index < itemsCount)) error (SWT.ERROR_INVALID_RANGE); + return items [index]; +} +/** + * 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 TableItem getItem (Point point) { + checkWidget (); + if (point == null) error (SWT.ERROR_NULL_ARGUMENT); + int index = (point.y - getHeaderHeight ()) / itemHeight + topIndex; + if (!(0 <= index && index < itemsCount)) return null; /* below the last item */ + TableItem result = items [index]; + if (!result.getHitBounds ().contains (point)) return null; /* considers the x value */ + return result; +} +/** + * Returns the number of items contained in 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 (); + return itemsCount; +} +/** + * Returns the height of the area which would be used to + * display <em>one</em> of the items in the receiver. + * + * @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> + */ +public int getItemHeight () { + checkWidget (); + return itemHeight; +} +/** + * Returns a (possibly empty) array of <code>TableItem</code>s which + * are the items in 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 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 TableItem[] getItems () { + checkWidget (); + TableItem[] result = new TableItem [itemsCount]; + System.arraycopy (items, 0, result, 0, itemsCount); + return result; +} +/* + * Returns the current y-coordinate that the specified item should have. + */ +int getItemY (TableItem item) { + return (item.index - topIndex) * itemHeight + getHeaderHeight (); +} +/** + * Returns <code>true</code> if the receiver's lines are visible, + * and <code>false</code> otherwise. Note that some platforms draw + * grid lines while others may draw alternating row colors. + * <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; +} +TableColumn[] getOrderedColumns () { + if (orderedColumns != null) return orderedColumns; + return columns; +} +/** + * Returns an array of <code>TableItem</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 TableItem[] getSelection () { + checkWidget (); + TableItem[] result = new TableItem [selectedItems.length]; + System.arraycopy (selectedItems, 0, result, 0, selectedItems.length); + sortAscent (result); + 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 selectedItems.length; +} +/** + * Returns the zero-relative index of the item which is currently + * selected in the receiver, or -1 if no item is selected. + * + * @return the index of the selected 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 int getSelectionIndex () { + checkWidget (); + if (selectedItems.length == 0) return -1; + return selectedItems [0].index; +} +/* + * Returns the index of the argument in the receiver's array of currently- + * selected items, or -1 if the item is not currently selected. + */ +int getSelectionIndex (TableItem item) { + for (int i = 0; i < selectedItems.length; i++) { + if (selectedItems [i] == item) return i; + } + return -1; +} +/** + * Returns the zero-relative indices of the items which are currently + * selected in the receiver. The order of the indices is unspecified. + * The array is empty if 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 the array of indices of the 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 [] getSelectionIndices () { + checkWidget (); + int[] result = new int [selectedItems.length]; + for (int i = 0; i < selectedItems.length; i++) { + result [i] = selectedItems [i].index; + } + sortAscent (result); + return result; +} +/** + * 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(TableColumn) + * + * @since 3.2 + */ +public TableColumn 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) + * + * @since 3.2 + */ +public int getSortDirection () { + checkWidget (); + return sortDirection; +} +/** + * Returns the zero-relative index of the item which is currently + * at the top of the receiver. This index can change when items are + * scrolled or new items are added or removed. + * + * @return the index of the top 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 int getTopIndex () { + checkWidget (); + return topIndex; +} +Image getUncheckedImage () { + return (Image) display.getData (ID_UNCHECKED); +} +void handleEvents (Event event) { + switch (event.type) { + case SWT.Paint: + if (event.widget == header) { + headerOnPaint (event); + } else { + onPaint (event); + } + break; + case SWT.MenuDetect: { + notifyListeners (SWT.MenuDetect, event); + break; + } + case SWT.MouseDown: + if (event.widget == header) { + headerOnMouseDown (event); + } else { + onMouseDown (event); + } + break; + case SWT.MouseUp: + if (event.widget == header) { + headerOnMouseUp (event); + } else { + onMouseUp (event); + } + break; + case SWT.MouseHover: + headerOnMouseHover (event); break; + case SWT.MouseMove: + headerOnMouseMove (event); break; + case SWT.MouseDoubleClick: + if (event.widget == header) { + headerOnMouseDoubleClick (event); + } else { + onMouseDoubleClick (event); + } + break; + case SWT.MouseExit: + headerOnMouseExit (); break; + case SWT.Dispose: + onDispose (event); break; + case SWT.KeyDown: + onKeyDown (event); break; + case SWT.Resize: + onResize (event); break; + case SWT.Selection: + if (event.widget == getHorizontalBar ()) { + onScrollHorizontal (event); + } + if (event.widget == getVerticalBar ()) { + onScrollVertical (event); + } + break; + case SWT.FocusOut: + onFocusOut (); break; + case SWT.FocusIn: + onFocusIn (); break; + case SWT.Traverse: + switch (event.detail) { + case SWT.TRAVERSE_ESCAPE: + case SWT.TRAVERSE_RETURN: + case SWT.TRAVERSE_TAB_NEXT: + case SWT.TRAVERSE_TAB_PREVIOUS: + case SWT.TRAVERSE_PAGE_NEXT: + case SWT.TRAVERSE_PAGE_PREVIOUS: + event.doit = true; + break; + } + break; + } +} +String headerGetToolTip (int x) { + if (resizeColumn != null) return null; + int orderedIndex = computeColumnIntersect (x, 0); + if (orderedIndex == -1) return null; + TableColumn[] orderedColumns = getOrderedColumns (); + TableColumn column = orderedColumns [orderedIndex]; + if (column.toolTipText == null) return null; + + /* no tooltip should appear if the hover is at a column resize opportunity */ + int columnX = column.getX (); + if (orderedIndex > 0 && orderedColumns [orderedIndex - 1].resizable) { + /* left column bound is resizable */ + if (x - columnX <= TOLLERANCE_COLUMNRESIZE) return null; + } + if (column.resizable) { + /* right column bound is resizable */ + int columnRightX = columnX + column.width; + if (columnRightX - x <= TOLLERANCE_COLUMNRESIZE) return null; + } + return removeMnemonics (column.toolTipText); +} +void headerHideToolTip() { + if (toolTipShell == null) return; + for (int i = 0; i < toolTipEvents.length; i++) { + header.removeListener (toolTipEvents [i], toolTipListener); + } + toolTipShell.dispose (); + toolTipShell = null; + toolTipLabel = null; +} +void headerOnMouseDoubleClick (Event event) { + if (!isFocusControl ()) setFocus (); + if (columns.length == 0) return; + TableColumn[] orderedColumns = getOrderedColumns (); + int x = -horizontalOffset; + for (int i = 0; i < orderedColumns.length; i++) { + TableColumn column = orderedColumns [i]; + x += column.width; + if (event.x < x) { + /* found the clicked column */ + TableColumn packColumn = null; + if (x - event.x <= TOLLERANCE_COLUMNRESIZE) { + /* clicked on column bound for this column */ + packColumn = column; + } else { + if (i > 0 && event.x - column.getX () <= TOLLERANCE_COLUMNRESIZE) { + /* clicked on column bound that applies to previous column */ + packColumn = orderedColumns [i - 1]; + } + } + if (packColumn != null) { + packColumn.pack (); + resizeColumn = null; + if (Math.abs (packColumn.getX () + packColumn.width - event.x) > TOLLERANCE_COLUMNRESIZE) { + /* column separator has relocated away from pointer location */ + setCursor (null); + } + return; + } + /* did not click on column separator, so just fire column event */ + Event newEvent = new Event (); + newEvent.widget = column; + column.postEvent (SWT.DefaultSelection, newEvent); + return; + } + } +} +void headerOnMouseDown (Event event) { + if (event.button != 1) return; + TableColumn[] orderedColumns = getOrderedColumns (); + int x = -horizontalOffset; + for (int i = 0; i < orderedColumns.length; i++) { + TableColumn column = orderedColumns [i]; + x += column.width; + /* if close to a resizable column separator line then begin column resize */ + if (column.resizable && Math.abs (x - event.x) <= TOLLERANCE_COLUMNRESIZE) { + resizeColumn = column; + resizeColumnX = x; + return; + } + /* + * If within column but not near separator line then start column drag + * if column is moveable, or just fire column Selection otherwise. + */ + if (event.x < x) { + if (column.moveable) { + /* open tracker on the dragged column's header cell */ + int columnX = column.getX (); + int pointerOffset = event.x - columnX; + headerHideToolTip (); + Tracker tracker = new Tracker (this, SWT.NONE); + tracker.setRectangles (new Rectangle[] { + new Rectangle (columnX, 0, column.width, getHeaderHeight ()) + }); + if (!tracker.open ()) return; /* cancelled */ + /* determine which column was dragged onto */ + Rectangle result = tracker.getRectangles () [0]; + int pointerX = result.x + pointerOffset; + if (pointerX < 0) return; /* dragged too far left */ + x = -horizontalOffset; + for (int destIndex = 0; destIndex < orderedColumns.length; destIndex++) { + TableColumn destColumn = orderedColumns [destIndex]; + x += destColumn.width; + if (pointerX < x) { + int oldIndex = column.getOrderIndex (); + if (destIndex == oldIndex) { /* dragged onto self */ + Event newEvent = new Event (); + newEvent.widget = column; + column.postEvent (SWT.Selection, newEvent); + return; + } + int leftmostIndex = Math.min (destIndex, oldIndex); + int[] oldOrder = getColumnOrder (); + int[] newOrder = new int [oldOrder.length]; + System.arraycopy (oldOrder, 0, newOrder, 0, leftmostIndex); + if (leftmostIndex == oldIndex) { + /* column moving to the right */ + System.arraycopy (oldOrder, oldIndex + 1, newOrder, oldIndex, destIndex - oldIndex); + } else { + /* column moving to the left */ + System.arraycopy (oldOrder, destIndex, newOrder, destIndex + 1, oldIndex - destIndex); + } + newOrder [destIndex] = oldOrder [oldIndex]; + int rightmostIndex = Math.max (destIndex, oldIndex); + System.arraycopy ( + oldOrder, + rightmostIndex + 1, + newOrder, + rightmostIndex + 1, + newOrder.length - rightmostIndex - 1); + setColumnOrder (newOrder); + return; + } + } + return; /* dragged too far right */ + } + /* column is not moveable */ + Event newEvent = new Event (); + newEvent.widget = column; + column.postEvent (SWT.Selection, newEvent); + return; + } + } +} +void headerOnMouseExit () { + if (resizeColumn != null) return; + setCursor (null); /* ensure that a column resize cursor does not escape */ +} +void headerOnMouseHover (Event event) { + headerShowToolTip (event.x); +} +void headerOnMouseMove (Event event) { + if (resizeColumn == null) { + /* not currently resizing a column */ + for (int i = 0; i < columns.length; i++) { + TableColumn column = columns [i]; + int x = column.getX () + column.width; + if (Math.abs (x - event.x) <= TOLLERANCE_COLUMNRESIZE) { + if (column.resizable) { + setCursor (display.getSystemCursor (SWT.CURSOR_SIZEWE)); + } else { + setCursor (null); + } + return; + } + } + setCursor (null); + return; + } + + /* currently resizing a column */ + + /* don't allow the resize x to move left of the column's x position */ + if (event.x <= resizeColumn.getX ()) return; + + /* redraw the resizing line at its new location */ + GC gc = new GC (this); + gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK)); + int lineHeight = clientArea.height; + redraw (resizeColumnX - 1, 0, 1, lineHeight, false); + resizeColumnX = event.x; + gc.drawLine (resizeColumnX - 1, 0, resizeColumnX - 1, lineHeight); + gc.dispose (); +} +void headerOnMouseUp (Event event) { + if (resizeColumn == null) return; /* not resizing a column */ + + /* remove the resize line */ + GC gc = new GC (this); + redraw (resizeColumnX - 1, 0, 1, clientArea.height, false); + gc.dispose (); + + int newWidth = resizeColumnX - resizeColumn.getX (); + if (newWidth != resizeColumn.width) { + setCursor (null); + updateColumnWidth (resizeColumn, newWidth); + } + resizeColumnX = -1; + resizeColumn = null; +} +void headerOnPaint (Event event) { + TableColumn[] orderedColumns = getOrderedColumns (); + int numColumns = orderedColumns.length; + GC gc = event.gc; + Rectangle clipping = gc.getClipping (); + int startColumn = -1, endColumn = -1; + if (numColumns > 0) { + startColumn = computeColumnIntersect (clipping.x, 0); + if (startColumn != -1) { /* the clip x is within a column's bounds */ + endColumn = computeColumnIntersect (clipping.x + clipping.width, startColumn); + if (endColumn == -1) endColumn = numColumns - 1; + } + } else { + startColumn = endColumn = 0; + } + + /* paint the column header shadow that spans the full header width */ + Point headerSize = header.getSize (); + headerPaintHShadows (gc, 0, 0, headerSize.x, headerSize.y); + + /* if all damage is to the right of the last column then finished */ + if (startColumn == -1) return; + + /* paint each of the column headers */ + if (numColumns == 0) return; /* no headers to paint */ + for (int i = startColumn; i <= endColumn; i++) { + headerPaintVShadows (gc, orderedColumns [i].getX (), 0, orderedColumns [i].width, headerSize.y); + orderedColumns [i].paint (gc); + } +} +void headerPaintHShadows (GC gc, int x, int y, int width, int height) { + gc.setClipping (x, y, width, height); + int endX = x + width; + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW)); + gc.drawLine (x, y, endX, y); /* highlight shadow */ + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_NORMAL_SHADOW)); + gc.drawLine (x, height - 2, endX, height - 2); /* lowlight shadow */ + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_DARK_SHADOW)); + gc.drawLine (x, height - 1, endX, height - 1); /* outer shadow */ +} +void headerPaintVShadows (GC gc, int x, int y, int width, int height) { + gc.setClipping (x, y, width, height); + int endX = x + width; + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW)); + gc.drawLine (x, y, x, y + height - 1); /* highlight shadow */ + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_NORMAL_SHADOW)); + gc.drawLine (endX - 2, y + 1, endX - 2, height - 2); /* light inner shadow */ + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_DARK_SHADOW)); + gc.drawLine (endX - 1, y, endX - 1, height - 1); /* dark outer shadow */ +} +void headerShowToolTip (int x) { + String tooltip = headerGetToolTip (x); + if (tooltip == null || tooltip.length () == 0) return; + + if (toolTipShell == null) { + toolTipShell = new Shell (getShell (), SWT.ON_TOP | SWT.TOOL); + toolTipLabel = new Label (toolTipShell, SWT.CENTER); + Display display = toolTipShell.getDisplay (); + toolTipLabel.setForeground (display.getSystemColor (SWT.COLOR_INFO_FOREGROUND)); + toolTipLabel.setBackground (display.getSystemColor (SWT.COLOR_INFO_BACKGROUND)); + for (int i = 0; i < toolTipEvents.length; i++) { + header.addListener (toolTipEvents [i], toolTipListener); + } + } + if (headerUpdateToolTip (x)) { + toolTipShell.setVisible (true); + } else { + headerHideToolTip (); + } +} +boolean headerUpdateToolTip (int x) { + String tooltip = headerGetToolTip (x); + if (tooltip == null || tooltip.length () == 0) return false; + if (tooltip.equals (toolTipLabel.getText ())) return true; + + toolTipLabel.setText (tooltip); + TableColumn column = getOrderedColumns () [computeColumnIntersect (x, 0)]; + toolTipShell.setData (new Integer (column.getIndex ())); + Point labelSize = toolTipLabel.computeSize (SWT.DEFAULT, SWT.DEFAULT, true); + labelSize.x += 2; labelSize.y += 2; + toolTipLabel.setSize (labelSize); + toolTipShell.pack (); + /* + * On some platforms, there is a minimum size for a shell + * which may be greater than the label size. + * To avoid having the background of the tip shell showing + * around the label, force the label to fill the entire client area. + */ + Rectangle area = toolTipShell.getClientArea (); + toolTipLabel.setSize (area.width, area.height); + + /* Position the tooltip and ensure it's not located off the screen */ + Point cursorLocation = getDisplay ().getCursorLocation (); + int cursorHeight = 21; /* assuming cursor is 21x21 */ + Point size = toolTipShell.getSize (); + Rectangle rect = getMonitor ().getBounds (); + Point pt = new Point (cursorLocation.x, cursorLocation.y + cursorHeight + 2); + pt.x = Math.max (pt.x, rect.x); + if (pt.x + size.x > rect.x + rect.width) pt.x = rect.x + rect.width - size.x; + if (pt.y + size.y > rect.y + rect.height) pt.y = cursorLocation.y - 2 - size.y; + toolTipShell.setLocation (pt); + return true; +} +/** + * 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 (TableColumn column) { + checkWidget (); + if (column == null) error (SWT.ERROR_NULL_ARGUMENT); + if (column.parent != this) return -1; + return column.getIndex (); +} +/** + * 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 item 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 (TableItem item) { + checkWidget (); + if (item == null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.parent != this) return -1; + return item.index; +} +static void initImages (final Display display) { + PaletteData arrowPalette = new PaletteData (new RGB[] { + new RGB (0, 0, 0), new RGB (255, 255, 255)}); + if (display.getData (ID_ARROWDOWN) == null) { + ImageData arrowDown = new ImageData ( + 7, 4, 1, + arrowPalette, 1, + new byte[] {0x00, (byte)0x83, (byte)0xC7, (byte)0xEF}); + arrowDown.transparentPixel = 0x1; /* use white for transparency */ + display.setData (ID_ARROWDOWN, new Image (display, arrowDown)); + } + if (display.getData (ID_ARROWUP) == null) { + ImageData arrowUp = new ImageData ( + 7, 4, 1, + arrowPalette, 1, + new byte[] {(byte)0xEF, (byte)0xC7, (byte)0x83, 0x00}); + arrowUp.transparentPixel = 0x1; /* use white for transparency */ + display.setData (ID_ARROWUP, new Image (display, arrowUp)); + } + + PaletteData checkMarkPalette = new PaletteData ( + new RGB[] {new RGB (0, 0, 0), new RGB (252, 3, 251)}); + byte[] checkbox = new byte[] {0, 0, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 0, 0}; + ImageData checkmark = new ImageData (7, 7, 1, checkMarkPalette, 1, new byte[] {-4, -8, 112, 34, 6, -114, -34}); + checkmark.transparentPixel = 1; + if (display.getData (ID_CHECKMARK) == null) { + display.setData (ID_CHECKMARK, new Image (display, checkmark)); + } + + if (display.getData (ID_UNCHECKED) == null) { + PaletteData uncheckedPalette = new PaletteData ( + new RGB[] {new RGB (128, 128, 128), new RGB (255, 255, 255)}); + ImageData unchecked = new ImageData (11, 11, 1, uncheckedPalette, 2, checkbox); + display.setData (ID_UNCHECKED, new Image (display, unchecked)); + } + + if (display.getData (ID_GRAYUNCHECKED) == null) { + PaletteData grayUncheckedPalette = new PaletteData ( + new RGB[] {new RGB (128, 128, 128), new RGB (192, 192, 192)}); + ImageData grayUnchecked = new ImageData (11, 11, 1, grayUncheckedPalette, 2, checkbox); + display.setData (ID_GRAYUNCHECKED, new Image (display, grayUnchecked)); + } + + display.disposeExec (new Runnable () { + public void run() { + Image unchecked = (Image) display.getData (ID_UNCHECKED); + if (unchecked != null) unchecked.dispose (); + Image grayUnchecked = (Image) display.getData (ID_GRAYUNCHECKED); + if (grayUnchecked != null) grayUnchecked.dispose (); + Image checkmark = (Image) display.getData (ID_CHECKMARK); + if (checkmark != null) checkmark.dispose (); + Image arrowDown = (Image) display.getData (ID_ARROWDOWN); + if (arrowDown != null) arrowDown.dispose (); + Image arrowUp = (Image) display.getData (ID_ARROWUP); + if (arrowUp != null) arrowUp.dispose (); + + display.setData (ID_UNCHECKED, null); + display.setData (ID_GRAYUNCHECKED, null); + display.setData (ID_CHECKMARK, null); + display.setData (ID_ARROWDOWN, null); + display.setData (ID_ARROWUP, null); + } + }); +} +/** + * Returns <code>true</code> if the item is selected, + * and <code>false</code> otherwise. Indices out of + * range are ignored. + * + * @param index the index of the item + * @return the selection state of the item at the index + * + * @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 isSelected (int index) { + checkWidget (); + if (!(0 <= index && index < itemsCount)) return false; + return items [index].isSelected (); +} +void onArrowDown (int stateMask) { + if ((stateMask & (SWT.SHIFT | SWT.CTRL)) == 0) { + /* Down Arrow with no modifiers */ + int newFocusIndex = focusItem.index + 1; + if (newFocusIndex == itemsCount) return; /* at bottom */ + selectItem (items [newFocusIndex], false); + setFocusItem (items [newFocusIndex], true); + redrawItem (newFocusIndex, true); + showItem (items [newFocusIndex]); + Event newEvent = new Event (); + newEvent.item = items [newFocusIndex]; + postEvent (SWT.Selection, newEvent); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+Down Arrow, CTRL+Shift+Down Arrow */ + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if (itemsCount <= topIndex + visibleItemCount) return; /* at bottom */ + update (); + topIndex++; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, -itemHeight); + gc.dispose (); + return; + } + /* Shift+Down Arrow */ + int newFocusIndex = focusItem.index + 1; + if (newFocusIndex == itemsCount) return; /* at bottom */ + selectItem (items [newFocusIndex], false); + setFocusItem (items [newFocusIndex], true); + redrawItem (newFocusIndex, true); + showItem (items [newFocusIndex]); + Event newEvent = new Event (); + newEvent.item = items [newFocusIndex]; + postEvent (SWT.Selection, newEvent); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* CTRL+Shift+Down Arrow */ + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if (itemsCount <= topIndex + visibleItemCount) return; /* at bottom */ + update (); + topIndex++; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, -itemHeight); + gc.dispose (); + return; + } + /* CTRL+Down Arrow */ + int focusIndex = focusItem.index; + if (focusIndex == itemsCount - 1) return; /* at bottom */ + TableItem newFocusItem = items [focusIndex + 1]; + setFocusItem (newFocusItem, true); + redrawItem (newFocusItem.index, true); + showItem (newFocusItem); + return; + } + /* Shift+Down Arrow */ + int newFocusIndex = focusItem.index + 1; + if (newFocusIndex == itemsCount) return; /* at bottom */ + if (anchorItem == null) anchorItem = focusItem; + if (focusItem.index < anchorItem.index) { + deselectItem (focusItem); + redrawItem (focusItem.index, true); + } + selectItem (items [newFocusIndex], true); + setFocusItem (items [newFocusIndex], true); + redrawItem (newFocusIndex, true); + showItem (items [newFocusIndex]); + Event newEvent = new Event (); + newEvent.item = items [newFocusIndex]; + postEvent (SWT.Selection, newEvent); +} +void onArrowLeft (int stateMask) { + if (horizontalOffset == 0) return; + int newSelection = Math.max (0, horizontalOffset - SIZE_HORIZONTALSCROLL); + update (); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose (); + if (header.getVisible ()) { + header.update (); + Rectangle headerClientArea = header.getClientArea (); + gc = new GC (header); + gc.copyArea ( + 0, 0, + headerClientArea.width, headerClientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose(); + } + horizontalOffset = newSelection; + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) hBar.setSelection (horizontalOffset); +} +void onArrowRight (int stateMask) { + ScrollBar hBar = getHorizontalBar (); + if (hBar == null) return; + int maximum = hBar.getMaximum (); + int clientWidth = clientArea.width; + if ((horizontalOffset + clientArea.width) == maximum) return; + if (maximum <= clientWidth) return; + int newSelection = Math.min (horizontalOffset + SIZE_HORIZONTALSCROLL, maximum - clientWidth); + update (); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose (); + if (header.getVisible ()) { + Rectangle headerClientArea = header.getClientArea (); + header.update (); + gc = new GC (header); + gc.copyArea ( + 0, 0, + headerClientArea.width, headerClientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose(); + } + horizontalOffset = newSelection; + hBar.setSelection (horizontalOffset); +} +void onArrowUp (int stateMask) { + if ((stateMask & (SWT.SHIFT | SWT.CTRL)) == 0) { + /* Up Arrow with no modifiers */ + int newFocusIndex = focusItem.index - 1; + if (newFocusIndex < 0) return; /* at top */ + TableItem item = items [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (newFocusIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+Up Arrow, CTRL+Shift+Up Arrow */ + if (topIndex == 0) return; /* at top */ + update (); + topIndex--; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, itemHeight); + gc.dispose (); + return; + } + /* Shift+Up Arrow */ + int newFocusIndex = focusItem.index - 1; + if (newFocusIndex < 0) return; /* at top */ + TableItem item = items [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (newFocusIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* CTRL+Shift+Up Arrow */ + if (topIndex == 0) return; /* at top */ + update (); + topIndex--; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, itemHeight); + gc.dispose (); + return; + } + /* CTRL+Up Arrow */ + int focusIndex = focusItem.index; + if (focusIndex == 0) return; /* at top */ + TableItem newFocusItem = items [focusIndex - 1]; + setFocusItem (newFocusItem, true); + showItem (newFocusItem); + redrawItem (newFocusItem.index, true); + return; + } + /* Shift+Up Arrow */ + int newFocusIndex = focusItem.index - 1; + if (newFocusIndex < 0) return; /* at top */ + if (anchorItem == null) anchorItem = focusItem; + if (anchorItem.index < focusItem.index) { + deselectItem (focusItem); + redrawItem (focusItem.index, true); + } + TableItem item = items [newFocusIndex]; + selectItem (item, true); + setFocusItem (item, true); + redrawItem (newFocusIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); +} +void onCR () { + if (focusItem == null) return; + Event event = new Event (); + event.item = focusItem; + postEvent (SWT.DefaultSelection, event); +} +void onDispose (Event event) { + if (isDisposed ()) return; + if (ignoreDispose) return; + ignoreDispose = true; + notifyListeners(SWT.Dispose, event); + event.type = SWT.None; + for (int i = 0; i < itemsCount; i++) { + items [i].dispose (false); + } + for (int i = 0; i < columns.length; i++) { + columns [i].dispose (false); + } + if (toolTipShell != null) { + toolTipShell.dispose (); + toolTipShell = null; + toolTipLabel = null; + } + toolTipListener = null; + itemsCount = topIndex = horizontalOffset = 0; + items = selectedItems = null; + columns = orderedColumns = null; + focusItem = anchorItem = lastClickedItem = null; + lastSelectionEvent = null; + header = null; + resizeColumn = sortColumn = null; +} +void onEnd (int stateMask) { + int lastAvailableIndex = itemsCount - 1; + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + /* End with no modifiers */ + if (focusItem.index == lastAvailableIndex) return; /* at bottom */ + TableItem item = items [lastAvailableIndex]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (lastAvailableIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+End, CTRL+Shift+End */ + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + setTopIndex (itemsCount - visibleItemCount); + return; + } + /* Shift+End */ + if (focusItem.index == lastAvailableIndex) return; /* at bottom */ + TableItem item = items [lastAvailableIndex]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (lastAvailableIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* CTRL+Shift+End */ + showItem (items [lastAvailableIndex]); + return; + } + /* CTRL+End */ + if (focusItem.index == lastAvailableIndex) return; /* at bottom */ + TableItem item = items [lastAvailableIndex]; + setFocusItem (item, true); + showItem (item); + redrawItem (item.index, true); + return; + } + /* Shift+End */ + if (anchorItem == null) anchorItem = focusItem; + TableItem selectedItem = items [lastAvailableIndex]; + if (selectedItem == focusItem && selectedItem.isSelected ()) return; + int anchorIndex = anchorItem.index; + int selectIndex = selectedItem.index; + TableItem[] newSelection = new TableItem [selectIndex - anchorIndex + 1]; + int writeIndex = 0; + for (int i = anchorIndex; i <= selectIndex; i++) { + newSelection [writeIndex++] = items [i]; + } + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + redrawItems (anchorIndex, selectIndex, true); + showItem (selectedItem); + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); +} +void onFocusIn () { + hasFocus = true; + if (itemsCount == 0) { + redraw (); + return; + } + if ((style & (SWT.HIDE_SELECTION | SWT.MULTI)) == (SWT.HIDE_SELECTION | SWT.MULTI)) { + for (int i = 0; i < selectedItems.length; i++) { + redrawItem (selectedItems [i].index, true); + } + } + if (focusItem != null) { + redrawItem (focusItem.index, true); + return; + } + /* an initial focus item must be selected */ + TableItem initialFocus; + if (selectedItems.length > 0) { + initialFocus = selectedItems [0]; + } else { + initialFocus = items [topIndex]; + } + setFocusItem (initialFocus, false); + redrawItem (initialFocus.index, true); + return; +} +void onFocusOut () { + hasFocus = false; + if (itemsCount == 0) { + redraw (); + return; + } + if (focusItem != null) { + redrawItem (focusItem.index, true); + } + if ((style & (SWT.HIDE_SELECTION | SWT.MULTI)) == (SWT.HIDE_SELECTION | SWT.MULTI)) { + for (int i = 0; i < selectedItems.length; i++) { + redrawItem (selectedItems [i].index, true); + } + } +} +void onHome (int stateMask) { + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + /* Home with no modifiers */ + if (focusItem.index == 0) return; /* at top */ + TableItem item = items [0]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (0, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+Home, CTRL+Shift+Home */ + setTopIndex (0); + return; + } + /* Shift+Home */ + if (focusItem.index == 0) return; /* at top */ + TableItem item = items [0]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (0, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* CTRL+Shift+Home */ + setTopIndex (0); + return; + } + /* CTRL+Home */ + if (focusItem.index == 0) return; /* at top */ + TableItem item = items [0]; + setFocusItem (item, true); + showItem (item); + redrawItem (item.index, true); + return; + } + /* Shift+Home */ + if (anchorItem == null) anchorItem = focusItem; + TableItem selectedItem = items [0]; + if (selectedItem == focusItem && selectedItem.isSelected ()) return; + int anchorIndex = anchorItem.index; + int selectIndex = selectedItem.index; + TableItem[] newSelection = new TableItem [anchorIndex + 1]; + int writeIndex = 0; + for (int i = anchorIndex; i >= 0; i--) { + newSelection [writeIndex++] = items [i]; + } + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + redrawItems (anchorIndex, selectIndex, true); + showItem (selectedItem); + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); +} +void onKeyDown (Event event) { + if (ignoreKey) { + ignoreKey = false; + return; + } + ignoreKey = true; + notifyListeners (event.type, event); + event.type = SWT.None; + if (!event.doit) return; + if (focusItem == null) return; + if ((event.stateMask & SWT.SHIFT) == 0 && event.keyCode != SWT.SHIFT) { + anchorItem = null; + } + switch (event.keyCode) { + case SWT.ARROW_UP: + onArrowUp (event.stateMask); + return; + case SWT.ARROW_DOWN: + onArrowDown (event.stateMask); + return; + case SWT.ARROW_LEFT: + onArrowLeft (event.stateMask); + return; + case SWT.ARROW_RIGHT: + onArrowRight (event.stateMask); + return; + case SWT.PAGE_UP: + onPageUp (event.stateMask); + return; + case SWT.PAGE_DOWN: + onPageDown (event.stateMask); + return; + case SWT.HOME: + onHome (event.stateMask); + return; + case SWT.END: + onEnd (event.stateMask); + return; + } + if (event.character == ' ') { + onSpace (); + return; + } + if (event.character == SWT.CR) { + onCR (); + return; + } + if ((event.stateMask & SWT.CTRL) != 0) return; + + int initialIndex = focusItem.index; + char character = Character.toLowerCase (event.character); + /* check available items from current focus item to bottom */ + for (int i = initialIndex + 1; i < itemsCount; i++) { + TableItem item = items [i]; + String text = item.getText (0, false); + if (text.length () > 0) { + if (Character.toLowerCase (text.charAt (0)) == character) { + selectItem (item, false); + setFocusItem (item, true); + redrawItem (i, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + } + } + /* check available items from top to current focus item */ + for (int i = 0; i < initialIndex; i++) { + TableItem item = items [i]; + String text = item.getText (0, false); + if (text.length () > 0) { + if (Character.toLowerCase (text.charAt (0)) == character) { + selectItem (item, false); + setFocusItem (item, true); + redrawItem (i, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + } + } +} +void onMouseDoubleClick (Event event) { + if (!isFocusControl ()) setFocus (); + int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex; + if (!(0 <= index && index < itemsCount)) return; /* not on an available item */ + TableItem selectedItem = items [index]; + + /* + * If the two clicks of the double click did not occur over the same item then do not + * consider this to be a default selection. + */ + if (selectedItem != lastClickedItem) return; + + if (!selectedItem.getHitBounds ().contains (event.x, event.y)) return; /* considers x */ + + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.DefaultSelection, newEvent); +} +void onMouseDown (Event event) { + if (!isFocusControl ()) forceFocus (); + int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex; + if (!(0 <= index && index < itemsCount)) return; /* not on an available item */ + TableItem selectedItem = items [index]; + + /* if click was in checkbox */ + if ((style & SWT.CHECK) != 0 && selectedItem.getCheckboxBounds ().contains (event.x, event.y)) { + if (event.button != 1) return; + selectedItem.setChecked (!selectedItem.checked); + Event newEvent = new Event (); + newEvent.item = selectedItem; + newEvent.detail = SWT.CHECK; + postEvent (SWT.Selection, newEvent); + return; + } + + if (!selectedItem.getHitBounds ().contains (event.x, event.y)) return; + + if ((event.stateMask & SWT.SHIFT) == 0 && event.keyCode != SWT.SHIFT) anchorItem = null; + + boolean sendSelection = true; + /* Detect when this is the second click of a DefaultSelection and don't fire Selection */ + if (lastSelectionEvent != null && lastSelectionEvent.item == selectedItem) { + if (event.time - lastSelectionEvent.time <= display.getDoubleClickTime ()) { + sendSelection = false; + } else { + lastSelectionEvent = event; + event.item = selectedItem; + } + } else { + lastSelectionEvent = event; + event.item = selectedItem; + } + + if ((style & SWT.SINGLE) != 0) { + if (!selectedItem.isSelected ()) { + if (event.button == 1) { + selectItem (selectedItem, false); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.index, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + selectItem (selectedItem, false); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.index, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + } + /* item is selected */ + if (event.button == 1) { + /* fire a selection event, though the selection did not change */ + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + } + /* SWT.MULTI */ + if (!selectedItem.isSelected ()) { + if (event.button == 1) { + if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == SWT.SHIFT) { + if (anchorItem == null) anchorItem = focusItem; + int anchorIndex = anchorItem.index; + int selectIndex = selectedItem.index; + TableItem[] newSelection = new TableItem [Math.abs (anchorIndex - selectIndex) + 1]; + int step = anchorIndex < selectIndex ? 1 : -1; + int writeIndex = 0; + for (int i = anchorIndex; i != selectIndex; i += step) { + newSelection [writeIndex++] = items [i]; + } + newSelection [writeIndex] = items [selectIndex]; + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + redrawItems ( + Math.min (anchorIndex, selectIndex), + Math.max (anchorIndex, selectIndex), + true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + selectItem (selectedItem, (event.stateMask & SWT.CTRL) != 0); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.index, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + /* button 3 */ + if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + selectItem (selectedItem, false); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.index, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + } + /* item is selected */ + if (event.button != 1) return; + if ((event.stateMask & SWT.CTRL) != 0) { + removeSelectedItem (getSelectionIndex (selectedItem)); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.index, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + if ((event.stateMask & SWT.SHIFT) != 0) { + if (anchorItem == null) anchorItem = focusItem; + int anchorIndex = anchorItem.index; + int selectIndex = selectedItem.index; + TableItem[] newSelection = new TableItem [Math.abs (anchorIndex - selectIndex) + 1]; + int step = anchorIndex < selectIndex ? 1 : -1; + int writeIndex = 0; + for (int i = anchorIndex; i != selectIndex; i += step) { + newSelection [writeIndex++] = items [i]; + } + newSelection [writeIndex] = items [selectIndex]; + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + redrawItems ( + Math.min (anchorIndex, selectIndex), + Math.max (anchorIndex, selectIndex), + true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + selectItem (selectedItem, false); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.index, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } +} +void onMouseUp (Event event) { + int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex; + if (!(0 <= index && index < itemsCount)) return; /* not on an available item */ + lastClickedItem = items [index]; +} +void onPageDown (int stateMask) { + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + /* PageDown with no modifiers */ + int newFocusIndex = focusItem.index + visibleItemCount - 1; + newFocusIndex = Math.min (newFocusIndex, itemsCount - 1); + if (newFocusIndex == focusItem.index) return; + TableItem item = items [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + showItem (item); + redrawItem (item.index, true); + return; + } + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == (SWT.CTRL | SWT.SHIFT)) { + /* CTRL+Shift+PageDown */ + int newTopIndex = topIndex + visibleItemCount; + newTopIndex = Math.min (newTopIndex, itemsCount - visibleItemCount); + if (newTopIndex == topIndex) return; + setTopIndex (newTopIndex); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* Shift+PageDown */ + int newFocusIndex = focusItem.index + visibleItemCount - 1; + newFocusIndex = Math.min (newFocusIndex, itemsCount - 1); + if (newFocusIndex == focusItem.index) return; + TableItem item = items [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + showItem (item); + redrawItem (item.index, true); + return; + } + /* CTRL+PageDown */ + int newTopIndex = topIndex + visibleItemCount; + newTopIndex = Math.min (newTopIndex, itemsCount - visibleItemCount); + if (newTopIndex == topIndex) return; + setTopIndex (newTopIndex); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+PageDown */ + int bottomIndex = Math.min (topIndex + visibleItemCount - 1, itemsCount - 1); + if (focusItem.index != bottomIndex) { + /* move focus to bottom item in viewport */ + setFocusItem (items [bottomIndex], true); + redrawItem (bottomIndex, true); + } else { + /* at bottom of viewport, so set focus to bottom item one page down */ + int newFocusIndex = Math.min (itemsCount - 1, bottomIndex + visibleItemCount); + if (newFocusIndex == focusItem.index) return; + setFocusItem (items [newFocusIndex], true); + showItem (items [newFocusIndex]); + redrawItem (newFocusIndex, true); + } + return; + } + /* Shift+PageDown */ + if (anchorItem == null) anchorItem = focusItem; + int anchorIndex = anchorItem.index; + int bottomIndex = Math.min (topIndex + visibleItemCount - 1, itemsCount - 1); + int selectIndex; + if (focusItem.index != bottomIndex) { + /* select from focus to bottom item in viewport */ + selectIndex = bottomIndex; + } else { + /* already at bottom of viewport, so select to bottom of one page down */ + selectIndex = Math.min (itemsCount - 1, bottomIndex + visibleItemCount); + if (selectIndex == focusItem.index && focusItem.isSelected ()) return; + } + TableItem selectedItem = items [selectIndex]; + TableItem[] newSelection = new TableItem [Math.abs (anchorIndex - selectIndex) + 1]; + int step = anchorIndex < selectIndex ? 1 : -1; + int writeIndex = 0; + for (int i = anchorIndex; i != selectIndex; i += step) { + newSelection [writeIndex++] = items [i]; + } + newSelection [writeIndex] = items [selectIndex]; + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + showItem (selectedItem); + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); +} +void onPageUp (int stateMask) { + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + /* PageUp with no modifiers */ + int newFocusIndex = Math.max (0, focusItem.index - visibleItemCount + 1); + if (newFocusIndex == focusItem.index) return; + TableItem item = items [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + showItem (item); + redrawItem (item.index, true); + return; + } + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == (SWT.CTRL | SWT.SHIFT)) { + /* CTRL+Shift+PageUp */ + int newTopIndex = Math.max (0, topIndex - visibleItemCount); + if (newTopIndex == topIndex) return; + setTopIndex (newTopIndex); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* Shift+PageUp */ + int newFocusIndex = Math.max (0, focusItem.index - visibleItemCount + 1); + if (newFocusIndex == focusItem.index) return; + TableItem item = items [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + showItem (item); + redrawItem (item.index, true); + return; + } + /* CTRL+PageUp */ + int newTopIndex = Math.max (0, topIndex - visibleItemCount); + if (newTopIndex == topIndex) return; + setTopIndex (newTopIndex); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+PageUp */ + if (focusItem.index != topIndex) { + /* move focus to top item in viewport */ + setFocusItem (items [topIndex], true); + redrawItem (topIndex, true); + } else { + /* at top of viewport, so set focus to top item one page up */ + int newFocusIndex = Math.max (0, focusItem.index - visibleItemCount); + if (newFocusIndex == focusItem.index) return; + setFocusItem (items [newFocusIndex], true); + showItem (items [newFocusIndex]); + redrawItem (newFocusIndex, true); + } + return; + } + /* Shift+PageUp */ + if (anchorItem == null) anchorItem = focusItem; + int anchorIndex = anchorItem.index; + int selectIndex; + if (focusItem.index != topIndex) { + /* select from focus to top item in viewport */ + selectIndex = topIndex; + } else { + /* already at top of viewport, so select to top of one page up */ + selectIndex = Math.max (0, topIndex - visibleItemCount); + if (selectIndex == focusItem.index && focusItem.isSelected ()) return; + } + TableItem selectedItem = items [selectIndex]; + TableItem[] newSelection = new TableItem [Math.abs (anchorIndex - selectIndex) + 1]; + int step = anchorIndex < selectIndex ? 1 : -1; + int writeIndex = 0; + for (int i = anchorIndex; i != selectIndex; i += step) { + newSelection [writeIndex++] = items [i]; + } + newSelection [writeIndex] = items [selectIndex]; + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + showItem (selectedItem); + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); +} +void onPaint (Event event) { + TableColumn[] orderedColumns = getOrderedColumns (); + GC gc = event.gc; + Rectangle clipping = gc.getClipping (); + int headerHeight = getHeaderHeight (); + int numColumns = orderedColumns.length; + int startColumn = -1, endColumn = -1; + if (numColumns > 0) { + startColumn = computeColumnIntersect (clipping.x, 0); + if (startColumn != -1) { /* the clip x is within a column's bounds */ + endColumn = computeColumnIntersect (clipping.x + clipping.width, startColumn); + if (endColumn == -1) endColumn = numColumns - 1; + } + } else { + startColumn = endColumn = 0; + } + + /* Determine the items to be painted */ + int startIndex = (clipping.y - headerHeight) / itemHeight + topIndex; + int endIndex = -1; + if (startIndex < itemsCount) { + endIndex = startIndex + Compatibility.ceil (clipping.height, itemHeight); + } + startIndex = Math.max (0, startIndex); + endIndex = Math.min (endIndex, itemsCount - 1); + + /* fill background not handled by items */ + gc.setBackground (getBackground ()); + gc.setClipping (clipping); + int bottomY = endIndex >= 0 ? getItemY (items [endIndex]) + itemHeight : 0; + int fillHeight = Math.max (0, clientArea.height - bottomY); + if (fillHeight > 0) { /* space below bottom item */ + drawBackground (gc, 0, bottomY, clientArea.width, fillHeight); + } + if (columns.length > 0) { + TableColumn column = orderedColumns [orderedColumns.length - 1]; /* last column */ + int rightX = column.getX () + column.width; + if (rightX < clientArea.width) { + drawBackground (gc, rightX, 0, clientArea.width - rightX, clientArea.height - fillHeight); + } + } + + /* paint the items */ + boolean noFocusDraw = false; + int[] lineDash = gc.getLineDash (); + int lineWidth = gc.getLineWidth (); + for (int i = startIndex; i <= Math.min (endIndex, itemsCount - 1); i++) { + TableItem item = items [i]; + if (!item.isDisposed ()) { /* ensure that item was not disposed in a callback */ + if (startColumn == -1) { + /* indicates that region to paint is to the right of the last column */ + noFocusDraw = item.paint (gc, null, true) || noFocusDraw; + } else { + if (numColumns == 0) { + noFocusDraw = item.paint (gc, null, false) || noFocusDraw; + } else { + for (int j = startColumn; j <= Math.min (endColumn, columns.length - 1); j++) { + if (!item.isDisposed ()) { /* ensure that item was not disposed in a callback */ + noFocusDraw = item.paint (gc, orderedColumns [j], false) || noFocusDraw; + } + if (isDisposed () || gc.isDisposed ()) return; /* ensure that receiver was not disposed in a callback */ + } + } + } + } + if (isDisposed () || gc.isDisposed ()) return; /* ensure that receiver was not disposed in a callback */ + } + + /* repaint grid lines */ + gc.setClipping(clipping); + gc.setLineWidth (lineWidth); + if (linesVisible) { + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_LIGHT_SHADOW)); + gc.setLineDash (lineDash); + if (numColumns > 0 && startColumn != -1) { + /* vertical column lines */ + for (int i = startColumn; i <= endColumn; i++) { + int x = orderedColumns [i].getX () + orderedColumns [i].width - 1; + gc.drawLine (x, clipping.y, x, clipping.y + clipping.height); + } + } + /* horizontal item lines */ + bottomY = clipping.y + clipping.height; + int rightX = clipping.x + clipping.width; + int y = (clipping.y - headerHeight) / itemHeight * itemHeight + headerHeight; + while (y <= bottomY) { + gc.drawLine (clipping.x, y, rightX, y); + y += itemHeight; + } + } + + /* paint focus rectangle */ + if (!noFocusDraw && isFocusControl ()) { + if (focusItem != null) { + Rectangle focusBounds = focusItem.getFocusBounds (); + if (focusBounds.width > 0) { + gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK)); + gc.setClipping (focusBounds); + if (focusItem.isSelected ()) { + gc.setLineDash (new int[] {2, 2}); + } else { + gc.setLineDash (new int[] {1, 1}); + } + gc.drawFocus (focusBounds.x, focusBounds.y, focusBounds.width, focusBounds.height); + } + } else { + /* no items, so draw focus border around Table */ + int y = headerHeight + 1; + int width = Math.max (0, clientArea.width - 2); + int height = Math.max (0, clientArea.height - headerHeight - 2); + gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK)); + gc.setClipping (1, y, width, height); + gc.setLineDash (new int[] {1, 1}); + gc.drawFocus (1, y, width, height); + } + } +} +void onResize (Event event) { + clientArea = getClientArea (); + /* vertical scrollbar */ + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + int clientHeight = (clientArea.height - getHeaderHeight ()) / itemHeight; + int thumb = Math.min (clientHeight, itemsCount); + vBar.setThumb (thumb); + vBar.setPageIncrement (thumb); + int index = vBar.getSelection (); + if (index != topIndex) { + topIndex = index; + redraw (); + } + boolean visible = clientHeight < itemsCount; + if (visible != vBar.getVisible ()) { + vBar.setVisible (visible); + clientArea = getClientArea (); + } + } + + /* horizontal scrollbar */ + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + int hBarMaximum = hBar.getMaximum (); + int thumb = Math.min (clientArea.width, hBarMaximum); + hBar.setThumb (thumb); + hBar.setPageIncrement (thumb); + horizontalOffset = hBar.getSelection (); + boolean visible = clientArea.width < hBarMaximum; + if (visible != hBar.getVisible ()) { + hBar.setVisible (visible); + clientArea = getClientArea (); + } + } + + /* header */ + int headerHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding (); + header.setSize (clientArea.width, headerHeight); + + /* if this is the focus control but there are no items then the boundary focus ring must be repainted */ + if (itemsCount == 0 && isFocusControl ()) redraw (); +} +void onScrollHorizontal (Event event) { + ScrollBar hBar = getHorizontalBar (); + if (hBar == null) return; + int newSelection = hBar.getSelection (); + update (); + if (itemsCount > 0) { + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose (); + } else { + redraw (); /* ensure that static focus rectangle updates properly */ + } + + if (drawCount <= 0 && header.isVisible ()) { + header.update (); + Rectangle headerClientArea = header.getClientArea (); + GC gc = new GC (header); + gc.copyArea ( + 0, 0, + headerClientArea.width, headerClientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose (); + } + horizontalOffset = newSelection; +} +void onScrollVertical (Event event) { + ScrollBar vBar = getVerticalBar (); + if (vBar == null) return; + int newSelection = vBar.getSelection (); + update (); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, (topIndex - newSelection) * itemHeight); + gc.dispose (); + topIndex = newSelection; +} +void onSpace () { + if (focusItem == null) return; + if (!focusItem.isSelected ()) { + selectItem (focusItem, (style & SWT.MULTI) != 0); + redrawItem (focusItem.index, true); + } + if ((style & SWT.CHECK) != 0) { + focusItem.setChecked (!focusItem.checked); + } + showItem (focusItem); + Event event = new Event (); + event.item = focusItem; + postEvent (SWT.Selection, event); + if ((style & SWT.CHECK) == 0) return; + + /* SWT.CHECK */ + event = new Event (); + event.item = focusItem; + event.detail = SWT.CHECK; + postEvent (SWT.Selection, event); +} +/* + * The current focus item is about to become unavailable, so reassign focus. + */ +void reassignFocus () { + if (focusItem == null) return; + + /* + * reassign to the previous root-level item if there is one, or the next + * root-level item otherwise + */ + int index = focusItem.index; + if (index != 0) { + index--; + } else { + index++; + } + if (index < itemsCount) { + TableItem item = items [index]; + setFocusItem (item, false); + showItem (item); + } else { + setFocusItem (null, false); /* no items left */ + } +} +public void redraw () { + checkWidget (); + if (drawCount <= 0) super.redraw (); +} +public void redraw (int x, int y, int width, int height, boolean all) { + checkWidget (); + if (drawCount <= 0) super.redraw (x, y, width, height, all); +} +/* + * Redraws from the specified index down to the last available item inclusive. Note + * that the redraw bounds do not extend beyond the current last item, so clients + * that reduce the number of available items should use #redrawItems(int,int) instead + * to ensure that redrawing extends down to the previous bottom item boundary. + */ +void redrawFromItemDownwards (int index) { + redrawItems (index, itemsCount - 1, false); +} +/* + * Redraws the table item at the specified index. It is valid for this index to reside + * beyond the last available item. + */ +void redrawItem (int itemIndex, boolean focusBoundsOnly) { + if (itemIndex < itemsCount && !items [itemIndex].isInViewport ()) return; + redrawItems (itemIndex, itemIndex, focusBoundsOnly); +} +/* + * Redraws the table between the start and end item indices inclusive. It is valid + * for the end index value to extend beyond the last available item. + */ +void redrawItems (int startIndex, int endIndex, boolean focusBoundsOnly) { + if (drawCount > 0) return; + + int startY = (startIndex - topIndex) * itemHeight + getHeaderHeight (); + int height = (endIndex - startIndex + 1) * itemHeight; + if (focusBoundsOnly) { + boolean custom = hooks (SWT.EraseItem) || hooks (SWT.PaintItem); + if (!custom && columns.length > 0) { + TableColumn lastColumn; + if ((style & SWT.FULL_SELECTION) != 0) { + TableColumn[] orderedColumns = getOrderedColumns (); + lastColumn = orderedColumns [orderedColumns.length - 1]; + } else { + lastColumn = columns [0]; + } + int rightX = lastColumn.getX () + lastColumn.getWidth (); + if (rightX <= 0) return; /* focus column(s) not visible */ + } + endIndex = Math.min (endIndex, itemsCount - 1); + for (int i = startIndex; i <= endIndex; i++) { + TableItem item = items [i]; + if (item.isInViewport ()) { + /* if custom painting is being done then repaint the full item */ + if (custom) { + redraw (0, getItemY (item), clientArea.width, itemHeight, false); + } else { + /* repaint the item's focus bounds */ + Rectangle bounds = item.getFocusBounds (); + redraw (bounds.x, startY, bounds.width, height, false); + } + } + } + } else { + redraw (0, startY, clientArea.width, height, false); + } +} +/** + * Removes the item from the receiver at the given + * zero-relative index. + * + * @param index the index for the item + * + * @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 void remove (int index) { + checkWidget (); + if (!(0 <= index && index < itemsCount)) error (SWT.ERROR_INVALID_RANGE); + items [index].dispose (); +} +/** + * Removes the items from the receiver which are + * between the given zero-relative start and end + * indices (inclusive). + * + * @param start the start of the range + * @param end the end of the range + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if either the start or end are 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 void remove (int start, int end) { + checkWidget (); + if (start > end) return; + if (!(0 <= start && start <= end && end < itemsCount)) { + error (SWT.ERROR_INVALID_RANGE); + } + if (start == 0 && end == itemsCount - 1) { + removeAll (); + } else { + for (int i = end; i >= start; i--) { + items [i].dispose (); + } + } +} +/** + * Removes the items from the receiver's list at the given + * zero-relative indices. + * + * @param indices the array of indices of the items + * + * @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> + * <li>ERROR_NULL_ARGUMENT - if the indices array 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 remove (int [] indices) { + checkWidget (); + if (indices == null) error (SWT.ERROR_NULL_ARGUMENT); + if (indices.length == 0) return; + int [] newIndices = new int [indices.length]; + System.arraycopy (indices, 0, newIndices, 0, indices.length); + sortDescent (newIndices); + int start = newIndices [newIndices.length - 1], end = newIndices [0]; + if (!(0 <= start && start <= end && end < itemsCount)) { + error (SWT.ERROR_INVALID_RANGE); + } + int lastRemovedIndex = -1; + for (int i = 0; i < newIndices.length; i++) { + if (newIndices [i] != lastRemovedIndex) { + items [newIndices [i]].dispose (); + lastRemovedIndex = newIndices [i]; + } + } +} +/** + * 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 (); + if (itemsCount == 0) return; + setRedraw (false); + + setFocusItem (null, false); + for (int i = 0; i < itemsCount; i++) { + items [i].dispose (false); + } + items = new TableItem [0]; + selectedItems = new TableItem [0]; + itemsCount = topIndex = 0; + anchorItem = lastClickedItem = null; + lastSelectionEvent = null; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + vBar.setMaximum (1); + vBar.setVisible (false); + } + if (columns.length == 0) { + horizontalOffset = 0; + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setMaximum (1); + hBar.setVisible (false); + } + } + + setRedraw (true); +} +String removeMnemonics (String string) { + /* removes single ampersands and preserves double-ampersands */ + char [] chars = new char [string.length ()]; + string.getChars (0, chars.length, chars, 0); + int i = 0, j = 0; + for ( ; i < chars.length; i++, j++) { + if (chars[i] == '&') { + if (++i == chars.length) break; + if (chars[i] == '&') { + chars[j++] = chars[i - 1]; + } + } + chars[j] = chars[i]; + } + if (i == j) return string; + return new String (chars, 0, j); +} +void removeSelectedItem (int index) { + TableItem[] newSelectedItems = new TableItem [selectedItems.length - 1]; + System.arraycopy (selectedItems, 0, newSelectedItems, 0, index); + System.arraycopy (selectedItems, index + 1, newSelectedItems, index, newSelectedItems.length - index); + selectedItems = newSelectedItems; +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the user changes the receiver's selection. + * + * @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(SelectionListener) + */ +public void removeSelectionListener (SelectionListener listener) { + checkWidget (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + removeListener (SWT.Selection, listener); + removeListener (SWT.DefaultSelection, listener); +} +/** + * Selects the item at the given zero-relative index in the receiver. + * If the item at the index was already selected, it remains + * selected. Indices that are out of range are ignored. + * + * @param index the index of the item to select + * + * @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 select (int index) { + checkWidget (); + if (!(0 <= index && index < itemsCount)) return; + selectItem (items [index], (style & SWT.MULTI) != 0); + if (hasFocus () || (style & SWT.HIDE_SELECTION) == 0) { + redrawItem (index, false); + } +} +/** + * Selects the items in the range specified by the given zero-relative + * indices in the receiver. The range of indices is inclusive. + * The current selection is not cleared before the new items are selected. + * <p> + * If an item in the given range is not selected, it is selected. + * If an item in the given range was already selected, it remains selected. + * Indices that are out of range are ignored and no items will be selected + * if start is greater than end. + * If the receiver is single-select and there is more than one item in the + * given range, then all indices are ignored. + * </p> + * + * @param start the start of the range + * @param end the end of the range + * + * @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 Table#setSelection(int,int) + */ +public void select (int start, int end) { + checkWidget (); + if (end < 0 || start > end || ((style & SWT.SINGLE) != 0 && start != end)) return; + if (itemsCount == 0 || start >= itemsCount) return; + start = Math.max (start, 0); + end = Math.min (end, itemsCount - 1); + for (int i = start; i <= end; i++) { + selectItem (items [i], (style & SWT.MULTI) != 0); + } + if (hasFocus () || (style & SWT.HIDE_SELECTION) == 0) { + redrawItems (start, end, false); + } +} +/** + * Selects the items at the given zero-relative indices in the receiver. + * The current selection is not cleared before the new items are selected. + * <p> + * If the item at a given index is not selected, it is selected. + * If the item at a given index was already selected, it remains selected. + * Indices that are out of range and duplicate indices are ignored. + * If the receiver is single-select and multiple indices are specified, + * then all indices are ignored. + * </p> + * + * @param indices the array of indices for the items to select + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the array of indices 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 Table#setSelection(int[]) + */ +public void select (int [] indices) { + checkWidget (); + if (indices == null) error (SWT.ERROR_NULL_ARGUMENT); + if (indices.length == 0 || ((style & SWT.SINGLE) != 0 && indices.length > 1)) return; + + for (int i = 0; i < indices.length; i++) { + if (0 <= indices [i] && indices [i] < itemsCount) { + selectItem (items [indices [i]], (style & SWT.MULTI) != 0); + } + } + if (hasFocus () || (style & SWT.HIDE_SELECTION) == 0) { + for (int i = 0; i < indices.length; i++) { + if (0 <= indices [i] && indices [i] < itemsCount) { + redrawItem (indices [i], false); + } + } + } +} +/** + * 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.SINGLE) != 0) return; + selectedItems = new TableItem [itemsCount]; + System.arraycopy (items, 0, selectedItems, 0, itemsCount); + if (hasFocus () || (style & SWT.HIDE_SELECTION) == 0) { + redraw (); + } +} +void selectItem (TableItem item, boolean addToSelection) { + TableItem[] oldSelectedItems = selectedItems; + if (!addToSelection || (style & SWT.SINGLE) != 0) { + selectedItems = new TableItem[] {item}; + if (hasFocus () || (style & SWT.HIDE_SELECTION) == 0) { + for (int i = 0; i < oldSelectedItems.length; i++) { + if (oldSelectedItems [i] != item) { + redrawItem (oldSelectedItems [i].index, true); + } + } + } + } else { + if (item.isSelected ()) return; + selectedItems = new TableItem [selectedItems.length + 1]; + System.arraycopy (oldSelectedItems, 0, selectedItems, 0, oldSelectedItems.length); + selectedItems [selectedItems.length - 1] = item; + } +} +public void setBackground (Color color) { + checkWidget (); + if (color == null) color = display.getSystemColor (SWT.COLOR_LIST_BACKGROUND); + super.setBackground (color); +} +public void setForeground (Color color) { + checkWidget (); + if (color == null) color = display.getSystemColor (SWT.COLOR_LIST_FOREGROUND); + super.setForeground (color); +} +/** + * 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 Table#getColumnOrder() + * @see TableColumn#getMoveable() + * @see TableColumn#setMoveable(boolean) + * @see SWT#Move + * + * @since 3.1 + */ +public void setColumnOrder (int [] order) { + checkWidget (); + if (order == null) error (SWT.ERROR_NULL_ARGUMENT); + if (columns.length == 0) { + if (order.length != 0) error (SWT.ERROR_INVALID_ARGUMENT); + return; + } + if (order.length != columns.length) error (SWT.ERROR_INVALID_ARGUMENT); + boolean reorder = false; + boolean [] seen = new boolean [columns.length]; + int[] oldOrder = getColumnOrder (); + for (int i = 0; i < order.length; i++) { + int index = order [i]; + if (index < 0 || index >= columns.length) error (SWT.ERROR_INVALID_RANGE); + if (seen [index]) error (SWT.ERROR_INVALID_ARGUMENT); + seen [index] = true; + if (index != oldOrder [i]) reorder = true; + } + if (!reorder) return; + + headerHideToolTip (); + int[] oldX = new int [columns.length]; + for (int i = 0; i < columns.length; i++) { + oldX [i] = columns [i].getX (); + } + orderedColumns = new TableColumn [order.length]; + for (int i = 0; i < order.length; i++) { + orderedColumns [i] = columns [order [i]]; + } + for (int i = 0; i < orderedColumns.length; i++) { + TableColumn column = orderedColumns [i]; + if (!column.isDisposed () && column.getX () != oldX [column.getIndex ()]) { + column.sendEvent (SWT.Move); + } + } + + redraw (); + if (drawCount <= 0 && header.isVisible ()) header.redraw (); +} +void setFocusItem (TableItem item, boolean redrawOldFocus) { + if (item == focusItem) return; + TableItem oldFocusItem = focusItem; + focusItem = item; + if (redrawOldFocus && oldFocusItem != null) { + redrawItem (oldFocusItem.index, true); + } +} +public void setFont (Font value) { + checkWidget (); + Font oldFont = getFont (); + super.setFont (value); + Font font = getFont (); + if (font.equals (oldFont)) return; + + GC gc = new GC (this); + + /* recompute the receiver's cached font height and item height values */ + fontHeight = gc.getFontMetrics ().getHeight (); + setItemHeight (Math.max (fontHeight, imageHeight) + 2 * getCellPadding ()); + Point headerSize = header.getSize (); + int newHeaderHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding (); + if (headerSize.y != newHeaderHeight) { + header.setSize (headerSize.x, newHeaderHeight); + } + header.setFont (font); + + /* + * Notify all columns and items of the font change so that elements that + * use the receiver's font can recompute their cached string widths. + */ + for (int i = 0; i < columns.length; i++) { + columns [i].updateFont (gc); + } + for (int i = 0; i < itemsCount; i++) { + items [i].updateFont (gc); + } + + gc.dispose (); + + if (drawCount <= 0 && header.isVisible ()) header.redraw (); + + /* update scrollbars */ + if (columns.length == 0) updateHorizontalBar (); + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + int thumb = (clientArea.height - getHeaderHeight ()) / itemHeight; + vBar.setThumb (thumb); + vBar.setPageIncrement (thumb); + topIndex = vBar.getSelection (); + vBar.setVisible (thumb < vBar.getMaximum ()); + } + redraw (); +} +void setHeaderImageHeight (int value) { + headerImageHeight = value; + Point headerSize = header.getSize (); + int newHeaderHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding (); + if (headerSize.y != newHeaderHeight) { + header.setSize (headerSize.x, newHeaderHeight); + } +} +/** + * 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 show 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 (header.getVisible () == value) return; /* no change */ + headerHideToolTip (); + header.setVisible (value); + updateVerticalBar (); + redraw (); +} +void setImageHeight (int value) { + imageHeight = value; + setItemHeight (Math.max (fontHeight, imageHeight) + 2 * getCellPadding ()); +} +/** + * Sets the number of 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> + * + * @since 3.0 + */ +public void setItemCount (int count) { + checkWidget (); + count = Math.max (0, count); + if (count == itemsCount) return; + int oldCount = itemsCount; + int redrawStart, redrawEnd; + + /* if the new item count is less than the current count then remove all excess items from the end */ + if (count < itemsCount) { + redrawStart = count; + redrawEnd = itemsCount - 1; + for (int i = count; i < itemsCount; i++) { + items [i].dispose (false); + } + + int newSelectedCount = 0; + for (int i = 0; i < selectedItems.length; i++) { + if (!selectedItems [i].isDisposed ()) newSelectedCount++; + } + if (newSelectedCount != selectedItems.length) { + /* one or more selected items have been disposed */ + TableItem[] newSelectedItems = new TableItem [newSelectedCount]; + int pos = 0; + for (int i = 0; i < selectedItems.length; i++) { + TableItem item = selectedItems [i]; + if (!item.isDisposed ()) { + newSelectedItems [pos++] = item; + } + } + selectedItems = newSelectedItems; + } + + if (anchorItem != null && anchorItem.isDisposed ()) anchorItem = null; + if (lastClickedItem != null && lastClickedItem.isDisposed ()) lastClickedItem = null; + if (focusItem != null && focusItem.isDisposed ()) { + TableItem newFocusItem = count > 0 ? items [count - 1] : null; + setFocusItem (newFocusItem, false); + } + itemsCount = count; + if (columns.length == 0) updateHorizontalBar (); + } else { + redrawStart = itemsCount; + redrawEnd = count - 1; + TableItem[] newItems = new TableItem [count]; + System.arraycopy (items, 0, newItems, 0, itemsCount); + items = newItems; + for (int i = itemsCount; i < count; i++) { + items [i] = new TableItem (this, SWT.NONE, i, false); + itemsCount++; + } + if (oldCount == 0) focusItem = items [0]; + } + + updateVerticalBar (); + /* + * If this is the focus control and the item count is going from 0->!0 or !0->0 then the + * receiver must be redrawn to ensure that its boundary focus ring is updated. + */ + if ((oldCount == 0 || itemsCount == 0) && isFocusControl ()) { + redraw (); + return; + } + redrawItems (redrawStart, redrawEnd, false); +} +boolean setItemHeight (int value) { + boolean update = !customHeightSet || itemHeight < value; + if (update) itemHeight = value; + return update; +} +/** + * Marks the receiver's lines as visible if the argument is <code>true</code>, + * and marks it invisible otherwise. Note that some platforms draw grid lines + * while others may draw alternating row colors. + * <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 show 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; + redraw (); +} +public void setMenu (Menu menu) { + super.setMenu (menu); + header.setMenu (menu); +} +public void setRedraw (boolean value) { + checkWidget(); + if (value) { + if (--drawCount == 0) { + if (items.length - itemsCount > 3) { + TableItem[] newItems = new TableItem [itemsCount]; + System.arraycopy (items, 0, newItems, 0, itemsCount); + items = newItems; + } + updateVerticalBar (); + updateHorizontalBar (); + } + } else { + drawCount++; + } + super.setRedraw (value); + header.setRedraw (value); +} +/** + * 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 item 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> + * + * @since 3.2 + */ +public void setSelection (TableItem item) { + checkWidget (); + if (item == null) error (SWT.ERROR_NULL_ARGUMENT); + setSelection (new TableItem[] {item}, true); +} +/** + * 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 items 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 Table#deselectAll() + * @see Table#select(int[]) + * @see Table#setSelection(int[]) + */ +public void setSelection (TableItem[] items) { + checkWidget (); + if (items == null) error (SWT.ERROR_NULL_ARGUMENT); + setSelection (items, true); +} +void setSelection (TableItem[] items, boolean updateViewport) { + if (items.length == 0 || ((style & SWT.SINGLE) != 0 && items.length > 1)) { + deselectAll (); + return; + } + TableItem[] oldSelection = selectedItems; + + /* remove null and duplicate items */ + int index = 0; + selectedItems = new TableItem [items.length]; /* assume all valid items */ + for (int i = 0; i < items.length; i++) { + TableItem item = items [i]; + if (item != null && item.parent == this && !item.isSelected ()) { + selectedItems [index++] = item; + } + } + if (index != items.length) { + /* an invalid item was provided so resize the array accordingly */ + TableItem[] temp = new TableItem [index]; + System.arraycopy (selectedItems, 0, temp, 0, index); + selectedItems = temp; + } + if (selectedItems.length == 0) { /* no valid items */ + deselectAll (); + return; + } + + if (hasFocus () || (style & SWT.HIDE_SELECTION) == 0) { + for (int i = 0; i < oldSelection.length; i++) { + if (!oldSelection [i].isSelected ()) { + redrawItem (oldSelection [i].index, true); + } + } + for (int i = 0; i < selectedItems.length; i++) { + redrawItem (selectedItems [i].index, true); + } + } + if (updateViewport) { + showItem (selectedItems [0]); + setFocusItem (selectedItems [0], true); + } +} +/** + * 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> + * + * @since 3.2 + */ +public void setSortColumn (TableColumn 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> + * + * @since 3.2 + */ +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); +} +/** + * Selects the item at the given zero-relative index in the receiver. + * The current selection is first cleared, then the new item is selected. + * + * @param index the index of the item to select + * + * @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 Table#deselectAll() + * @see Table#select(int) + */ +public void setSelection (int index) { + checkWidget (); + deselectAll (); + if (!(0 <= index && index < itemsCount)) return; + selectItem (items [index], false); + setFocusItem (items [index], true); + redrawItem (index, true); + showSelection (); +} +/** + * Selects the items in the range specified by the given zero-relative + * indices in the receiver. The range of indices is inclusive. + * The current selection is cleared before the new items are selected. + * <p> + * Indices that are out of range are ignored and no items will be selected + * if start is greater than end. + * If the receiver is single-select and there is more than one item in the + * given range, then all indices are ignored. + * </p> + * + * @param start the start index of the items to select + * @param end the end index of the items to select + * + * @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 Table#deselectAll() + * @see Table#select(int,int) + */ +public void setSelection (int start, int end) { + checkWidget (); + deselectAll (); + if (end < 0 || start > end || ((style & SWT.SINGLE) != 0 && start != end)) return; + if (itemsCount == 0 || start >= itemsCount) return; + start = Math.max (0, start); + end = Math.min (end, itemsCount - 1); + select (start, end); + setFocusItem (items [start], true); + showSelection (); +} +/** + * Selects the items at the given zero-relative indices in the receiver. + * The current selection is cleared before the new items are selected. + * <p> + * Indices that are out of range and duplicate indices are ignored. + * If the receiver is single-select and multiple indices are specified, + * then all indices are ignored. + * </p> + * + * @param indices the indices of the items to select + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the array of indices 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 Table#deselectAll() + * @see Table#select(int[]) + */ +public void setSelection (int [] indices) { + checkWidget (); + if (indices == null) error (SWT.ERROR_NULL_ARGUMENT); + deselectAll (); + int length = indices.length; + if (length == 0 || ((style & SWT.SINGLE) != 0 && length > 1)) return; + select (indices); + int focusIndex = -1; + for (int i = 0; i < indices.length && focusIndex == -1; i++) { + if (0 <= indices [i] && indices [i] < itemsCount) { + focusIndex = indices [i]; + } + } + if (focusIndex != -1) setFocusItem (items [focusIndex], true); + showSelection (); +} +/** + * Sets the zero-relative index of the item which is currently + * at the top of the receiver. This index can change when items + * are scrolled or new items are added and removed. + * + * @param index the index of the top 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 void setTopIndex (int index) { + checkWidget (); + if (!(0 <= index && index < itemsCount)) return; + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if (itemsCount <= visibleItemCount) return; + index = Math.min (index, itemsCount - visibleItemCount); + if (index == topIndex) return; + + update (); + int change = topIndex - index; + topIndex = index; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + if (drawCount <= 0) { + GC gc = new GC (this); + gc.copyArea (0, 0, clientArea.width, clientArea.height, 0, change * itemHeight); + gc.dispose (); + } +} +/** + * 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 3.0 + */ +public void showColumn (TableColumn column) { + checkWidget (); + if (column == null) error (SWT.ERROR_NULL_ARGUMENT); + if (column.isDisposed ()) error(SWT.ERROR_INVALID_ARGUMENT); + if (column.parent != this) return; + + int x = column.getX (); + int rightX = x + column.width; + if (0 <= x && rightX <= clientArea.width) return; /* column is fully visible */ + + headerHideToolTip (); + int absX = 0; /* the X of the column irrespective of the horizontal scroll */ + TableColumn[] orderedColumns = getOrderedColumns (); + for (int i = 0; i < column.getOrderIndex (); i++) { + absX += orderedColumns [i].width; + } + if (x < clientArea.x) { /* column is to left of viewport */ + horizontalOffset = absX; + } else { + horizontalOffset = absX + column.width - clientArea.width; + } + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) hBar.setSelection (horizontalOffset); + redraw (); + if (drawCount <= 0 && header.isVisible ()) header.redraw (); +} +/** + * Shows the item. If the item is already showing in the receiver, + * this method simply returns. Otherwise, the items are scrolled 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 Table#showSelection() + */ +public void showItem (TableItem item) { + checkWidget (); + if (item == null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + if (item.parent != this) return; + + int index = item.index; + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + /* nothing to do if item is already in viewport */ + if (topIndex <= index && index < topIndex + visibleItemCount) return; + + if (index <= topIndex) { + /* item is above current viewport, so show on top */ + setTopIndex (item.index); + } else { + /* item is below current viewport, so show on bottom */ + visibleItemCount = Math.max (visibleItemCount, 1); /* item to show should be top item */ + setTopIndex (Math.min (index - visibleItemCount + 1, itemsCount - 1)); + } +} +/** + * 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 Table#showItem(TableItem) + */ +public void showSelection () { + checkWidget (); + if (selectedItems.length == 0) return; + showItem (selectedItems [0]); +} +void sortDescent (int [] items) { + /* Shell Sort from K&R, pg 108 */ + int length = items.length; + for (int gap = length / 2; gap > 0; gap /= 2) { + for (int i = gap; i < length; i++) { + for (int j = i - gap; j >= 0; j -= gap) { + if (items [j] <= items [j + gap]) { + int swap = items [j]; + items [j] = items [j + gap]; + items [j + gap] = swap; + } + } + } + } +} +void sortAscent (int [] items) { + /* Shell Sort from K&R, pg 108 */ + int length = items.length; + for (int gap = length / 2; gap > 0; gap /= 2) { + for (int i = gap; i < length; i++) { + for (int j = i - gap; j >= 0; j -= gap) { + if (items [j] >= items [j + gap]) { + int swap = items [j]; + items [j] = items [j + gap]; + items [j + gap] = swap; + } + } + } + } +} +void sortAscent (TableItem [] items) { + /* Shell Sort from K&R, pg 108 */ + int length = items.length; + for (int gap = length / 2; gap > 0; gap /= 2) { + for (int i = gap; i < length; i++) { + for (int j = i - gap; j >= 0; j -= gap) { + if (items [j].index >= items [j + gap].index) { + TableItem swap = items [j]; + items [j] = items [j + gap]; + items [j + gap] = swap; + } + } + } + } +} +void updateColumnWidth (TableColumn column, int width) { + headerHideToolTip (); + int oldWidth = column.width; + int columnX = column.getX (); + int x = columnX + oldWidth - 1; /* -1 ensures that grid line is included */ + + update (); + GC gc = new GC (this); + gc.copyArea (x, 0, clientArea.width - x, clientArea.height, columnX + width - 1, 0); /* dest x -1 offsets x's -1 above */ + if (width > oldWidth) { + /* column width grew */ + int change = width - oldWidth + 1; /* +1 offsets x's -1 above */ + /* -1/+1 below ensure that right bound of selection redraws correctly in column */ + redraw (x - 1, 0, change + 1, clientArea.height, false); + } else { + int change = oldWidth - width + 1; /* +1 offsets x's -1 above */ + redraw (clientArea.width - change, 0, change, clientArea.height, false); + } + /* the focus box must be repainted because its stipple may become shifted as a result of its new width */ + if (focusItem != null) redrawItem (focusItem.index, true); + + GC headerGC = new GC (header); + if (drawCount <= 0 && header.getVisible ()) { + Rectangle headerBounds = header.getClientArea (); + header.update (); + x -= 1; /* -1 ensures that full header column separator is included */ + headerGC.copyArea (x, 0, headerBounds.width - x, headerBounds.height, columnX + width - 2, 0); /* dest x -2 offsets x's -1s above */ + if (width > oldWidth) { + /* column width grew */ + int change = width - oldWidth + 2; /* +2 offsets x's -1s above */ + header.redraw (x, 0, change, headerBounds.height, false); + } else { + int change = oldWidth - width + 2; /* +2 offsets x's -1s above */ + header.redraw (headerBounds.width - change, 0, change, headerBounds.height, false); + } + } + + column.width = width; + + /* + * Notify column and all items of column width change so that display labels + * can be recomputed if needed. + */ + column.updateWidth (headerGC); + headerGC.dispose (); + for (int i = 0; i < itemsCount; i++) { + items [i].updateColumnWidth (column, gc); + } + gc.dispose (); + + int maximum = 0; + for (int i = 0; i < columns.length; i++) { + maximum += columns [i].width; + } + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setMaximum (Math.max (1, maximum)); /* setting a value of 0 here is ignored */ + if (hBar.getThumb () != clientArea.width) { + hBar.setThumb (clientArea.width); + hBar.setPageIncrement (clientArea.width); + } + int oldHorizontalOffset = horizontalOffset; /* hBar.setVisible() can modify horizontalOffset */ + hBar.setVisible (clientArea.width < maximum); + int selection = hBar.getSelection (); + if (selection != oldHorizontalOffset) { + horizontalOffset = selection; + redraw (); + if (drawCount <= 0 && header.getVisible ()) header.redraw (); + } + } + + column.sendEvent (SWT.Resize); + TableColumn[] orderedColumns = getOrderedColumns (); + for (int i = column.getOrderIndex () + 1; i < orderedColumns.length; i++) { + if (!orderedColumns [i].isDisposed ()) { + orderedColumns [i].sendEvent (SWT.Move); + } + } + + if (itemsCount == 0) redraw (); /* ensure that static focus rectangle updates properly */ +} +/* + * This is a naive implementation that computes the value from scratch. + */ +void updateHorizontalBar () { + if (drawCount > 0) return; + ScrollBar hBar = getHorizontalBar (); + if (hBar == null) return; + + int maxX = 0; + if (columns.length > 0) { + for (int i = 0; i < columns.length; i++) { + maxX += columns [i].width; + } + } else { + for (int i = 0; i < itemsCount; i++) { + Rectangle itemBounds = items [i].getCellBounds (0); + maxX = Math.max (maxX, itemBounds.x + itemBounds.width + horizontalOffset); + } + } + + int clientWidth = clientArea.width; + if (maxX != hBar.getMaximum ()) { + hBar.setMaximum (Math.max (1, maxX)); /* setting a value of 0 here is ignored */ + } + int thumb = Math.min (clientWidth, maxX); + if (thumb != hBar.getThumb ()) { + hBar.setThumb (thumb); + hBar.setPageIncrement (thumb); + } + hBar.setVisible (clientWidth < maxX); + + /* reclaim any space now left on the right */ + if (maxX < horizontalOffset + thumb) { + horizontalOffset = maxX - thumb; + hBar.setSelection (horizontalOffset); + redraw (); + } else { + int selection = hBar.getSelection (); + if (selection != horizontalOffset) { + horizontalOffset = selection; + redraw (); + } + } +} +/* + * Update the horizontal bar, if needed, in response to an item change (eg.- created, + * disposed, expanded, etc.). newRightX is the new rightmost X value of the item, + * and rightXchange is the change that led to the item's rightmost X value becoming + * newRightX (so oldRightX + rightXchange = newRightX) + */ +void updateHorizontalBar (int newRightX, int rightXchange) { + if (drawCount > 0) return; + ScrollBar hBar = getHorizontalBar (); + if (hBar == null) return; + + newRightX += horizontalOffset; + int barMaximum = hBar.getMaximum (); + if (newRightX > barMaximum) { /* item has extended beyond previous maximum */ + hBar.setMaximum (newRightX); + int clientAreaWidth = clientArea.width; + int thumb = Math.min (newRightX, clientAreaWidth); + hBar.setThumb (thumb); + hBar.setPageIncrement (thumb); + hBar.setVisible (clientAreaWidth <= newRightX); + return; + } + + int previousRightX = newRightX - rightXchange; + if (previousRightX != barMaximum) { + /* this was not the rightmost item, so just check for client width change */ + int clientAreaWidth = clientArea.width; + int thumb = Math.min (barMaximum, clientAreaWidth); + hBar.setThumb (thumb); + hBar.setPageIncrement (thumb); + hBar.setVisible (clientAreaWidth <= barMaximum); + return; + } + updateHorizontalBar (); /* must search for the new rightmost item */ +} +void updateVerticalBar () { + if (drawCount > 0) return; + ScrollBar vBar = getVerticalBar (); + if (vBar == null) return; + + int pageSize = (clientArea.height - getHeaderHeight ()) / itemHeight; + int maximum = Math.max (1, itemsCount); /* setting a value of 0 here is ignored */ + if (maximum != vBar.getMaximum ()) { + vBar.setMaximum (maximum); + } + int thumb = Math.min (pageSize, maximum); + if (thumb != vBar.getThumb ()) { + vBar.setThumb (thumb); + vBar.setPageIncrement (thumb); + } + vBar.setVisible (pageSize < maximum); + + /* reclaim any space now left on the bottom */ + if (maximum < topIndex + thumb) { + topIndex = maximum - thumb; + vBar.setSelection (topIndex); + redraw (); + } else { + int selection = vBar.getSelection (); + if (selection != topIndex) { + topIndex = selection; + redraw (); + } + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TableColumn.java b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TableColumn.java new file mode 100644 index 0000000000..7ef48a0fdd --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TableColumn.java @@ -0,0 +1,762 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; + +/** + * Instances of this class represent a column in a table widget. + * <p><dl> + * <dt><b>Styles:</b></dt> + * <dd>LEFT, RIGHT, CENTER</dd> + * <dt><b>Events:</b></dt> + * <dd> Move, Resize, Selection</dd> + * </dl> + * </p><p> + * Note: Only one of the styles LEFT, RIGHT and CENTER may be specified. + * </p><p> + * IMPORTANT: This class is <em>not</em> intended to be subclassed. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#table">Table, TableItem, TableColumn snippets</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ +public class TableColumn extends Item { + Table parent; + String displayText = ""; + int width; + boolean moveable, resizable = true; + int sort = SWT.NONE; + String toolTipText; + +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>Table</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 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#LEFT + * @see SWT#RIGHT + * @see SWT#CENTER + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public TableColumn (Table parent, int style) { + this (parent, style, checkNull (parent).columns.length); +} +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>Table</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> + * <p> + * Note that due to a restriction on some platforms, the first column + * is always left aligned. + * </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 + * @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#LEFT + * @see SWT#RIGHT + * @see SWT#CENTER + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public TableColumn (Table parent, int style, int index) { + super (parent, checkStyle (style), index); + if (!(0 <= index && index <= parent.columns.length)) error (SWT.ERROR_INVALID_RANGE); + this.parent = parent; + parent.createItem (this, index); +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the control is moved or resized, by sending + * it one of the messages defined in the <code>ControlListener</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 ControlListener + * @see #removeControlListener + */ +public void addControlListener (ControlListener listener) { + checkWidget (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Resize, typedListener); + addListener (SWT.Move, typedListener); +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the control is selected by the user, by sending + * it one of the messages defined in the <code>SelectionListener</code> + * interface. + * <p> + * <code>widgetSelected</code> is called when the column header is selected. + * <code>widgetDefaultSelected</code> is not called. + * </p> + * + * @param listener the listener which should be notified when the control is selected by the user + * + * @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 (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Selection, typedListener); + addListener (SWT.DefaultSelection, typedListener); +} +static Table checkNull (Table table) { + if (table == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + return table; +} +static int checkStyle (int style) { + return checkBits (style, SWT.LEFT, SWT.CENTER, SWT.RIGHT, 0, 0, 0); +} +protected void checkSubclass () { + if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS); +} +void computeDisplayText (GC gc) { + int availableWidth = width - 2 * parent.getHeaderPadding (); + if (image != null) { + availableWidth -= image.getBounds ().width; + availableWidth -= Table.MARGIN_IMAGE; + } + if (sort != SWT.NONE) { + availableWidth -= parent.arrowBounds.width; + availableWidth -= Table.MARGIN_IMAGE; + } + String text = this.text; + int textWidth = gc.textExtent (text, SWT.DRAW_MNEMONIC).x; + if (textWidth <= availableWidth) { + displayText = text; + return; + } + + /* Ellipsis will be needed, so subtract their width from the available text width */ + int ellipsisWidth = gc.stringExtent (Table.ELLIPSIS).x; + availableWidth -= ellipsisWidth; + if (availableWidth <= 0) { + displayText = Table.ELLIPSIS; + return; + } + + /* Make initial guess. */ + int index = Math.min (availableWidth / gc.getFontMetrics ().getAverageCharWidth (), text.length ()); + textWidth = gc.textExtent (text.substring (0, index), SWT.DRAW_MNEMONIC).x; + + /* Initial guess is correct. */ + if (availableWidth == textWidth) { + displayText = text.substring (0, index) + Table.ELLIPSIS; + return; + } + + /* Initial guess is too high, so reduce until fit is found. */ + if (availableWidth < textWidth) { + do { + index--; + if (index < 0) { + displayText = Table.ELLIPSIS; + return; + } + text = text.substring (0, index); + textWidth = gc.textExtent (text, SWT.DRAW_MNEMONIC).x; + } while (availableWidth < textWidth); + displayText = text + Table.ELLIPSIS; + return; + } + + /* Initial guess is too low, so increase until overrun is found. */ + while (textWidth < availableWidth) { + index++; + textWidth = gc.textExtent (text.substring (0, index), SWT.DRAW_MNEMONIC).x; + } + displayText = text.substring (0, index - 1) + Table.ELLIPSIS; +} +public void dispose () { + if (isDisposed ()) return; + Rectangle parentBounds = parent.clientArea; + int x = getX (); + int index = getIndex (); + int orderIndex = getOrderIndex (); + int nextColumnAlignment = parent.columns.length > 1 ? parent.columns [1].getAlignment () : SWT.LEFT; + Table parent = this.parent; + dispose (true); + + int width = parentBounds.width - x; + parent.redraw (x, 0, width, parentBounds.height, false); + /* + * If column 0 was disposed then the new column 0 must be redrawn if it appears to the + * left of the disposed column in the column order AND one the following are true: + * - the parent has style CHECK, since these will now appear in the new column 0 + * - the new column 0 had non-left alignment before the dispose, since the parent will have + * changed this to LEFT in the call to dispose(true) + */ + if (index == 0 && ((parent.style & SWT.CHECK) != 0 || nextColumnAlignment != SWT.LEFT)) { + if (parent.columns.length > 0) { + TableColumn newColumn0 = parent.columns [0]; + if (newColumn0.getOrderIndex () < orderIndex) { + int newColumn0x = newColumn0.getX (); + parent.redraw (newColumn0x, 0, newColumn0.width, parentBounds.height, false); + /* if the alignment changed then the header text must be repainted with its new alignment */ + if (nextColumnAlignment != SWT.LEFT && parent.getHeaderVisible () && parent.drawCount <= 0) { + parent.header.redraw (newColumn0x, 0, newColumn0.width, parent.header.getClientArea ().height, false); + } + } + } + } + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + parent.header.redraw (x, 0, width, parent.getHeaderHeight (), false); + } +} +void dispose (boolean notifyParent) { + super.dispose (); /* super is intentional here */ + if (notifyParent) parent.destroyItem (this); + parent = null; +} +/** + * Returns a value which describes the position of the + * text or image in the receiver. The value will be one of + * <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>. + * + * @return the alignment + * + * @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 getAlignment () { + checkWidget (); + if ((style & SWT.CENTER) != 0) return SWT.CENTER; + if ((style & SWT.RIGHT) != 0) return SWT.RIGHT; + return SWT.LEFT; +} +/* + * Returns the width of the header's content + * (image + text + sort arrow + internal margins) + */ +int getContentWidth (GC gc, boolean useDisplayText) { + int contentWidth = 0; + String text = useDisplayText ? displayText : this.text; + if (text.length () > 0) { + contentWidth += gc.textExtent (text, SWT.DRAW_MNEMONIC).x; + } + if (image != null) { + contentWidth += image.getBounds ().width; + if (text.length () > 0) contentWidth += Table.MARGIN_IMAGE; + } + if (sort != SWT.NONE) { + contentWidth += parent.arrowBounds.width; + if (text.length () > 0 || image != null) { + contentWidth += Table.MARGIN_IMAGE; + } + } + return contentWidth; +} +int getIndex () { + TableColumn[] columns = parent.columns; + for (int i = 0; i < columns.length; i++) { + if (columns [i] == this) return i; + } + return -1; +} +/** + * Gets the moveable attribute. A column that is + * not moveable cannot be reordered by the user + * by dragging the header but may be reordered + * by the programmer. + * + * @return the moveable attribute + * + * @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 Table#getColumnOrder() + * @see Table#setColumnOrder(int[]) + * @see TableColumn#setMoveable(boolean) + * @see SWT#Move + * + * @since 3.1 + */ +public boolean getMoveable () { + checkWidget (); + return moveable; +} +int getOrderIndex () { + TableColumn[] orderedColumns = parent.orderedColumns; + if (orderedColumns == null) return getIndex (); + for (int i = 0; i < orderedColumns.length; i++) { + if (orderedColumns [i] == this) return i; + } + return -1; +} +/** + * Returns the receiver's parent, which must be a <code>Table</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 Table getParent () { + checkWidget (); + return parent; +} +int getPreferredWidth () { + if (!parent.getHeaderVisible ()) return 0; + GC gc = new GC (parent); + int result = getContentWidth (gc, false); + gc.dispose (); + return result + 2 * parent.getHeaderPadding (); +} +/** + * Gets the resizable attribute. A column that is + * not resizable cannot be dragged by the user but + * may be resized by the programmer. + * + * @return the resizable attribute + * + * @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 getResizable () { + checkWidget (); + return resizable; +} +/** + * Returns the receiver's tool tip text, or null if it has + * not been set. + * + * @return the receiver's tool tip 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> + * + * @since 3.2 + */ +public String getToolTipText () { + checkWidget (); + return toolTipText; +} +/** + * Gets the width of the receiver. + * + * @return the width + * + * @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 getWidth () { + checkWidget (); + return width; +} +int getX () { + TableColumn[] orderedColumns = parent.getOrderedColumns (); + int index = getOrderIndex (); + int result = -parent.horizontalOffset; + for (int i = 0; i < index; i++) { + result += orderedColumns [i].width; + } + return result; +} +/** + * Causes the receiver to be resized to its preferred size. + * For a composite, this involves computing the preferred size + * from its layout, if there is one. + * + * @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 pack () { + checkWidget (); + TableItem[] items = parent.items; + int index = getIndex (); + int newWidth = getPreferredWidth (); + for (int i = 0; i < parent.itemsCount; i++) { + int width = items [i].getPreferredWidth (index); + /* ensure that receiver and parent were not disposed in a callback */ + if (parent.isDisposed () || isDisposed ()) return; + if (!items [i].isDisposed ()) { + newWidth = Math.max (newWidth, width); + } + } + if (newWidth != width) parent.updateColumnWidth (this, newWidth); +} +void paint (GC gc) { + int padding = parent.getHeaderPadding (); + + int x = getX (); + int startX = x + padding; + if ((style & SWT.LEFT) == 0) { + int contentWidth = getContentWidth (gc, true); + if ((style & SWT.RIGHT) != 0) { + startX = Math.max (startX, x + width - padding - contentWidth); + } else { /* SWT.CENTER */ + startX = Math.max (startX, x + (width - contentWidth) / 2); + } + } + int headerHeight = parent.getHeaderHeight (); + + /* restrict the clipping region to the header cell */ + gc.setClipping ( + x + padding, + padding, + width - 2 * padding, + headerHeight - 2 * padding); + + if (image != null) { + Rectangle imageBounds = image.getBounds (); + int drawHeight = Math.min (imageBounds.height, headerHeight - 2 * padding); + gc.drawImage ( + image, + 0, 0, + imageBounds.width, imageBounds.height, + startX, (headerHeight - drawHeight) / 2, + imageBounds.width, drawHeight); + startX += imageBounds.width + Table.MARGIN_IMAGE; + } + if (displayText.length () > 0) { + gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK)); + int fontHeight = parent.fontHeight; + gc.drawText (displayText, startX, (headerHeight - fontHeight) / 2, SWT.DRAW_MNEMONIC); + startX += gc.textExtent (displayText, SWT.DRAW_MNEMONIC).x + Table.MARGIN_IMAGE; + } + if (sort != SWT.NONE) { + Image image = sort == SWT.DOWN ? parent.getArrowDownImage () : parent.getArrowUpImage (); + int y = (headerHeight - parent.arrowBounds.height) / 2; + gc.drawImage (image, startX, y); + } +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the control is moved or resized. + * + * @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 ControlListener + * @see #addControlListener + */ +public void removeControlListener (ControlListener listener) { + checkWidget (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + if (eventTable == null) return; + eventTable.unhook (SWT.Move, listener); + eventTable.unhook (SWT.Resize, listener); +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the control is selected by the user. + * + * @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 (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + removeListener (SWT.Selection, listener); + removeListener (SWT.DefaultSelection, listener); +} +/** + * Controls how text and images will be displayed in the receiver. + * The argument should be one of <code>LEFT</code>, <code>RIGHT</code> + * or <code>CENTER</code>. + * <p> + * Note that due to a restriction on some platforms, the first column + * is always left aligned. + * </p> + * @param alignment the new alignment + * + * @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 setAlignment (int alignment) { + checkWidget (); + if ((alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER)) == 0) return; + int index = getIndex (); + if (index == -1 || index == 0) return; /* column 0 can only have left-alignment */ + alignment = checkBits (alignment, SWT.LEFT, SWT.CENTER, SWT.RIGHT, 0, 0, 0); + if ((style & alignment) != 0) return; /* same value */ + style &= ~(SWT.LEFT | SWT.CENTER | SWT.RIGHT); + style |= alignment; + int x = getX (); + parent.redraw (x, 0, width, parent.clientArea.height, false); + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + /* don't damage the header's drawn borders */ + parent.header.redraw (x, 1, width - 2, parent.getHeaderHeight () - 3, false); + } +} +public void setImage (Image value) { + checkWidget (); + if (value == image) return; + if (value != null && value.equals (image)) return; /* same value */ + super.setImage (value); + + /* An image width change may affect the space available for the column's displayText. */ + GC gc = new GC (parent); + computeDisplayText (gc); + gc.dispose (); + + /* + * If this is the first image being put into the header then the header + * height may be adjusted, in which case a full redraw is needed. + */ + if (parent.headerImageHeight == 0) { + int oldHeaderHeight = parent.getHeaderHeight (); + parent.setHeaderImageHeight (value.getBounds ().height); + if (oldHeaderHeight != parent.getHeaderHeight ()) { + /* parent header height changed */ + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + parent.header.redraw (); + } + parent.redraw (); + return; + } + } + + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + /* don't damage the header's drawn borders */ + parent.header.redraw (getX (), 1, width - 2, parent.getHeaderHeight () - 3, false); + } +} +/** + * Sets the moveable attribute. A column that is + * moveable can be reordered by the user by dragging + * the header. A column that is not moveable cannot be + * dragged by the user but may be reordered + * by the programmer. + * + * @param moveable the moveable attribute + * + * @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 Table#setColumnOrder(int[]) + * @see Table#getColumnOrder() + * @see TableColumn#getMoveable() + * @see SWT#Move + * + * @since 3.1 + */ +public void setMoveable (boolean moveable) { + checkWidget (); + this.moveable = moveable; +} +/** + * Sets the resizable attribute. A column that is + * resizable can be resized by the user dragging the + * edge of the header. A column that is not resizable + * cannot be dragged by the user but may be resized + * by the programmer. + * + * @param resizable the resize attribute + * + * @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 setResizable (boolean value) { + checkWidget (); + resizable = value; +} +void setSortDirection (int value) { + if (value == sort) return; + boolean widthChange = value == SWT.NONE || sort == SWT.NONE; + sort = value; + if (widthChange) { + /* + * adding/removing the sort arrow decreases/increases the width that is + * available for the column's header text, so recompute the display text + */ + GC gc = new GC (parent); + computeDisplayText (gc); + gc.dispose (); + } + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + /* don't damage the header's drawn borders */ + parent.header.redraw (getX (), 1, width - 2, parent.getHeaderHeight () - 3, false); + } +} +public void setText (String value) { + checkWidget (); + if (value == null) error (SWT.ERROR_NULL_ARGUMENT); + if (value.equals (text)) return; /* same value */ + super.setText (value); + GC gc = new GC (parent); + computeDisplayText (gc); + gc.dispose (); + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + /* don't damage the header's drawn borders */ + parent.header.redraw (getX (), 1, width - 2, parent.getHeaderHeight () - 3, false); + } +} +/** + * Sets the receiver's tool tip text to the argument, which + * may be null indicating that the default tool tip for the + * control will be shown. For a control that has a default + * tool tip, such as the Tree control on Windows, setting + * the tool tip text to an empty string replaces the default, + * causing no tool tip text to be shown. + * <p> + * The mnemonic indicator (character '&') is not displayed in a tool tip. + * To display a single '&' in the tool tip, the character '&' can be + * escaped by doubling it in the string. + * </p> + * + * @param string the new tool tip text (or null) + * + * @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 3.2 + */ +public void setToolTipText (String string) { + checkWidget (); + if (toolTipText == string) return; + if (toolTipText != null && toolTipText.equals (string)) return; + toolTipText = string; + if (parent.toolTipShell == null) return; /* tooltip not currently showing */ + if (((Integer) parent.toolTipShell.getData ()).intValue () != getIndex ()) return; /* tooltip showing for different column */ + parent.headerUpdateToolTip (getX () + (width / 2)); /* update the tooltip text */ +} +/** + * Sets the width of the receiver. + * + * @param width the new width + * + * @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 setWidth (int value) { + checkWidget (); + if (value < 0) return; + if (width == value) return; /* same value */ + parent.updateColumnWidth (this, value); +} +void updateFont (GC gc) { + computeDisplayText (gc); +} +/* + * Perform any internal changes necessary to reflect a changed width. + */ +void updateWidth (GC gc) { + String oldDisplayText = displayText; + computeDisplayText (gc); + /* the header must be damaged if the display text has changed or if the alignment is not LEFT */ + if (parent.getHeaderVisible ()) { + if ((style & SWT.LEFT) == 0 || !oldDisplayText.equals (displayText)) { + int padding = parent.getHeaderPadding (); + parent.header.redraw (getX () + padding, 0, width - padding, parent.getHeaderHeight (), false); + } + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TableItem.java b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TableItem.java new file mode 100644 index 0000000000..e4c3476995 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TableItem.java @@ -0,0 +1,2032 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Instances of this class represent a selectable user interface object + * that represents an item in a table. + * <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> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#table">Table, TableItem, TableColumn snippets</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ +public class TableItem extends Item { + Table parent; + int index = -1; + boolean checked, grayed, cached; + + String[] texts; + int[] textWidths = new int [1]; /* cached string measurements */ + int customWidth = -1; /* width specified by Measure callback */ + int fontHeight; /* cached item font height */ + int[] fontHeights; + int imageIndent; + Image[] images; + Color foreground, background; + String[] displayTexts; + Color[] cellForegrounds, cellBackgrounds; + Font font; + Font[] cellFonts; + + static final int MARGIN_TEXT = 3; /* the left and right margins within the text's space */ + +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>Table</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 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 + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public TableItem (Table parent, int style) { + this (parent, style, checkNull (parent).itemsCount); +} +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>Table</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 composite 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 TableItem (Table parent, int style, int index) { + this (parent, style, index, true); +} +TableItem (Table parent, int style, int index, boolean notifyParent) { + super (parent, style); + int validItemIndex = parent.itemsCount; + if (!(0 <= index && index <= validItemIndex)) error (SWT.ERROR_INVALID_RANGE); + this.parent = parent; + this.index = index; + int columnCount = parent.columns.length; + if (columnCount > 0) { + displayTexts = new String [columnCount]; + if (columnCount > 1) { + texts = new String [columnCount]; + textWidths = new int [columnCount]; + images = new Image [columnCount]; + } + } + if (notifyParent) parent.createItem (this); +} +/* + * Updates internal structures in the receiver and its child items to handle the creation of a new column. + */ +void addColumn (TableColumn column) { + int index = column.getIndex (); + int columnCount = parent.columns.length; + + if (columnCount > 1) { + if (columnCount == 2) { + texts = new String [2]; + } else { + String[] newTexts = new String [columnCount]; + System.arraycopy (texts, 0, newTexts, 0, index); + System.arraycopy (texts, index, newTexts, index + 1, columnCount - index - 1); + texts = newTexts; + } + if (index == 0) { + texts [1] = text; + text = ""; //$NON-NLS-1$ + } + + if (columnCount == 2) { + images = new Image [2]; + } else { + Image[] newImages = new Image [columnCount]; + System.arraycopy (images, 0, newImages, 0, index); + System.arraycopy (images, index, newImages, index + 1, columnCount - index - 1); + images = newImages; + } + if (index == 0) { + images [1] = image; + image = null; + } + + int[] newTextWidths = new int [columnCount]; + System.arraycopy (textWidths, 0, newTextWidths, 0, index); + System.arraycopy (textWidths, index, newTextWidths, index + 1, columnCount - index - 1); + textWidths = newTextWidths; + } else { + customWidth = -1; /* columnCount == 1 */ + } + + /* + * The length of displayTexts always matches the parent's column count, unless this + * count is zero, in which case displayTexts is null. + */ + String[] newDisplayTexts = new String [columnCount]; + if (columnCount > 1) { + System.arraycopy (displayTexts, 0, newDisplayTexts, 0, index); + System.arraycopy (displayTexts, index, newDisplayTexts, index + 1, columnCount - index - 1); + } + displayTexts = newDisplayTexts; + + if (cellBackgrounds != null) { + Color[] newCellBackgrounds = new Color [columnCount]; + System.arraycopy (cellBackgrounds, 0, newCellBackgrounds, 0, index); + System.arraycopy (cellBackgrounds, index, newCellBackgrounds, index + 1, columnCount - index - 1); + cellBackgrounds = newCellBackgrounds; + } + if (cellForegrounds != null) { + Color[] newCellForegrounds = new Color [columnCount]; + System.arraycopy (cellForegrounds, 0, newCellForegrounds, 0, index); + System.arraycopy (cellForegrounds, index, newCellForegrounds, index + 1, columnCount - index - 1); + cellForegrounds = newCellForegrounds; + } + if (cellFonts != null) { + Font[] newCellFonts = new Font [columnCount]; + System.arraycopy (cellFonts, 0, newCellFonts, 0, index); + System.arraycopy (cellFonts, index, newCellFonts, index + 1, columnCount - index - 1); + cellFonts = newCellFonts; + + int[] newFontHeights = new int [columnCount]; + System.arraycopy (fontHeights, 0, newFontHeights, 0, index); + System.arraycopy (fontHeights, index, newFontHeights, index + 1, columnCount - index - 1); + fontHeights = newFontHeights; + } + + if (index == 0 && columnCount > 1) { + /* + * The new second column may have more width available to it than it did when it was + * the first column if checkboxes are being shown, so recompute its displayText if needed. + */ + if ((parent.style & SWT.CHECK) != 0) { + GC gc = new GC (parent); + gc.setFont (getFont (1, false)); + computeDisplayText (1, gc); + gc.dispose (); + } + } +} +static Table checkNull (Table table) { + if (table == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + return table; +} +protected void checkSubclass () { + if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS); +} +void clear () { + checked = grayed = false; + texts = null; + textWidths = new int [1]; + fontHeight = 0; + fontHeights = null; + images = null; + foreground = background = null; + displayTexts = null; + cellForegrounds = cellBackgrounds = null; + font = null; + cellFonts = null; + cached = false; + text = ""; + image = null; + + int columnCount = parent.columns.length; + if (columnCount > 0) { + displayTexts = new String [columnCount]; + if (columnCount > 1) { + texts = new String [columnCount]; + textWidths = new int [columnCount]; + images = new Image [columnCount]; + } + } +} +void computeDisplayText (int columnIndex, GC gc) { + if ((parent.style & SWT.VIRTUAL) != 0 && !cached) return; /* nothing to do */ + + int columnCount = parent.columns.length; + if (columnCount == 0) { + String text = getText (0, false); + textWidths [columnIndex] = gc.stringExtent (text).x; + return; + } + + TableColumn column = parent.columns [columnIndex]; + int availableWidth = column.width - 2 * parent.getCellPadding () - 2 * MARGIN_TEXT; + if (columnIndex == 0) { + availableWidth -= parent.col0ImageWidth; + if (parent.col0ImageWidth > 0) availableWidth -= Table.MARGIN_IMAGE; + if ((parent.style & SWT.CHECK) != 0) { + availableWidth -= parent.checkboxBounds.width; + availableWidth -= Table.MARGIN_IMAGE; + } + } else { + Image image = getImage (columnIndex, false); + if (image != null) { + availableWidth -= image.getBounds ().width; + availableWidth -= Table.MARGIN_IMAGE; + } + } + + String text = getText (columnIndex, false); + int textWidth = gc.stringExtent (text).x; + if (textWidth <= availableWidth) { + displayTexts [columnIndex] = text; + textWidths [columnIndex] = textWidth; + return; + } + + /* Ellipsis will be needed, so subtract their width from the available text width */ + int ellipsisWidth = gc.stringExtent (Table.ELLIPSIS).x; + availableWidth -= ellipsisWidth; + if (availableWidth <= 0) { + displayTexts [columnIndex] = Table.ELLIPSIS; + textWidths [columnIndex] = ellipsisWidth; + return; + } + + /* Make initial guess. */ + int index = Math.min (availableWidth / gc.getFontMetrics ().getAverageCharWidth (), text.length ()); + textWidth = gc.stringExtent (text.substring (0, index)).x; + + /* Initial guess is correct. */ + if (availableWidth == textWidth) { + displayTexts [columnIndex] = text.substring (0, index) + Table.ELLIPSIS; + textWidths [columnIndex] = textWidth + ellipsisWidth; + return; + } + + /* Initial guess is too high, so reduce until fit is found. */ + if (availableWidth < textWidth) { + do { + index--; + if (index < 0) { + displayTexts [columnIndex] = Table.ELLIPSIS; + textWidths [columnIndex] = ellipsisWidth; + return; + } + text = text.substring (0, index); + textWidth = gc.stringExtent (text).x; + } while (availableWidth < textWidth); + displayTexts [columnIndex] = text + Table.ELLIPSIS; + textWidths [columnIndex] = textWidth + ellipsisWidth; + return; + } + + /* Initial guess is too low, so increase until overrun is found. */ + int previousWidth = 0; + while (textWidth < availableWidth) { + index++; + previousWidth = textWidth; + textWidth = gc.stringExtent (text.substring (0, index)).x; + } + displayTexts [columnIndex] = text.substring (0, index - 1) + Table.ELLIPSIS; + textWidths [columnIndex] = previousWidth + ellipsisWidth; +} +void computeDisplayTexts (GC gc) { + if ((parent.style & SWT.VIRTUAL) != 0 && !cached) return; /* nothing to do */ + + int columnCount = parent.columns.length; + if (columnCount == 0) return; + + for (int i = 0; i < columnCount; i++) { + gc.setFont (getFont (i, false)); + computeDisplayText (i, gc); + } +} +/* + * Computes the cached text widths. + */ +void computeTextWidths (GC gc) { + if ((parent.style & SWT.VIRTUAL) != 0 && !cached) return; /* nothing to do */ + + int validColumnCount = Math.max (1, parent.columns.length); + textWidths = new int [validColumnCount]; + for (int i = 0; i < textWidths.length; i++) { + String value = getDisplayText (i); + if (value != null) { + gc.setFont (getFont (i, false)); + textWidths [i] = gc.stringExtent (value).x; + } + } +} +public void dispose () { + if (isDisposed ()) return; + Table parent = this.parent; + int startIndex = index; + int endIndex = parent.itemsCount - 1; + dispose (true); + parent.redrawItems (startIndex, endIndex, false); +} +void dispose (boolean notifyParent) { + if (isDisposed ()) return; + if (notifyParent) parent.destroyItem (this); + super.dispose (); /* super is intentional here */ + background = foreground = null; + cellBackgrounds = cellForegrounds = null; + font = null; + cellFonts = null; + images = null; + texts = displayTexts = null; + textWidths = fontHeights = null; + parent = null; +} +/** + * 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> + * + * @since 2.0 + */ +public Color getBackground () { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (background != null) return background; + return parent.getBackground (); +} +/** + * 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> + * + * @since 3.0 + */ +public Color getBackground (int columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return getBackground (); + if (cellBackgrounds == null || cellBackgrounds [columnIndex] == null) return getBackground (); + return cellBackgrounds [columnIndex]; +} +/** + * 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> + * + * @since 3.2 + */ +public Rectangle getBounds () { + checkWidget (); + return getBounds (true); +} +Rectangle getBounds (boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int x = getTextX (0); + int width = textWidths [0] + 2 * MARGIN_TEXT; + if (parent.columns.length > 0) { + TableColumn column = parent.columns [0]; + int right = column.getX () + column.width; + if (x + width > right) { + width = Math.max (0, right - x); + } + } + return new Rectangle (x, parent.getItemY (this), width, parent.itemHeight); +} +/** + * Returns a rectangle describing the receiver's size and location + * relative to its parent at a column in the table. + * + * @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 columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + TableColumn[] columns = parent.columns; + int columnCount = columns.length; + int validColumnCount = Math.max (1, columnCount); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) { + return new Rectangle (0, 0, 0, 0); + } + /* + * If there are no columns then this is the bounds of the receiver's content. + */ + if (columnCount == 0) { + int width = getContentWidth (0); + return new Rectangle ( + getContentX (0), + parent.getItemY (this), + width, + parent.itemHeight - 1); + } + + TableColumn column = columns [columnIndex]; + if (columnIndex == 0) { + /* + * For column 0 this is bounds from the beginning of the content to the + * end of the column. + */ + int x = getContentX (0); + int offset = x - column.getX (); + int width = Math.max (0, column.width - offset - 1); /* max is for columns with small widths */ + return new Rectangle (x, parent.getItemY (this) + 1, width, parent.itemHeight - 1); + } + /* + * For columns > 0 this is the bounds of the table cell. + */ + return new Rectangle (column.getX (), parent.getItemY (this) + 1, column.width, parent.itemHeight - 1); +} +/* + * Returns the full bounds of a cell in a table, regardless of its content. + */ +Rectangle getCellBounds (int columnIndex) { + int y = parent.getItemY (this); + if (parent.columns.length == 0) { + int width; + if (customWidth != -1) { + width = getContentX (0) + customWidth + parent.horizontalOffset; + } else { + int textPaintWidth = textWidths [0] + 2 * MARGIN_TEXT; + width = getTextX (0) + textPaintWidth + parent.horizontalOffset; + } + return new Rectangle (-parent.horizontalOffset, y, width, parent.itemHeight); + } + TableColumn column = parent.columns [columnIndex]; + return new Rectangle (column.getX (), y, column.width, parent.itemHeight); +} +/* + * Returns the bounds of the receiver's checkbox, or null if the parent's style does not + * include SWT.CHECK. + */ +Rectangle getCheckboxBounds () { + if ((parent.getStyle () & SWT.CHECK) == 0) return null; + Rectangle result = parent.checkboxBounds; + if (parent.columns.length == 0) { + result.x = parent.getCellPadding () - parent.horizontalOffset; + } else { + result.x = parent.columns [0].getX () + parent.getCellPadding (); + } + result.y = parent.getItemY (this) + (parent.itemHeight - result.height) / 2; + return result; +} +/** + * Returns <code>true</code> if the receiver is checked, + * and false otherwise. When the parent does not have + * the <code>CHECK</code> style, return false. + * + * @return the checked 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 getChecked () { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return checked; +} +int getContentWidth (int columnIndex) { + int width = textWidths [columnIndex] + 2 * MARGIN_TEXT; + if (columnIndex == 0) { + width += parent.col0ImageWidth; + if (parent.col0ImageWidth > 0) width += Table.MARGIN_IMAGE; + } else { + Image image = getImage (columnIndex, false); + if (image != null) { + width += image.getBounds ().width + Table.MARGIN_IMAGE; + } + } + return width; +} +/* + * Returns the x value where the receiver's content (ie.- its image or text) begins + * for the specified column. + */ +int getContentX (int columnIndex) { + int minX = parent.getCellPadding (); + if (columnIndex == 0) { + Rectangle checkboxBounds = getCheckboxBounds (); + if (checkboxBounds != null) { + minX += checkboxBounds.width + Table.MARGIN_IMAGE; + } + } + + if (parent.columns.length == 0) return minX - parent.horizontalOffset; /* free first column */ + + TableColumn column = parent.columns [columnIndex]; + int columnX = column.getX (); + if ((column.style & SWT.LEFT) != 0) return columnX + minX; + + /* column is not left-aligned */ + int contentWidth = getContentWidth (columnIndex); + int contentX = 0; + if ((column.style & SWT.RIGHT) != 0) { + contentX = column.width - parent.getCellPadding () - contentWidth; + } else { /* SWT.CENTER */ + contentX = (column.width - contentWidth) / 2; + } + return Math.max (columnX + minX, columnX + contentX); +} +String getDisplayText (int columnIndex) { + if (parent.columns.length == 0) return getText (0, false); + String result = displayTexts [columnIndex]; + return result != null ? result : ""; //$NON-NLS-1$ +} +/* + * Returns the bounds that should be used for drawing a focus rectangle on the receiver + */ +Rectangle getFocusBounds () { + int x = 0; + TableColumn[] columns = parent.columns; + int[] columnOrder = parent.getColumnOrder (); + if ((parent.style & SWT.FULL_SELECTION) != 0) { + int col0index = columnOrder.length == 0 ? 0 : columnOrder [0]; + if (col0index == 0) { + x = getTextX (0); + } else { + x = -parent.horizontalOffset; + } + } else { + x = getTextX (0); + } + + if (columns.length > 0) { + /* ensure that the focus x does not start beyond the right bound of column 0 */ + int rightX = columns [0].getX () + columns [0].width; + x = Math.min (x, rightX - 1); + } + + int width; + if (columns.length == 0) { + if (customWidth != -1) { + width = customWidth; + } else { + width = textWidths [0] + 2 * MARGIN_TEXT; + } + } else { + TableColumn column; + if ((parent.style & SWT.FULL_SELECTION) != 0) { + column = columns [columnOrder [columnOrder.length - 1]]; + } else { + column = columns [0]; + } + width = column.getX () + column.width - x - 1; + } + return new Rectangle ( + x, + parent.getItemY (this) + (parent.linesVisible ? 1 : 0), + width, + parent.itemHeight - (parent.linesVisible ? 1 : 0)); +} +/** + * 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> + * + * @since 3.0 + */ +public Font getFont () { + checkWidget (); + return getFont (true); +} +Font getFont (boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (font != null) return font; + return parent.getFont (); +} +/** + * 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> + * + * @since 3.0 + */ +public Font getFont (int columnIndex) { + checkWidget (); + return getFont (columnIndex, true); +} +Font getFont (int columnIndex, boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return getFont (checkData); + if (cellFonts == null || cellFonts [columnIndex] == null) return getFont (checkData); + return cellFonts [columnIndex]; +} +int getFontHeight () { + if (fontHeight != 0) return fontHeight; + return parent.fontHeight; +} +int getFontHeight (int columnIndex) { + if (fontHeights == null || fontHeights [columnIndex] == 0) return getFontHeight (); + return fontHeights [columnIndex]; +} +/** + * 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> + * + * @since 2.0 + */ +public Color getForeground () { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (foreground != null) return foreground; + return parent.getForeground (); +} +/** + * + * 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> + * + * @since 3.0 + */ +public Color getForeground (int columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return getForeground (); + if (cellForegrounds == null || cellForegrounds [columnIndex] == null) return getForeground (); + return cellForegrounds [columnIndex]; +} +/** + * Returns <code>true</code> if the receiver is grayed, + * and false otherwise. When the parent does not have + * the <code>CHECK</code> style, return false. + * + * @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, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return grayed; +} +/* + * Returns the bounds representing the clickable region that should select the receiver. + */ +Rectangle getHitBounds () { + int[] columnOrder = parent.getColumnOrder (); + int contentX = 0; + if ((parent.style & SWT.FULL_SELECTION) != 0) { + int col0index = columnOrder.length == 0 ? 0 : columnOrder [0]; + if (col0index == 0) { + contentX = getContentX (0); + } else { + contentX = 0; + } + } else { + contentX = getContentX (0); + } + + int width = 0; + TableColumn[] columns = parent.columns; + if (columns.length == 0) { + width = getContentWidth (0); + } else { + /* + * If there are columns then this spans from the beginning of the receiver's column 0 + * image or text to the end of either column 0 or the last column (FULL_SELECTION). + */ + TableColumn column; + if ((parent.style & SWT.FULL_SELECTION) != 0) { + column = columns [columnOrder [columnOrder.length - 1]]; + } else { + column = columns [0]; + } + width = column.getX () + column.width - contentX; + } + return new Rectangle (contentX, parent.getItemY (this), width, parent.itemHeight); +} +public Image getImage () { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return super.getImage (); +} +/** + * 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 columnIndex) { + checkWidget (); + return getImage (columnIndex, true); +} +Image getImage (int columnIndex, boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return null; + if (columnIndex == 0) return super.getImage (); /* super is intentional here */ + return images [columnIndex]; +} +/** + * Returns a rectangle describing the size and location + * relative to its parent of an image at a column in the + * table. An empty rectangle is returned if index exceeds + * the index of the table's last column. + * + * @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 columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return new Rectangle (0,0,0,0); + + int padding = parent.getCellPadding (); + int startX = getContentX (columnIndex); + int itemHeight = parent.itemHeight; + int imageSpaceY = itemHeight - 2 * padding; + int y = parent.getItemY (this); + Image image = getImage (columnIndex, false); + int drawWidth = 0; + if (columnIndex == 0) { + /* for column 0 all images have the same width */ + drawWidth = parent.col0ImageWidth; + } else { + if (image != null) drawWidth = image.getBounds ().width; + } + return new Rectangle (startX, y + padding, drawWidth, imageSpaceY); +} +/** + * Gets the image indent. + * + * @return the indent + * + * @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 getImageIndent () { + checkWidget(); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return imageIndent; // TODO +} +String getNameText () { + if ((parent.style & SWT.VIRTUAL) != 0) { + if (!cached) return "*virtual*"; //$NON-NLS-1$ + } + return super.getNameText (); +} +/** + * Returns the receiver's parent, which must be a <code>Table</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 Table getParent () { + checkWidget (); + return parent; +} +/* + * Returns the receiver's ideal width for the specified columnIndex. + */ +int getPreferredWidth (int columnIndex) { + int width = 0; + GC gc = new GC (parent); + gc.setFont (getFont (columnIndex, false)); + width += gc.stringExtent (getText (columnIndex, false)).x + 2 * MARGIN_TEXT; + if (columnIndex == 0) { + if (parent.col0ImageWidth > 0) { + width += parent.col0ImageWidth; + width += Table.MARGIN_IMAGE; + } + } else { + Image image = getImage (columnIndex, false); + if (image != null) { + width += image.getBounds ().width; + width += Table.MARGIN_IMAGE; + } + } + + if (parent.hooks (SWT.MeasureItem)) { + Event event = new Event (); + event.item = this; + event.gc = gc; + event.index = columnIndex; + event.x = getContentX (columnIndex); + event.y = parent.getItemY (this); + event.width = width; + event.height = parent.itemHeight; + parent.sendEvent (SWT.MeasureItem, event); + if (parent.itemHeight != event.height) { + parent.customHeightSet = true; + boolean update = parent.setItemHeight (event.height + 2 * parent.getCellPadding ()); + if (update) parent.redraw (); + } + width = event.width; + } + + gc.dispose (); + if (columnIndex == 0 && (parent.style & SWT.CHECK) != 0) { + width += parent.checkboxBounds.width; + width += Table.MARGIN_IMAGE; + } + return width + 2 * parent.getCellPadding (); +} +public String getText () { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return super.getText (); +} +/** + * 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 columnIndex) { + checkWidget (); + return getText (columnIndex, true); +} +String getText (int columnIndex, boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return ""; //$NON-NLS-1$ + if (columnIndex == 0) return super.getText (); /* super is intentional here */ + if (texts [columnIndex] == null) return ""; //$NON-NLS-1$ + return texts [columnIndex]; +} +/** + * Returns a rectangle describing the size and location + * relative to its parent of the text at a column in the + * table. An empty rectangle is returned if index exceeds + * the index of the table's last column. + * + * @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 3.3 + */ +public Rectangle getTextBounds (int columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + TableColumn[] columns = parent.columns; + int columnCount = columns.length; + int validColumnCount = Math.max (1, columnCount); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) { + return new Rectangle (0, 0, 0, 0); + } + /* + * If there are no columns then this is the bounds of the receiver's content, + * starting from the text. + */ + if (columnCount == 0) { + int x = getTextX (0) + MARGIN_TEXT; + int width = Math.max (0, getContentX(0) + getContentWidth (0) - x); + return new Rectangle ( + x, + parent.getItemY (this), + width, + parent.itemHeight - 1); + } + + TableColumn column = columns [columnIndex]; + if (columnIndex == 0) { + /* + * For column 0 this is bounds from the beginning of the content to the + * end of the column, starting from the text. + */ + int x = getTextX (0) + MARGIN_TEXT; + int offset = x - column.getX (); + int width = Math.max (0, column.width - offset - 1); /* max is for columns with small widths */ + return new Rectangle (x, parent.getItemY (this) + 1, width, parent.itemHeight - 1); + } + /* + * For columns > 0 this is the bounds of the table cell, starting from the text. + */ + int x = getTextX (columnIndex) + MARGIN_TEXT; + int offset = x - column.getX (); + int width = Math.max (0, column.width - offset - MARGIN_TEXT); + return new Rectangle (x, parent.getItemY (this) + 1, width, parent.itemHeight - 1); +} +/* + * Returns the x value where the receiver's text begins. + */ +int getTextX (int columnIndex) { + int textX = getContentX (columnIndex); + if (columnIndex == 0) { + textX += parent.col0ImageWidth; + if (parent.col0ImageWidth > 0) textX += Table.MARGIN_IMAGE; + } else { + Image image = getImage (columnIndex, false); + if (image != null) { + textX += image.getBounds ().width + Table.MARGIN_IMAGE; + } + } + return textX; +} +/* + * Answers a boolean indicating whether the receiver's y is within the current + * viewport of the parent. + */ +boolean isInViewport () { + int topIndex = parent.topIndex; + if (index < topIndex) return false; + int visibleCount = parent.clientArea.height / parent.itemHeight; + return index <= topIndex + visibleCount; +} +boolean isSelected () { + return parent.getSelectionIndex (this) != -1; +} +/* + * The backgroundOnly argument indicates whether the item should only + * worry about painting its background color and selection. + * + * Returns a boolean indicating whether to abort drawing focus on the item. + * If the receiver is not the current focus item then this value is irrelevant. + */ +boolean paint (GC gc, TableColumn column, boolean backgroundOnly) { + if (!parent.checkData (this, true)) return false; + int columnIndex = 0, x = 0; + if (column != null) { + columnIndex = column.getIndex (); + x = column.getX (); + } + + /* + * Capture GC attributes that will need to be restored later in the paint + * process to ensure that the item paints as intended without being affected + * by GC changes made in MeasureItem/EraseItem/PaintItem callbacks. + */ + int oldAlpha = gc.getAlpha (); + boolean oldAdvanced = gc.getAdvanced (); + int oldAntialias = gc.getAntialias (); + Pattern oldBackgroundPattern = gc.getBackgroundPattern (); + Pattern oldForegroundPattern = gc.getForegroundPattern (); + int oldInterpolation = gc.getInterpolation (); + int oldTextAntialias = gc.getTextAntialias (); + + if (parent.hooks (SWT.MeasureItem)) { + int contentWidth = getContentWidth (columnIndex); + int contentX = getContentX (columnIndex); + gc.setFont (getFont (columnIndex, false)); + Event event = new Event (); + event.item = this; + event.gc = gc; + event.index = columnIndex; + event.x = contentX; + event.y = parent.getItemY (this); + event.width = contentWidth; + event.height = parent.itemHeight; + parent.sendEvent (SWT.MeasureItem, event); + event.gc = null; + if (gc.isDisposed ()) return false; + gc.setAlpha (oldAlpha); + gc.setAntialias (oldAntialias); + gc.setBackgroundPattern (oldBackgroundPattern); + gc.setForegroundPattern (oldForegroundPattern); + gc.setInterpolation (oldInterpolation); + gc.setTextAntialias (oldTextAntialias); + gc.setAdvanced (oldAdvanced); + if (isDisposed ()) return false; + if (parent.itemHeight != event.height) { + parent.customHeightSet = true; + boolean update = parent.setItemHeight (event.height + 2 * parent.getCellPadding ()); + if (update) parent.redraw (); + } + if (parent.columns.length == 0) { + int change = event.width - (customWidth != -1 ? customWidth : contentWidth); + if (event.width != contentWidth || customWidth != -1) customWidth = event.width; + if (change != 0) { /* scrollbar may be affected since no columns */ + parent.updateHorizontalBar (contentX + event.width, change); + // TODO what if clip is too small now? + } + } + } + + /* if this cell is completely to the right of the client area then there's no need to paint it */ + Rectangle clientArea = parent.clientArea; + if (clientArea.x + clientArea.width < x) return false; + + Rectangle cellBounds = getCellBounds (columnIndex); + if (parent.linesVisible) { + cellBounds.y++; + cellBounds.height--; + } + int cellRightX = 0; + if (column != null) { + cellRightX = column.getX () + column.width; + } else { + cellRightX = cellBounds.x + cellBounds.width; + } + + /* restrict the clipping region to the cell */ + gc.setClipping (x, cellBounds.y, clientArea.width - x, cellBounds.height); + + int y = parent.getItemY (this); + int itemHeight = parent.itemHeight; + + /* draw the parent background color/image of this cell */ + if (column == null) { + parent.drawBackground (gc, 0, y, clientArea.width, itemHeight); + } else { + int fillWidth = cellBounds.width; + if (parent.linesVisible) fillWidth--; + parent.drawBackground (gc, cellBounds.x, cellBounds.y, fillWidth, cellBounds.height); + } + + boolean isSelected = isSelected (); + boolean isFocusItem = parent.focusItem == this && parent.isFocusControl (); + boolean drawBackground = true; + boolean drawForeground = true; + boolean drawSelection = isSelected; + boolean drawFocus = isFocusItem; + if (parent.hooks (SWT.EraseItem)) { + drawBackground = background != null || (cellBackgrounds != null && cellBackgrounds [columnIndex] != null); + gc.setFont (getFont (columnIndex, false)); + if (isSelected && (columnIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) { + gc.setForeground (display.getSystemColor (SWT.COLOR_LIST_SELECTION_TEXT)); + gc.setBackground (display.getSystemColor (SWT.COLOR_LIST_SELECTION)); + } else { + gc.setForeground (getForeground (columnIndex)); + gc.setBackground (getBackground (columnIndex)); + } + Event event = new Event (); + event.item = this; + event.gc = gc; + event.index = columnIndex; + event.doit = true; + event.detail = SWT.FOREGROUND; + if (drawBackground) event.detail |= SWT.BACKGROUND; + if (isSelected) event.detail |= SWT.SELECTED; + if (isFocusItem) event.detail |= SWT.FOCUSED; + event.x = cellBounds.x; + event.y = cellBounds.y; + event.width = cellBounds.width; + event.height = cellBounds.height; + gc.setClipping (cellBounds); + parent.sendEvent (SWT.EraseItem, event); + event.gc = null; + if (gc.isDisposed ()) return false; + gc.setAlpha (oldAlpha); + gc.setAntialias (oldAntialias); + gc.setBackgroundPattern (oldBackgroundPattern); + gc.setClipping (cellBounds); + gc.setForegroundPattern (oldForegroundPattern); + gc.setInterpolation (oldInterpolation); + gc.setTextAntialias (oldTextAntialias); + gc.setAdvanced (oldAdvanced); + if (isDisposed ()) return false; + if (!event.doit) { + drawBackground = drawForeground = drawSelection = drawFocus = false; + } else { + drawBackground = drawBackground && (event.detail & SWT.BACKGROUND) != 0; + drawForeground = (event.detail & SWT.FOREGROUND) != 0; + drawSelection = isSelected && (event.detail & SWT.SELECTED) != 0; + drawFocus = isFocusItem && (event.detail & SWT.FOCUSED) != 0; + } + } + + /* draw the cell's set background if appropriate */ + if (drawBackground) { + gc.setBackground (getBackground (columnIndex)); + if (columnIndex == 0 && (column == null || column.getOrderIndex () == 0)) { + Rectangle focusBounds = getFocusBounds (); + int fillWidth = 0; + if (column == null) { + fillWidth = focusBounds.width; + } else { + fillWidth = column.width - focusBounds.x; + if (parent.linesVisible) fillWidth--; + } + gc.fillRectangle (focusBounds.x, focusBounds.y, fillWidth, focusBounds.height); + } else { + int fillWidth = cellBounds.width; + gc.fillRectangle (cellBounds.x, cellBounds.y, fillWidth, cellBounds.height); + } + } + + /* draw the selection bar if the receiver is selected */ + if (drawSelection && (columnIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) { + if (parent.hasFocus () || (parent.style & SWT.HIDE_SELECTION) == 0) { + gc.setBackground (display.getSystemColor (SWT.COLOR_LIST_SELECTION)); + if (columnIndex == 0) { + Rectangle focusBounds = getFocusBounds (); + int startX, fillWidth; + if (column == null || column.getOrderIndex () == 0 || (parent.style & SWT.FULL_SELECTION) == 0) { + startX = focusBounds.x + 1; /* space for left bound of focus rect */ + } else { + startX = column.getX (); + } + if (column == null) { + fillWidth = focusBounds.width - 2; + } else { + fillWidth = column.getX () + column.width - startX; + if (column.getOrderIndex () == parent.columns.length - 1 || (parent.style & SWT.FULL_SELECTION) == 0) { + fillWidth -= 2; /* space for right bound of focus rect */ + } + } + if (fillWidth > 0) { + gc.fillRectangle (startX, focusBounds.y + 1, fillWidth, focusBounds.height - 2); + } + } else { + int startX = column.getX (); + int fillWidth = column.width; + if (column.getOrderIndex () == 0) { + startX += 1; /* space for left bound of focus rect */ + fillWidth -= 1; + } + if (column.getOrderIndex () == parent.columns.length - 1) { + fillWidth -= 2; /* space for right bound of focus rect */ + } + if (fillWidth > 0) { + gc.fillRectangle ( + column.getX (), + cellBounds.y + 1, + fillWidth, + cellBounds.height - 2); + } + } + } + } + + if (backgroundOnly) return false; + + /* Draw checkbox if drawing column 0 and parent has style SWT.CHECK */ + if (columnIndex == 0 && (parent.style & SWT.CHECK) != 0) { + Image baseImage = grayed ? parent.getGrayUncheckedImage () : parent.getUncheckedImage (); + Rectangle checkboxBounds = getCheckboxBounds (); + gc.drawImage (baseImage, checkboxBounds.x, checkboxBounds.y); + /* Draw checkmark if item is checked */ + if (checked) { + Image checkmarkImage = parent.getCheckmarkImage (); + Rectangle checkmarkBounds = checkmarkImage.getBounds (); + int xInset = (checkboxBounds.width - checkmarkBounds.width) / 2; + int yInset = (checkboxBounds.height - checkmarkBounds.height) / 2; + gc.drawImage (checkmarkImage, checkboxBounds.x + xInset, checkboxBounds.y + yInset); + } + } + + if (drawForeground) { + Image image = getImage (columnIndex, false); + String text = getDisplayText (columnIndex); + Rectangle imageArea = getImageBounds (columnIndex); + int startX = imageArea.x; + + /* while painting the cell's content restrict the clipping region */ + int padding = parent.getCellPadding (); + gc.setClipping ( + startX, + cellBounds.y + padding - (parent.linesVisible ? 1 : 0), + cellRightX - startX - padding, + cellBounds.height - 2 * (padding - (parent.linesVisible ? 1 : 0))); + + /* draw the image */ + if (image != null) { + Rectangle imageBounds = image.getBounds (); + gc.drawImage ( + image, + 0, 0, /* source x, y */ + imageBounds.width, imageBounds.height, /* source width, height */ + imageArea.x, imageArea.y, /* dest x, y */ + imageArea.width, imageArea.height); /* dest width, height */ + } + + /* draw the text */ + if (text.length () > 0) { + gc.setFont (getFont (columnIndex, false)); + int fontHeight = getFontHeight (columnIndex); + if (drawSelection && (columnIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) { + if (parent.hasFocus () || (parent.style & SWT.HIDE_SELECTION) == 0) { + gc.setForeground (display.getSystemColor (SWT.COLOR_LIST_SELECTION_TEXT)); + } + } else { + if (!isSelected || drawSelection) { + gc.setForeground (getForeground (columnIndex)); + } + } + x = getTextX (columnIndex) + MARGIN_TEXT; + gc.drawString (text, x, y + (itemHeight - fontHeight) / 2, true); + } + } + + if (parent.hooks (SWT.PaintItem)) { + int contentWidth = getContentWidth (columnIndex); + int contentX = getContentX (columnIndex); + gc.setFont (getFont (columnIndex, false)); + if (isSelected && (columnIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) { + gc.setForeground (display.getSystemColor (SWT.COLOR_LIST_SELECTION_TEXT)); + gc.setBackground (display.getSystemColor (SWT.COLOR_LIST_SELECTION)); + } else { + gc.setForeground (getForeground (columnIndex)); + gc.setBackground (getBackground (columnIndex)); + } + Event event = new Event (); + event.item = this; + event.gc = gc; + event.index = columnIndex; + if (isSelected) event.detail |= SWT.SELECTED; + if (drawFocus) event.detail |= SWT.FOCUSED; + event.x = contentX; + event.y = cellBounds.y; + event.width = contentWidth; + event.height = cellBounds.height; + gc.setClipping (cellBounds); + parent.sendEvent (SWT.PaintItem, event); + event.gc = null; + if (gc.isDisposed ()) return false; + gc.setAlpha (oldAlpha); + gc.setAntialias (oldAntialias); + gc.setBackgroundPattern (oldBackgroundPattern); + gc.setClipping (cellBounds); + gc.setForegroundPattern (oldForegroundPattern); + gc.setInterpolation (oldInterpolation); + gc.setTextAntialias (oldTextAntialias); + gc.setAdvanced (oldAdvanced); + drawFocus = isFocusItem && (event.detail & SWT.FOCUSED) != 0; + } + + return isFocusItem && !drawFocus; +} +/* + * Redraw part of the receiver. If either EraseItem or PaintItem is hooked then + * only full cells should be damaged, so adjust accordingly. If neither of these + * events are hooked then the exact bounds given for damaging can be used. + */ +void redraw (int x, int y, int width, int height, int columnIndex) { + if (!parent.hooks (SWT.EraseItem) && !parent.hooks (SWT.PaintItem)) { + parent.redraw (x, y, width, height, false); + return; + } + Rectangle cellBounds = getCellBounds (columnIndex); + parent.redraw (cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height, false); +} +void redrawItem () { + parent.redraw (0, parent.getItemY (this), parent.clientArea.width, parent.itemHeight, false); +} +/* + * Updates internal structures in the receiver and its child items to handle the removal of a column. + */ +void removeColumn (TableColumn column, int index) { + int columnCount = parent.columns.length; + + if (columnCount == 0) { + /* reverts to normal table when last column disposed */ + cellBackgrounds = cellForegrounds = null; + displayTexts = null; + cellFonts = null; + fontHeights = null; + GC gc = new GC (parent); + computeTextWidths (gc); + gc.dispose (); + return; + } + + String[] newTexts = new String [columnCount]; + System.arraycopy (texts, 0, newTexts, 0, index); + System.arraycopy (texts, index + 1, newTexts, index, columnCount - index); + texts = newTexts; + + Image[] newImages = new Image [columnCount]; + System.arraycopy (images, 0, newImages, 0, index); + System.arraycopy (images, index + 1, newImages, index, columnCount - index); + images = newImages; + + int[] newTextWidths = new int [columnCount]; + System.arraycopy (textWidths, 0, newTextWidths, 0, index); + System.arraycopy (textWidths, index + 1, newTextWidths, index, columnCount - index); + textWidths = newTextWidths; + + String[] newDisplayTexts = new String [columnCount]; + System.arraycopy (displayTexts, 0, newDisplayTexts, 0, index); + System.arraycopy (displayTexts, index + 1, newDisplayTexts, index, columnCount - index); + displayTexts = newDisplayTexts; + + if (cellBackgrounds != null) { + Color[] newCellBackgrounds = new Color [columnCount]; + System.arraycopy (cellBackgrounds, 0, newCellBackgrounds, 0, index); + System.arraycopy (cellBackgrounds, index + 1, newCellBackgrounds, index, columnCount - index); + cellBackgrounds = newCellBackgrounds; + } + if (cellForegrounds != null) { + Color[] newCellForegrounds = new Color [columnCount]; + System.arraycopy (cellForegrounds, 0, newCellForegrounds, 0, index); + System.arraycopy (cellForegrounds, index + 1, newCellForegrounds, index, columnCount - index); + cellForegrounds = newCellForegrounds; + } + if (cellFonts != null) { + Font[] newCellFonts = new Font [columnCount]; + System.arraycopy (cellFonts, 0, newCellFonts, 0, index); + System.arraycopy (cellFonts, index + 1, newCellFonts, index, columnCount - index); + cellFonts = newCellFonts; + + int[] newFontHeights = new int [columnCount]; + System.arraycopy (fontHeights, 0, newFontHeights, 0, index); + System.arraycopy (fontHeights, index + 1, newFontHeights, index, columnCount - index); + fontHeights = newFontHeights; + } + + if (index == 0) { + text = texts [0] != null ? texts [0] : ""; //$NON-NLS-1$ + texts [0] = null; + image = images [0]; + images [0] = null; + /* + * The new first column may not have as much width available to it as it did when it was + * the second column if checkboxes are being shown, so recompute its displayText if needed. + */ + if ((parent.style & SWT.CHECK) != 0) { + GC gc = new GC (parent); + gc.setFont (getFont (0, false)); + computeDisplayText (0, gc); + gc.dispose (); + } + } + if (columnCount < 2) { + texts = null; + images = null; + } +} +/** + * 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 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> + * + * @since 2.0 + */ +public void setBackground (Color color) { + checkWidget (); + if (color != null && color.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + Color oldColor = background; + if (oldColor == color) return; + background = color; + if (oldColor != null && oldColor.equals (color)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + redrawItem (); +} +/** + * 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> + * + * @since 3.0 + */ +public void setBackground (int columnIndex, Color color) { + checkWidget (); + if (color != null && color.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + if (cellBackgrounds == null) { + if (color == null) return; + cellBackgrounds = new Color [validColumnCount]; + } + Color oldColor = cellBackgrounds [columnIndex]; + if (oldColor == color) return; + cellBackgrounds [columnIndex] = color; + if (oldColor != null && oldColor.equals (color)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + if (isInViewport ()) { + Rectangle bounds = getCellBounds (columnIndex); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } +} +/** + * Sets the checked state of the checkbox for this item. This state change + * only applies if the Table was created with the SWT.CHECK style. + * + * @param checked the new checked 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 setChecked (boolean value) { + checkWidget (); + if ((parent.getStyle () & SWT.CHECK) == 0) return; + if (checked == value) return; + checked = value; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + if (isInViewport ()) { + if (parent.hooks (SWT.EraseItem) || parent.hooks (SWT.PaintItem)) { + redrawItem (); + } else { + Rectangle bounds = getCheckboxBounds (); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } + } +} +/** + * 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> + * + * @since 3.0 + */ +public void setFont (Font font) { + checkWidget (); + if (font != null && font.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + Font oldFont = this.font; + if (oldFont == font) return; + this.font = font; + if (oldFont != null && oldFont.equals (font)) return; + + Rectangle bounds = getBounds (false); + int oldRightX = bounds.x + bounds.width; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + /* recompute cached values for string measurements */ + GC gc = new GC (parent); + gc.setFont (getFont (false)); + fontHeight = gc.getFontMetrics ().getHeight (); + computeDisplayTexts (gc); + computeTextWidths (gc); + gc.dispose (); + + /* horizontal bar could be affected if table has no columns */ + if (parent.columns.length == 0) { + bounds = getBounds (false); + int newRightX = bounds.x + bounds.width; + parent.updateHorizontalBar (newRightX, newRightX - oldRightX); + } + redrawItem (); +} +/** + * 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> + * + * @since 3.0 + */ +public void setFont (int columnIndex, Font font) { + checkWidget (); + if (font != null && font.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + if (cellFonts == null) { + if (font == null) return; + cellFonts = new Font [validColumnCount]; + } + Font oldFont = cellFonts [columnIndex]; + if (oldFont == font) return; + cellFonts [columnIndex] = font; + if (oldFont != null && oldFont.equals (font)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + /* recompute cached values for string measurements */ + GC gc = new GC (parent); + gc.setFont (getFont (columnIndex, false)); + if (fontHeights == null) fontHeights = new int [validColumnCount]; + fontHeights [columnIndex] = gc.getFontMetrics ().getHeight (); + computeDisplayText (columnIndex, gc); + gc.dispose (); + + if (isInViewport ()) { + Rectangle bounds = getCellBounds (columnIndex); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } +} +/** + * 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 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> + * + * @since 2.0 + */ +public void setForeground (Color color) { + checkWidget (); + if (color != null && color.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + Color oldColor = foreground; + if (oldColor == color) return; + foreground = color; + if (oldColor != null && oldColor.equals (color)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + redrawItem (); +} +/** + * 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> + * + * @since 3.0 + */ +public void setForeground (int columnIndex, Color color) { + checkWidget (); + if (color != null && color.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + if (cellForegrounds == null) { + if (color == null) return; + cellForegrounds = new Color [validColumnCount]; + } + Color oldColor = cellForegrounds [columnIndex]; + if (oldColor == color) return; + cellForegrounds [columnIndex] = color; + if (oldColor != null && oldColor.equals (color)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + if (isInViewport ()) { + redraw ( + getTextX (columnIndex), + parent.getItemY (this), + textWidths [columnIndex] + 2 * MARGIN_TEXT, + parent.itemHeight, + columnIndex); + } +} +/** + * Sets the grayed state of the checkbox for this item. This state change + * only applies if the Table 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 value) { + checkWidget (); + if ((parent.getStyle () & SWT.CHECK) == 0) return; + if (grayed == value) return; + grayed = value; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + if (isInViewport ()) { + Rectangle bounds = getCheckboxBounds (); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } +} +public void setImage (Image value) { + checkWidget (); + setImage (0, value); +} +/** + * Sets the image for multiple columns in the table. + * + * @param images 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); + + // TODO make a smarter implementation of this + for (int i = 0; i < value.length; i++) { + if (value [i] != null) setImage (i, value [i]); + } +} +/** + * 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 columnIndex, Image value) { + checkWidget (); + if (value != null && value.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + + TableColumn[] columns = parent.columns; + int validColumnCount = Math.max (1, columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + Image image = getImage (columnIndex, false); + if (value == image) return; + if (value != null && value.equals (image)) return; + if (columnIndex == 0) { + super.setImage (value); + } else { + images [columnIndex] = value; + } + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + /* + * An image width change may affect the space available for the item text, so + * recompute the displayText if there are columns. + */ + if (columns.length > 0) { + GC gc = new GC (parent); + gc.setFont (getFont (columnIndex, false)); + computeDisplayText (columnIndex, gc); + gc.dispose (); + } + + if (value == null) { + redrawItem (); // TODO why the whole item? + return; + } + + /* + * If this is the first image being put into the table then its item height + * may be adjusted, in which case a full redraw is needed. + */ + if (parent.imageHeight == 0) { + int oldItemHeight = parent.itemHeight; + parent.setImageHeight (value.getBounds ().height); + if (oldItemHeight != parent.itemHeight) { + if (columnIndex == 0) { + parent.col0ImageWidth = value.getBounds ().width; + if (columns.length > 0) { + /* + * All column 0 cells will now have less room available for their texts, + * so all items must now recompute their column 0 displayTexts. + */ + GC gc = new GC (parent); + TableItem[] rootItems = parent.items; + for (int i = 0; i < parent.itemsCount; i++) { + rootItems [i].updateColumnWidth (columns [0], gc); + } + gc.dispose (); + } + } + parent.redraw (); + return; + } + } + + /* + * If this is the first image being put into column 0 then all cells + * in the column should also indent accordingly. + */ + if (columnIndex == 0 && parent.col0ImageWidth == 0) { + parent.col0ImageWidth = value.getBounds ().width; + /* redraw the column */ + if (columns.length == 0) { + parent.redraw (); + } else { + /* + * All column 0 cells will now have less room available for their texts, + * so all items must now recompute their column 0 displayTexts. + */ + GC gc = new GC (parent); + TableItem[] rootItems = parent.items; + for (int i = 0; i < parent.itemsCount; i++) { + rootItems [i].updateColumnWidth (columns [0], gc); + } + gc.dispose (); + parent.redraw ( + columns [0].getX (), 0, + columns [0].width, + parent.clientArea.height, + false); + } + return; + } + redrawItem (); // TODO why the whole item? +} +/** + * Sets the indent of the first column's image, expressed in terms of the image's width. + * + * @param indent the new indent + * + * </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> + * + * @deprecated this functionality is not supported on most platforms + */ +public void setImageIndent (int indent) { + checkWidget(); + if (indent < 0) return; + if (imageIndent == indent) return; + imageIndent = indent; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; +} +/** + * Sets the receiver's text at a column + * + * @param index the column index + * @param string 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 columnIndex, String value) { + checkWidget (); + if (value == null) error (SWT.ERROR_NULL_ARGUMENT); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + if (value.equals (getText (columnIndex, false))) return; + if (columnIndex == 0) { + super.setText (value); + } else { + texts [columnIndex] = value; + } + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + int oldWidth = textWidths [columnIndex]; + GC gc = new GC (parent); + gc.setFont (getFont (columnIndex, false)); + computeDisplayText (columnIndex, gc); + gc.dispose (); + + if (parent.columns.length == 0) { + Rectangle bounds = getBounds (false); + int rightX = bounds.x + bounds.width; + parent.updateHorizontalBar (rightX, textWidths [columnIndex] - oldWidth); + } + if (isInViewport ()) { + redraw ( + getTextX (columnIndex), + parent.getItemY (this), + Math.max (oldWidth, textWidths [columnIndex]) + 2 * MARGIN_TEXT, + parent.itemHeight, + columnIndex); + } +} +public void setText (String value) { + checkWidget (); + Rectangle bounds = getBounds (false); + int oldRightX = bounds.x + bounds.width; + setText (0, value); + /* horizontal bar could be affected if table has no columns */ + if (parent.columns.length == 0) { + bounds = getBounds (false); + int newRightX = bounds.x + bounds.width; + parent.updateHorizontalBar (newRightX, newRightX - oldRightX); + } +} +/** + * Sets the text for multiple columns in the table. + * + * @param strings 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); + Rectangle bounds = getBounds (false); + int oldRightX = bounds.x + bounds.width; + // TODO make a smarter implementation of this + for (int i = 0; i < value.length; i++) { + if (value [i] != null) setText (i, value [i]); + } + /* horizontal bar could be affected if table has no columns */ + if (parent.columns.length == 0) { + bounds = getBounds (false); + int newRightX = bounds.x + bounds.width; + parent.updateHorizontalBar (newRightX, newRightX - oldRightX); + } +} +/* + * Perform any internal changes necessary to reflect a changed column width. + */ +void updateColumnWidth (TableColumn column, GC gc) { + int columnIndex = column.getIndex (); + gc.setFont (getFont (columnIndex, false)); + String oldDisplayText = displayTexts [columnIndex]; + computeDisplayText (columnIndex, gc); + + /* the cell must be damaged if there is custom drawing being done or if the alignment is not LEFT */ + if (isInViewport ()) { + boolean columnIsLeft = (column.style & SWT.LEFT) != 0; + if (!columnIsLeft || parent.hooks (SWT.EraseItem) || parent.hooks (SWT.PaintItem)) { + Rectangle cellBounds = getCellBounds (columnIndex); + parent.redraw (cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height, false); + return; + } + /* if the display text has changed then the cell text must be damaged in order to repaint */ + if (oldDisplayText == null || !oldDisplayText.equals (displayTexts [columnIndex])) { + Rectangle cellBounds = getCellBounds (columnIndex); + int textX = getTextX (columnIndex); + parent.redraw (textX, cellBounds.y, cellBounds.x + cellBounds.width - textX, cellBounds.height, false); + } + } +} +/* + * The parent's font has changed, so if this font was being used by the receiver then + * recompute its cached text sizes using the gc argument. + */ +void updateFont (GC gc) { + if (font == null) { /* receiver is using the Table's font */ + computeDisplayTexts (gc); + computeTextWidths (gc); + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/Tree.java b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/Tree.java new file mode 100644 index 0000000000..1e65f8c530 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/Tree.java @@ -0,0 +1,4295 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.*; + +/** + * 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 normally make sense to add <code>Control</code> children to + * it, or set a layout on it, unless implementing something like a cell + * editor. + * </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> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#tree">Tree, TreeItem, TreeColumn snippets</a> + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ +public class Tree extends Composite { + Canvas header; + TreeColumn[] columns = new TreeColumn [0]; + TreeColumn[] orderedColumns; + TreeItem[] items = NO_ITEMS; + TreeItem[] availableItems = NO_ITEMS; + TreeItem[] selectedItems = NO_ITEMS; + TreeItem focusItem, anchorItem, insertMarkItem; + TreeItem lastClickedItem; + Event lastSelectionEvent; + int availableItemsCount = 0; + boolean insertMarkPrecedes = false; + boolean linesVisible, ignoreKey, ignoreDispose, customHeightSet; + int topIndex = 0, horizontalOffset = 0; + int fontHeight = 0, imageHeight = 0, itemHeight = 0; + int headerImageHeight = 0, orderedCol0imageWidth = 0; + TreeColumn resizeColumn; + int resizeColumnX = -1; + int drawCount = 0; + boolean inExpand = false; /* for item creation within Expand callback */ + TreeColumn sortColumn; + int sortDirection = SWT.NONE; + + /* column header tooltip */ + Listener toolTipListener; + Shell toolTipShell; + Label toolTipLabel; + + Rectangle arrowBounds, expanderBounds, checkboxBounds, clientArea; + + static final TreeItem[] NO_ITEMS = new TreeItem [0]; + + static final int MARGIN_IMAGE = 3; + static final int MARGIN_CELL = 1; + static final int SIZE_HORIZONTALSCROLL = 5; + static final int TOLLERANCE_COLUMNRESIZE = 2; + static final int WIDTH_HEADER_SHADOW = 2; + static final int WIDTH_CELL_HIGHLIGHT = 1; + static final int [] toolTipEvents = new int[] {SWT.MouseExit, SWT.MouseHover, SWT.MouseMove, SWT.MouseDown}; + static final String ELLIPSIS = "..."; //$NON-NLS-1$ + static final String ID_EXPANDED = "EXPANDED"; //$NON-NLS-1$ + static final String ID_COLLAPSED = "COLLAPSED"; //$NON-NLS-1$ + static final String ID_UNCHECKED = "UNCHECKED"; //$NON-NLS-1$ + static final String ID_GRAYUNCHECKED = "GRAYUNCHECKED"; //$NON-NLS-1$ + static final String ID_CHECKMARK = "CHECKMARK"; //$NON-NLS-1$ + static final String ID_CONNECTOR_COLOR = "CONNECTOR_COLOR"; //$NON-NLS-1$ + static final String ID_ARROWUP = "ARROWUP"; //$NON-NLS-1$ + static final String ID_ARROWDOWN = "ARROWDOWN"; //$NON-NLS-1$ + +// TEMPORARY CODE +boolean hasFocus; + +public boolean isFocusControl() { + return hasFocus; +} + +/** + * 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#VIRTUAL + * @see SWT#NO_SCROLL + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public Tree (Composite parent, int style) { + super (parent, checkStyle (style)); + setForeground (null); /* set foreground and background to chosen default colors */ + setBackground (null); + GC gc = new GC (this); + fontHeight = gc.getFontMetrics ().getHeight (); + gc.dispose (); + itemHeight = fontHeight + (2 * getCellPadding ()); + initImages (display); + expanderBounds = getExpandedImage ().getBounds (); + checkboxBounds = getUncheckedImage ().getBounds (); + arrowBounds = getArrowDownImage ().getBounds (); + clientArea = getClientArea (); + + Listener listener = new Listener () { + public void handleEvent (Event event) { + handleEvents (event); + } + }; + addListener (SWT.Paint, listener); + addListener (SWT.MouseDown, listener); + addListener (SWT.MouseUp, listener); + addListener (SWT.MouseDoubleClick, listener); + addListener (SWT.Dispose, listener); + addListener (SWT.Resize, listener); + addListener (SWT.KeyDown, listener); + addListener (SWT.FocusOut, listener); + addListener (SWT.FocusIn, listener); + addListener (SWT.Traverse, listener); + + header = new Canvas (this, SWT.NO_REDRAW_RESIZE | SWT.NO_FOCUS); + header.setVisible (false); + header.setBounds (0, 0, 0, fontHeight + 2 * getHeaderPadding ()); + header.addListener (SWT.Paint, listener); + header.addListener (SWT.MouseDown, listener); + header.addListener (SWT.MouseUp, listener); + header.addListener (SWT.MouseHover, listener); + header.addListener (SWT.MouseDoubleClick, listener); + header.addListener (SWT.MouseMove, listener); + header.addListener (SWT.MouseExit, listener); + header.addListener (SWT.MenuDetect, listener); + + toolTipListener = new Listener () { + public void handleEvent (Event event) { + switch (event.type) { + case SWT.MouseHover: + case SWT.MouseMove: + if (headerUpdateToolTip (event.x)) break; + // FALL THROUGH + case SWT.MouseExit: + case SWT.MouseDown: + headerHideToolTip (); + break; + } + } + }; + + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setValues (0, 0, 1, 1, 1, 1); + hBar.setVisible (false); + hBar.addListener (SWT.Selection, listener); + } + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + vBar.setValues (0, 0, 1, 1, 1, 1); + vBar.setVisible (false); + vBar.addListener (SWT.Selection, listener); + } +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the user changes the receiver's selection, 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 the <code>SWT.CHECK</code> style 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 when the user changes the receiver's selection + * + * @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 (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Selection, typedListener); + addListener (SWT.DefaultSelection, typedListener); +} +/** + * 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 (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Expand, typedListener); + addListener (SWT.Collapse, typedListener); +} +boolean checkData (TreeItem item, boolean redraw) { + if (item.cached) return true; + if ((style & SWT.VIRTUAL) != 0) { + item.cached = true; + Event event = new Event (); + TreeItem parentItem = item.getParentItem (); + event.item = item; + event.index = parentItem == null ? indexOf (item) : parentItem.indexOf (item); + sendEvent (SWT.SetData, event); + if (isDisposed () || item.isDisposed ()) return false; + if (redraw) redrawItem (item.availableIndex, false); + } + return true; +} +static int checkStyle (int style) { + /* + * Feature in Windows. Even when WS_HSCROLL or + * WS_VSCROLL is not specified, Windows creates + * trees and tables with scroll bars. The fix + * is to set H_SCROLL and V_SCROLL. + * + * NOTE: This code appears on all platforms so that + * applications have consistent scroll bar behavior. + */ + if ((style & SWT.NO_SCROLL) == 0) { + style |= SWT.H_SCROLL | SWT.V_SCROLL; + } + style |= SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED; + //TEMPORARY CODE + style |= SWT.FULL_SELECTION; + return checkBits (style, SWT.SINGLE, SWT.MULTI, 0, 0, 0, 0); +} +protected void checkSubclass () { + if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS); +} +/** + * 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 all <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 + * + * @since 3.2 + */ +public void clear (int index, boolean recursive) { + checkWidget (); + if (!(0 <= index && index < items.length)) error (SWT.ERROR_INVALID_RANGE); + TreeItem item = items [index]; + + /* if there are no columns then the horizontal scrollbar may need adjusting */ + TreeItem[] availableDescendents = null; + int oldRightX = 0; + if (columns.length == 0) { + if (recursive) { + availableDescendents = item.computeAvailableDescendents (); + for (int i = 0; i < availableDescendents.length; i++) { + Rectangle bounds = availableDescendents [i].getBounds (false); + oldRightX = Math.max (oldRightX, bounds.x + bounds.width); + } + } else { + Rectangle bounds = item.getBounds (false); + oldRightX = bounds.x + bounds.width; + } + } + + /* clear the item(s) */ + item.clear (); + if (recursive) { + item.clearAll (true, false); + } + + /* adjust the horizontal scrollbar if needed */ + if (columns.length == 0) { + int newRightX = 0; + if (recursive) { + for (int i = 0; i < availableDescendents.length; i++) { + Rectangle bounds = availableDescendents [i].getBounds (false); + newRightX = Math.max (newRightX, bounds.x + bounds.width); + } + } else { + Rectangle bounds = item.getBounds (false); + newRightX = bounds.x + bounds.width; + } + updateHorizontalBar (newRightX, newRightX - oldRightX); + } + + /* redraw the item(s) */ + if (recursive && item.expanded) { + int descendentCount = availableDescendents == null ? + item.computeAvailableDescendentCount () : + availableDescendents.length; + redrawItems (item.availableIndex, item.availableIndex + descendentCount - 1, false); + } else { + redrawItem (item.availableIndex, false); + } +} +/** + * 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 all <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 + * + * @since 3.2 + */ +public void clearAll (boolean recursive) { + checkWidget (); + if (items.length == 0) return; + + /* if there are no columns then the horizontal scrollbar may need adjusting */ + int oldRightX = 0; + if (columns.length == 0 && !recursive) { + for (int i = 0; i < items.length; i++) { + Rectangle bounds = items [i].getBounds (false); + oldRightX = Math.max (oldRightX, bounds.x + bounds.width); + } + } + + /* clear the item(s) */ + for (int i = 0; i < items.length; i++) { + items [i].clear (); + if (recursive) items [i].clearAll (true, false); + } + + /* adjust the horizontal scrollbar if needed */ + if (columns.length == 0) { + if (recursive) { + updateHorizontalBar (); /* recompute from scratch */ + } else { + /* + * All cleared root items will have the same x and width values now, + * so just measure the first one as a sample. + */ + Rectangle bounds = items [0].getBounds (false); + int newRightX = bounds.x + bounds.width; + updateHorizontalBar (newRightX, newRightX - oldRightX); + } + } + + /* redraw the item(s) */ + if (recursive) { + redrawItems (0, availableItemsCount - 1, false); + } else { + for (int i = 0; i < items.length; i++) { + redrawItem (items [i].availableIndex, false); + } + } +} +/* + * Returns the ORDERED index of the column that the specified x falls within, + * or -1 if the x lies to the right of the last column. + */ +int computeColumnIntersect (int x, int startColumn) { + TreeColumn[] orderedColumns = getOrderedColumns (); + if (orderedColumns.length - 1 < startColumn) return -1; + int rightX = orderedColumns [startColumn].getX (); + for (int i = startColumn; i < orderedColumns.length; i++) { + rightX += orderedColumns [i].width; + if (x < rightX) return i; + } + return -1; +} +public Point computeSize (int wHint, int hHint, boolean changed) { + checkWidget (); + int width = 0, height = 0; + if (wHint != SWT.DEFAULT) { + width = wHint; + } else { + if (columns.length == 0) { + for (int i = 0; i < availableItemsCount; i++) { + Rectangle itemBounds = availableItems [i].getBounds (false); + width = Math.max (width, itemBounds.x + itemBounds.width); + } + } else { + TreeColumn[] orderedColumns = getOrderedColumns (); + TreeColumn lastColumn = orderedColumns [orderedColumns.length - 1]; + width = lastColumn.getX () + lastColumn.width; + } + } + if (hHint != SWT.DEFAULT) { + height = hHint; + } else { + height = getHeaderHeight () + availableItemsCount * itemHeight; + } + Rectangle result = computeTrim (0, 0, width, height); + return new Point (result.width, result.height); +} +void createItem (TreeColumn column, int index) { + TreeColumn[] newColumns = new TreeColumn [columns.length + 1]; + System.arraycopy (columns, 0, newColumns, 0, index); + newColumns [index] = column; + System.arraycopy (columns, index, newColumns, index + 1, columns.length - index); + columns = newColumns; + + if (orderedColumns != null) { + int insertIndex = 0; + if (index > 0) { + insertIndex = columns [index - 1].getOrderIndex () + 1; + } + TreeColumn[] newOrderedColumns = new TreeColumn [orderedColumns.length + 1]; + System.arraycopy (orderedColumns, 0, newOrderedColumns, 0, insertIndex); + newOrderedColumns [insertIndex] = column; + System.arraycopy ( + orderedColumns, + insertIndex, + newOrderedColumns, + insertIndex + 1, + orderedColumns.length - insertIndex); + orderedColumns = newOrderedColumns; + } + + if (columns.length == 1) { + column.itemImageWidth = orderedCol0imageWidth; + } else { + if (column.getOrderIndex () == 0) orderedCol0imageWidth = 0; + } + + /* allow all items to update their internal structures accordingly */ + for (int i = 0; i < items.length; i++) { + items [i].addColumn (column); + } + + /* existing items become hidden when going from 0 to 1 column (0 width) */ + if (columns.length == 1 && availableItemsCount > 0) { + redrawFromItemDownwards (topIndex); + } else { + /* checkboxes become hidden when creating a column with index == ordered index == 0 (0 width) */ + if (availableItemsCount > 0 && (style & SWT.CHECK) != 0 && index == 0 && column.getOrderIndex () == 0) { + redrawFromItemDownwards (topIndex); + } + } +} +void createItem (TreeItem item, int index) { + TreeItem[] newItems = new TreeItem [items.length + 1]; + System.arraycopy (items, 0, newItems, 0, index); + newItems [index] = item; + System.arraycopy (items, index, newItems, index + 1, items.length - index); + items = newItems; + + /* determine the item's availability index */ + int startIndex; + if (index == items.length - 1) { + startIndex = availableItemsCount; /* last item */ + } else { + startIndex = items [index + 1].availableIndex; + } + + if (availableItemsCount == availableItems.length) { + int grow = drawCount <= 0 ? 4 : Math.max (4, availableItems.length * 3 / 2); + TreeItem[] newAvailableItems = new TreeItem [availableItems.length + grow]; + System.arraycopy (availableItems, 0, newAvailableItems, 0, availableItems.length); + availableItems = newAvailableItems; + } + if (startIndex != availableItemsCount) { + /* new item is not at end of list, so shift other items right to create space for it */ + System.arraycopy ( + availableItems, + startIndex, + availableItems, + startIndex + 1, + availableItemsCount - startIndex); + } + availableItems [startIndex] = item; + availableItemsCount++; + + /* update the availableIndex for items bumped down by this new item */ + for (int i = startIndex; i < availableItemsCount; i++) { + availableItems [i].availableIndex = i; + } + + /* update scrollbars */ + updateVerticalBar (); + Rectangle bounds = item.getBounds (false); + int rightX = bounds.x + bounds.width; + updateHorizontalBar (rightX, rightX); + /* + * If new item is above viewport then adjust topIndex and the vertical + * scrollbar so that the current viewport items will not change. + */ + if (item.availableIndex < topIndex) { + topIndex++; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + return; + } + /* + * If this is the first item and the receiver has focus then its boundary + * focus ring must be removed. + */ + if (availableItemsCount == 1 && isFocusControl ()) { + focusItem = item; + redraw (); + return; + } + int redrawIndex = index; + if (redrawIndex > 0 && item.isLastChild ()) redrawIndex--; + redrawFromItemDownwards (items [redrawIndex].availableIndex); +} +/** + * 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 3.4 + */ +public void deselect (TreeItem item) { + checkWidget (); + if (item == null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + deselectItem (item); + redrawItem (item.availableIndex, true); +} +/** + * 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 (); + TreeItem[] oldSelection = selectedItems; + selectedItems = NO_ITEMS; + for (int i = 0; i < oldSelection.length; i++) { + redrawItem (oldSelection [i].availableIndex, true); + } +} +void deselectItem (TreeItem item) { + int index = getSelectionIndex (item); + if (index == -1) return; + TreeItem[] newSelectedItems = new TreeItem [selectedItems.length - 1]; + System.arraycopy (selectedItems, 0, newSelectedItems, 0, index); + System.arraycopy ( + selectedItems, + index + 1, + newSelectedItems, + index, + newSelectedItems.length - index); + selectedItems = newSelectedItems; +} +void destroyItem (TreeColumn column) { + headerHideToolTip (); + int index = column.getIndex (); + int orderedIndex = column.getOrderIndex (); + + TreeColumn[] newColumns = new TreeColumn [columns.length - 1]; + System.arraycopy (columns, 0, newColumns, 0, index); + System.arraycopy (columns, index + 1, newColumns, index, newColumns.length - index); + columns = newColumns; + + if (orderedColumns != null) { + if (columns.length < 2) { + orderedColumns = null; + } else { + int removeIndex = column.getOrderIndex (); + TreeColumn[] newOrderedColumns = new TreeColumn [orderedColumns.length - 1]; + System.arraycopy (orderedColumns, 0, newOrderedColumns, 0, removeIndex); + System.arraycopy ( + orderedColumns, + removeIndex + 1, + newOrderedColumns, + removeIndex, + newOrderedColumns.length - removeIndex); + orderedColumns = newOrderedColumns; + } + } + + if (orderedIndex == 0 && columns.length > 0) { + orderedCol0imageWidth = columns [getColumnOrder ()[0]].itemImageWidth; + } + + /* allow all items to update their internal structures accordingly */ + for (int i = 0; i < items.length; i++) { + items [i].removeColumn (column, index, orderedIndex); + } + + /* update horizontal scrollbar */ + int lastColumnIndex = columns.length - 1; + if (lastColumnIndex < 0) { /* no more columns */ + updateHorizontalBar (); + } else { + int newWidth = 0; + for (int i = 0; i < columns.length; i++) { + newWidth += columns [i].width; + } + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setMaximum (newWidth); + hBar.setVisible (clientArea.width < newWidth); + int selection = hBar.getSelection (); + if (selection != horizontalOffset) { + horizontalOffset = selection; + redraw (); + if (header.isVisible () && drawCount <= 0) header.redraw (); + } + } + } + TreeColumn[] orderedColumns = getOrderedColumns (); + for (int i = orderedIndex; i < orderedColumns.length; i++) { + if (!orderedColumns [i].isDisposed ()) { + orderedColumns [i].sendEvent (SWT.Move); + } + } + + if (sortColumn == column) { + sortColumn = null; + } +} +/* + * Allows the Tree to update internal structures it has that may contain the + * item being destroyed. The argument is not necessarily a root-level item. + */ +void destroyItem (TreeItem item) { + if (item == focusItem) reassignFocus (); + + /* availableItems array */ + int availableIndex = item.availableIndex; + if (availableIndex != -1) { + Rectangle bounds = item.getBounds (false); + int rightX = bounds.x + bounds.width; + + if (availableIndex != availableItemsCount - 1) { + /* item is not at end of available items list, so must shift items left to reclaim its slot */ + System.arraycopy ( + availableItems, + availableIndex + 1, + availableItems, + availableIndex, + availableItemsCount - availableIndex - 1); + availableItems [availableItemsCount - 1] = null; + } else { + availableItems [availableIndex] = null; /* last item, so no array copy needed */ + } + availableItemsCount--; + + if (drawCount <= 0 && availableItems.length - availableItemsCount == 4) { + /* shrink the available items array */ + TreeItem[] newAvailableItems = new TreeItem [availableItemsCount]; + System.arraycopy (availableItems, 0, newAvailableItems, 0, newAvailableItems.length); + availableItems = newAvailableItems; + } + + /* update the availableIndex on affected items */ + for (int i = availableIndex; i < availableItemsCount; i++) { + availableItems [i].availableIndex = i; + } + item.availableIndex = -1; + int oldTopIndex = topIndex; + updateVerticalBar (); + updateHorizontalBar (0, -rightX); + /* + * If destroyed item is above viewport then adjust topIndex and the vertical + * scrollbar so that the current viewport items will not change. + */ + if (availableIndex < topIndex) { + topIndex = oldTopIndex - 1; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + } + } + /* selectedItems array */ + if (item.isSelected ()) { + int selectionIndex = getSelectionIndex (item); + TreeItem[] newSelectedItems = new TreeItem [selectedItems.length - 1]; + System.arraycopy (selectedItems, 0, newSelectedItems, 0, selectionIndex); + System.arraycopy ( + selectedItems, + selectionIndex + 1, + newSelectedItems, + selectionIndex, + newSelectedItems.length - selectionIndex); + selectedItems = newSelectedItems; + } + /* root-level items array */ + if (item.depth == 0) { + int index = item.getIndex (); + TreeItem[] newItems = new TreeItem [items.length - 1]; + System.arraycopy (items, 0, newItems, 0, index); + System.arraycopy (items, index + 1, newItems, index, newItems.length - index); + items = newItems; + } + if (item == anchorItem) anchorItem = null; + if (item == insertMarkItem) insertMarkItem = null; + if (item == lastClickedItem) lastClickedItem = null; + /* + * If this was the last item and the receiver has focus then its boundary + * focus ring must be redrawn. + */ + if (availableItemsCount == 0 && isFocusControl ()) { + redraw (); + return; + } +} +Image getArrowDownImage () { + return (Image) display.getData (ID_ARROWDOWN); +} +Image getArrowUpImage () { + return (Image) display.getData (ID_ARROWUP); +} +int getCellPadding () { + return MARGIN_CELL + WIDTH_CELL_HIGHLIGHT; +} +Image getCheckmarkImage () { + return (Image) display.getData (ID_CHECKMARK); +} +public Control[] getChildren () { + checkWidget (); + Control[] controls = _getChildren (); + if (header == null) return controls; + Control[] result = new Control [controls.length - 1]; + /* remove the Header from the returned set of children */ + int index = 0; + for (int i = 0; i < controls.length; i++) { + if (controls [i] != header) { + result [index++] = controls [i]; + } + } + return result; +} +Image getCollapsedImage () { + return (Image) display.getData (ID_COLLAPSED); +} +/** + * 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 + * + * @since 3.1 + */ +public TreeColumn getColumn (int index) { + checkWidget (); + if (!(0 <= index && index < columns.length)) error (SWT.ERROR_INVALID_RANGE); + return columns [index]; +} +/** + * 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> + * + * @since 3.1 + */ +public int getColumnCount () { + checkWidget (); + return columns.length; +} +/** + * 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 + * + * @since 3.2 + */ +public int[] getColumnOrder () { + checkWidget (); + int[] result = new int [columns.length]; + if (orderedColumns != null) { + for (int i = 0; i < result.length; i++) { + result [i] = orderedColumns [i].getIndex (); + } + } else { + for (int i = 0; i < columns.length; i++) { + result [i] = i; + } + } + return result; +} +/** + * 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 + * + * @since 3.1 + */ +public TreeColumn[] getColumns () { + checkWidget (); + TreeColumn[] result = new TreeColumn [columns.length]; + System.arraycopy (columns, 0, result, 0, columns.length); + return result; +} +Color getConnectorColor () { + return (Color) display.getData (ID_CONNECTOR_COLOR); +} +Image getExpandedImage () { + return (Image) display.getData (ID_EXPANDED); +} +Image getGrayUncheckedImage () { + return (Image) display.getData (ID_GRAYUNCHECKED); +} +/** + * 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 3.1 + */ +public int getGridLineWidth () { + checkWidget (); + return 1; +} +/** + * 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> + * + * @since 3.1 + */ +public int getHeaderHeight () { + checkWidget (); + if (!header.getVisible ()) return 0; + return header.getSize ().y; +} +int getHeaderPadding () { + return MARGIN_CELL + WIDTH_HEADER_SHADOW; +} +/** + * 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> + * + * @since 3.1 + */ +public boolean getHeaderVisible () { + checkWidget (); + return header.getVisible (); +} +/** + * 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); + int index = (point.y - getHeaderHeight ()) / itemHeight + topIndex; + if (!(0 <= index && index < availableItemsCount)) return null; /* below the last item */ + TreeItem result = availableItems [index]; + if (!result.getHitBounds ().contains (point)) return null; /* considers the x value */ + 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> + * + * @since 3.1 + */ +public TreeItem getItem (int index) { + checkWidget (); + if (!(0 <= index && index < items.length)) error (SWT.ERROR_INVALID_RANGE); + return items [index]; +} +/** + * 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 items.length; +} +/** + * 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> + */ +public int getItemHeight () { + checkWidget (); + return itemHeight; +} +/** + * 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 [items.length]; + System.arraycopy (items, 0, result, 0, items.length); + return result; +} +/* + * Returns the current y-coordinate that the specified item should have. + */ +int getItemY (TreeItem item) { + int index = item.availableIndex; + if (index == -1) return -1; + return (index - topIndex) * itemHeight + getHeaderHeight (); +} +/** + * Returns <code>true</code> if the receiver's lines are visible, + * and <code>false</code> otherwise. Note that some platforms draw + * grid lines while others may draw alternating row colors. + * <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> + * + * @since 3.1 + */ +public boolean getLinesVisible () { + checkWidget (); + return linesVisible; +} +TreeColumn[] getOrderedColumns () { + if (orderedColumns != null) return orderedColumns; + return columns; +} +/** + * 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; +} +/** + * 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 (); + int count = selectedItems.length; + TreeItem[] result = new TreeItem [count]; + if (count > 0) { + if (count == 1) { + System.arraycopy (selectedItems, 0, result, 0, count); + } else { + getSelection (result, items, 0); + } + } + return result; +} +int getSelection (TreeItem[] result, TreeItem[] items, int index) { + for (int i = 0; i < items.length; i++) { + TreeItem item = items [i]; + if (item.isSelected ()) result [index++] = item; + if (index == result.length) break; + index = getSelection (result, items [i].items, index); + if (index == result.length) break; + } + return index; +} +/** + * 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 selectedItems.length; +} +/* + * Returns the index of the argument in the receiver's array of currently- + * selected items, or -1 if the item is not currently selected. + */ +int getSelectionIndex (TreeItem item) { + for (int i = 0; i < selectedItems.length; i++) { + if (selectedItems [i] == item) return i; + } + return -1; +} +/** + * 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) + * + * @since 3.2 + */ +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) + * + * @since 3.2 + */ +public int getSortDirection () { + checkWidget (); + return sortDirection; +} +/** + * 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 2.1 + */ +public TreeItem getTopItem () { + checkWidget (); + if (availableItemsCount == 0) return null; + return availableItems [topIndex]; +} +Image getUncheckedImage () { + return (Image) display.getData (ID_UNCHECKED); +} +void handleEvents (Event event) { + switch (event.type) { + case SWT.Paint: + if (event.widget == header) { + headerOnPaint (event); + } else { + onPaint (event); + } + break; + case SWT.MenuDetect: { + notifyListeners (SWT.MenuDetect, event); + break; + } + case SWT.MouseDown: + if (event.widget == header) { + headerOnMouseDown (event); + } else { + onMouseDown (event); + } + break; + case SWT.MouseUp: + if (event.widget == header) { + headerOnMouseUp (event); + } else { + onMouseUp (event); + } + break; + case SWT.MouseHover: + headerOnMouseHover (event); break; + case SWT.MouseMove: + headerOnMouseMove (event); break; + case SWT.MouseDoubleClick: + if (event.widget == header) { + headerOnMouseDoubleClick (event); + } else { + onMouseDoubleClick (event); + } + break; + case SWT.MouseExit: + headerOnMouseExit (); break; + case SWT.Dispose: + onDispose (event); break; + case SWT.KeyDown: + onKeyDown (event); break; + case SWT.Resize: + onResize (event); break; + case SWT.Selection: + if (event.widget == getHorizontalBar ()) { + onScrollHorizontal (event); + } + if (event.widget == getVerticalBar ()) { + onScrollVertical (event); + } + break; + case SWT.FocusOut: + onFocusOut (); break; + case SWT.FocusIn: + onFocusIn (); break; + case SWT.Traverse: + switch (event.detail) { + case SWT.TRAVERSE_ESCAPE: + case SWT.TRAVERSE_RETURN: + case SWT.TRAVERSE_TAB_NEXT: + case SWT.TRAVERSE_TAB_PREVIOUS: + case SWT.TRAVERSE_PAGE_NEXT: + case SWT.TRAVERSE_PAGE_PREVIOUS: + event.doit = true; + break; + } + break; + } +} +String headerGetToolTip (int x) { + if (resizeColumn != null) return null; + int orderedIndex = computeColumnIntersect (x, 0); + if (orderedIndex == -1) return null; + TreeColumn[] orderedColumns = getOrderedColumns (); + TreeColumn column = orderedColumns [orderedIndex]; + if (column.toolTipText == null) return null; + + /* no tooltip should appear if the hover is at a column resize opportunity */ + int columnX = column.getX (); + if (orderedIndex > 0 && orderedColumns [orderedIndex - 1].resizable) { + /* left column bound is resizable */ + if (x - columnX <= TOLLERANCE_COLUMNRESIZE) return null; + } + if (column.resizable) { + /* right column bound is resizable */ + int columnRightX = columnX + column.width; + if (columnRightX - x <= TOLLERANCE_COLUMNRESIZE) return null; + } + return removeMnemonics (column.toolTipText); +} +void headerHideToolTip() { + if (toolTipShell == null) return; + for (int i = 0; i < toolTipEvents.length; i++) { + header.removeListener (toolTipEvents [i], toolTipListener); + } + toolTipShell.dispose (); + toolTipShell = null; + toolTipLabel = null; +} +void headerOnMouseDoubleClick (Event event) { + if (!isFocusControl ()) setFocus (); + if (columns.length == 0) return; + TreeColumn[] orderedColumns = getOrderedColumns (); + int x = -horizontalOffset; + for (int i = 0; i < orderedColumns.length; i++) { + TreeColumn column = orderedColumns [i]; + x += column.width; + if (event.x < x) { + /* found the clicked column */ + TreeColumn packColumn = null; + if (x - event.x <= TOLLERANCE_COLUMNRESIZE) { + /* clicked on column bound for this column */ + packColumn = column; + } else { + if (i > 0 && event.x - column.getX () <= TOLLERANCE_COLUMNRESIZE) { + /* clicked on column bound that applies to previous column */ + packColumn = orderedColumns [i - 1]; + } + } + if (packColumn != null) { + packColumn.pack (); + resizeColumn = null; + if (Math.abs (packColumn.getX () + packColumn.width - event.x) > TOLLERANCE_COLUMNRESIZE) { + /* column separator has relocated away from pointer location */ + setCursor (null); + } + return; + } + /* did not click on column separator, so just fire column event */ + Event newEvent = new Event (); + newEvent.widget = column; + column.postEvent (SWT.DefaultSelection, newEvent); + return; + } + } +} +void headerOnMouseDown (Event event) { + if (event.button != 1) return; + TreeColumn[] orderedColumns = getOrderedColumns (); + int x = -horizontalOffset; + for (int i = 0; i < orderedColumns.length; i++) { + TreeColumn column = orderedColumns [i]; + x += column.width; + /* if close to a resizable column separator line then begin column resize */ + if (column.resizable && Math.abs (x - event.x) <= TOLLERANCE_COLUMNRESIZE) { + resizeColumn = column; + resizeColumnX = x; + return; + } + /* + * If within column but not near separator line then start column drag + * if column is moveable, or just fire column Selection otherwise. + */ + if (event.x < x) { + if (column.moveable) { + /* open tracker on the dragged column's header cell */ + int columnX = column.getX (); + int pointerOffset = event.x - columnX; + headerHideToolTip (); + Tracker tracker = new Tracker (this, SWT.NONE); + tracker.setRectangles (new Rectangle[] { + new Rectangle (columnX, 0, column.width, getHeaderHeight ()) + }); + if (!tracker.open ()) return; /* cancelled */ + /* determine which column was dragged onto */ + Rectangle result = tracker.getRectangles () [0]; + int pointerX = result.x + pointerOffset; + if (pointerX < 0) return; /* dragged too far left */ + x = -horizontalOffset; + for (int destIndex = 0; destIndex < orderedColumns.length; destIndex++) { + TreeColumn destColumn = orderedColumns [destIndex]; + x += destColumn.width; + if (pointerX < x) { + int oldIndex = column.getOrderIndex (); + if (destIndex == oldIndex) { /* dragged onto self */ + Event newEvent = new Event (); + newEvent.widget = column; + column.postEvent (SWT.Selection, newEvent); + return; + } + int leftmostIndex = Math.min (destIndex, oldIndex); + int[] oldOrder = getColumnOrder (); + int[] newOrder = new int [oldOrder.length]; + System.arraycopy (oldOrder, 0, newOrder, 0, leftmostIndex); + if (leftmostIndex == oldIndex) { + /* column moving to the right */ + System.arraycopy (oldOrder, oldIndex + 1, newOrder, oldIndex, destIndex - oldIndex); + } else { + /* column moving to the left */ + System.arraycopy (oldOrder, destIndex, newOrder, destIndex + 1, oldIndex - destIndex); + } + newOrder [destIndex] = oldOrder [oldIndex]; + int rightmostIndex = Math.max (destIndex, oldIndex); + System.arraycopy ( + oldOrder, + rightmostIndex + 1, + newOrder, + rightmostIndex + 1, + newOrder.length - rightmostIndex - 1); + setColumnOrder (newOrder); + return; + } + } + return; /* dragged too far right */ + } + /* column is not moveable */ + Event newEvent = new Event (); + newEvent.widget = column; + column.postEvent (SWT.Selection, newEvent); + return; + } + } +} +void headerOnMouseExit () { + if (resizeColumn != null) return; + setCursor (null); /* ensure that a column resize cursor does not escape */ +} +void headerOnMouseHover (Event event) { + headerShowToolTip (event.x); +} +void headerOnMouseMove (Event event) { + if (resizeColumn == null) { + /* not currently resizing a column */ + for (int i = 0; i < columns.length; i++) { + TreeColumn column = columns [i]; + int x = column.getX () + column.width; + if (Math.abs (x - event.x) <= TOLLERANCE_COLUMNRESIZE) { + if (column.resizable) { + setCursor (display.getSystemCursor (SWT.CURSOR_SIZEWE)); + } else { + setCursor (null); + } + return; + } + } + setCursor (null); + return; + } + + /* currently resizing a column */ + + /* don't allow the resize x to move left of the column's x position */ + if (event.x <= resizeColumn.getX ()) return; + + /* redraw the resizing line at its new location */ + GC gc = new GC (this); + gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK)); + int lineHeight = clientArea.height; + redraw (resizeColumnX - 1, 0, 1, lineHeight, false); + resizeColumnX = event.x; + gc.drawLine (resizeColumnX - 1, 0, resizeColumnX - 1, lineHeight); + gc.dispose (); +} +void headerOnMouseUp (Event event) { + if (resizeColumn == null) return; /* not resizing a column */ + + /* remove the resize line */ + GC gc = new GC (this); + redraw (resizeColumnX - 1, 0, 1, clientArea.height, false); + gc.dispose (); + + int newWidth = resizeColumnX - resizeColumn.getX (); + if (newWidth != resizeColumn.width) { + setCursor (null); + updateColumnWidth (resizeColumn, newWidth); + } + resizeColumnX = -1; + resizeColumn = null; +} +void headerOnPaint (Event event) { + TreeColumn[] orderedColumns = getOrderedColumns (); + int numColumns = orderedColumns.length; + GC gc = event.gc; + Rectangle clipping = gc.getClipping (); + int startColumn = -1, endColumn = -1; + if (numColumns > 0) { + startColumn = computeColumnIntersect (clipping.x, 0); + if (startColumn != -1) { /* the clip x is within a column's bounds */ + endColumn = computeColumnIntersect (clipping.x + clipping.width, startColumn); + if (endColumn == -1) endColumn = numColumns - 1; + } + } else { + startColumn = endColumn = 0; + } + + /* paint the column header shadow that spans the full header width */ + Point headerSize = header.getSize (); + headerPaintHShadows (gc, 0, 0, headerSize.x, headerSize.y); + + /* if all damage is to the right of the last column then finished */ + if (startColumn == -1) return; + + /* paint each of the column headers */ + if (numColumns == 0) return; /* no headers to paint */ + for (int i = startColumn; i <= endColumn; i++) { + headerPaintVShadows (gc, orderedColumns [i].getX (), 0, orderedColumns [i].width, headerSize.y); + orderedColumns [i].paint (gc); + } +} +void headerPaintHShadows (GC gc, int x, int y, int width, int height) { + gc.setClipping (x, y, width, height); + int endX = x + width; + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW)); + gc.drawLine (x, y, endX, y); /* highlight shadow */ + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_NORMAL_SHADOW)); + gc.drawLine (x, height - 2, endX, height - 2); /* lowlight shadow */ + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_DARK_SHADOW)); + gc.drawLine (x, height - 1, endX, height - 1); /* outer shadow */ +} +void headerPaintVShadows (GC gc, int x, int y, int width, int height) { + gc.setClipping (x, y, width, height); + int endX = x + width; + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW)); + gc.drawLine (x, y, x, y + height - 1); /* highlight shadow */ + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_NORMAL_SHADOW)); + gc.drawLine (endX - 2, y + 1, endX - 2, height - 2); /* light inner shadow */ + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_DARK_SHADOW)); + gc.drawLine (endX - 1, y, endX - 1, height - 1); /* dark outer shadow */ +} +void headerShowToolTip (int x) { + String tooltip = headerGetToolTip (x); + if (tooltip == null || tooltip.length () == 0) return; + + if (toolTipShell == null) { + toolTipShell = new Shell (getShell (), SWT.ON_TOP | SWT.TOOL); + toolTipLabel = new Label (toolTipShell, SWT.CENTER); + Display display = toolTipShell.getDisplay (); + toolTipLabel.setForeground (display.getSystemColor (SWT.COLOR_INFO_FOREGROUND)); + toolTipLabel.setBackground (display.getSystemColor (SWT.COLOR_INFO_BACKGROUND)); + for (int i = 0; i < toolTipEvents.length; i++) { + header.addListener (toolTipEvents [i], toolTipListener); + } + } + if (headerUpdateToolTip (x)) { + toolTipShell.setVisible (true); + } else { + headerHideToolTip (); + } +} +boolean headerUpdateToolTip (int x) { + String tooltip = headerGetToolTip (x); + if (tooltip == null || tooltip.length () == 0) return false; + if (tooltip.equals (toolTipLabel.getText ())) return true; + + toolTipLabel.setText (tooltip); + TreeColumn column = getOrderedColumns () [computeColumnIntersect (x, 0)]; + toolTipShell.setData (new Integer (column.getIndex ())); + Point labelSize = toolTipLabel.computeSize (SWT.DEFAULT, SWT.DEFAULT, true); + labelSize.x += 2; labelSize.y += 2; + toolTipLabel.setSize (labelSize); + toolTipShell.pack (); + /* + * On some platforms, there is a minimum size for a shell + * which may be greater than the label size. + * To avoid having the background of the tip shell showing + * around the label, force the label to fill the entire client area. + */ + Rectangle area = toolTipShell.getClientArea (); + toolTipLabel.setSize (area.width, area.height); + + /* Position the tooltip and ensure it's not located off the screen */ + Point cursorLocation = getDisplay ().getCursorLocation (); + int cursorHeight = 21; /* assuming cursor is 21x21 */ + Point size = toolTipShell.getSize (); + Rectangle rect = getMonitor ().getBounds (); + Point pt = new Point (cursorLocation.x, cursorLocation.y + cursorHeight + 2); + pt.x = Math.max (pt.x, rect.x); + if (pt.x + size.x > rect.x + rect.width) pt.x = rect.x + rect.width - size.x; + if (pt.y + size.y > rect.y + rect.height) pt.y = cursorLocation.y - 2 - size.y; + toolTipShell.setLocation (pt); + return true; +} +/** + * 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> + * + * @since 3.1 + */ +public int indexOf (TreeColumn column) { + checkWidget (); + if (column == null) error (SWT.ERROR_NULL_ARGUMENT); + if (column.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + return column.getIndex (); +} +/** + * 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 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 3.1 + */ +public int indexOf (TreeItem item) { + checkWidget (); + if (item == null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + if (item.parentItem != null || item.parent != this) return -1; + return item.getIndex (); +} +static void initImages (final Display display) { + PaletteData fourBit = new PaletteData (new RGB[] { + new RGB (0, 0, 0), new RGB (128, 0, 0), new RGB (0, 128, 0), new RGB (128, 128, 0), + new RGB (0, 0, 128), new RGB (128, 0, 128), new RGB (0, 128, 128), new RGB (128, 128, 128), + new RGB (192, 192, 192), new RGB (255, 0, 0), new RGB (0, 255, 0), new RGB (255, 255, 0), + new RGB (0, 0, 255), new RGB (255, 0, 255), new RGB (0, 255, 255), new RGB (255, 255, 255)}); + + if (display.getData (ID_EXPANDED) == null) { + ImageData expanded = new ImageData ( + 9, 9, 4, /* width, height, depth */ + fourBit, 4, + new byte[] { + 119, 119, 119, 119, 112, 0, 0, 0, + 127, -1, -1, -1, 112, 0, 0, 0, + 127, -1, -1, -1, 112, 0, 0, 0, + 127, -1, -1, -1, 112, 0, 0, 0, + 127, 0, 0, 15, 112, 0, 0, 0, + 127, -1, -1, -1, 112, 0, 0, 0, + 127, -1, -1, -1, 112, 0, 0, 0, + 127, -1, -1, -1, 112, 0, 0, 0, + 119, 119, 119, 119, 112, 0, 0, 0}); + expanded.transparentPixel = 15; /* use white for transparency */ + display.setData (ID_EXPANDED, new Image (display, expanded)); + } + + if (display.getData (ID_COLLAPSED) == null) { + ImageData collapsed = new ImageData ( + 9, 9, 4, /* width, height, depth */ + fourBit, 4, + new byte[] { + 119, 119, 119, 119, 112, 0, 0, 0, + 127, -1, -1, -1, 112, 0, 0, 0, + 127, -1, 15, -1, 112, 0, 0, 0, + 127, -1, 15, -1, 112, 0, 0, 0, + 127, 0, 0, 15, 112, 0, 0, 0, + 127, -1, 15, -1, 112, 0, 0, 0, + 127, -1, 15, -1, 112, 0, 0, 0, + 127, -1, -1, -1, 112, 0, 0, 0, + 119, 119, 119, 119, 112, 0, 0, 0}); + collapsed.transparentPixel = 15; /* use white for transparency */ + display.setData (ID_COLLAPSED, new Image (display, collapsed)); + } + + PaletteData arrowPalette = new PaletteData (new RGB[] { + new RGB (0, 0, 0), new RGB (255, 255, 255)}); + if (display.getData (ID_ARROWDOWN) == null) { + ImageData arrowDown = new ImageData ( + 7, 4, 1, + arrowPalette, 1, + new byte[] {0x00, (byte)0x83, (byte)0xC7, (byte)0xEF}); + arrowDown.transparentPixel = 0x1; /* use white for transparency */ + display.setData (ID_ARROWDOWN, new Image (display, arrowDown)); + } + if (display.getData (ID_ARROWUP) == null) { + ImageData arrowUp = new ImageData ( + 7, 4, 1, + arrowPalette, 1, + new byte[] {(byte)0xEF, (byte)0xC7, (byte)0x83, 0x00}); + arrowUp.transparentPixel = 0x1; /* use white for transparency */ + display.setData (ID_ARROWUP, new Image (display, arrowUp)); + } + + PaletteData checkMarkPalette = new PaletteData ( + new RGB[] {new RGB (0, 0, 0), new RGB (252, 3, 251)}); + byte[] checkbox = new byte[] {0, 0, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 127, -64, 0, 0}; + ImageData checkmark = new ImageData (7, 7, 1, checkMarkPalette, 1, new byte[] {-4, -8, 112, 34, 6, -114, -34}); + checkmark.transparentPixel = 1; + if (display.getData (ID_CHECKMARK) == null) { + display.setData (ID_CHECKMARK, new Image (display, checkmark)); + } + + if (display.getData (ID_UNCHECKED) == null) { + PaletteData uncheckedPalette = new PaletteData ( + new RGB[] {new RGB (128, 128, 128), new RGB (255, 255, 255)}); + ImageData unchecked = new ImageData (11, 11, 1, uncheckedPalette, 2, checkbox); + display.setData (ID_UNCHECKED, new Image (display, unchecked)); + } + + if (display.getData (ID_GRAYUNCHECKED) == null) { + PaletteData grayUncheckedPalette = new PaletteData ( + new RGB[] {new RGB (128, 128, 128), new RGB (192, 192, 192)}); + ImageData grayUnchecked = new ImageData (11, 11, 1, grayUncheckedPalette, 2, checkbox); + display.setData (ID_GRAYUNCHECKED, new Image (display, grayUnchecked)); + } + + if (display.getData (ID_CONNECTOR_COLOR) == null) { + display.setData (ID_CONNECTOR_COLOR, new Color (display, 170, 170, 170)); + } + + display.disposeExec (new Runnable () { + public void run() { + Image expanded = (Image) display.getData (ID_EXPANDED); + if (expanded != null) expanded.dispose (); + Image collapsed = (Image) display.getData (ID_COLLAPSED); + if (collapsed != null) collapsed.dispose (); + Color connectorColor = (Color) display.getData (ID_CONNECTOR_COLOR); + if (connectorColor != null) connectorColor.dispose (); + Image unchecked = (Image) display.getData (ID_UNCHECKED); + if (unchecked != null) unchecked.dispose (); + Image grayUnchecked = (Image) display.getData (ID_GRAYUNCHECKED); + if (grayUnchecked != null) grayUnchecked.dispose (); + Image checkmark = (Image) display.getData (ID_CHECKMARK); + if (checkmark != null) checkmark.dispose (); + Image arrowDown = (Image) display.getData (ID_ARROWDOWN); + if (arrowDown != null) arrowDown.dispose (); + Image arrowUp = (Image) display.getData (ID_ARROWUP); + if (arrowUp != null) arrowUp.dispose (); + + display.setData (ID_EXPANDED, null); + display.setData (ID_COLLAPSED, null); + display.setData (ID_CONNECTOR_COLOR, null); + display.setData (ID_UNCHECKED, null); + display.setData (ID_GRAYUNCHECKED, null); + display.setData (ID_CHECKMARK, null); + display.setData (ID_ARROWDOWN, null); + display.setData (ID_ARROWUP, null); + } + }); +} +/* + * Important: Assumes that item just became available (ie.- was either created + * or the parent item was expanded) and the parent is available. + */ +void makeAvailable (TreeItem item) { + int parentItemCount = item.parentItem.items.length; + int index = 0; + if (parentItemCount == 1) { /* this is the only child of parentItem */ + index = item.parentItem.availableIndex + 1; + } else { + /* determine this item's index in its parent */ + int itemIndex = 0; + TreeItem[] items = item.parentItem.items; + for (int i = 0; i < items.length; i++) { + if (items [i] == item) { + itemIndex = i; + break; + } + } + if (itemIndex != parentItemCount - 1) { /* this is not the last child */ + index = items [itemIndex + 1].availableIndex; + } else { /* this is the last child */ + TreeItem previousItem = items [itemIndex - 1]; + index = previousItem.availableIndex + previousItem.computeAvailableDescendentCount (); + } + } + + if (availableItemsCount == availableItems.length) { + int grow = drawCount <= 0 ? 4 : Math.max (4, availableItems.length * 3 / 2); + TreeItem[] newAvailableItems = new TreeItem [availableItems.length + grow]; + System.arraycopy (availableItems, 0, newAvailableItems, 0, availableItems.length); + availableItems = newAvailableItems; + } + if (index != availableItemsCount) { + /* new item is not at end of list, so shift other items right to create space for it */ + System.arraycopy (availableItems, index, availableItems, index + 1, availableItemsCount - index); + } + availableItems [index] = item; + availableItemsCount++; + + /* update availableIndex as needed */ + for (int i = index; i < availableItemsCount; i++) { + availableItems [i].availableIndex = i; + } +} + +/* + * Important: Assumes that item is available and its descendents have just become + * available (ie.- they were either created or the item was expanded). + */ +void makeDescendentsAvailable (TreeItem item, TreeItem[] descendents) { + int itemAvailableIndex = item.availableIndex; + TreeItem[] newAvailableItems = new TreeItem [availableItemsCount + descendents.length - 1]; + + System.arraycopy (availableItems, 0, newAvailableItems, 0, itemAvailableIndex); + System.arraycopy (descendents, 0, newAvailableItems, itemAvailableIndex, descendents.length); + int startIndex = itemAvailableIndex + 1; + System.arraycopy ( + availableItems, + startIndex, + newAvailableItems, + itemAvailableIndex + descendents.length, + availableItemsCount - startIndex); + availableItems = newAvailableItems; + availableItemsCount = availableItems.length; + + /* update availableIndex as needed */ + for (int i = itemAvailableIndex; i < availableItemsCount; i++) { + availableItems [i].availableIndex = i; + } +} + +/* + * Important: Assumes that item is available and its descendents have just become + * unavailable (ie.- they were either disposed or the item was collapsed). + */ +void makeDescendentsUnavailable (TreeItem item, TreeItem[] descendents) { + int descendentsLength = descendents.length; + TreeItem[] newAvailableItems = new TreeItem [availableItemsCount - descendentsLength + 1]; + + System.arraycopy (availableItems, 0, newAvailableItems, 0, item.availableIndex + 1); + int startIndex = item.availableIndex + descendentsLength; + System.arraycopy ( + availableItems, + startIndex, + newAvailableItems, + item.availableIndex + 1, + availableItemsCount - startIndex); + availableItems = newAvailableItems; + availableItemsCount = availableItems.length; + + /* update availableIndexes */ + for (int i = 1; i < descendents.length; i++) { + /* skip the first descendent since this is the item being collapsed */ + descendents [i].availableIndex = -1; + } + for (int i = item.availableIndex; i < availableItemsCount; i++) { + availableItems [i].availableIndex = i; + } + + /* remove the selection from all descendents */ + for (int i = selectedItems.length - 1; i >= 0; i--) { + if (selectedItems [i] != item && selectedItems [i].hasAncestor (item)) { + removeSelectedItem (i); + } + } + + /* if the anchorItem is being hidden then clear it */ + if (anchorItem != null && anchorItem != item && anchorItem.hasAncestor (item)) { + anchorItem = null; + } +} +void onArrowDown (int stateMask) { + if ((stateMask & (SWT.SHIFT | SWT.CTRL)) == 0) { + /* Down Arrow with no modifiers */ + int newFocusIndex = focusItem.availableIndex + 1; + if (newFocusIndex == availableItemsCount) return; /* at bottom */ + selectItem (availableItems [newFocusIndex], false); + setFocusItem (availableItems [newFocusIndex], true); + redrawItem (newFocusIndex, true); + showItem (availableItems [newFocusIndex]); + Event newEvent = new Event (); + newEvent.item = availableItems [newFocusIndex]; + postEvent (SWT.Selection, newEvent); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+Down Arrow, CTRL+Shift+Down Arrow */ + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if (availableItemsCount <= topIndex + visibleItemCount) return; /* at bottom */ + update (); + topIndex++; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, -itemHeight); + gc.dispose (); + return; + } + /* Shift+Down Arrow */ + int newFocusIndex = focusItem.availableIndex + 1; + if (newFocusIndex == availableItemsCount) return; /* at bottom */ + selectItem (availableItems [newFocusIndex], false); + setFocusItem (availableItems [newFocusIndex], true); + redrawItem (newFocusIndex, true); + showItem (availableItems [newFocusIndex]); + Event newEvent = new Event (); + newEvent.item = availableItems [newFocusIndex]; + postEvent (SWT.Selection, newEvent); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* CTRL+Shift+Down Arrow */ + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if (availableItemsCount <= topIndex + visibleItemCount) return; /* at bottom */ + update (); + topIndex++; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, -itemHeight); + gc.dispose (); + return; + } + /* CTRL+Down Arrow */ + int focusIndex = focusItem.availableIndex; + if (focusIndex == availableItemsCount - 1) return; /* at bottom */ + TreeItem newFocusItem = availableItems [focusIndex + 1]; + setFocusItem (newFocusItem, true); + redrawItem (newFocusItem.availableIndex, true); + showItem (newFocusItem); + return; + } + /* Shift+Down Arrow */ + int newFocusIndex = focusItem.availableIndex + 1; + if (newFocusIndex == availableItemsCount) return; /* at bottom */ + if (anchorItem == null) anchorItem = focusItem; + if (focusItem.availableIndex < anchorItem.availableIndex) { + deselectItem (focusItem); + redrawItem (focusItem.availableIndex, true); + } + selectItem (availableItems [newFocusIndex], true); + setFocusItem (availableItems [newFocusIndex], true); + redrawItem (newFocusIndex, true); + showItem (availableItems [newFocusIndex]); + Event newEvent = new Event (); + newEvent.item = availableItems [newFocusIndex]; + postEvent (SWT.Selection, newEvent); +} +void onArrowLeft (int stateMask) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+Left Arrow, CTRL+Shift+Left Arrow */ + if (horizontalOffset == 0) return; + int newSelection = Math.max (0, horizontalOffset - SIZE_HORIZONTALSCROLL); + update (); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose (); + if (header.getVisible ()) { + header.update (); + Rectangle headerClientArea = header.getClientArea (); + gc = new GC (header); + gc.copyArea ( + 0, 0, + headerClientArea.width, headerClientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose(); + } + horizontalOffset = newSelection; + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) hBar.setSelection (horizontalOffset); + return; + } + /* Left Arrow with no modifiers, Shift+Left Arrow */ + if (focusItem.expanded) { + focusItem.setExpanded (false); + Event newEvent = new Event (); + newEvent.item = focusItem; + sendEvent (SWT.Collapse, newEvent); + return; + } + TreeItem parentItem = focusItem.parentItem; + if (parentItem == null) return; + + selectItem (parentItem, false); + setFocusItem (parentItem, true); + redrawItem (parentItem.availableIndex, true); + showItem (parentItem); + Event newEvent = new Event (); + newEvent.item = parentItem; + postEvent (SWT.Selection, newEvent); +} +void onArrowRight (int stateMask) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+Right Arrow, CTRL+Shift+Right Arrow */ + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + int maximum = hBar.getMaximum (); + int clientWidth = clientArea.width; + if ((horizontalOffset + clientWidth) == maximum) return; + if (maximum <= clientWidth) return; + int newSelection = Math.min (horizontalOffset + SIZE_HORIZONTALSCROLL, maximum - clientWidth); + update (); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose (); + if (header.getVisible ()) { + Rectangle headerClientArea = header.getClientArea (); + header.update (); + gc = new GC (header); + gc.copyArea ( + 0, 0, + headerClientArea.width, headerClientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose(); + } + horizontalOffset = newSelection; + hBar.setSelection (horizontalOffset); + } + return; + } + /* Right Arrow with no modifiers, Shift+Right Arrow */ + TreeItem[] children = focusItem.items; + if (children.length == 0) return; + if (!focusItem.expanded) { + focusItem.setExpanded (true); + Event newEvent = new Event (); + newEvent.item = focusItem; + inExpand = true; + sendEvent (SWT.Expand, newEvent); + inExpand = false; + if (isDisposed ()) return; + if (focusItem.items.length == 0) { + focusItem.expanded = false; + } + return; + } + selectItem (children [0], false); + setFocusItem (children [0], true); + redrawItem (children [0].availableIndex, true); + showItem (children [0]); + Event newEvent = new Event (); + newEvent.item = children [0]; + postEvent (SWT.Selection, newEvent); +} +void onArrowUp (int stateMask) { + if ((stateMask & (SWT.SHIFT | SWT.CTRL)) == 0) { + /* Up Arrow with no modifiers */ + int newFocusIndex = focusItem.availableIndex - 1; + if (newFocusIndex < 0) return; /* at top */ + TreeItem item = availableItems [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (newFocusIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+Up Arrow, CTRL+Shift+Up Arrow */ + if (topIndex == 0) return; /* at top */ + update (); + topIndex--; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, itemHeight); + gc.dispose (); + return; + } + /* Shift+Up Arrow */ + int newFocusIndex = focusItem.availableIndex - 1; + if (newFocusIndex < 0) return; /* at top */ + TreeItem item = availableItems [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (newFocusIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* CTRL+Shift+Up Arrow */ + if (topIndex == 0) return; /* at top */ + update (); + topIndex--; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, itemHeight); + gc.dispose (); + return; + } + /* CTRL+Up Arrow */ + int focusIndex = focusItem.availableIndex; + if (focusIndex == 0) return; /* at top */ + TreeItem newFocusItem = availableItems [focusIndex - 1]; + setFocusItem (newFocusItem, true); + showItem (newFocusItem); + redrawItem (newFocusItem.availableIndex, true); + return; + } + /* Shift+Up Arrow */ + int newFocusIndex = focusItem.availableIndex - 1; + if (newFocusIndex < 0) return; /* at top */ + if (anchorItem == null) anchorItem = focusItem; + if (anchorItem.availableIndex < focusItem.availableIndex) { + deselectItem (focusItem); + redrawItem (focusItem.availableIndex, true); + } + TreeItem item = availableItems [newFocusIndex]; + selectItem (item, true); + setFocusItem (item, true); + redrawItem (newFocusIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); +} +void onCR () { + if (focusItem == null) return; + Event event = new Event (); + event.item = focusItem; + postEvent (SWT.DefaultSelection, event); +} +void onDispose (Event event) { + if (isDisposed ()) return; + if (ignoreDispose) return; + ignoreDispose = true; + notifyListeners(SWT.Dispose, event); + event.type = SWT.None; + for (int i = 0; i < items.length; i++) { + items [i].dispose (false); + } + for (int i = 0; i < columns.length; i++) { + columns [i].dispose (false); + } + if (toolTipShell != null) { + toolTipShell.dispose (); + toolTipShell = null; + toolTipLabel = null; + } + toolTipListener = null; + topIndex = availableItemsCount = horizontalOffset = 0; + availableItems = items = selectedItems = null; + columns = orderedColumns = null; + focusItem = anchorItem = insertMarkItem = lastClickedItem = null; + lastSelectionEvent = null; + header = null; + resizeColumn = sortColumn = null; + expanderBounds = null; +} +void onEnd (int stateMask) { + int lastAvailableIndex = availableItemsCount - 1; + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + /* End with no modifiers */ + if (focusItem.availableIndex == lastAvailableIndex) return; /* at bottom */ + TreeItem item = availableItems [lastAvailableIndex]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (lastAvailableIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+End, CTRL+Shift+End */ + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + setTopItem (availableItems [availableItemsCount - visibleItemCount]); + return; + } + /* Shift+End */ + if (focusItem.availableIndex == lastAvailableIndex) return; /* at bottom */ + TreeItem item = availableItems [lastAvailableIndex]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (lastAvailableIndex, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* CTRL+Shift+End */ + showItem (availableItems [lastAvailableIndex]); + return; + } + /* CTRL+End */ + if (focusItem.availableIndex == lastAvailableIndex) return; /* at bottom */ + TreeItem item = availableItems [lastAvailableIndex]; + setFocusItem (item, true); + showItem (item); + redrawItem (item.availableIndex, true); + return; + } + /* Shift+End */ + if (anchorItem == null) anchorItem = focusItem; + TreeItem selectedItem = availableItems [lastAvailableIndex]; + if (selectedItem == focusItem && selectedItem.isSelected ()) return; + int anchorIndex = anchorItem.availableIndex; + int selectIndex = selectedItem.availableIndex; + TreeItem[] newSelection = new TreeItem [selectIndex - anchorIndex + 1]; + int writeIndex = 0; + for (int i = anchorIndex; i <= selectIndex; i++) { + newSelection [writeIndex++] = availableItems [i]; + } + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + redrawItems (anchorIndex, selectIndex, true); + showItem (selectedItem); + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); +} +void onFocusIn () { + hasFocus = true; + + if (items.length == 0) { + redraw (); + return; + } + if (focusItem != null) { + redrawItem (focusItem.availableIndex, true); + return; + } + /* an initial focus item must be selected */ + TreeItem initialFocus = null; + if (selectedItems.length > 0) { + for (int i = 0; i < selectedItems.length && initialFocus == null; i++) { + if (selectedItems [i].isAvailable ()) { + initialFocus = selectedItems [i]; + } + } + if (initialFocus == null) { + /* none of the selected items are available */ + initialFocus = availableItems [topIndex]; + } + } else { + initialFocus = availableItems [topIndex]; + } + setFocusItem (initialFocus, false); + redrawItem (initialFocus.availableIndex, true); + return; +} +void onFocusOut () { + hasFocus = false; + + if (items.length == 0) { + redraw (); + return; + } + + if (focusItem != null) { + redrawItem (focusItem.availableIndex, true); + } +} +void onHome (int stateMask) { + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + /* Home with no modifiers */ + if (focusItem.availableIndex == 0) return; /* at top */ + TreeItem item = availableItems [0]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (0, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+Home, CTRL+Shift+Home */ + setTopItem (availableItems [0]); + return; + } + /* Shift+Home */ + if (focusItem.availableIndex == 0) return; /* at top */ + TreeItem item = availableItems [0]; + selectItem (item, false); + setFocusItem (item, true); + redrawItem (0, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* CTRL+Shift+Home */ + setTopItem (availableItems [0]); + return; + } + /* CTRL+Home */ + if (focusItem.availableIndex == 0) return; /* at top */ + TreeItem item = availableItems [0]; + setFocusItem (item, true); + showItem (item); + redrawItem (item.availableIndex, true); + return; + } + /* Shift+Home */ + if (anchorItem == null) anchorItem = focusItem; + TreeItem selectedItem = availableItems [0]; + if (selectedItem == focusItem && selectedItem.isSelected ()) return; + int anchorIndex = anchorItem.availableIndex; + int selectIndex = selectedItem.availableIndex; + TreeItem[] newSelection = new TreeItem [anchorIndex + 1]; + int writeIndex = 0; + for (int i = anchorIndex; i >= 0; i--) { + newSelection [writeIndex++] = availableItems [i]; + } + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + redrawItems (anchorIndex, selectIndex, true); + showItem (selectedItem); + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); +} +void onKeyDown (Event event) { + if (ignoreKey) { + ignoreKey = false; + return; + } + ignoreKey = true; + notifyListeners (event.type, event); + event.type = SWT.None; + if (!event.doit) return; + if (focusItem == null) return; + if ((event.stateMask & SWT.SHIFT) == 0 && event.keyCode != SWT.SHIFT) { + anchorItem = null; + } + switch (event.keyCode) { + case SWT.ARROW_UP: + onArrowUp (event.stateMask); + return; + case SWT.ARROW_DOWN: + onArrowDown (event.stateMask); + return; + case SWT.ARROW_LEFT: + onArrowLeft (event.stateMask); + return; + case SWT.ARROW_RIGHT: + onArrowRight (event.stateMask); + return; + case SWT.PAGE_UP: + onPageUp (event.stateMask); + return; + case SWT.PAGE_DOWN: + onPageDown (event.stateMask); + return; + case SWT.HOME: + onHome (event.stateMask); + return; + case SWT.END: + onEnd (event.stateMask); + return; + } + if (event.character == ' ') { + onSpace (); + return; + } + if (event.character == SWT.CR) { + onCR (); + return; + } + if ((event.stateMask & SWT.CTRL) != 0) return; + + int initialIndex = focusItem.availableIndex; + char character = Character.toLowerCase (event.character); + /* check available items from current focus item to bottom */ + for (int i = initialIndex + 1; i < availableItemsCount; i++) { + TreeItem item = availableItems [i]; + String text = item.getText (0, false); + if (text.length() > 0) { + if (Character.toLowerCase (text.charAt (0)) == character) { + selectItem (item, false); + setFocusItem (item, true); + redrawItem (i, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + } + } + /* check available items from top to current focus item */ + for (int i = 0; i < initialIndex; i++) { + TreeItem item = availableItems [i]; + String text = item.getText (0, false); + if (text.length() > 0) { + if (Character.toLowerCase (text.charAt (0)) == character) { + selectItem (item, false); + setFocusItem (item, true); + redrawItem (i, true); + showItem (item); + Event newEvent = new Event (); + newEvent.item = item; + postEvent (SWT.Selection, newEvent); + return; + } + } + } +} +void onMouseDoubleClick (Event event) { + if (!isFocusControl ()) setFocus (); + int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex; + if (!(0 <= index && index < availableItemsCount)) return; /* not on an available item */ + TreeItem selectedItem = availableItems [index]; + + /* + * If the two clicks of the double click did not occur over the same item then do not + * consider this to be a default selection. + */ + if (selectedItem != lastClickedItem) return; + + /* if click was in expander box then don't fire event */ + if (selectedItem.items.length > 0 && selectedItem.getExpanderBounds ().contains (event.x, event.y)) { + return; + } + + if (!selectedItem.getHitBounds ().contains (event.x, event.y)) return; /* considers x */ + + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.DefaultSelection, newEvent); +} +void onMouseDown (Event event) { + if (!isFocusControl ()) forceFocus (); + int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex; + if (!(0 <= index && index < availableItemsCount)) return; /* not on an available item */ + TreeItem selectedItem = availableItems [index]; + + /* if click was in expander box */ + if (selectedItem.items.length > 0 && selectedItem.getExpanderBounds ().contains (event.x, event.y)) { + if (event.button != 1) return; + boolean expand = !selectedItem.expanded; + selectedItem.setExpanded (expand); + Event newEvent = new Event (); + newEvent.item = selectedItem; + if (expand) { + inExpand = true; + sendEvent (SWT.Expand, newEvent); + inExpand = false; + if (isDisposed ()) return; + if (selectedItem.items.length == 0) { + selectedItem.expanded = false; + } + } else { + sendEvent (SWT.Collapse, newEvent); + } + return; + } + /* if click was in checkbox */ + if ((style & SWT.CHECK) != 0 && selectedItem.getCheckboxBounds ().contains (event.x, event.y)) { + if (event.button != 1) return; + selectedItem.setChecked (!selectedItem.checked); + Event newEvent = new Event (); + newEvent.item = selectedItem; + newEvent.detail = SWT.CHECK; + postEvent (SWT.Selection, newEvent); + return; + } + + if (!selectedItem.getHitBounds ().contains (event.x, event.y)) return; + + if ((event.stateMask & SWT.SHIFT) == 0 && event.keyCode != SWT.SHIFT) anchorItem = null; + + boolean sendSelection = true; + /* Detect when this is the second click of a DefaultSelection and don't fire Selection */ + if (lastSelectionEvent != null && lastSelectionEvent.item == selectedItem) { + if (event.time - lastSelectionEvent.time <= display.getDoubleClickTime ()) { + sendSelection = false; + } else { + lastSelectionEvent = event; + event.item = selectedItem; + } + } else { + lastSelectionEvent = event; + event.item = selectedItem; + } + + if ((style & SWT.SINGLE) != 0) { + if (!selectedItem.isSelected ()) { + if (event.button == 1) { + selectItem (selectedItem, false); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.availableIndex, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + selectItem (selectedItem, false); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.availableIndex, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + } + /* item is selected */ + if (event.button == 1) { + /* fire a selection event, though the selection did not change */ + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + } + /* SWT.MULTI */ + if (!selectedItem.isSelected ()) { + if (event.button == 1) { + if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == SWT.SHIFT) { + if (anchorItem == null) anchorItem = focusItem; + int anchorIndex = anchorItem.availableIndex; + int selectIndex = selectedItem.availableIndex; + TreeItem[] newSelection = new TreeItem [Math.abs (anchorIndex - selectIndex) + 1]; + int step = anchorIndex < selectIndex ? 1 : -1; + int writeIndex = 0; + for (int i = anchorIndex; i != selectIndex; i += step) { + newSelection [writeIndex++] = availableItems [i]; + } + newSelection [writeIndex] = availableItems [selectIndex]; + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + redrawItems ( + Math.min (anchorIndex, selectIndex), + Math.max (anchorIndex, selectIndex), + true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + selectItem (selectedItem, (event.stateMask & SWT.CTRL) != 0); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.availableIndex, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + /* button 3 */ + if ((event.stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + selectItem (selectedItem, false); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.availableIndex, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + } + /* item is selected */ + if (event.button != 1) return; + if ((event.stateMask & SWT.CTRL) != 0) { + removeSelectedItem (getSelectionIndex (selectedItem)); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.availableIndex, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + if ((event.stateMask & SWT.SHIFT) != 0) { + if (anchorItem == null) anchorItem = focusItem; + int anchorIndex = anchorItem.availableIndex; + int selectIndex = selectedItem.availableIndex; + TreeItem[] newSelection = new TreeItem [Math.abs (anchorIndex - selectIndex) + 1]; + int step = anchorIndex < selectIndex ? 1 : -1; + int writeIndex = 0; + for (int i = anchorIndex; i != selectIndex; i += step) { + newSelection [writeIndex++] = availableItems [i]; + } + newSelection [writeIndex] = availableItems [selectIndex]; + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + redrawItems ( + Math.min (anchorIndex, selectIndex), + Math.max (anchorIndex, selectIndex), + true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } + return; + } + selectItem (selectedItem, false); + setFocusItem (selectedItem, true); + redrawItem (selectedItem.availableIndex, true); + if (sendSelection) { + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); + } +} +void onMouseUp (Event event) { + int index = (event.y - getHeaderHeight ()) / itemHeight + topIndex; + if (!(0 <= index && index < availableItemsCount)) return; /* not on an available item */ + lastClickedItem = availableItems [index]; +} +void onPageDown (int stateMask) { + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + /* PageDown with no modifiers */ + int newFocusIndex = focusItem.availableIndex + visibleItemCount - 1; + newFocusIndex = Math.min (newFocusIndex, availableItemsCount - 1); + if (newFocusIndex == focusItem.availableIndex) return; + TreeItem item = availableItems [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + showItem (item); + redrawItem (item.availableIndex, true); + return; + } + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == (SWT.CTRL | SWT.SHIFT)) { + /* CTRL+Shift+PageDown */ + int newTopIndex = topIndex + visibleItemCount; + newTopIndex = Math.min (newTopIndex, availableItemsCount - visibleItemCount); + if (newTopIndex == topIndex) return; + setTopItem (availableItems [newTopIndex]); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* Shift+PageDown */ + int newFocusIndex = focusItem.availableIndex + visibleItemCount - 1; + newFocusIndex = Math.min (newFocusIndex, availableItemsCount - 1); + if (newFocusIndex == focusItem.availableIndex) return; + TreeItem item = availableItems [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + showItem (item); + redrawItem (item.availableIndex, true); + return; + } + /* CTRL+PageDown */ + int newTopIndex = topIndex + visibleItemCount; + newTopIndex = Math.min (newTopIndex, availableItemsCount - visibleItemCount); + if (newTopIndex == topIndex) return; + setTopItem (availableItems [newTopIndex]); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+PageDown */ + int bottomIndex = Math.min (topIndex + visibleItemCount - 1, availableItemsCount - 1); + if (focusItem.availableIndex != bottomIndex) { + /* move focus to bottom item in viewport */ + setFocusItem (availableItems [bottomIndex], true); + redrawItem (bottomIndex, true); + } else { + /* at bottom of viewport, so set focus to bottom item one page down */ + int newFocusIndex = Math.min (availableItemsCount - 1, bottomIndex + visibleItemCount); + if (newFocusIndex == focusItem.availableIndex) return; + setFocusItem (availableItems [newFocusIndex], true); + showItem (availableItems [newFocusIndex]); + redrawItem (newFocusIndex, true); + } + return; + } + /* Shift+PageDown */ + if (anchorItem == null) anchorItem = focusItem; + int anchorIndex = anchorItem.availableIndex; + int bottomIndex = Math.min (topIndex + visibleItemCount - 1, availableItemsCount - 1); + int selectIndex; + if (focusItem.availableIndex != bottomIndex) { + /* select from focus to bottom item in viewport */ + selectIndex = bottomIndex; + } else { + /* already at bottom of viewport, so select to bottom of one page down */ + selectIndex = Math.min (availableItemsCount - 1, bottomIndex + visibleItemCount); + if (selectIndex == focusItem.availableIndex && focusItem.isSelected ()) return; + } + TreeItem selectedItem = availableItems [selectIndex]; + TreeItem[] newSelection = new TreeItem [Math.abs (anchorIndex - selectIndex) + 1]; + int step = anchorIndex < selectIndex ? 1 : -1; + int writeIndex = 0; + for (int i = anchorIndex; i != selectIndex; i += step) { + newSelection [writeIndex++] = availableItems [i]; + } + newSelection [writeIndex] = availableItems [selectIndex]; + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + showItem (selectedItem); + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); +} +void onPageUp (int stateMask) { + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == 0) { + /* PageUp with no modifiers */ + int newFocusIndex = Math.max (0, focusItem.availableIndex - visibleItemCount + 1); + if (newFocusIndex == focusItem.availableIndex) return; + TreeItem item = availableItems [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + showItem (item); + redrawItem (item.availableIndex, true); + return; + } + if ((stateMask & (SWT.CTRL | SWT.SHIFT)) == (SWT.CTRL | SWT.SHIFT)) { + /* CTRL+Shift+PageUp */ + int newTopIndex = Math.max (0, topIndex - visibleItemCount); + if (newTopIndex == topIndex) return; + setTopItem (availableItems [newTopIndex]); + return; + } + if ((style & SWT.SINGLE) != 0) { + if ((stateMask & SWT.SHIFT) != 0) { + /* Shift+PageUp */ + int newFocusIndex = Math.max (0, focusItem.availableIndex - visibleItemCount + 1); + if (newFocusIndex == focusItem.availableIndex) return; + TreeItem item = availableItems [newFocusIndex]; + selectItem (item, false); + setFocusItem (item, true); + showItem (item); + redrawItem (item.availableIndex, true); + return; + } + /* CTRL+PageUp */ + int newTopIndex = Math.max (0, topIndex - visibleItemCount); + if (newTopIndex == topIndex) return; + setTopItem (availableItems [newTopIndex]); + return; + } + /* SWT.MULTI */ + if ((stateMask & SWT.CTRL) != 0) { + /* CTRL+PageUp */ + if (focusItem.availableIndex != topIndex) { + /* move focus to top item in viewport */ + setFocusItem (availableItems [topIndex], true); + redrawItem (topIndex, true); + } else { + /* at top of viewport, so set focus to top item one page up */ + int newFocusIndex = Math.max (0, focusItem.availableIndex - visibleItemCount); + if (newFocusIndex == focusItem.availableIndex) return; + setFocusItem (availableItems [newFocusIndex], true); + showItem (availableItems [newFocusIndex]); + redrawItem (newFocusIndex, true); + } + return; + } + /* Shift+PageUp */ + if (anchorItem == null) anchorItem = focusItem; + int anchorIndex = anchorItem.availableIndex; + int selectIndex; + if (focusItem.availableIndex != topIndex) { + /* select from focus to top item in viewport */ + selectIndex = topIndex; + } else { + /* already at top of viewport, so select to top of one page up */ + selectIndex = Math.max (0, topIndex - visibleItemCount); + if (selectIndex == focusItem.availableIndex && focusItem.isSelected ()) return; + } + TreeItem selectedItem = availableItems [selectIndex]; + TreeItem[] newSelection = new TreeItem [Math.abs (anchorIndex - selectIndex) + 1]; + int step = anchorIndex < selectIndex ? 1 : -1; + int writeIndex = 0; + for (int i = anchorIndex; i != selectIndex; i += step) { + newSelection [writeIndex++] = availableItems [i]; + } + newSelection [writeIndex] = availableItems [selectIndex]; + setSelection (newSelection, false); + setFocusItem (selectedItem, true); + showItem (selectedItem); + Event newEvent = new Event (); + newEvent.item = selectedItem; + postEvent (SWT.Selection, newEvent); +} +void onPaint (Event event) { + TreeColumn[] orderedColumns = getOrderedColumns (); + GC gc = event.gc; + Rectangle clipping = gc.getClipping (); + int headerHeight = getHeaderHeight (); + int numColumns = orderedColumns.length; + int startColumn = -1, endColumn = -1; + if (numColumns > 0) { + startColumn = computeColumnIntersect (clipping.x, 0); + if (startColumn != -1) { /* the clip x is within a column's bounds */ + endColumn = computeColumnIntersect (clipping.x + clipping.width, startColumn); + if (endColumn == -1) endColumn = numColumns - 1; + } + } else { + startColumn = endColumn = 0; + } + + /* Determine the items to be painted */ + int startIndex = (clipping.y - headerHeight) / itemHeight + topIndex; + int endIndex = -1; + if (startIndex < availableItemsCount) { + endIndex = startIndex + Compatibility.ceil (clipping.height, itemHeight); + } + startIndex = Math.max (0, startIndex); + endIndex = Math.min (endIndex, availableItemsCount - 1); + + /* fill background not handled by items */ + gc.setBackground (getBackground ()); + gc.setClipping (clipping); + int bottomY = endIndex >= 0 ? getItemY (availableItems [endIndex]) + itemHeight : 0; + int fillHeight = Math.max (0, clientArea.height - bottomY); + if (fillHeight > 0) { /* space below bottom item */ + drawBackground (gc, 0, bottomY, clientArea.width, fillHeight); + } + if (columns.length > 0) { + TreeColumn column = orderedColumns [orderedColumns.length - 1]; /* last column */ + int rightX = column.getX () + column.width; + if (rightX < clientArea.width) { + drawBackground (gc, rightX, 0, clientArea.width - rightX, clientArea.height - fillHeight); + } + } + + /* paint the items */ + boolean noFocusDraw = false; + int[] lineDash = gc.getLineDash (); + int lineWidth = gc.getLineWidth (); + for (int i = startIndex; i <= Math.min (endIndex, availableItemsCount - 1); i++) { + TreeItem item = availableItems [i]; + if (!item.isDisposed ()) { /* ensure that item was not disposed in a callback */ + if (startColumn == -1) { + /* indicates that region to paint is to the right of the last column */ + noFocusDraw = item.paint (gc, null, true) || noFocusDraw; + } else { + if (numColumns == 0) { + noFocusDraw = item.paint (gc, null, false) || noFocusDraw; + } else { + for (int j = startColumn; j <= Math.min (endColumn, columns.length - 1); j++) { + if (!item.isDisposed ()) { /* ensure that item was not disposed in a callback */ + noFocusDraw = item.paint (gc, orderedColumns [j], false) || noFocusDraw; + } + if (isDisposed () || gc.isDisposed ()) return; /* ensure that receiver was not disposed in a callback */ + } + } + } + } + if (isDisposed () || gc.isDisposed ()) return; /* ensure that receiver was not disposed in a callback */ + } + + /* repaint grid lines */ + gc.setClipping(clipping); + gc.setLineWidth (lineWidth); + if (linesVisible) { + gc.setForeground (display.getSystemColor (SWT.COLOR_WIDGET_LIGHT_SHADOW)); + gc.setLineDash (lineDash); + if (numColumns > 0 && startColumn != -1) { + /* vertical column lines */ + for (int i = startColumn; i <= endColumn; i++) { + int x = orderedColumns [i].getX () + orderedColumns [i].width - 1; + gc.drawLine (x, clipping.y, x, clipping.y + clipping.height); + } + } + /* horizontal item lines */ + bottomY = clipping.y + clipping.height; + int rightX = clipping.x + clipping.width; + int y = (clipping.y - headerHeight) / itemHeight * itemHeight + headerHeight; + while (y <= bottomY) { + gc.drawLine (clipping.x, y, rightX, y); + y += itemHeight; + } + } + + /* draw focus rectangle */ + if (!noFocusDraw && isFocusControl ()) { + if (focusItem != null) { + Rectangle focusBounds = focusItem.getFocusBounds (); + if (focusBounds.width > 0) { + gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK)); + gc.setClipping (focusBounds); + if (focusItem.isSelected ()) { + gc.setLineDash (new int[] {2, 2}); + } else { + gc.setLineDash (new int[] {1, 1}); + } + gc.drawFocus (focusBounds.x, focusBounds.y, focusBounds.width, focusBounds.height); + } + } else { + /* no items, so draw focus border around Tree */ + int y = headerHeight + 1; + int width = Math.max (0, clientArea.width - 2); + int height = Math.max (0, clientArea.height - headerHeight - 2); + gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK)); + gc.setClipping (1, y, width, height); + gc.setLineDash (new int[] {1, 1}); + gc.drawFocus (1, y, width, height); + } + } + + /* draw insert mark */ + if (insertMarkItem != null) { + Rectangle focusBounds = insertMarkItem.getFocusBounds (); + gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK)); + gc.setClipping (focusBounds); + gc.setLineDash (lineDash); + if (insertMarkPrecedes) { + gc.drawLine (focusBounds.x, focusBounds.y, focusBounds.x + focusBounds.width, focusBounds.y); + } else { + int y = focusBounds.y + focusBounds.height - 1; + gc.drawLine (focusBounds.x, y, focusBounds.x + focusBounds.width, y); + } + } +} +void onResize (Event event) { + clientArea = getClientArea (); + /* vertical scrollbar */ + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + int clientHeight = (clientArea.height - getHeaderHeight ()) / itemHeight; + int thumb = Math.min (clientHeight, availableItemsCount); + vBar.setThumb (thumb); + vBar.setPageIncrement (thumb); + int index = vBar.getSelection (); + if (index != topIndex) { + topIndex = index; + redraw (); + } + boolean visible = clientHeight < availableItemsCount; + if (visible != vBar.getVisible ()) { + vBar.setVisible (visible); + clientArea = getClientArea (); + } + } + + /* horizontal scrollbar */ + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + int hBarMaximum = hBar.getMaximum (); + int thumb = Math.min (clientArea.width, hBarMaximum); + hBar.setThumb (thumb); + hBar.setPageIncrement (thumb); + horizontalOffset = hBar.getSelection (); + boolean visible = clientArea.width < hBarMaximum; + if (visible != hBar.getVisible ()) { + hBar.setVisible (visible); + clientArea = getClientArea (); + } + } + + /* header */ + int headerHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding (); + header.setSize (clientArea.width, headerHeight); + + /* if this is the focus control but there are no items then the boundary focus ring must be repainted */ + if (availableItemsCount == 0 && isFocusControl ()) redraw (); +} +void onScrollHorizontal (Event event) { + ScrollBar hBar = getHorizontalBar (); + if (hBar == null) return; + int newSelection = hBar.getSelection (); + update (); + if (availableItemsCount > 0) { + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose (); + } else { + redraw (); /* ensure that static focus rectangle updates properly */ + } + if (drawCount <= 0 && header.isVisible ()) { + header.update (); + Rectangle headerClientArea = header.getClientArea (); + GC gc = new GC (header); + gc.copyArea ( + 0, 0, + headerClientArea.width, headerClientArea.height, + horizontalOffset - newSelection, 0); + gc.dispose (); + } + horizontalOffset = newSelection; +} +void onScrollVertical (Event event) { + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + int newSelection = vBar.getSelection (); + update (); + GC gc = new GC (this); + gc.copyArea ( + 0, 0, + clientArea.width, clientArea.height, + 0, (topIndex - newSelection) * itemHeight); + gc.dispose (); + topIndex = newSelection; + } +} +void onSpace () { + if (focusItem == null) return; + if (!focusItem.isSelected ()) { + selectItem (focusItem, (style & SWT.MULTI) != 0); + redrawItem (focusItem.availableIndex, true); + } + if ((style & SWT.CHECK) != 0) { + focusItem.setChecked (!focusItem.checked); + } + showItem (focusItem); + Event event = new Event (); + event.item = focusItem; + postEvent (SWT.Selection, event); + if ((style & SWT.CHECK) == 0) return; + + /* SWT.CHECK */ + event = new Event (); + event.item = focusItem; + event.detail = SWT.CHECK; + postEvent (SWT.Selection, event); +} +/* + * The current focus item is about to become unavailable, so reassign focus. + */ +void reassignFocus () { + if (focusItem == null) return; + + /* reassign to current focus' parent item if it has one */ + if (focusItem.parentItem != null) { + TreeItem item = focusItem.parentItem; + setFocusItem (item, false); + showItem (item); + return; + } + + /* + * reassign to the previous root-level item if there is one, or the next + * root-level item otherwise + */ + int index = focusItem.getIndex (); + if (index != 0) { + index--; + } else { + index++; + } + if (index < items.length) { + TreeItem item = items [index]; + setFocusItem (item, false); + showItem (item); + } else { + setFocusItem (null, false); /* no items left */ + } +} +public void redraw () { + checkWidget (); + if (drawCount <= 0) super.redraw (); +} +public void redraw (int x, int y, int width, int height, boolean all) { + checkWidget (); + if (drawCount <= 0) super.redraw (x, y, width, height, all); +} +/* + * Redraws from the specified index down to the last available item inclusive. Note + * that the redraw bounds do not extend beyond the current last item, so clients + * that reduce the number of available items should use #redrawItems(int,int) instead + * to ensure that redrawing extends down to the previous bottom item boundary. + */ +void redrawFromItemDownwards (int index) { + redrawItems (index, availableItemsCount - 1, false); +} +/* + * Redraws the tree item at the specified index. It is valid for this index to reside + * beyond the last available item. + */ +void redrawItem (int itemIndex, boolean focusBoundsOnly) { + if (itemIndex == -1) return; + if (itemIndex < availableItemsCount && !availableItems [itemIndex].isInViewport ()) return; + redrawItems (itemIndex, itemIndex, focusBoundsOnly); +} +/* + * Redraws the tree between the start and end item indices inclusive. It is valid + * for the end index value to extend beyond the last available item. + */ +void redrawItems (int startIndex, int endIndex, boolean focusBoundsOnly) { + if (drawCount > 0) return; + + int startY = (startIndex - topIndex) * itemHeight + getHeaderHeight (); + int height = (endIndex - startIndex + 1) * itemHeight; + if (focusBoundsOnly) { + boolean custom = hooks (SWT.EraseItem) || hooks (SWT.PaintItem); + if (!custom && columns.length > 0) { + TreeColumn lastColumn; + if ((style & SWT.FULL_SELECTION) != 0) { + TreeColumn[] orderedColumns = getOrderedColumns (); + lastColumn = orderedColumns [orderedColumns.length - 1]; + } else { + lastColumn = columns [0]; + } + int rightX = lastColumn.getX () + lastColumn.getWidth (); + if (rightX <= 0) return; /* focus column(s) not visible */ + } + endIndex = Math.min (endIndex, availableItemsCount - 1); + for (int i = startIndex; i <= endIndex; i++) { + TreeItem item = availableItems [i]; + if (item.isInViewport ()) { + /* if custom painting is being done then repaint the full item */ + if (custom) { + redraw (0, getItemY (item), clientArea.width, itemHeight, false); + } else { + /* repaint the item's focus bounds */ + Rectangle bounds = item.getFocusBounds (); + redraw (bounds.x, startY, bounds.width, height, false); + } + } + } + } else { + redraw (0, startY, clientArea.width, height, false); + } +} +/** + * 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 (); + if (items.length == 0) return; + setRedraw (false); + + setFocusItem (null, false); + for (int i = 0; i < items.length; i++) { + items [i].dispose (false); + } + items = availableItems = selectedItems = NO_ITEMS; + availableItemsCount = topIndex = 0; + anchorItem = lastClickedItem = insertMarkItem = null; + lastSelectionEvent = null; + inExpand = false; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + vBar.setMaximum (1); + vBar.setVisible (false); + } + if (columns.length == 0) { + horizontalOffset = 0; + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setMaximum (1); + hBar.setVisible (false); + } + } + + setRedraw (true); +} +String removeMnemonics (String string) { + /* removes single ampersands and preserves double-ampersands */ + char [] chars = new char [string.length ()]; + string.getChars (0, chars.length, chars, 0); + int i = 0, j = 0; + for ( ; i < chars.length; i++, j++) { + if (chars[i] == '&') { + if (++i == chars.length) break; + if (chars[i] == '&') { + chars[j++] = chars[i - 1]; + } + } + chars[j] = chars[i]; + } + if (i == j) return string; + return new String (chars, 0, j); +} +void removeSelectedItem (int index) { + TreeItem[] newSelectedItems = new TreeItem [selectedItems.length - 1]; + System.arraycopy (selectedItems, 0, newSelectedItems, 0, index); + System.arraycopy (selectedItems, index + 1, newSelectedItems, index, newSelectedItems.length - index); + selectedItems = newSelectedItems; +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the user changes the receiver's selection. + * + * @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 (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + removeListener (SWT.Selection, listener); + removeListener (SWT.DefaultSelection, 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 (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + removeListener (SWT.Expand, listener); + removeListener (SWT.Collapse, listener); +} +/** + * 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 3.4 + */ +public void select (TreeItem item) { + checkWidget (); + if (item == null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + selectItem (item, (style & SWT.MULTI) != 0); + redrawItem (item.availableIndex, true); +} +/** + * 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.SINGLE) != 0) return; + selectedItems = new TreeItem [availableItemsCount]; + System.arraycopy (availableItems, 0, selectedItems, 0, availableItemsCount); + redraw (); +} +void selectItem (TreeItem item, boolean addToSelection) { + TreeItem[] oldSelectedItems = selectedItems; + if (!addToSelection || (style & SWT.SINGLE) != 0) { + selectedItems = new TreeItem[] {item}; + for (int i = 0; i < oldSelectedItems.length; i++) { + if (oldSelectedItems [i] != item) { + redrawItem (oldSelectedItems [i].availableIndex, true); + } + } + } else { + if (item.isSelected ()) return; + selectedItems = new TreeItem [selectedItems.length + 1]; + System.arraycopy (oldSelectedItems, 0, selectedItems, 0, oldSelectedItems.length); + selectedItems [selectedItems.length - 1] = item; + } +} +public void setBackground (Color color) { + checkWidget (); + if (color == null) color = display.getSystemColor (SWT.COLOR_LIST_BACKGROUND); + super.setBackground (color); +} +/** + * 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 + * + * @since 3.2 + */ +public void setColumnOrder (int [] order) { + checkWidget (); + if (order == null) error (SWT.ERROR_NULL_ARGUMENT); + if (columns.length == 0) { + if (order.length != 0) error (SWT.ERROR_INVALID_ARGUMENT); + return; + } + if (order.length != columns.length) error (SWT.ERROR_INVALID_ARGUMENT); + boolean reorder = false; + boolean [] seen = new boolean [columns.length]; + int[] oldOrder = getColumnOrder (); + for (int i = 0; i < order.length; i++) { + int index = order [i]; + if (index < 0 || index >= columns.length) error (SWT.ERROR_INVALID_RANGE); + if (seen [index]) error (SWT.ERROR_INVALID_ARGUMENT); + seen [index] = true; + if (index != oldOrder [i]) reorder = true; + } + if (!reorder) return; + + headerHideToolTip (); + int[] oldX = new int [columns.length]; + for (int i = 0; i < columns.length; i++) { + oldX [i] = columns [i].getX (); + } + orderedColumns = new TreeColumn [order.length]; + for (int i = 0; i < order.length; i++) { + orderedColumns [i] = columns [order [i]]; + } + /* + * If the first ordered column has changed then the old and new ordered column 0's + * have to recompute their display texts since they will now have just gained/lost + * space as a result of the hierarchy decorations that appear in ordered column 0. + */ + if (oldOrder [0] != order [0]) { + orderedCol0imageWidth = columns [order [0]].itemImageWidth; + GC gc = new GC (this); + for (int i = 0; i < items.length; i++) { + items [i].updateColumnWidth (columns [oldOrder [0]], gc); + items [i].updateColumnWidth (columns [order [0]], gc); + } + gc.dispose(); + } + for (int i = 0; i < orderedColumns.length; i++) { + TreeColumn column = orderedColumns [i]; + if (!column.isDisposed () && column.getX () != oldX [column.getIndex ()]) { + column.sendEvent (SWT.Move); + } + } + + redraw (); + if (drawCount <= 0 && header.isVisible ()) header.redraw (); +} +void setFocusItem (TreeItem item, boolean redrawOldFocus) { + if (item == focusItem) return; + TreeItem oldFocusItem = focusItem; + focusItem = item; + if (redrawOldFocus && oldFocusItem != null) { + redrawItem (oldFocusItem.availableIndex, true); + } +} +public void setFont (Font value) { + checkWidget (); + Font oldFont = getFont (); + super.setFont (value); + Font font = getFont (); + if (font.equals (oldFont)) return; + + GC gc = new GC (this); + + /* recompute the receiver's cached font height and item height values */ + fontHeight = gc.getFontMetrics ().getHeight (); + setItemHeight (Math.max (fontHeight, imageHeight) + 2 * getCellPadding ()); + Point headerSize = header.getSize (); + int newHeaderHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding (); + if (headerSize.y != newHeaderHeight) { + header.setSize (headerSize.x, newHeaderHeight); + } + header.setFont (font); + + /* + * Notify all columns and items of the font change so that elements that + * use the receiver's font can recompute their cached string widths. + */ + for (int i = 0; i < columns.length; i++) { + columns [i].updateFont (gc); + } + for (int i = 0; i < items.length; i++) { + items [i].updateFont (gc); + } + + gc.dispose (); + + if (drawCount <= 0 && header.isVisible ()) header.redraw (); + + /* update scrollbars */ + if (columns.length == 0) updateHorizontalBar (); + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + int thumb = (clientArea.height - getHeaderHeight ()) / itemHeight; + if (vBar.getThumb () != thumb) { + vBar.setThumb (thumb); + vBar.setPageIncrement (thumb); + } + topIndex = vBar.getSelection (); + vBar.setVisible (thumb < vBar.getMaximum ()); + } + redraw (); +} +public void setForeground (Color color) { + checkWidget (); + if (color == null) color = display.getSystemColor (SWT.COLOR_LIST_FOREGROUND); + super.setForeground (color); +} +void setHeaderImageHeight (int value) { + headerImageHeight = value; + Point headerSize = header.getSize (); + int newHeaderHeight = Math.max (fontHeight, headerImageHeight) + 2 * getHeaderPadding (); + if (headerSize.y != newHeaderHeight) { + header.setSize (headerSize.x, newHeaderHeight); + } +} +/** + * 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 show 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> + * + * @since 3.1 + */ +public void setHeaderVisible (boolean value) { + checkWidget (); + if (header.getVisible () == value) return; /* no change */ + headerHideToolTip (); + header.setVisible (value); + updateVerticalBar (); + redraw (); +} +void setImageHeight (int value) { + imageHeight = value; + setItemHeight (Math.max (fontHeight, imageHeight) + 2 * getCellPadding ()); +} +/** + * Display a mark indicating the point at which an item will be inserted. + * The drop insert item has a visual hint to show where a dragged item + * will be inserted when dropped on the tree. + * + * @param item the insert item. Null will clear the insertion mark. + * @param before true places the insert mark above 'item'. false places + * the insert mark below 'item'. + * + * @exception IllegalArgumentException <ul> + * <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 setInsertMark (TreeItem item, boolean before) { + checkWidget (); + if (item != null && item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + if (item != null && item.parent != this) return; + if (item == insertMarkItem && before == insertMarkPrecedes) return; /* no change */ + + TreeItem oldInsertItem = insertMarkItem; + insertMarkItem = item; + insertMarkPrecedes = before; + if (oldInsertItem != null && oldInsertItem.availableIndex != -1) { + redrawItem (oldInsertItem.availableIndex, true); + } + if (item != null && item != oldInsertItem && item.availableIndex != -1) { + redrawItem (item.availableIndex, true); + } +} +/** + * 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> + * + * @since 3.2 + */ +public void setItemCount (int count) { + checkWidget (); + count = Math.max (0, count); + if (count == items.length) return; + int oldCount = availableItemsCount; + int redrawStart, redrawEnd; + + /* if the new item count is less than the current count then remove all excess items from the end */ + if (count < items.length) { + redrawStart = count > 0 ? items [count - 1].availableIndex : 0; + redrawEnd = availableItemsCount - 1; + availableItemsCount = items [count].availableIndex; + for (int i = count; i < items.length; i++) { + items [i].dispose (false); + } + if (count == 0) { + items = Tree.NO_ITEMS; + } else { + TreeItem[] newItems = new TreeItem [count]; + System.arraycopy (items, 0, newItems, 0, count); + items = newItems; + } + + int newSelectedCount = 0; + for (int i = 0; i < selectedItems.length; i++) { + if (!selectedItems [i].isDisposed ()) newSelectedCount++; + } + if (newSelectedCount != selectedItems.length) { + /* one or more selected items have been disposed */ + TreeItem[] newSelectedItems = new TreeItem [newSelectedCount]; + int pos = 0; + for (int i = 0; i < selectedItems.length; i++) { + TreeItem item = selectedItems [i]; + if (!item.isDisposed ()) { + newSelectedItems [pos++] = item; + } + } + selectedItems = newSelectedItems; + } + + if (insertMarkItem != null && insertMarkItem.isDisposed ()) insertMarkItem = null; + if (lastClickedItem != null && lastClickedItem.isDisposed ()) lastClickedItem = null; + if (anchorItem != null && anchorItem.isDisposed ()) anchorItem = null; + if (focusItem != null && focusItem.isDisposed ()) { + TreeItem newFocusItem = count > 0 ? items [count - 1] : null; + setFocusItem (newFocusItem, false); + } + if (columns.length == 0) updateHorizontalBar (); + } else { + int grow = count - items.length; + redrawStart = items.length == 0 ? 0 : items [items.length - 1].availableIndex; + redrawEnd = availableItemsCount + grow - 1; + TreeItem[] newItems = new TreeItem [count]; + System.arraycopy (items, 0, newItems, 0, items.length); + items = newItems; + if (availableItems.length < availableItemsCount + grow) { + TreeItem[] newAvailableItems = new TreeItem [availableItemsCount + grow]; + System.arraycopy (availableItems, 0, newAvailableItems, 0, availableItemsCount); + availableItems = newAvailableItems; + } + for (int i = items.length - grow; i < count; i++) { + TreeItem newItem = new TreeItem (this, SWT.NONE, i, false); + items [i] = newItem; + items [i].availableIndex = availableItemsCount; + availableItems [availableItemsCount++] = newItem; + } + if (oldCount == 0) focusItem = availableItems [0]; + } + + updateVerticalBar (); + /* + * If this is the focus control and the available item count is going from 0->!0 or !0->0 + * then the receiver must be redrawn to ensure that its boundary focus ring is updated. + */ + if ((oldCount == 0 || availableItemsCount == 0) && isFocusControl ()) { + redraw (); + return; + } + redrawItems (redrawStart, redrawEnd, false); +} +boolean setItemHeight (int value) { + boolean update = !customHeightSet || itemHeight < value; + if (update) itemHeight = value; + return update; +} +/** + * Marks the receiver's lines as visible if the argument is <code>true</code>, + * and marks it invisible otherwise. Note that some platforms draw + * grid lines while others may draw alternating row colors. + * <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 show 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> + * + * @since 3.1 + */ +public void setLinesVisible (boolean value) { + checkWidget (); + if (linesVisible == value) return; /* no change */ + linesVisible = value; + redraw (); +} +public void setMenu (Menu menu) { + super.setMenu (menu); + header.setMenu (menu); +} +public void setRedraw (boolean value) { + checkWidget(); + if (value) { + if (--drawCount == 0) { + if (availableItems.length - availableItemsCount > 3) { + TreeItem[] newAvailableItems = new TreeItem [availableItemsCount]; + System.arraycopy (availableItems, 0, newAvailableItems, 0, availableItemsCount); + availableItems = newAvailableItems; + } + updateVerticalBar (); + updateHorizontalBar (); + } + } else { + drawCount++; + } + super.setRedraw (value); + header.setRedraw (value); +} +/** + * 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 item 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> + * + * @since 3.2 + */ +public void setSelection (TreeItem item) { + checkWidget (); + if (item == null) error (SWT.ERROR_NULL_ARGUMENT); + setSelection (new TreeItem [] {item}, true); +} +/** + * 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 items 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[] items) { + checkWidget (); + if (items == null) error (SWT.ERROR_NULL_ARGUMENT); + setSelection (items, true); +} + +void setSelection (TreeItem[] items, boolean updateViewport) { + if (items.length == 0 || ((style & SWT.SINGLE) != 0 && items.length > 1)) { + deselectAll (); + return; + } + TreeItem[] oldSelection = selectedItems; + + /* remove null and duplicate items */ + int index = 0; + selectedItems = new TreeItem [items.length]; /* initially assume all valid items */ + for (int i = 0; i < items.length; i++) { + TreeItem item = items [i]; + if (item != null && item.parent == this && !item.isSelected ()) { + selectedItems [index++] = item; + } + } + if (index != items.length) { + /* an invalid item was provided so resize the array accordingly */ + TreeItem[] temp = new TreeItem [index]; + System.arraycopy (selectedItems, 0, temp, 0, index); + selectedItems = temp; + } + if (selectedItems.length == 0) { /* no valid items */ + deselectAll (); + return; + } + + for (int i = 0; i < oldSelection.length; i++) { + if (!oldSelection [i].isSelected ()) { + int availableIndex = oldSelection [i].availableIndex; + if (availableIndex != -1) { + redrawItem (availableIndex, true); + } + } + } + if (updateViewport) { + showItem (selectedItems [0]); + setFocusItem (selectedItems [0], true); + } + for (int i = 0; i < selectedItems.length; i++) { + int availableIndex = selectedItems [i].availableIndex; + if (availableIndex != -1) { + redrawItem (availableIndex, true); + } + } +} +/** + * 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> + * + * @since 3.2 + */ +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> + * + * @since 3.2 + */ +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); +} +/** + * 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 2.1 + */ +public void setTopItem (TreeItem item) { + checkWidget (); + if (item == null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + if (item.parent != this) return; + + /* item must be available */ + if (!item.isAvailable ()) item.parentItem.expandAncestors (); + + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + if (availableItemsCount < visibleItemCount) return; + int index = Math.min (item.availableIndex, availableItemsCount - visibleItemCount); + if (topIndex == index) return; + + update (); + int change = topIndex - index; + topIndex = index; + ScrollBar vBar = getVerticalBar (); + if (vBar != null) vBar.setSelection (topIndex); + if (drawCount <= 0) { + GC gc = new GC (this); + gc.copyArea (0, 0, clientArea.width, clientArea.height, 0, change * itemHeight); + gc.dispose (); + } +} +/** + * 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 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 3.1 + */ +public void showColumn (TreeColumn column) { + checkWidget (); + if (column == null) error (SWT.ERROR_NULL_ARGUMENT); + if (column.isDisposed ()) error(SWT.ERROR_INVALID_ARGUMENT); + if (column.parent != this) return; + + int x = column.getX (); + int rightX = x + column.width; + if (0 <= x && rightX <= clientArea.width) return; /* column is fully visible */ + + headerHideToolTip (); + int absX = 0; /* the X of the column irrespective of the horizontal scroll */ + TreeColumn[] orderedColumns = getOrderedColumns (); + for (int i = 0; i < column.getOrderIndex (); i++) { + absX += orderedColumns [i].width; + } + if (x < clientArea.x) { /* column is to left of viewport */ + horizontalOffset = absX; + } else { + horizontalOffset = absX + column.width - clientArea.width; + } + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) hBar.setSelection (horizontalOffset); + redraw (); + if (drawCount <= 0 && header.isVisible ()) header.redraw (); +} +/** + * 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.parent != this) return; + + /* item must be available */ + if (!item.isAvailable ()) item.parentItem.expandAncestors (); + + int index = item.availableIndex; + int visibleItemCount = (clientArea.height - getHeaderHeight ()) / itemHeight; + /* nothing to do if item is already in viewport */ + if (topIndex <= index && index < topIndex + visibleItemCount) return; + + if (index <= topIndex) { + /* item is above current viewport, so show on top */ + setTopItem (item); + } else { + /* item is below current viewport, so show on bottom */ + visibleItemCount = Math.max (visibleItemCount, 1); /* item to show should be top item */ + setTopItem (availableItems [Math.min (index - visibleItemCount + 1, availableItemsCount - 1)]); + } +} +/** + * 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 (selectedItems.length == 0) return; + showItem (selectedItems [0]); +} +void updateColumnWidth (TreeColumn column, int width) { + headerHideToolTip (); + int oldWidth = column.width; + int columnX = column.getX (); + int x = columnX + oldWidth - 1; /* -1 ensures that grid line is included */ + + update (); + GC gc = new GC (this); + gc.copyArea (x, 0, clientArea.width - x, clientArea.height, columnX + width - 1, 0); /* dest x -1 offsets x's -1 above */ + if (width > oldWidth) { + /* column width grew */ + int change = width - oldWidth + 1; /* +1 offsets x's -1 above */ + /* -1/+1 below ensure that right bound of selection redraws correctly in column */ + redraw (x - 1, 0, change + 1, clientArea.height, false); + } else { + int change = oldWidth - width + 1; /* +1 offsets x's -1 above */ + redraw (clientArea.width - change, 0, change, clientArea.height, false); + } + /* the focus box must be repainted because its stipple may become shifted as a result of its new width */ + if (focusItem != null) redrawItem (focusItem.availableIndex, true); + + GC headerGC = new GC (header); + if (drawCount <= 0 && header.getVisible ()) { + Rectangle headerBounds = header.getClientArea (); + header.update (); + x -= 1; /* -1 ensures that full header column separator is included */ + headerGC.copyArea (x, 0, headerBounds.width - x, headerBounds.height, columnX + width - 2, 0); /* dest x -2 offsets x's -1s above */ + if (width > oldWidth) { + /* column width grew */ + int change = width - oldWidth + 2; /* +2 offsets x's -1s above */ + header.redraw (x, 0, change, headerBounds.height, false); + } else { + int change = oldWidth - width + 2; /* +2 offsets x's -1s above */ + header.redraw (headerBounds.width - change, 0, change, headerBounds.height, false); + } + } + + column.width = width; + + /* + * Notify column and all items of column width change so that display labels + * can be recomputed if needed. + */ + column.updateWidth (headerGC); + headerGC.dispose (); + for (int i = 0; i < items.length; i++) { + items [i].updateColumnWidth (column, gc); + } + gc.dispose (); + + int maximum = 0; + for (int i = 0; i < columns.length; i++) { + maximum += columns [i].width; + } + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setMaximum (Math.max (1, maximum)); /* setting a value of 0 here is ignored */ + if (hBar.getThumb () != clientArea.width) { + hBar.setThumb (clientArea.width); + hBar.setPageIncrement (clientArea.width); + } + int oldHorizontalOffset = horizontalOffset; /* hBar.setVisible() can modify horizontalOffset */ + hBar.setVisible (clientArea.width < maximum); + int selection = hBar.getSelection (); + if (selection != oldHorizontalOffset) { + horizontalOffset = selection; + redraw (); + if (drawCount <= 0 && header.getVisible ()) header.redraw (); + } + } + + column.sendEvent (SWT.Resize); + TreeColumn[] orderedColumns = getOrderedColumns (); + for (int i = column.getOrderIndex () + 1; i < orderedColumns.length; i++) { + if (!orderedColumns [i].isDisposed ()) { + orderedColumns [i].sendEvent (SWT.Move); + } + } + + if (availableItemsCount == 0) redraw (); /* ensure that static focus rectangle updates properly */ +} +/* + * This is a naive implementation that computes the value from scratch. + */ +void updateHorizontalBar () { + if (drawCount > 0) return; + ScrollBar hBar = getHorizontalBar (); + if (hBar == null) return; + + int maxX = 0; + if (columns.length > 0) { + for (int i = 0; i < columns.length; i++) { + maxX += columns [i].width; + } + } else { + for (int i = 0; i < availableItemsCount; i++) { + Rectangle itemBounds = availableItems [i].getCellBounds (0); + maxX = Math.max (maxX, itemBounds.x + itemBounds.width + horizontalOffset); + } + } + + int clientWidth = clientArea.width; + if (maxX != hBar.getMaximum ()) { + hBar.setMaximum (Math.max (1, maxX)); /* setting a value of 0 here is ignored */ + } + int thumb = Math.min (clientWidth, maxX); + if (thumb != hBar.getThumb ()) { + hBar.setThumb (thumb); + hBar.setPageIncrement (thumb); + } + hBar.setVisible (clientWidth < maxX); + + /* reclaim any space now left on the right */ + if (maxX < horizontalOffset + thumb) { + horizontalOffset = maxX - thumb; + hBar.setSelection (horizontalOffset); + redraw (); + } else { + int selection = hBar.getSelection (); + if (selection != horizontalOffset) { + horizontalOffset = selection; + redraw (); + } + } +} +/* + * Update the horizontal bar, if needed, in response to an item change (eg.- created, + * disposed, expanded, etc.). newRightX is the new rightmost X value of the item, + * and rightXchange is the change that led to the item's rightmost X value becoming + * newRightX (so oldRightX + rightXchange = newRightX) + */ +void updateHorizontalBar (int newRightX, int rightXchange) { + if (drawCount > 0) return; + ScrollBar hBar = getHorizontalBar (); + if (hBar == null) return; + + newRightX += horizontalOffset; + int barMaximum = hBar.getMaximum (); + if (newRightX > barMaximum) { /* item has extended beyond previous maximum */ + hBar.setMaximum (newRightX); + int clientAreaWidth = clientArea.width; + int thumb = Math.min (newRightX, clientAreaWidth); + if (hBar.getThumb () != thumb) { + hBar.setThumb (thumb); + hBar.setPageIncrement (thumb); + } + hBar.setVisible (clientAreaWidth <= newRightX); + return; + } + + int previousRightX = newRightX - rightXchange; + if (previousRightX != barMaximum) { + /* this was not the rightmost item, so just check for client width change */ + int clientAreaWidth = clientArea.width; + int thumb = Math.min (barMaximum, clientAreaWidth); + if (hBar.getThumb () != thumb) { + hBar.setThumb (thumb); + hBar.setPageIncrement (thumb); + } + hBar.setVisible (clientAreaWidth <= barMaximum); + return; + } + updateHorizontalBar (); /* must search for the new rightmost item */ +} +void updateVerticalBar () { + if (drawCount > 0) return; + ScrollBar vBar = getVerticalBar (); + if (vBar == null) return; + + int pageSize = (clientArea.height - getHeaderHeight ()) / itemHeight; + int maximum = Math.max (1, availableItemsCount); + if (maximum != vBar.getMaximum ()) { + vBar.setMaximum (maximum); + } + int thumb = Math.min (pageSize, maximum); + if (thumb != vBar.getThumb ()) { + vBar.setThumb (thumb); + vBar.setPageIncrement (thumb); + } + vBar.setVisible (pageSize < maximum); + + /* reclaim any space now left on the bottom */ + if (maximum < topIndex + thumb) { + topIndex = maximum - thumb; + vBar.setSelection (topIndex); + redraw (); + } else { + int selection = vBar.getSelection (); + if (selection != topIndex) { + topIndex = selection; + redraw (); + } + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TreeColumn.java b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TreeColumn.java new file mode 100644 index 0000000000..41d1715a87 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TreeColumn.java @@ -0,0 +1,738 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; + +/** + * Instances of this class represent a column in a tree widget. + * <p><dl> + * <dt><b>Styles:</b></dt> + * <dd>LEFT, RIGHT, CENTER</dd> + * <dt><b>Events:</b></dt> + * <dd> Move, Resize, Selection</dd> + * </dl> + * </p><p> + * Note: Only one of the styles LEFT, RIGHT and CENTER may be specified. + * </p><p> + * IMPORTANT: This class is <em>not</em> intended to be subclassed. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#tree">Tree, TreeItem, TreeColumn snippets</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 3.1 + * @noextend This class is not intended to be subclassed by clients. + */ +public class TreeColumn extends Item { + Tree parent; + String displayText = ""; + int width, itemImageWidth; + boolean moveable, resizable = true; + int sort = SWT.NONE; + String toolTipText; + +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>Tree</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 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#LEFT + * @see SWT#RIGHT + * @see SWT#CENTER + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public TreeColumn (Tree parent, int style) { + this (parent, style, checkNull (parent).columns.length); +} +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>Tree</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> + * <p> + * Note that due to a restriction on some platforms, the first column + * is always left aligned. + * </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 + * @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#LEFT + * @see SWT#RIGHT + * @see SWT#CENTER + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public TreeColumn (Tree parent, int style, int index) { + super (parent, checkStyle (style), index); + if (!(0 <= index && index <= parent.columns.length)) error (SWT.ERROR_INVALID_RANGE); + this.parent = parent; + parent.createItem (this, index); +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the control is moved or resized, by sending + * it one of the messages defined in the <code>ControlListener</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 ControlListener + * @see #removeControlListener + */ +public void addControlListener (ControlListener listener) { + checkWidget (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Resize, typedListener); + addListener (SWT.Move, typedListener); +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the control is selected by the user, by sending + * it one of the messages defined in the <code>SelectionListener</code> + * interface. + * <p> + * <code>widgetSelected</code> is called when the column header is selected. + * <code>widgetDefaultSelected</code> is not called. + * </p> + * + * @param listener the listener which should be notified when the control is selected by the user + * + * @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 (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Selection, typedListener); + addListener (SWT.DefaultSelection, typedListener); +} +static Tree checkNull (Tree tree) { + if (tree == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + return tree; +} +static int checkStyle (int style) { + return checkBits (style, SWT.LEFT, SWT.CENTER, SWT.RIGHT, 0, 0, 0); +} +protected void checkSubclass () { + if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS); +} +void computeDisplayText (GC gc) { + int availableWidth = width - 2 * parent.getHeaderPadding (); + if (image != null) { + availableWidth -= image.getBounds ().width; + availableWidth -= Tree.MARGIN_IMAGE; + } + if (sort != SWT.NONE) { + availableWidth -= parent.arrowBounds.width; + availableWidth -= Tree.MARGIN_IMAGE; + } + String text = this.text; + int textWidth = gc.textExtent (text, SWT.DRAW_MNEMONIC).x; + if (textWidth <= availableWidth) { + displayText = text; + return; + } + + /* Ellipsis will be needed, so subtract their width from the available text width */ + int ellipsisWidth = gc.stringExtent (Tree.ELLIPSIS).x; + availableWidth -= ellipsisWidth; + if (availableWidth <= 0) { + displayText = Tree.ELLIPSIS; + return; + } + + /* Make initial guess. */ + int index = Math.min (availableWidth / gc.getFontMetrics ().getAverageCharWidth (), text.length ()); + textWidth = gc.textExtent (text.substring (0, index), SWT.DRAW_MNEMONIC).x; + + /* Initial guess is correct. */ + if (availableWidth == textWidth) { + displayText = text.substring (0, index) + Tree.ELLIPSIS; + return; + } + + /* Initial guess is too high, so reduce until fit is found. */ + if (availableWidth < textWidth) { + do { + index--; + if (index < 0) { + displayText = Tree.ELLIPSIS; + return; + } + text = text.substring (0, index); + textWidth = gc.textExtent (text, SWT.DRAW_MNEMONIC).x; + } while (availableWidth < textWidth); + displayText = text + Tree.ELLIPSIS; + return; + } + + /* Initial guess is too low, so increase until overrun is found. */ + while (textWidth < availableWidth) { + index++; + textWidth = gc.textExtent (text.substring (0, index), SWT.DRAW_MNEMONIC).x; + } + displayText = text.substring (0, index - 1) + Tree.ELLIPSIS; +} +public void dispose () { + if (isDisposed ()) return; + Rectangle parentBounds = parent.clientArea; + int x = getX (); + Tree parent = this.parent; + dispose (true); + int width = parentBounds.width - x; + parent.redraw (x, 0, width, parentBounds.height, false); + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + parent.header.redraw (x, 0, width, parent.getHeaderHeight (), false); + } +} +void dispose (boolean notifyParent) { + super.dispose (); /* super is intentional here */ + if (notifyParent) parent.destroyItem (this); + parent = null; +} +/** + * Returns a value which describes the position of the + * text or image in the receiver. The value will be one of + * <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>. + * + * @return the alignment + * + * @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 getAlignment () { + checkWidget (); + if ((style & SWT.CENTER) != 0) return SWT.CENTER; + if ((style & SWT.RIGHT) != 0) return SWT.RIGHT; + return SWT.LEFT; +} +/* + * Returns the width of the header's content + * (image + text + sort arrow + internal margins) + */ +int getContentWidth (GC gc, boolean useDisplayText) { + int contentWidth = 0; + String text = useDisplayText ? displayText : this.text; + if (text.length () > 0) { + contentWidth += gc.textExtent (text, SWT.DRAW_MNEMONIC).x; + } + if (image != null) { + contentWidth += image.getBounds ().width; + if (text.length () > 0) contentWidth += Tree.MARGIN_IMAGE; + } + if (sort != SWT.NONE) { + contentWidth += parent.arrowBounds.width; + if (text.length () > 0 || image != null) { + contentWidth += Tree.MARGIN_IMAGE; + } + } + return contentWidth; +} +int getIndex () { + TreeColumn[] columns = parent.columns; + for (int i = 0; i < columns.length; i++) { + if (columns [i] == this) return i; + } + return -1; +} +/** + * Gets the moveable attribute. A column that is + * not moveable cannot be reordered by the user + * by dragging the header but may be reordered + * by the programmer. + * + * @return the moveable attribute + * + * @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#setMoveable(boolean) + * @see SWT#Move + * + * @since 3.2 + */ +public boolean getMoveable () { + checkWidget (); + return moveable; +} +int getOrderIndex () { + TreeColumn[] orderedColumns = parent.orderedColumns; + if (orderedColumns == null) return getIndex (); + for (int i = 0; i < orderedColumns.length; i++) { + if (orderedColumns [i] == this) return i; + } + return -1; +} +/** + * 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; +} +int getPreferredWidth () { + if (!parent.getHeaderVisible ()) return 0; + GC gc = new GC (parent); + int result = getContentWidth (gc, false); + gc.dispose (); + return result + 2 * parent.getHeaderPadding (); +} +/** + * Gets the resizable attribute. A column that is + * not resizable cannot be dragged by the user but + * may be resized by the programmer. + * + * @return the resizable attribute + * + * @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 getResizable () { + checkWidget (); + return resizable; +} +/** + * Returns the receiver's tool tip text, or null if it has + * not been set. + * + * @return the receiver's tool tip 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> + * + * @since 3.2 + */ +public String getToolTipText () { + checkWidget (); + return toolTipText; +} +/** + * Gets the width of the receiver. + * + * @return the width + * + * @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 getWidth () { + checkWidget (); + return width; +} +int getX () { + TreeColumn[] orderedColumns = parent.getOrderedColumns (); + int index = getOrderIndex (); + int result = -parent.horizontalOffset; + for (int i = 0; i < index; i++) { + result += orderedColumns [i].width; + } + return result; +} +void paint (GC gc) { + int padding = parent.getHeaderPadding (); + + int x = getX (); + int startX = x + padding; + if (getOrderIndex () != 0 && (style & SWT.LEFT) == 0) { + int contentWidth = getContentWidth (gc, true); + if ((style & SWT.RIGHT) != 0) { + startX = Math.max (startX, x + width - padding - contentWidth); + } else { /* SWT.CENTER */ + startX = Math.max (startX, x + (width - contentWidth) / 2); + } + } + int headerHeight = parent.getHeaderHeight (); + + /* restrict the clipping region to the header cell */ + gc.setClipping ( + x + padding, + padding, + width - 2 * padding, + headerHeight - 2 * padding); + + if (image != null) { + Rectangle imageBounds = image.getBounds (); + int drawHeight = Math.min (imageBounds.height, headerHeight - 2 * padding); + gc.drawImage ( + image, + 0, 0, + imageBounds.width, imageBounds.height, + startX, (headerHeight - drawHeight) / 2, + imageBounds.width, drawHeight); + startX += imageBounds.width; + startX += Tree.MARGIN_IMAGE; + } + if (displayText.length () > 0) { + gc.setForeground (display.getSystemColor (SWT.COLOR_BLACK)); + int fontHeight = parent.fontHeight; + gc.drawText (displayText, startX, (headerHeight - fontHeight) / 2, SWT.DRAW_MNEMONIC); + startX += gc.textExtent (displayText, SWT.DRAW_MNEMONIC).x + Tree.MARGIN_IMAGE; + } + if (sort != SWT.NONE) { + Image image = sort == SWT.DOWN ? parent.getArrowDownImage () : parent.getArrowUpImage (); + int y = (headerHeight - parent.arrowBounds.height) / 2; + gc.drawImage (image, startX, y); + } +} +/** + * Causes the receiver to be resized to its preferred size. + * For a composite, this involves computing the preferred size + * from its layout, if there is one. + * + * @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 pack () { + checkWidget (); + TreeItem[] availableItems = parent.availableItems; + int index = getIndex (); + int newWidth = getPreferredWidth (); + for (int i = 0; i < parent.availableItemsCount; i++) { + int width = availableItems [i].getPreferredWidth (index); + /* ensure that receiver and parent were not disposed in a callback */ + if (parent.isDisposed () || isDisposed ()) return; + if (!availableItems [i].isDisposed ()) { + newWidth = Math.max (newWidth, width); + } + } + if (newWidth != width) parent.updateColumnWidth (this, newWidth); +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the control is moved or resized. + * + * @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 ControlListener + * @see #addControlListener + */ +public void removeControlListener (ControlListener listener) { + checkWidget (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + if (eventTable == null) return; + eventTable.unhook (SWT.Move, listener); + eventTable.unhook (SWT.Resize, listener); +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the control is selected by the user. + * + * @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 (); + if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); + removeListener (SWT.Selection, listener); + removeListener (SWT.DefaultSelection, listener); +} +/** + * Controls how text and images will be displayed in the receiver. + * The argument should be one of <code>LEFT</code>, <code>RIGHT</code> + * or <code>CENTER</code>. + * <p> + * Note that due to a restriction on some platforms, the first column + * is always left aligned. + * </p> + * @param alignment the new alignment + * + * @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 setAlignment (int alignment) { + checkWidget (); + if ((alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER)) == 0) return; + alignment = checkBits (alignment, SWT.LEFT, SWT.CENTER, SWT.RIGHT, 0, 0, 0); + if ((style & alignment) != 0) return; /* same value */ + style &= ~(SWT.LEFT | SWT.CENTER | SWT.RIGHT); + style |= alignment; + if (getOrderIndex () == 0) return; /* no update needed since first ordered column appears left-aligned */ + int x = getX (); + parent.redraw (x, 0, width, parent.clientArea.height, false); + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + /* don't damage the header's drawn borders */ + parent.header.redraw (x, 1, width - 2, parent.getHeaderHeight () - 3, false); + } +} +public void setImage (Image value) { + checkWidget (); + if (value == image) return; + if (value != null && value.equals (image)) return; /* same value */ + super.setImage (value); + + /* An image width change may affect the space available for the column's displayText. */ + GC gc = new GC (parent); + computeDisplayText (gc); + gc.dispose (); + + /* + * If this is the first image being put into the header then the header + * height may be adjusted, in which case a full redraw is needed. + */ + if (parent.headerImageHeight == 0) { + int oldHeaderHeight = parent.getHeaderHeight (); + parent.setHeaderImageHeight (value.getBounds ().height); + if (oldHeaderHeight != parent.getHeaderHeight ()) { + /* parent header height changed */ + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + parent.header.redraw (); + } + parent.redraw (); + return; + } + } + + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + /* don't damage the header's drawn borders */ + parent.header.redraw (getX (), 1, width - 2, parent.getHeaderHeight () - 3, false); + } +} +/** + * Sets the moveable attribute. A column that is + * moveable can be reordered by the user by dragging + * the header. A column that is not moveable cannot be + * dragged by the user but may be reordered + * by the programmer. + * + * @param moveable the moveable attribute + * + * @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 Tree#getColumnOrder() + * @see TreeColumn#getMoveable() + * @see SWT#Move + * + * @since 3.2 + */ +public void setMoveable (boolean moveable) { + checkWidget (); + this.moveable = moveable; +} +/** + * Sets the resizable attribute. A column that is + * not resizable cannot be dragged by the user but + * may be resized by the programmer. + * + * @param resizable the resize attribute + * + * @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 setResizable (boolean value) { + checkWidget (); + resizable = value; +} +void setSortDirection (int value) { + if (value == sort) return; + boolean widthChange = value == SWT.NONE || sort == SWT.NONE; + sort = value; + if (widthChange) { + /* + * adding/removing the sort arrow decreases/increases the width that is + * available for the column's header text, so recompute the display text + */ + GC gc = new GC (parent); + computeDisplayText (gc); + gc.dispose (); + } + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + /* don't damage the header's drawn borders */ + parent.header.redraw (getX (), 1, width - 2, parent.getHeaderHeight () - 3, false); + } +} +public void setText (String value) { + checkWidget (); + if (value == null) error (SWT.ERROR_NULL_ARGUMENT); + if (value.equals (text)) return; /* same value */ + super.setText (value); + GC gc = new GC (parent); + computeDisplayText (gc); + gc.dispose (); + if (parent.drawCount <= 0 && parent.getHeaderVisible ()) { + /* don't damage the header's drawn borders */ + parent.header.redraw (getX (), 1, width - 2, parent.getHeaderHeight () - 3, false); + } +} +/** + * Sets the receiver's tool tip text to the argument, which + * may be null indicating that the default tool tip for the + * control will be shown. For a control that has a default + * tool tip, such as the Tree control on Windows, setting + * the tool tip text to an empty string replaces the default, + * causing no tool tip text to be shown. + * <p> + * The mnemonic indicator (character '&') is not displayed in a tool tip. + * To display a single '&' in the tool tip, the character '&' can be + * escaped by doubling it in the string. + * </p> + * + * @param string the new tool tip text (or null) + * + * @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 3.2 + */ +public void setToolTipText (String string) { + checkWidget (); + if (toolTipText == string) return; + if (toolTipText != null && toolTipText.equals (string)) return; + toolTipText = string; + if (parent.toolTipShell == null) return; /* tooltip not currently showing */ + if (((Integer) parent.toolTipShell.getData ()).intValue () != getIndex ()) return; /* tooltip showing for different column */ + parent.headerUpdateToolTip (getX () + (width / 2)); /* update the tooltip text */ +} +/** + * Sets the width of the receiver. + * + * @param width the new width + * + * @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 setWidth (int value) { + checkWidget (); + if (value < 0) return; + if (width == value) return; /* same value */ + parent.updateColumnWidth (this, value); +} +/* + * Perform any internal changes necessary to reflect a changed width. + */ +void updateWidth (GC gc) { + String oldDisplayText = displayText; + computeDisplayText (gc); + /* the header must be damaged if the display text has changed or if the alignment is not LEFT */ + if (parent.getHeaderVisible ()) { + if ((style & SWT.LEFT) == 0 || !oldDisplayText.equals (displayText)) { + int padding = parent.getHeaderPadding (); + parent.header.redraw (getX () + padding, 0, width - padding, parent.getHeaderHeight (), false); + } + } +} +void updateFont (GC gc) { + computeDisplayText (gc); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TreeItem.java b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TreeItem.java new file mode 100644 index 0000000000..f762439f45 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/emulated/treetable/org/eclipse/swt/widgets/TreeItem.java @@ -0,0 +1,2959 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.Compatibility; + +/** + * 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> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#tree">Tree, TreeItem, TreeColumn snippets</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ +public class TreeItem extends Item { + Tree parent; + TreeItem parentItem; + TreeItem[] items = Tree.NO_ITEMS; + int availableIndex = -1; /* index in parent's flat list of available (though not necessarily within viewport) items */ + int depth = 0; /* cached for performance, does not change after instantiation */ + boolean checked, grayed, expanded, cached; + + String[] texts; + int[] textWidths = new int [1]; /* cached string measurements */ + int customWidth = -1; /* width specified by Measure callback */ + int fontHeight; /* cached item font height */ + int[] fontHeights; + Image[] images; + Color foreground, background; + String[] displayTexts; + Color[] cellForegrounds, cellBackgrounds; + Font font; + Font[] cellFonts; + + static final int INDENT_HIERARCHY = 6; /* the margin between an item's expander and its checkbox or content */ + static final int MARGIN_TEXT = 3; /* the left and right margins within the text's space */ + +/** + * 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, style, checkNull (parent).items.length); +} +/** + * 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, 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, style, checkNull (parentItem).items.length); +} +/** + * 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, style, index, true); +} +TreeItem (TreeItem parentItem, int style, int index, boolean notifyParent) { + super (parentItem, style); + this.parentItem = parentItem; + parent = parentItem.parent; + depth = parentItem.depth + 1; + int validItemIndex = parentItem.items.length; + if (!(0 <= index && index <= validItemIndex)) error (SWT.ERROR_INVALID_RANGE); + int columnCount = parent.columns.length; + if (columnCount > 0) { + displayTexts = new String [columnCount]; + if (columnCount > 1) { + texts = new String [columnCount]; + textWidths = new int [columnCount]; + images = new Image [columnCount]; + } + } + if (notifyParent) parentItem.addItem (this, index); +} +TreeItem (Tree parent, int style, int index, boolean notifyParent) { + super (parent, style); + int validItemIndex = parent.items.length; + if (!(0 <= index && index <= validItemIndex)) error (SWT.ERROR_INVALID_RANGE); + this.parent = parent; + int columnCount = parent.columns.length; + if (columnCount > 0) { + displayTexts = new String [columnCount]; + if (columnCount > 1) { + texts = new String [columnCount]; + textWidths = new int [columnCount]; + images = new Image [columnCount]; + } + } + if (notifyParent) parent.createItem (this, index); +} +/* + * Updates internal structures in the receiver and its child items to handle the creation of a new column. + */ +void addColumn (TreeColumn column) { + int index = column.getIndex (); + int columnCount = parent.columns.length; + + if (columnCount > 1) { + if (columnCount == 2) { + texts = new String [2]; + } else { + String[] newTexts = new String [columnCount]; + System.arraycopy (texts, 0, newTexts, 0, index); + System.arraycopy (texts, index, newTexts, index + 1, columnCount - index - 1); + texts = newTexts; + } + if (index == 0) { + texts [1] = text; + text = ""; //$NON-NLS-1$ + } + + if (columnCount == 2) { + images = new Image [2]; + } else { + Image[] newImages = new Image [columnCount]; + System.arraycopy (images, 0, newImages, 0, index); + System.arraycopy (images, index, newImages, index + 1, columnCount - index - 1); + images = newImages; + } + if (index == 0) { + images [1] = image; + image = null; + } + + int[] newTextWidths = new int [columnCount]; + System.arraycopy (textWidths, 0, newTextWidths, 0, index); + System.arraycopy (textWidths, index, newTextWidths, index + 1, columnCount - index - 1); + textWidths = newTextWidths; + } else { + customWidth = -1; /* columnCount == 1 */ + } + + /* + * The length of displayTexts always matches the parent's column count, unless this + * count is zero, in which case displayTexts is null. + */ + String[] newDisplayTexts = new String [columnCount]; + if (columnCount > 1) { + System.arraycopy (displayTexts, 0, newDisplayTexts, 0, index); + System.arraycopy (displayTexts, index, newDisplayTexts, index + 1, columnCount - index - 1); + } + displayTexts = newDisplayTexts; + + if (cellBackgrounds != null) { + Color[] newCellBackgrounds = new Color [columnCount]; + System.arraycopy (cellBackgrounds, 0, newCellBackgrounds, 0, index); + System.arraycopy (cellBackgrounds, index, newCellBackgrounds, index + 1, columnCount - index - 1); + cellBackgrounds = newCellBackgrounds; + } + if (cellForegrounds != null) { + Color[] newCellForegrounds = new Color [columnCount]; + System.arraycopy (cellForegrounds, 0, newCellForegrounds, 0, index); + System.arraycopy (cellForegrounds, index, newCellForegrounds, index + 1, columnCount - index - 1); + cellForegrounds = newCellForegrounds; + } + if (cellFonts != null) { + Font[] newCellFonts = new Font [columnCount]; + System.arraycopy (cellFonts, 0, newCellFonts, 0, index); + System.arraycopy (cellFonts, index, newCellFonts, index + 1, columnCount - index - 1); + cellFonts = newCellFonts; + + int[] newFontHeights = new int [columnCount]; + System.arraycopy (fontHeights, 0, newFontHeights, 0, index); + System.arraycopy (fontHeights, index, newFontHeights, index + 1, columnCount - index - 1); + fontHeights = newFontHeights; + } + + int orderedIndex = column.getOrderIndex (); + if (orderedIndex == 0 && columnCount > 1) { + /* + * The new second ordered column now has more space available to it than it did while + * it was the first ordered column since it no longer has to show hierarchy decorations, + * so recompute its displayText. + */ + TreeColumn[] orderedColumns = parent.getOrderedColumns (); + int secondColumnIndex = orderedColumns [1].getIndex (); + GC gc = new GC (parent); + gc.setFont (getFont (secondColumnIndex, false)); + computeDisplayText (secondColumnIndex, gc); + gc.dispose (); + } + + /* notify all child items as well */ + for (int i = 0; i < items.length; i++) { + items[i].addColumn (column); + } +} +/* + * Adds a child item to the receiver. + */ +void addItem (TreeItem item, int index) { + TreeItem[] newChildren = new TreeItem [items.length + 1]; + System.arraycopy (items, 0, newChildren, 0, index); + newChildren [index] = item; + System.arraycopy (items, index, newChildren, index + 1, items.length - index); + items = newChildren; + + if (!item.isAvailable ()) { + /* receiver will now need an expander box if this is its first child */ + if (isInViewport () && items.length == 1) { + Rectangle bounds = getExpanderBounds (); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } + return; + } + + /* item should be available immediately so update parent */ + parent.makeAvailable (item); + + /* update scrollbars */ + Rectangle bounds = item.getBounds (false); + int rightX = bounds.x + bounds.width; + parent.updateHorizontalBar (rightX, rightX); + parent.updateVerticalBar (); + /* + * If new item is above viewport then adjust topIndex and the vertical scrollbar + * so that the current viewport items will not change. + */ + if (item.availableIndex < parent.topIndex) { + parent.topIndex++; + ScrollBar vBar = parent.getVerticalBar (); + if (vBar != null) vBar.setSelection (parent.topIndex); + return; + } + + parent.redrawFromItemDownwards (availableIndex); +} +static Tree checkNull (Tree tree) { + if (tree == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + return tree; +} +static TreeItem checkNull (TreeItem item) { + if (item == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + return item; +} +protected void checkSubclass () { + if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS); +} +void clear () { + checked = grayed = false; + texts = null; + textWidths = new int [1]; + fontHeight = 0; + fontHeights = null; + images = null; + foreground = background = null; + displayTexts = null; + cellForegrounds = cellBackgrounds = null; + font = null; + cellFonts = null; + cached = false; + text = ""; + image = null; + + int columnCount = parent.columns.length; + if (columnCount > 0) { + displayTexts = new String [columnCount]; + if (columnCount > 1) { + texts = new String [columnCount]; + textWidths = new int [columnCount]; + images = new Image [columnCount]; + } + } +} +/** + * 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 all <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 + * + * @since 3.2 + */ +public void clear (int index, boolean recursive) { + checkWidget (); + if (!(0 <= index && index < items.length)) error (SWT.ERROR_INVALID_RANGE); + TreeItem item = items [index]; + + /* if there are no columns then the horizontal scrollbar may need adjusting */ + TreeItem[] availableDescendents = null; + int oldRightX = 0; + if (item.availableIndex != -1 && parent.columns.length == 0) { + if (recursive) { + availableDescendents = item.computeAvailableDescendents (); + for (int i = 0; i < availableDescendents.length; i++) { + Rectangle bounds = availableDescendents [i].getBounds (false); + oldRightX = Math.max (oldRightX, bounds.x + bounds.width); + } + } else { + Rectangle bounds = item.getBounds (false); + oldRightX = bounds.x + bounds.width; + } + } + + /* clear the item(s) */ + item.clear (); + if (recursive) { + item.clearAll (true, false); + } + if (item.availableIndex == -1) return; /* no visual update needed */ + + /* adjust the horizontal scrollbar if needed */ + if (parent.columns.length == 0) { + int newRightX = 0; + if (recursive) { + for (int i = 0; i < availableDescendents.length; i++) { + Rectangle bounds = availableDescendents [i].getBounds (false); + newRightX = Math.max (newRightX, bounds.x + bounds.width); + } + } else { + Rectangle bounds = item.getBounds (false); + newRightX = bounds.x + bounds.width; + } + parent.updateHorizontalBar (newRightX, newRightX - oldRightX); + } + + /* redraw the item(s) */ + if (recursive) { + int descendentCount = availableDescendents == null ? + item.computeAvailableDescendentCount () : + availableDescendents.length; + parent.redrawItems (item.availableIndex, item.availableIndex + descendentCount - 1, false); + } else { + parent.redrawItem (item.availableIndex, false); + } +} +/** + * 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 all <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 + * + * @since 3.2 + */ +public void clearAll (boolean recursive) { + clearAll (recursive, true); +} +void clearAll (boolean recursive, boolean doVisualUpdate) { + checkWidget (); + if (items.length == 0) return; + + /* if there are no columns then the horizontal scrollbar may need adjusting */ + TreeItem[] availableDescendents = null; + int oldRightX = 0; + if (doVisualUpdate && availableIndex != -1 && expanded && parent.columns.length == 0) { + if (recursive) { + availableDescendents = computeAvailableDescendents (); + /* + * i starts at 1 here because item 0 in availableDescendents + * will be the receiver, but this item is not being cleared. + */ + for (int i = 1; i < availableDescendents.length; i++) { + Rectangle bounds = availableDescendents [i].getBounds (false); + oldRightX = Math.max (oldRightX, bounds.x + bounds.width); + } + } else { + for (int i = 0; i < items.length; i++) { + Rectangle bounds = items [i].getBounds (false); + oldRightX = Math.max (oldRightX, bounds.x + bounds.width); + } + } + } + + /* clear the item(s) */ + for (int i = 0; i < items.length; i++) { + items [i].clear (); + if (recursive) items [i].clearAll (true, false); + } + + if (!doVisualUpdate || availableIndex == -1 || !expanded) return; /* no visual update needed */ + + /* adjust the horizontal scrollbar if needed */ + if (parent.columns.length == 0) { + int newRightX = 0; + if (recursive) { + /* + * i starts at 1 here because item 0 in availableDescendents + * is the receiver, but this item was not cleared. + */ + for (int i = 1; i < availableDescendents.length; i++) { + Rectangle bounds = availableDescendents [i].getBounds (false); + newRightX = Math.max (newRightX, bounds.x + bounds.width); + } + } else { + /* + * All cleared direct child items will have the same x and width + * values now, so just measure the first one as a sample. + */ + Rectangle bounds = items [0].getBounds (false); + newRightX = bounds.x + bounds.width; + } + parent.updateHorizontalBar (newRightX, newRightX - oldRightX); + } + + /* redraw the item(s) */ + if (recursive) { + int startIndex = items [0].availableIndex; + TreeItem lastChild = items [items.length - 1]; + int endIndex = lastChild.availableIndex + lastChild.computeAvailableDescendentCount () - 1; + parent.redrawItems (startIndex, endIndex, false); + } else { + for (int i = 0; i < items.length; i++) { + parent.redrawItem (items [i].availableIndex, false); + } + } +} +/* + * Returns a collection of all tree items descending from the receiver, including + * the receiver. The order of the items in this collection are receiver, child0tree, + * child1tree, ..., childNtree. + */ +TreeItem[] computeAllDescendents () { + int childCount = items.length; + TreeItem[][] childResults = new TreeItem [childCount][]; + int count = 1; /* receiver */ + for (int i = 0; i < childCount; i++) { + childResults [i] = items [i].computeAllDescendents (); + count += childResults [i].length; + } + TreeItem[] result = new TreeItem [count]; + int index = 0; + result [index++] = this; + for (int i = 0; i < childCount; i++) { + System.arraycopy (childResults [i], 0, result, index, childResults [i].length); + index += childResults [i].length; + } + return result; +} +/* + * Returns the number of tree items descending from the receiver, including the + * receiver, that are currently available. It is assumed that the receiver is + * currently available. + */ +int computeAvailableDescendentCount () { + int result = 1; /* receiver */ + if (!expanded) return result; + for (int i = 0; i < items.length; i++) { + result += items [i].computeAvailableDescendentCount (); + } + return result; +} +/* + * Returns a collection of the tree items descending from the receiver, including + * the receiver, that are currently available. It is assumed that the receiver is + * currently available. The order of the items in this collection are receiver, + * child0tree, child1tree, ..., childNtree. + */ +TreeItem[] computeAvailableDescendents () { + if (!expanded) return new TreeItem[] {this}; + int childCount = items.length; + TreeItem[][] childResults = new TreeItem [childCount][]; + int count = 1; /* receiver */ + for (int i = 0; i < childCount; i++) { + childResults [i] = items [i].computeAvailableDescendents (); + count += childResults [i].length; + } + TreeItem[] result = new TreeItem [count]; + int index = 0; + result [index++] = this; + for (int i = 0; i < childCount; i++) { + System.arraycopy (childResults [i], 0, result, index, childResults [i].length); + index += childResults [i].length; + } + return result; +} +void computeDisplayText (int columnIndex, GC gc) { + if ((parent.style & SWT.VIRTUAL) != 0 && !cached) return; /* nothing to do */ + + int columnCount = parent.columns.length; + if (columnCount == 0) { + String text = getText (0, false); + textWidths [columnIndex] = gc.stringExtent (text).x; + return; + } + + int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex (); + TreeColumn column = parent.columns [columnIndex]; + int availableWidth; + if (orderedIndex == 0) { + /* ordered column 0 is always LEFT and must consider hierarchy decorations */ + availableWidth = column.getX () + column.width - getTextX (columnIndex) - 2 * MARGIN_TEXT; + } else { + /* ordered columns > 0 may not be LEFT so cannot use getTextX (int) */ + availableWidth = column.width - 2 * parent.getCellPadding () - 2 * MARGIN_TEXT; + if (images [columnIndex] != null) { + availableWidth -= images [columnIndex].getBounds ().width; + availableWidth -= Tree.MARGIN_IMAGE; + } + } + String text = getText (columnIndex, false); + int textWidth = gc.stringExtent (text).x; + if (textWidth <= availableWidth) { + displayTexts [columnIndex] = text; + textWidths [columnIndex] = textWidth; + return; + } + + /* Ellipsis will be needed, so subtract their width from the available text width */ + int ellipsisWidth = gc.stringExtent (Tree.ELLIPSIS).x; + availableWidth -= ellipsisWidth; + if (availableWidth <= 0) { + displayTexts [columnIndex] = Tree.ELLIPSIS; + textWidths [columnIndex] = ellipsisWidth; + return; + } + + /* Make initial guess. */ + int index = Math.min (availableWidth / gc.getFontMetrics ().getAverageCharWidth (), text.length ()); + textWidth = gc.stringExtent (text.substring (0, index)).x; + + /* Initial guess is correct. */ + if (availableWidth == textWidth) { + displayTexts [columnIndex] = text.substring (0, index) + Tree.ELLIPSIS; + textWidths [columnIndex] = textWidth + ellipsisWidth; + return; + } + + /* Initial guess is too high, so reduce until fit is found. */ + if (availableWidth < textWidth) { + do { + index--; + if (index < 0) { + displayTexts [columnIndex] = Tree.ELLIPSIS; + textWidths [columnIndex] = ellipsisWidth; + return; + } + text = text.substring (0, index); + textWidth = gc.stringExtent (text).x; + } while (availableWidth < textWidth); + displayTexts [columnIndex] = text + Tree.ELLIPSIS; + textWidths [columnIndex] = textWidth + ellipsisWidth; + return; + } + + /* Initial guess is too low, so increase until overrun is found. */ + int previousWidth = 0; + while (textWidth < availableWidth) { + index++; + previousWidth = textWidth; + textWidth = gc.stringExtent (text.substring (0, index)).x; + } + displayTexts [columnIndex] = text.substring (0, index - 1) + Tree.ELLIPSIS; + textWidths [columnIndex] = previousWidth + ellipsisWidth; +} +void computeDisplayTexts (GC gc) { + if ((parent.style & SWT.VIRTUAL) != 0 && !cached) return; /* nothing to do */ + + int columnCount = parent.columns.length; + if (columnCount == 0) return; + + for (int i = 0; i < columnCount; i++) { + gc.setFont (getFont (i, false)); + computeDisplayText (i, gc); + } +} +/* + * Computes the cached text widths. + */ +void computeTextWidths (GC gc) { + if ((parent.style & SWT.VIRTUAL) != 0 && !cached) return; /* nothing to do */ + + int validColumnCount = Math.max (1, parent.columns.length); + textWidths = new int [validColumnCount]; + for (int i = 0; i < textWidths.length; i++) { + String value = getDisplayText (i); + if (value != null) { + gc.setFont (getFont (i, false)); + textWidths [i] = gc.stringExtent (value).x; + } + } +} +public void dispose () { + if (isDisposed ()) return; + int startIndex = -1, endIndex = -1; + Tree parent = this.parent; + int index = getIndex (); + + /* determine the indices, if any, that will need to be visually updated */ + if (isAvailable ()) { + if (isLastChild () && index > 0) { + /* vertical connector lines no longer needed for this item */ + if (parentItem != null) { + startIndex = parentItem.items [index - 1].availableIndex; + } else { + startIndex = parent.items [index - 1].availableIndex; + } + } else { + startIndex = availableIndex; + } + endIndex = parent.availableItemsCount - 1; + } + + /* for performance do this upfront for whole descendent chain */ + TreeItem focusItem = parent.focusItem; + if (focusItem != null && focusItem.hasAncestor (this)) { + parent.setFocusItem (this, false); + parent.reassignFocus (); + focusItem = parent.focusItem; + if (focusItem != null) { + parent.redrawItem (focusItem.availableIndex, true); + } + } + if (parentItem != null) parentItem.removeItem (this, index); + dispose (true); + if (startIndex != -1) { + parent.redrawItems (startIndex, endIndex, false); + } +} +void dispose (boolean notifyParent) { + if (isDisposed ()) return; + for (int i = 0; i < items.length; i++) { + items [i].dispose (notifyParent); + } + if (notifyParent) parent.destroyItem (this); + super.dispose (); /* super is intentional here */ + background = foreground = null; + cellBackgrounds = cellForegrounds = null; + font = null; + cellFonts = null; + images = null; + texts = displayTexts = null; + textWidths = fontHeights = null; + parent = null; + parentItem = null; + items = null; +} +/* + * Ensure that all ancestors of the receiver are expanded + */ +void expandAncestors () { + if (parentItem != null) parentItem.expandAncestors (); + setExpanded (true); + Event newEvent = new Event (); + newEvent.item = this; + parent.inExpand = true; + parent.sendEvent (SWT.Expand, newEvent); + parent.inExpand = false; + if (isDisposed ()) return; + if (items.length == 0) { + expanded = false; + } +} +/** + * 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> + * + * @since 2.0 + * + */ +public Color getBackground () { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (background != null) return background; + return parent.getBackground (); +} +/** + * 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> + * + * @since 3.1 + */ +public Color getBackground (int columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return getBackground (); + if (cellBackgrounds == null || cellBackgrounds [columnIndex] == null) return getBackground (); + return cellBackgrounds [columnIndex]; +} +/** + * 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 () { + checkWidget (); + return getBounds (true); +} +Rectangle getBounds (boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (!isAvailable ()) return new Rectangle (0, 0, 0, 0); + TreeColumn[] orderedColumns = parent.getOrderedColumns (); + int orderedCol0Index = orderedColumns.length == 0 ? 0 : orderedColumns [0].getIndex (); + int x = getTextX (orderedCol0Index); + int width = textWidths [orderedCol0Index] + 2 * MARGIN_TEXT; + if (orderedColumns.length > 0) { + TreeColumn column = orderedColumns [0]; + int right = column.getX () + column.width; + if (x + width > right) { + width = Math.max (0, right - x); + } + } + return new Rectangle (x, parent.getItemY (this), width, parent.itemHeight); +} +/** + * 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> + * + * @since 3.1 + */ +public Rectangle getBounds (int columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (!isAvailable ()) return new Rectangle (0, 0, 0, 0); + TreeColumn[] columns = parent.columns; + int columnCount = columns.length; + int validColumnCount = Math.max (1, columnCount); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) { + return new Rectangle (0, 0, 0, 0); + } + /* + * If there are no columns then this is the bounds of the receiver's content. + */ + if (columnCount == 0) { + return new Rectangle ( + getContentX (0), + parent.getItemY (this), + getContentWidth (0), + parent.itemHeight - 1); + } + + TreeColumn column = columns [columnIndex]; + if (column.getOrderIndex () == 0) { + /* + * For ordered column 0 this is bounds from the beginning of the content to the + * end of the column. + */ + int x = getContentX (columnIndex); + int offset = x - column.getX (); + int width = Math.max (0, column.width - offset - 1); /* max is for columns with small widths */ + return new Rectangle (x, parent.getItemY (this) + 1, width, parent.itemHeight - 1); + } + /* + * For ordered columns > 0 this is the bounds of the tree cell. + */ + return new Rectangle (column.getX (), parent.getItemY (this) + 1, column.width, parent.itemHeight - 1); +} +/* + * Returns the full bounds of a cell in a tree, regardless of its content. + */ +Rectangle getCellBounds (int columnIndex) { + int y = parent.getItemY (this); + if (parent.columns.length == 0) { + int width; + if (customWidth != -1) { + width = getContentX (0) + customWidth + parent.horizontalOffset; + } else { + int textPaintWidth = textWidths [0] + 2 * MARGIN_TEXT; + width = getTextX (0) + textPaintWidth + parent.horizontalOffset; + } + return new Rectangle (-parent.horizontalOffset, y, width, parent.itemHeight); + } + TreeColumn column = parent.columns [columnIndex]; + return new Rectangle (column.getX (), y, column.width, parent.itemHeight); +} +/* + * Returns the bounds of the receiver's checkbox, or null if the parent's style does not + * include SWT.CHECK. + */ +Rectangle getCheckboxBounds () { + if ((parent.getStyle () & SWT.CHECK) == 0) return null; + int itemHeight = parent.itemHeight; + Rectangle result = parent.checkboxBounds; + Point[] hLinePoints = getHconnectorEndpoints (); + result.x = hLinePoints [1].x; + result.y = parent.getItemY (this) + (itemHeight - result.height) / 2; + return result; +} +/** + * 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, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return checked; +} +int getContentWidth (int columnIndex) { + int width = textWidths [columnIndex] + 2 * MARGIN_TEXT; + int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex (); + if (orderedIndex == 0) { + width += parent.orderedCol0imageWidth; + if (parent.orderedCol0imageWidth > 0) width += Tree.MARGIN_IMAGE; + } else { + Image image = getImage (columnIndex, false); + if (image != null) { + width += image.getBounds ().width + Tree.MARGIN_IMAGE; + } + } + return width; +} +/* + * Returns the x value where the receiver's content (ie.- its image or text) begins + * for the specified column. For ordered columns > 0 this is dependent upon column + * alignment, and for ordered column 0 this is dependent upon the receiver's depth in + * the tree item hierarchy and the presence/absence of a checkbox. + */ +int getContentX (int columnIndex) { + int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex (); + if (orderedIndex > 0) { + TreeColumn column = parent.columns [columnIndex]; + int contentX = column.getX () + parent.getCellPadding (); + if ((column.style & SWT.LEFT) != 0) return contentX; + + /* column is not left-aligned */ + int contentWidth = getContentWidth (columnIndex); + if ((column.style & SWT.RIGHT) != 0) { + int padding = parent.getCellPadding (); + contentX = Math.max (contentX, column.getX () + column.width - padding - contentWidth); + } else { /* SWT.CENTER */ + contentX = Math.max (contentX, column.getX () + (column.width - contentWidth) / 2); + } + return contentX; + } + + /* ordered column 0 (always left-aligned) */ + if ((parent.style & SWT.CHECK) != 0) { + Rectangle checkBounds = getCheckboxBounds (); + return checkBounds.x + checkBounds.width + Tree.MARGIN_IMAGE; + } + + int contentX = parent.getCellPadding () - parent.horizontalOffset; + if (parentItem != null) { + int expanderWidth = parent.expanderBounds.width + INDENT_HIERARCHY; + contentX += expanderWidth * depth; + } + contentX += parent.expanderBounds.width; + return contentX + Tree.MARGIN_IMAGE + INDENT_HIERARCHY; +} +String getDisplayText (int columnIndex) { + if (parent.columns.length == 0) return getText (0, false); + String result = displayTexts [columnIndex]; + return result != null ? result : ""; //$NON-NLS-1$ +} +/** + * 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 (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return expanded; +} +/* + * Returns the bounds of the receiver's expander box, regardless of whether the + * receiver currently has children or not. + */ +Rectangle getExpanderBounds () { + int itemHeight = parent.itemHeight; + int x = parent.getCellPadding () - parent.horizontalOffset; + int y = parent.getItemY (this); + if (parentItem != null) { + int expanderWidth = parent.expanderBounds.width + INDENT_HIERARCHY; + x += expanderWidth * depth; + } + return new Rectangle ( + x, y + (itemHeight - parent.expanderBounds.height) / 2, + parent.expanderBounds.width, parent.expanderBounds.height); +} +/* + * Returns the bounds that should be used for drawing a focus rectangle on the receiver + */ +Rectangle getFocusBounds () { + TreeColumn[] columns = parent.columns; + int orderedCol0index = columns.length == 0 ? 0 : parent.getOrderedColumns ()[0].getIndex (); + int x = getTextX (orderedCol0index); + + int width; + if (columns.length > 0) { + /* ensure that the focus x does not start beyond the right bound of ordered column 0 */ + int rightX = columns [orderedCol0index].getX () + columns [orderedCol0index].width; + x = Math.min (x, rightX - 1); + + TreeColumn column; + if ((parent.style & SWT.FULL_SELECTION) != 0) { + int[] columnOrder = parent.getColumnOrder (); + column = columns [columnOrder [columnOrder.length - 1]]; /* last ordered column */ + } else { + column = columns [orderedCol0index]; + } + width = column.getX () + column.width - x - 1; + } else { /* no columns */ + if (customWidth != -1) { + width = customWidth; + } else { + width = textWidths [0] + 2 * MARGIN_TEXT; + } + } + + return new Rectangle ( + x, + parent.getItemY (this) + (parent.linesVisible ? 1 : 0), + width, + parent.itemHeight - (parent.linesVisible ? 1 : 0)); +} +/** + * 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> + * + * @since 3.0 + */ +public Font getFont () { + checkWidget (); + return getFont (true); +} +Font getFont (boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (font != null) return font; + return parent.getFont (); +} +/** + * 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> + * + * @since 3.1 + */ +public Font getFont (int columnIndex) { + checkWidget (); + return getFont (columnIndex, true); +} +Font getFont (int columnIndex, boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return getFont (checkData); + if (cellFonts == null || cellFonts [columnIndex] == null) return getFont (checkData); + return cellFonts [columnIndex]; +} +int getFontHeight () { + if (fontHeight != 0) return fontHeight; + return parent.fontHeight; +} +int getFontHeight (int columnIndex) { + if (fontHeights == null || fontHeights [columnIndex] == 0) return getFontHeight (); + return fontHeights [columnIndex]; +} +/** + * 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> + * + * @since 2.0 + * + */ +public Color getForeground () { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (foreground != null) return foreground; + return parent.getForeground (); +} +/** + * + * 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> + * + * @since 3.1 + */ +public Color getForeground (int columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return getForeground (); + if (cellForegrounds == null || cellForegrounds [columnIndex] == null) return getForeground (); + return cellForegrounds [columnIndex]; +} +/** + * 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, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return grayed; +} +/* + * Answers the start and end points of the horizontal connector line that is + * drawn between an item's expander box and its checkbox or content. + */ +Point[] getHconnectorEndpoints () { + Rectangle expanderBounds = getExpanderBounds (); + int x, width; + if (items.length == 0) { /* no child items, so no expander box */ + x = expanderBounds.x + Compatibility.ceil (expanderBounds.width, 2); + width = Compatibility.floor (expanderBounds.width, 2) + INDENT_HIERARCHY; + } else { /* has child items */ + x = expanderBounds.x + expanderBounds.width; + width = INDENT_HIERARCHY; + } + int y = expanderBounds.y + expanderBounds.height / 2; + return new Point[] { + new Point (x, y), + new Point (x + width, y) + }; +} +/* + * Returns the bounds representing the clickable region that should select the receiver. + */ +Rectangle getHitBounds () { + int[] columnOrder = parent.getColumnOrder (); + int orderedCol0index = columnOrder.length == 0 ? 0 : parent.columns [columnOrder [0]].getIndex (); + int contentX = getContentX (orderedCol0index); + int width = 0; + TreeColumn[] columns = parent.columns; + if (columns.length == 0) { + width = getContentWidth (0); + } else { + /* + * If there are columns then this spans from the beginning of the receiver's column 0 + * image or text to the end of either column 0 or the last column (FULL_SELECTION). + */ + TreeColumn column; + if ((parent.style & SWT.FULL_SELECTION) != 0) { + column = columns [columnOrder [columnOrder.length - 1]]; /* last column */ + } else { + column = columns [orderedCol0index]; + } + width = column.getX () + column.width - contentX; + } + return new Rectangle (contentX, parent.getItemY (this), width, parent.itemHeight); +} +public Image getImage () { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return super.getImage (); +} +/** + * 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> + * + * @since 3.1 + */ +public Image getImage (int columnIndex) { + checkWidget (); + return getImage (columnIndex, true); +} +Image getImage (int columnIndex, boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return null; + if (columnIndex == 0) return super.getImage (); /* super is intentional here */ + return images [columnIndex]; +} +/** + * 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> + * + * @since 3.1 + */ +public Rectangle getImageBounds (int columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return new Rectangle (0,0,0,0); + + int padding = parent.getCellPadding (); + int startX = getContentX (columnIndex); + int itemHeight = parent.itemHeight; + int imageSpaceY = itemHeight - 2 * padding; + int y = parent.getItemY (this); + int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex (); + Image image = getImage (columnIndex, false); + int drawWidth = 0; + if (orderedIndex == 0) { + /* for ordered column 0 all images have the same width */ + drawWidth = parent.orderedCol0imageWidth; + } else { + if (image != null) drawWidth = image.getBounds ().width; + } + return new Rectangle (startX, y + padding, drawWidth, imageSpaceY); +} +int getIndex () { + TreeItem[] items; + if (parentItem != null) { + items = parentItem.items; + } else { + items = parent.items; + } + for (int i = 0; i < items.length; i++) { + if (items [i] == this) return i; + } + return -1; +} +/** + * 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> + * + * @since 3.1 + */ +public TreeItem getItem (int index) { + checkWidget (); + if (index < 0) error (SWT.ERROR_INVALID_RANGE); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (index >= items.length) error (SWT.ERROR_INVALID_RANGE); + return items [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, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return items.length; +} +String getNameText () { + if ((parent.style & SWT.VIRTUAL) != 0) { + if (!cached) return "*virtual*"; //$NON-NLS-1$ + } + return super.getNameText (); +} +/** + * 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 (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + TreeItem[] result = new TreeItem [items.length]; + System.arraycopy (items, 0, result, 0, items.length); + return result; +} +/** + * 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; +} +/* + * Returns the receiver's ideal width for the specified columnIndex. + */ +int getPreferredWidth (int columnIndex) { + int width = 0; + GC gc = new GC (parent); + gc.setFont (getFont (columnIndex, false)); + width += gc.stringExtent (getText (columnIndex, false)).x + 2 * MARGIN_TEXT; + int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex (); + if (orderedIndex == 0) { + if (parent.orderedCol0imageWidth > 0) { + width += parent.orderedCol0imageWidth; + width += Tree.MARGIN_IMAGE; + } + } else { + Image image = getImage (columnIndex, false); + if (image != null) { + width += image.getBounds ().width; + width += Tree.MARGIN_IMAGE; + } + } + + if (parent.hooks (SWT.MeasureItem)) { + Event event = new Event (); + event.item = this; + event.gc = gc; + event.index = columnIndex; + event.x = getContentX (columnIndex); + event.y = parent.getItemY (this); + event.width = width; + event.height = parent.itemHeight; + parent.sendEvent (SWT.MeasureItem, event); + if (parent.itemHeight != event.height) { + parent.customHeightSet = true; + boolean update = parent.setItemHeight (event.height + 2 * parent.getCellPadding ()); + if (update) parent.redraw (); + } + width = event.width; + } + gc.dispose (); + + if (orderedIndex == 0) { + return getContentX (columnIndex) + parent.horizontalOffset + width + parent.getCellPadding (); /* right side cell pad */ + } + + return width + 2 * parent.getCellPadding (); +} +public String getText () { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + return super.getText (); +} +/** + * 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> + * + * @since 3.1 + */ +public String getText (int columnIndex) { + checkWidget (); + return getText (columnIndex, true); +} +String getText (int columnIndex, boolean checkData) { + if (checkData && !parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return ""; //$NON-NLS-1$ + if (columnIndex == 0) return super.getText (); /* super is intentional here */ + if (texts [columnIndex] == null) return ""; //$NON-NLS-1$ + return texts [columnIndex]; +} +/** + * 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 3.3 + */ +public Rectangle getTextBounds (int columnIndex) { + checkWidget (); + if (!parent.checkData (this, true)) error (SWT.ERROR_WIDGET_DISPOSED); + if (!isAvailable ()) return new Rectangle (0, 0, 0, 0); + TreeColumn[] columns = parent.columns; + int columnCount = columns.length; + int validColumnCount = Math.max (1, columnCount); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) { + return new Rectangle (0, 0, 0, 0); + } + /* + * If there are no columns then this is the bounds of the receiver's content. + */ + if (columnCount == 0) { + int x = getTextX (0) + MARGIN_TEXT; + int width = Math.max (0, getContentX(0) + getContentWidth (0) - x); + return new Rectangle ( + x, + parent.getItemY (this), + width, + parent.itemHeight - 1); + } + + TreeColumn column = columns [columnIndex]; + if (column.getOrderIndex () == 0) { + /* + * For ordered column 0 this is bounds from the beginning of the content to the + * end of the column. + */ + int x = getTextX (columnIndex) + MARGIN_TEXT; + int offset = x - column.getX (); + int width = Math.max (0, column.width - offset - 1); /* max is for columns with small widths */ + return new Rectangle (x, parent.getItemY (this) + 1, width, parent.itemHeight - 1); + } + /* + * For ordered columns > 0 this is the bounds of the tree cell. + */ + int x = getTextX (columnIndex) + MARGIN_TEXT; + int offset = x - column.getX (); + int width = Math.max (0, column.width - offset - MARGIN_TEXT); + return new Rectangle (x, parent.getItemY (this) + 1, width, parent.itemHeight - 1); +} +/* + * Returns the x value where the receiver's text begins. + */ +int getTextX (int columnIndex) { + int orderedIndex = parent.columns.length == 0 ? 0 : parent.columns [columnIndex].getOrderIndex (); + int textX = getContentX (columnIndex); + if (orderedIndex == 0) { + textX += parent.orderedCol0imageWidth; + if (parent.orderedCol0imageWidth > 0) textX += Tree.MARGIN_IMAGE; + } else { + Image image = getImage (columnIndex, false); + if (image != null) { + textX += image.getBounds ().width + Tree.MARGIN_IMAGE; + } + } + return textX; +} +/* + * Returns true if the receiver descends from (or is identical to) the item. + */ +boolean hasAncestor (TreeItem item) { + if (this == item) return true; + if (parentItem == null) return false; + return parentItem.hasAncestor (item); +} +/** + * 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 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 3.1 + */ +public int indexOf (TreeItem item) { + checkWidget (); + if (item == null) error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + if (item.parentItem != this) return -1; + return item.getIndex (); +} +/* + * Returns true if the receiver is currently available (though not necessary in the viewport). + */ +boolean isAvailable () { + if (parentItem == null) return true; /* root items are always available */ + if (!parentItem.expanded) return false; + return parentItem.isAvailable (); +} +/* + * Answers a boolean indicating whether the receiver's y is within the current + * viewport of the parent. + */ +boolean isInViewport () { + if (availableIndex == -1) return false; + int topIndex = parent.topIndex; + if (availableIndex < topIndex) return false; + int visibleCount = parent.clientArea.height / parent.itemHeight; + return availableIndex <= topIndex + visibleCount; +} +/* + * Returns true if the receiver is the last child of its parent item, or of its parent + * if the receiver is a root item, and false otherwise. + */ +boolean isLastChild () { + if (parentItem != null) { + return getIndex () == parentItem.items.length - 1; + } + return getIndex () == parent.items.length - 1; +} +boolean isSelected () { + return parent.getSelectionIndex (this) != -1; +} +/* + * The backgroundOnly argument indicates whether the item should only + * worry about painting its background color and selection. + * + * Returns a boolean indicating whether to abort drawing focus on the item. + * If the receiver is not the current focus item then this value is irrelevant. + */ +boolean paint (GC gc, TreeColumn column, boolean backgroundOnly) { + if (!parent.checkData (this, true)) return false; + int columnIndex = 0, orderedIndex = 0, x = 0; + if (column != null) { + columnIndex = column.getIndex (); + orderedIndex = column.getOrderIndex (); + x = column.getX (); + } + + /* + * Capture GC attributes that will need to be restored later in the paint + * process to ensure that the item paints as intended without being affected + * by GC changes made in MeasureItem/EraseItem/PaintItem callbacks. + */ + int oldAlpha = gc.getAlpha (); + boolean oldAdvanced = gc.getAdvanced (); + int oldAntialias = gc.getAntialias (); + Pattern oldBackgroundPattern = gc.getBackgroundPattern (); + Pattern oldForegroundPattern = gc.getForegroundPattern (); + int oldInterpolation = gc.getInterpolation (); + int[] oldLineDash = gc.getLineDash (); + int oldLineWidth = gc.getLineWidth (); + int oldTextAntialias = gc.getTextAntialias (); + + if (parent.hooks (SWT.MeasureItem)) { + int contentWidth = getContentWidth (columnIndex); + int contentX = getContentX (columnIndex); + gc.setFont (getFont (columnIndex, false)); + Event event = new Event (); + event.item = this; + event.gc = gc; + event.index = columnIndex; + event.x = contentX; + event.y = parent.getItemY (this); + event.width = contentWidth; + event.height = parent.itemHeight; + parent.sendEvent (SWT.MeasureItem, event); + event.gc = null; + if (gc.isDisposed ()) return false; + gc.setAlpha (oldAlpha); + gc.setAntialias (oldAntialias); + gc.setBackgroundPattern (oldBackgroundPattern); + gc.setForegroundPattern (oldForegroundPattern); + gc.setInterpolation (oldInterpolation); + gc.setLineDash (oldLineDash); + gc.setLineWidth (oldLineWidth); + gc.setTextAntialias (oldTextAntialias); + gc.setAdvanced (oldAdvanced); + if (isDisposed ()) return false; + if (parent.itemHeight != event.height) { + parent.customHeightSet = true; + boolean update = parent.setItemHeight (event.height + 2 * parent.getCellPadding ()); + if (update) parent.redraw (); + } + if (parent.columns.length == 0) { + int change = event.width - (customWidth != -1 ? customWidth : contentWidth); + if (event.width != contentWidth || customWidth != -1) customWidth = event.width; + if (change != 0) { /* scrollbar may be affected since no columns */ + parent.updateHorizontalBar (contentX + event.width, change); + // TODO what if clip is too small now? + } + } + } + + /* if this cell is completely to the right of the client area then there's no need to paint it */ + Rectangle clientArea = parent.clientArea; + if (clientArea.x + clientArea.width < x) return false; + + Rectangle cellBounds = getCellBounds (columnIndex); + if (parent.linesVisible) { + cellBounds.y++; + cellBounds.height--; + } + int cellRightX = 0; + if (column != null) { + cellRightX = column.getX () + column.width; + } else { + cellRightX = cellBounds.x + cellBounds.width; + } + + /* restrict the clipping region to the cell */ + gc.setClipping (x, cellBounds.y, clientArea.width - x, cellBounds.height); + + int y = parent.getItemY (this); + int itemHeight = parent.itemHeight; + + /* draw the parent background color/image of this cell */ + if (column == null) { + parent.drawBackground (gc, 0, y, clientArea.width, itemHeight); + } else { + int fillWidth = cellBounds.width; + if (parent.linesVisible) fillWidth--; + parent.drawBackground (gc, cellBounds.x, cellBounds.y, fillWidth, cellBounds.height); + } + + boolean isSelected = isSelected (); + boolean isFocusItem = parent.focusItem == this && parent.isFocusControl (); + boolean drawBackground = true; + boolean drawForeground = true; + boolean drawSelection = isSelected; + boolean drawFocus = isFocusItem; + if (parent.hooks (SWT.EraseItem)) { + drawBackground = background != null || (cellBackgrounds != null && cellBackgrounds [columnIndex] != null); + gc.setFont (getFont (columnIndex, false)); + if (isSelected && (columnIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) { + gc.setForeground (display.getSystemColor (SWT.COLOR_LIST_SELECTION_TEXT)); + gc.setBackground (display.getSystemColor (SWT.COLOR_LIST_SELECTION)); + } else { + gc.setForeground (getForeground (columnIndex)); + gc.setBackground (getBackground (columnIndex)); + } + Event event = new Event (); + event.item = this; + event.gc = gc; + event.index = columnIndex; + event.doit = true; + event.detail = SWT.FOREGROUND; + if (drawBackground) event.detail |= SWT.BACKGROUND; + if (isSelected) event.detail |= SWT.SELECTED; + if (isFocusItem) event.detail |= SWT.FOCUSED; + event.x = cellBounds.x; + event.y = cellBounds.y; + event.width = cellBounds.width; + event.height = cellBounds.height; + gc.setClipping (cellBounds); + parent.sendEvent (SWT.EraseItem, event); + event.gc = null; + if (gc.isDisposed ()) return false; + gc.setAlpha (oldAlpha); + gc.setAntialias (oldAntialias); + gc.setBackgroundPattern (oldBackgroundPattern); + gc.setClipping (cellBounds); + gc.setForegroundPattern (oldForegroundPattern); + gc.setInterpolation (oldInterpolation); + gc.setLineDash (oldLineDash); + gc.setLineWidth (oldLineWidth); + gc.setTextAntialias (oldTextAntialias); + gc.setAdvanced (oldAdvanced); + if (isDisposed ()) return false; + if (!event.doit) { + drawBackground = drawForeground = drawSelection = drawFocus = false; + } else { + drawBackground = drawBackground && (event.detail & SWT.BACKGROUND) != 0; + drawForeground = (event.detail & SWT.FOREGROUND) != 0; + drawSelection = isSelected && (event.detail & SWT.SELECTED) != 0; + drawFocus = isFocusItem && (event.detail & SWT.FOCUSED) != 0; + } + } + + /* draw the cell's set background if appropriate */ + if (drawBackground) { + gc.setBackground (getBackground (columnIndex)); + if (columnIndex == 0 && (column == null || column.getOrderIndex () == 0)) { + Rectangle focusBounds = getFocusBounds (); + int fillWidth = 0; + if (column == null) { + fillWidth = focusBounds.width; + } else { + fillWidth = column.width - focusBounds.x; + if (parent.linesVisible) fillWidth--; + } + gc.fillRectangle (focusBounds.x, focusBounds.y, fillWidth, focusBounds.height); + } else { + int fillWidth = cellBounds.width; + gc.fillRectangle (cellBounds.x, cellBounds.y, fillWidth, cellBounds.height); + } + } + + /* draw the selection bar if the receiver is selected */ + if (drawSelection && isSelected && (orderedIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) { + gc.setBackground (display.getSystemColor (SWT.COLOR_LIST_SELECTION)); + if (orderedIndex == 0) { + Rectangle focusBounds = getFocusBounds (); + int fillWidth = focusBounds.width; + if (parent.columns.length < 2 || (parent.style & SWT.FULL_SELECTION) == 0) { + fillWidth -= 2; /* space for right bound of focus rect */ + } + if (fillWidth > 0) { + gc.fillRectangle (focusBounds.x + 1, focusBounds.y + 1, fillWidth, focusBounds.height - 2); + } + } else { + int fillWidth = column.width; + int[] columnOrder = parent.getColumnOrder (); + if (columnIndex == columnOrder [columnOrder.length - 1]) { + fillWidth -= 2; /* space for right bound of focus rect */ + } + if (fillWidth > 0) { + gc.fillRectangle ( + column.getX (), + cellBounds.y + 1, + fillWidth, + cellBounds.height - 2); + } + } + } + + if (backgroundOnly) return false; + + /* Draw column 0 decorations */ + if (orderedIndex == 0) { + gc.setClipping (cellBounds); + + /* Draw hierarchy connector lines */ + Rectangle expanderBounds = getExpanderBounds (); + Color oldForeground = gc.getForeground (); + gc.setForeground (parent.getConnectorColor ()); + + /* Draw vertical line above expander */ + int lineX = expanderBounds.x + expanderBounds.width / 2; + int y2 = expanderBounds.y; + if (items.length == 0) { + y2 += expanderBounds.height / 2; + } + /* Do not draw this line iff this is the very first item in the tree */ + if (parentItem != null || getIndex () != 0) { + gc.drawLine (lineX, y, lineX, y2); + } + + /* Draw vertical line below expander if the receiver has lower siblings */ + if (!isLastChild ()) { + if (items.length != 0) y2 += expanderBounds.height; + gc.drawLine (lineX, y2, lineX, y + itemHeight); + } + + /* Draw horizontal line to right of expander */ + Point[] endpoints = getHconnectorEndpoints (); + gc.drawLine (endpoints [0].x, endpoints [0].y, endpoints [1].x - Tree.MARGIN_IMAGE, endpoints [1].y); + + /* + * Draw hierarchy lines that are needed by other items that are shown below + * this item but whose parents are shown above (ie.- lines to the left of + * this item's connector line). + */ + TreeItem item = parentItem; + while (item != null) { + if (!item.isLastChild ()) { + Rectangle itemExpanderBounds = item.getExpanderBounds (); + lineX = itemExpanderBounds.x + itemExpanderBounds.width / 2; + gc.drawLine (lineX, y, lineX, y + itemHeight); + } + item = item.parentItem; + } + + gc.setForeground (oldForeground); + + /* Draw expand/collapse image if receiver has children */ + if (items.length > 0) { + Image image = expanded ? parent.getExpandedImage () : parent.getCollapsedImage (); + gc.drawImage (image, expanderBounds.x, expanderBounds.y); + } + + /* Draw checkbox if parent Tree has style SWT.CHECK */ + if ((parent.style & SWT.CHECK) != 0) { + Image baseImage = grayed ? parent.getGrayUncheckedImage () : parent.getUncheckedImage (); + Rectangle checkboxBounds = getCheckboxBounds (); + gc.drawImage (baseImage, checkboxBounds.x, checkboxBounds.y); + /* Draw checkmark if item is checked */ + if (checked) { + Image checkmarkImage = parent.getCheckmarkImage (); + Rectangle checkmarkBounds = checkmarkImage.getBounds (); + int xInset = (checkboxBounds.width - checkmarkBounds.width) / 2; + int yInset = (checkboxBounds.height - checkmarkBounds.height) / 2; + gc.drawImage (checkmarkImage, checkboxBounds.x + xInset, checkboxBounds.y + yInset); + } + } + } + + if (drawForeground) { + Image image = getImage (columnIndex, false); + String text = getDisplayText (columnIndex); + Rectangle imageArea = getImageBounds (columnIndex); + int startX = imageArea.x; + + /* while painting the cell's content restrict the clipping region */ + int padding = parent.getCellPadding (); + gc.setClipping ( + startX, + cellBounds.y + padding - (parent.linesVisible ? 1 : 0), + cellRightX - startX - padding, + cellBounds.height - 2 * (padding - (parent.linesVisible ? 1 : 0))); + + /* draw the image */ + if (image != null) { + Rectangle imageBounds = image.getBounds (); + gc.drawImage ( + image, + 0, 0, /* source x, y */ + imageBounds.width, imageBounds.height, /* source width, height */ + imageArea.x, imageArea.y, /* dest x, y */ + imageArea.width, imageArea.height); /* dest width, height */ + } + + /* draw the text */ + if (text.length () > 0) { + gc.setFont (getFont (columnIndex, false)); + int fontHeight = getFontHeight (columnIndex); + if (drawSelection && (orderedIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) { + gc.setForeground (display.getSystemColor (SWT.COLOR_LIST_SELECTION_TEXT)); + } else { + if (!isSelected || drawSelection) { + gc.setForeground (getForeground (columnIndex)); + } + } + x = getTextX (columnIndex) + MARGIN_TEXT; + gc.drawString (text, x, y + (itemHeight - fontHeight) / 2, true); + } + } + + if (parent.hooks (SWT.PaintItem)) { + int contentWidth = getContentWidth (columnIndex); + int contentX = getContentX (columnIndex); + gc.setFont (getFont (columnIndex, false)); + if (isSelected && (columnIndex == 0 || (parent.style & SWT.FULL_SELECTION) != 0)) { + gc.setForeground (display.getSystemColor (SWT.COLOR_LIST_SELECTION_TEXT)); + gc.setBackground (display.getSystemColor (SWT.COLOR_LIST_SELECTION)); + } else { + gc.setForeground (getForeground (columnIndex)); + gc.setBackground (getBackground (columnIndex)); + } + Event event = new Event (); + event.item = this; + event.gc = gc; + event.index = columnIndex; + if (isSelected) event.detail |= SWT.SELECTED; + if (drawFocus) event.detail |= SWT.FOCUSED; + event.x = contentX; + event.y = cellBounds.y; + event.width = contentWidth; + event.height = cellBounds.height; + gc.setClipping (cellBounds); + parent.sendEvent (SWT.PaintItem, event); + event.gc = null; + if (gc.isDisposed ()) return false; + gc.setAlpha (oldAlpha); + gc.setAntialias (oldAntialias); + gc.setBackgroundPattern (oldBackgroundPattern); + gc.setClipping (cellBounds); + gc.setForegroundPattern (oldForegroundPattern); + gc.setInterpolation (oldInterpolation); + gc.setLineDash (oldLineDash); + gc.setLineWidth (oldLineWidth); + gc.setTextAntialias (oldTextAntialias); + gc.setAdvanced (oldAdvanced); + drawFocus = isFocusItem && (event.detail & SWT.FOCUSED) != 0; + } + + return isFocusItem && !drawFocus; +} +/* + * Redraw part of the receiver. If either EraseItem or PaintItem is hooked then + * only full cells should be damaged, so adjust accordingly. If neither of these + * events are hooked then the exact bounds given for damaging can be used. + */ +void redraw (int x, int y, int width, int height, int columnIndex) { + if (!parent.hooks (SWT.EraseItem) && !parent.hooks (SWT.PaintItem)) { + parent.redraw (x, y, width, height, false); + return; + } + Rectangle cellBounds = getCellBounds (columnIndex); + parent.redraw (cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height, false); +} +void redrawItem () { + if (!isAvailable ()) return; + parent.redraw (0, parent.getItemY (this), parent.clientArea.width, parent.itemHeight, false); +} +/* + * Updates internal structures in the receiver and its child items to handle the removal of a column. + */ +void removeColumn (TreeColumn column, int index, int orderedIndex) { + int columnCount = parent.columns.length; + + if (columnCount == 0) { + /* reverts to normal tree when last column disposed */ + cellBackgrounds = cellForegrounds = null; + displayTexts = null; + cellFonts = null; + fontHeights = null; + GC gc = new GC (parent); + computeTextWidths (gc); + gc.dispose (); + /* notify all child items as well */ + for (int i = 0; i < items.length; i++) { + items [i].removeColumn (column, index, orderedIndex); + } + return; + } + + String[] newTexts = new String [columnCount]; + System.arraycopy (texts, 0, newTexts, 0, index); + System.arraycopy (texts, index + 1, newTexts, index, columnCount - index); + texts = newTexts; + + Image[] newImages = new Image [columnCount]; + System.arraycopy (images, 0, newImages, 0, index); + System.arraycopy (images, index + 1, newImages, index, columnCount - index); + images = newImages; + + int[] newTextWidths = new int [columnCount]; + System.arraycopy (textWidths, 0, newTextWidths, 0, index); + System.arraycopy (textWidths, index + 1, newTextWidths, index, columnCount - index); + textWidths = newTextWidths; + + String[] newDisplayTexts = new String [columnCount]; + System.arraycopy (displayTexts, 0, newDisplayTexts, 0, index); + System.arraycopy (displayTexts, index + 1, newDisplayTexts, index, columnCount - index); + displayTexts = newDisplayTexts; + + if (cellBackgrounds != null) { + Color[] newCellBackgrounds = new Color [columnCount]; + System.arraycopy (cellBackgrounds, 0, newCellBackgrounds, 0, index); + System.arraycopy (cellBackgrounds, index + 1, newCellBackgrounds, index, columnCount - index); + cellBackgrounds = newCellBackgrounds; + } + if (cellForegrounds != null) { + Color[] newCellForegrounds = new Color [columnCount]; + System.arraycopy (cellForegrounds, 0, newCellForegrounds, 0, index); + System.arraycopy (cellForegrounds, index + 1, newCellForegrounds, index, columnCount - index); + cellForegrounds = newCellForegrounds; + } + if (cellFonts != null) { + Font[] newCellFonts = new Font [columnCount]; + System.arraycopy (cellFonts, 0, newCellFonts, 0, index); + System.arraycopy (cellFonts, index + 1, newCellFonts, index, columnCount - index); + cellFonts = newCellFonts; + + int[] newFontHeights = new int [columnCount]; + System.arraycopy (fontHeights, 0, newFontHeights, 0, index); + System.arraycopy (fontHeights, index + 1, newFontHeights, index, columnCount - index); + fontHeights = newFontHeights; + } + + if (index == 0) { + text = texts [0] != null ? texts [0] : ""; //$NON-NLS-1$ + texts [0] = null; + image = images [0]; + images [0] = null; + } + + if (orderedIndex == 0) { + /* + * The new first ordered column will not have as much width available to it as it did when + * it was the second ordered column since it now has to show hierarchy decorations as well, + * so recompute its displayText. + */ + int firstColumnIndex = parent.getOrderedColumns () [0].getIndex (); + GC gc = new GC (parent); + gc.setFont (getFont (firstColumnIndex, false)); + computeDisplayText (firstColumnIndex, gc); + gc.dispose (); + } + if (columnCount < 2) { + texts = null; + images = null; + } + + /* notify all child items as well */ + for (int i = 0; i < items.length; i++) { + items [i].removeColumn (column, index, orderedIndex); + } +} +/** + * 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> + * + * @since 3.1 + */ +public void removeAll () { + checkWidget (); + if (items.length == 0) return; + + int lastAvailableIndex = parent.availableItemsCount - 1; + /* for performance do this upfront for whole descendent chain */ + TreeItem focusItem = parent.focusItem; + if (focusItem != null && focusItem.hasAncestor (this)) { + parent.setFocusItem (this, false); + } + while (items.length > 0) { + items [0].dispose (true); + removeItem (items [0], 0); + } + items = Tree.NO_ITEMS; + expanded = false; + if (isAvailable ()) { + parent.redrawItems (availableIndex, lastAvailableIndex, false); + } +} +/* + * Removes a child item from the receiver. + */ +void removeItem (TreeItem item, int index) { + if (isDisposed ()) return; + TreeItem[] newItems = new TreeItem [items.length - 1]; + System.arraycopy (items, 0, newItems, 0, index); + System.arraycopy (items, index + 1, newItems, index, newItems.length - index); + items = newItems; + if (items.length == 0) { + items = Tree.NO_ITEMS; + /* condition below handles creation of item within Expand callback */ + if (!parent.inExpand) { + expanded = false; + if (isInViewport ()) { + Rectangle bounds = getExpanderBounds (); /* expander box no longer needed */ + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } + } + return; + } +} +/** + * 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 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> + * + * @since 2.0 + * + */ +public void setBackground (Color color) { + checkWidget (); + if (color != null && color.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + Color oldColor = background; + if (oldColor == color) return; + background = color; + if (oldColor != null && oldColor.equals (color)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + redrawItem (); +} +/** + * 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> + * + * @since 3.1 + * + */ +public void setBackground (int columnIndex, Color color) { + checkWidget (); + if (color != null && color.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + if (cellBackgrounds == null) { + if (color == null) return; + cellBackgrounds = new Color [validColumnCount]; + } + Color oldColor = cellBackgrounds [columnIndex]; + if (oldColor == color) return; + cellBackgrounds [columnIndex] = color; + if (oldColor != null && oldColor.equals (color)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + if (isInViewport ()) { + Rectangle bounds = getCellBounds (columnIndex); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } +} +/** + * 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 value) { + checkWidget (); + if ((parent.getStyle () & SWT.CHECK) == 0) return; + if (checked == value) return; + checked = value; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + if (isInViewport ()) { + if (parent.hooks (SWT.EraseItem) || parent.hooks (SWT.PaintItem)) { + redrawItem (); + } else { + Rectangle bounds = getCheckboxBounds (); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } + } +} +/** + * 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 value) { + checkWidget (); + if (expanded == value) return; + if (items.length == 0) return; + if (parent.inExpand) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + if (value) { + expanded = value; + if (availableIndex == -1) return; + + TreeItem[] availableDescendents = computeAvailableDescendents (); + int descendentsCount = availableDescendents.length; + if (availableIndex != parent.availableItemsCount - 1) { + /* the receiver is not the last available item */ + Rectangle clientArea = parent.clientArea; + int y = parent.getItemY (this) + parent.itemHeight; + if (0 < y && y < clientArea.height) { + if (parent.drawCount <= 0) { + parent.update (); + GC gc = new GC (parent); + gc.copyArea ( + 0, y, + clientArea.width, clientArea.height - y, + 0, y + ((descendentsCount - 1) * parent.itemHeight)); + gc.dispose (); + } + } + } + + parent.makeDescendentsAvailable (this, availableDescendents); + + /* update scrollbars */ + int rightX = 0; + for (int i = 1; i < availableDescendents.length; i++) { + Rectangle bounds = availableDescendents [i].getBounds (false); + rightX = Math.max (rightX, bounds.x + bounds.width); + } + parent.updateHorizontalBar (rightX, rightX); + parent.updateVerticalBar (); + /* + * If new item is above viewport then adjust topIndex and the vertical scrollbar + * so that the current viewport items will not change. + */ + if (availableIndex < parent.topIndex) { + parent.topIndex += descendentsCount - 1; + ScrollBar vBar = parent.getVerticalBar (); + if (vBar != null) vBar.setSelection (parent.topIndex); + return; + } + + int redrawStart = availableIndex + 1; + int redrawEnd = redrawStart + descendentsCount - 2; + parent.redrawItems (redrawStart, redrawEnd, false); + } else { + TreeItem[] descendents = computeAvailableDescendents (); + expanded = value; + if (availableIndex == -1) return; + Rectangle clientArea = parent.clientArea; + + int y = parent.getItemY (this) + parent.itemHeight; + int startY = y + (descendents.length - 1) * parent.itemHeight; + if (y < clientArea.height && 0 < startY) { /* determine whether any visual update is actually needed */ + if (parent.drawCount <= 0) { + parent.update (); + GC gc = new GC (parent); + gc.copyArea (0, startY, clientArea.width, clientArea.height - startY, 0, y); + gc.dispose (); + int redrawY = y + Math.max (0, clientArea.height - startY); + parent.redraw (0, redrawY, clientArea.width, clientArea.height - redrawY, false); + } + } + + parent.makeDescendentsUnavailable (this, descendents); + + /* + * If all collapsed items are above the viewport then adjust topIndex and + * the vertical scrollbar so that the current viewport items will not change. + */ + int bottomIndex = availableIndex + descendents.length - 1; + if (bottomIndex < parent.topIndex) { + parent.topIndex = parent.topIndex - descendents.length + 1; + ScrollBar vBar = parent.getVerticalBar (); + if (vBar != null) vBar.setSelection (parent.topIndex); + } + + parent.updateHorizontalBar (); + parent.updateVerticalBar (); + + /* move focus (and selection if SWT.SINGLE) to item if a descendent had focus */ + TreeItem focusItem = parent.focusItem; + if (focusItem != null && focusItem != this && focusItem.hasAncestor (this)) { + parent.setFocusItem (this, false); + if ((parent.style & SWT.SINGLE) != 0) { + parent.selectItem (this, false); + } + /* Fire an event since the selection is being changed automatically */ + Event newEvent = new Event (); + newEvent.item = this; + parent.sendEvent (SWT.Selection, newEvent); + if (isDisposed ()) return; + parent.showItem (this); + parent.redrawItem (availableIndex, true); + } + } + /* redraw the receiver's expander box */ + if (isInViewport ()) { + Rectangle bounds = getExpanderBounds (); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } +} +/** + * 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> + * + * @since 3.0 + */ +public void setFont (Font font) { + checkWidget (); + if (font != null && font.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + Font oldFont = this.font; + if (oldFont == font) return; + this.font = font; + if (oldFont != null && oldFont.equals (font)) return; + + Rectangle bounds = getBounds (false); + int oldRightX = bounds.x + bounds.width; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + /* recompute cached values for string measurements */ + GC gc = new GC (parent); + gc.setFont (getFont (false)); + fontHeight = gc.getFontMetrics ().getHeight (); + computeDisplayTexts (gc); + computeTextWidths (gc); + gc.dispose (); + + /* horizontal bar could be affected if tree has no columns */ + if (parent.columns.length == 0) { + bounds = getBounds (false); + int newRightX = bounds.x + bounds.width; + parent.updateHorizontalBar (newRightX, newRightX - oldRightX); + } + redrawItem (); +} +/** + * 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> + * + * @since 3.1 + */ +public void setFont (int columnIndex, Font font) { + checkWidget (); + if (font != null && font.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + if (cellFonts == null) { + if (font == null) return; + cellFonts = new Font [validColumnCount]; + } + Font oldFont = cellFonts [columnIndex]; + if (oldFont == font) return; + cellFonts [columnIndex] = font; + if (oldFont != null && oldFont.equals (font)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + /* recompute cached values for string measurements */ + GC gc = new GC (parent); + gc.setFont (getFont (columnIndex, false)); + if (fontHeights == null) fontHeights = new int [validColumnCount]; + fontHeights [columnIndex] = gc.getFontMetrics ().getHeight (); + computeDisplayText (columnIndex, gc); + gc.dispose (); + + if (isInViewport ()) { + Rectangle bounds = getCellBounds (columnIndex); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } +} +/** + * 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 color the new color (or null) + * + * @since 2.0 + * + * @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> + * + * @since 2.0 + * + */ +public void setForeground (Color color) { + checkWidget (); + if (color != null && color.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + Color oldColor = foreground; + if (oldColor == color) return; + foreground = color; + if (oldColor != null && oldColor.equals (color)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + redrawItem (); +} +/** + * 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> + * + * @since 3.1 + * + */ +public void setForeground (int columnIndex, Color value) { + checkWidget (); + if (value != null && value.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + if (cellForegrounds == null) { + if (value == null) return; + cellForegrounds = new Color [validColumnCount]; + } + Color oldColor = cellForegrounds [columnIndex]; + if (oldColor == value) return; + cellForegrounds [columnIndex] = value; + if (oldColor != null && oldColor.equals (value)) return; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + if (isInViewport ()) { + redraw ( + getTextX (columnIndex), + parent.getItemY (this), + textWidths [columnIndex] + 2 * MARGIN_TEXT, + parent.itemHeight, + columnIndex); + } +} +/** + * 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 value) { + checkWidget (); + if ((parent.getStyle () & SWT.CHECK) == 0) return; + if (grayed == value) return; + grayed = value; + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + if (isInViewport ()) { + Rectangle bounds = getCheckboxBounds (); + parent.redraw (bounds.x, bounds.y, bounds.width, bounds.height, false); + } +} +public void setImage (Image value) { + checkWidget (); + setImage (0, value); +} +/** + * Sets the image for multiple columns in the tree. + * + * @param images 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> + * + * @since 3.1 + */ +public void setImage (Image[] value) { + checkWidget (); + if (value == null) error (SWT.ERROR_NULL_ARGUMENT); + + // TODO make a smarter implementation of this + for (int i = 0; i < value.length; i++) { + if (value [i] != null) setImage (i, value [i]); + } +} +/** + * 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> + * + * @since 3.1 + */ +public void setImage (int columnIndex, Image value) { + checkWidget (); + if (value != null && value.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); + + TreeColumn[] columns = parent.columns; + int validColumnCount = Math.max (1, columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + Image image = getImage (columnIndex, false); + if (value == image) return; + if (value != null && value.equals (image)) return; + if (columnIndex == 0) { + super.setImage (value); + } else { + images [columnIndex] = value; + } + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + /* + * An image width change may affect the space available for the item text, so + * recompute the displayText if there are columns. + */ + if (columns.length > 0) { + GC gc = new GC (parent); + gc.setFont (getFont (columnIndex, false)); + computeDisplayText (columnIndex, gc); + gc.dispose (); + } + + if (value == null) { + redrawItem (); // TODO why the whole item? + return; + } + + if (columns.length == 0) { + if (parent.imageHeight == 0) { + /* this is the first image being put into the parent Tree */ + Rectangle bounds = value.getBounds (); + parent.orderedCol0imageWidth = bounds.width; + parent.setImageHeight (bounds.height); + parent.redrawItems (0, parent.availableItemsCount - 1, false); + } else { + redrawItem (); + } + return; + } + + /* there are 1+ columns */ + TreeColumn column = columns [columnIndex]; + int orderedIndex = column.getOrderIndex (); + Rectangle bounds = value.getBounds (); + if (column.itemImageWidth == 0) column.itemImageWidth = bounds.width; + + if (parent.imageHeight == 0) { + /* this is the first image being put into the parent Tree */ + int oldItemHeight = parent.itemHeight; + parent.setImageHeight (bounds.height); + + if (orderedIndex == 0) { /* the first ordered column */ + parent.orderedCol0imageWidth = bounds.width; + /* + * All column 0 cells will now have less room available for their texts, + * so all items must now recompute their column 0 displayTexts. + */ + TreeItem[] rootItems = parent.items; + GC gc = new GC (parent); + for (int i = 0; i < rootItems.length; i++) { + rootItems [i].updateColumnWidth (column, gc); + } + gc.dispose (); + if (oldItemHeight != parent.itemHeight) { + /* the item height grew as a result of the new image height, so redraw everything */ + parent.redraw (); + } else { + /* redraw the column since all items should now have image space */ + parent.redraw (column.getX (), 0, column.width, parent.clientArea.height, false); + } + } else { /* not the first ordered column */ + if (oldItemHeight != parent.itemHeight) { + /* the item height grew as a result of the new image height, so redraw everything */ + parent.redraw (); + } else { + redrawItem (); + } + } + return; + } + + if (orderedIndex == 0 && parent.orderedCol0imageWidth == 0) { + /* this is the first image being put into the current ordered column 0 */ + parent.orderedCol0imageWidth = bounds.width; + /* + * All column 0 cells will now have less room available for their texts, + * so all items must now recompute their column 0 displayTexts. + */ + TreeItem[] rootItems = parent.items; + GC gc = new GC (parent); + for (int i = 0; i < rootItems.length; i++) { + rootItems [i].updateColumnWidth (column, gc); + } + gc.dispose (); + parent.redraw (column.getX (), 0, column.width, parent.clientArea.height, false); + return; + } + + redrawItem (); // TODO why the whole item? +} +/** + * 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> + * + * @since 3.2 + */ +public void setItemCount (int count) { + checkWidget (); + count = Math.max (0, count); + if (count == items.length) return; + int redrawStart, redrawEnd; + + /* if the new item count is less than the current count then remove all excess items from the end */ + if (count < items.length) { + redrawStart = count > 0 ? items [count - 1].availableIndex : availableIndex; + redrawEnd = parent.availableItemsCount - 1; + for (int i = count; i < items.length; i++) { + items [i].dispose (true); + } + if (count == 0) { + items = Tree.NO_ITEMS; + } else { + TreeItem[] newItems = new TreeItem [count]; + System.arraycopy (items, 0, newItems, 0, count); + items = newItems; + } + if (count == 0) expanded = false; + } else { + int oldAvailableDescendentCount = computeAvailableDescendentCount (); + int grow = count - items.length; + redrawStart = items.length == 0 ? availableIndex : items [items.length - 1].availableIndex; + redrawEnd = expanded && isAvailable () ? parent.availableItemsCount + grow - 1: redrawStart; + TreeItem[] newItems = new TreeItem [count]; + System.arraycopy (items, 0, newItems, 0, items.length); + items = newItems; + for (int i = items.length - grow; i < count; i++) { + items [i] = new TreeItem (this, SWT.NONE, i, false); + } + + if (expanded && availableIndex != -1) { + /* expand the availableItems array if necessary */ + if (parent.availableItems.length < parent.availableItemsCount + grow) { + TreeItem[] newAvailableItems = new TreeItem [parent.availableItemsCount + grow]; + System.arraycopy (parent.availableItems, 0, newAvailableItems, 0, parent.availableItemsCount); + parent.availableItems = newAvailableItems; + } + TreeItem[] availableItems = parent.availableItems; + /* shift items right to create space for the new available items */ + int dest = availableIndex + oldAvailableDescendentCount + grow; + System.arraycopy ( + availableItems, + availableIndex + oldAvailableDescendentCount, + availableItems, + dest, + availableItems.length - dest); + parent.availableItemsCount += grow; + /* copy new items in */ + System.arraycopy ( + items, + items.length - grow, + availableItems, + availableIndex + oldAvailableDescendentCount, + grow); + /* update availableIndex for all affected items */ + for (int i = availableIndex + oldAvailableDescendentCount; i < parent.availableItemsCount; i++) { + availableItems [i].availableIndex = i; + } + } + } + + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + if (availableIndex != -1) { + if (expanded) parent.updateVerticalBar (); + parent.redrawItems (redrawStart, redrawEnd, false); + } +} +public void setText (String value) { + checkWidget (); + Rectangle bounds = getBounds (false); + int oldRightX = bounds.x + bounds.width; + setText (0, value); + /* horizontal bar could be affected if tree has no columns */ + if (parent.columns.length == 0) { + bounds = getBounds (false); + int newRightX = bounds.x + bounds.width; + parent.updateHorizontalBar (newRightX, newRightX - oldRightX); + } +} +/** + * Sets the text for multiple columns in the tree. + * + * @param strings 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> + * + * @since 3.1 + */ +public void setText (String[] value) { + checkWidget (); + if (value == null) error (SWT.ERROR_NULL_ARGUMENT); + Rectangle bounds = getBounds (false); + int oldRightX = bounds.x + bounds.width; + // TODO make a smarter implementation of this + for (int i = 0; i < value.length; i++) { + if (value [i] != null) setText (i, value [i]); + } + /* horizontal bar could be affected if tree has no columns */ + if (parent.columns.length == 0) { + bounds = getBounds (false); + int newRightX = bounds.x + bounds.width; + parent.updateHorizontalBar (newRightX, newRightX - oldRightX); + } +} +/** + * Sets the receiver's text at a column + * + * @param index the column index + * @param string 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> + * + * @since 3.1 + */ +public void setText (int columnIndex, String value) { + checkWidget (); + if (value == null) error (SWT.ERROR_NULL_ARGUMENT); + int validColumnCount = Math.max (1, parent.columns.length); + if (!(0 <= columnIndex && columnIndex < validColumnCount)) return; + if (value.equals (getText (columnIndex, false))) return; + if (columnIndex == 0) { + super.setText (value); + } else { + texts [columnIndex] = value; + } + if ((parent.style & SWT.VIRTUAL) != 0) cached = true; + + int oldWidth = textWidths [columnIndex]; + GC gc = new GC (parent); + gc.setFont (getFont (columnIndex, false)); + computeDisplayText (columnIndex, gc); + gc.dispose (); + if (availableIndex == -1) return; + if (parent.columns.length == 0) { + Rectangle bounds = getBounds (false); + int rightX = bounds.x + bounds.width; + parent.updateHorizontalBar (rightX, textWidths [columnIndex] - oldWidth); + } + if (isInViewport ()) { + redraw ( + getTextX (columnIndex), + parent.getItemY (this), + Math.max (oldWidth, textWidths [columnIndex]) + 2 * MARGIN_TEXT, + parent.itemHeight, + columnIndex); + } +} +/* + * Perform any internal changes necessary to reflect a changed column width. + */ +void updateColumnWidth (TreeColumn column, GC gc) { + int columnIndex = column.getIndex (); + gc.setFont (getFont (columnIndex, false)); + String oldDisplayText = displayTexts [columnIndex]; + computeDisplayText (columnIndex, gc); + + /* the cell must be damaged if there is custom drawing being done or if the alignment is not LEFT */ + if (isInViewport ()) { + boolean columnIsLeft = (column.style & SWT.LEFT) != 0; + if (!columnIsLeft || parent.hooks (SWT.EraseItem) || parent.hooks (SWT.PaintItem)) { + Rectangle cellBounds = getCellBounds (columnIndex); + parent.redraw (cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height, false); + } else { + /* if the display text has changed then the cell text must be damaged in order to repaint */ + if (oldDisplayText == null || !oldDisplayText.equals (displayTexts [columnIndex])) { + Rectangle cellBounds = getCellBounds (columnIndex); + int textX = getTextX (columnIndex); + parent.redraw (textX, cellBounds.y, cellBounds.x + cellBounds.width - textX, cellBounds.height, false); + } + } + } + + for (int i = 0; i < items.length; i++) { + items [i].updateColumnWidth (column, gc); + } +} + +/* + * The parent's font has changed, so if this font was being used by the receiver then + * recompute its cached text sizes using the gc argument. Pass this notification on to + * all child items as well. + */ +void updateFont (GC gc) { + if (font == null) { /* receiver is using the Tree's font */ + computeDisplayTexts (gc); + computeTextWidths (gc); + } + /* pass notification on to all children */ + for (int i = 0; i < items.length; i++) { + items [i].updateFont (gc); + } +} +} |