org.eclipse.swt.widgets.List.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.widgets.List.java

Source

/*******************************************************************************
 * Copyright (c) 2002, 2015 Innoopract Informationssysteme GmbH and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Innoopract Informationssysteme GmbH - initial API and implementation
 *    EclipseSource - ongoing development
 ******************************************************************************/
package org.eclipse.swt.widgets;

import static org.eclipse.rap.rwt.internal.textsize.TextSizeUtil.stringExtent;
import static org.eclipse.swt.internal.widgets.MarkupUtil.isMarkupEnabledFor;
import static org.eclipse.swt.internal.widgets.MarkupValidator.isValidationDisabledFor;

import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.internal.lifecycle.WidgetLCA;
import org.eclipse.rap.rwt.internal.textsize.TextSizeUtil;
import org.eclipse.rap.rwt.internal.theme.ThemeAdapter;
import org.eclipse.rap.rwt.theme.BoxDimensions;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.internal.widgets.IListAdapter;
import org.eclipse.swt.internal.widgets.ListModel;
import org.eclipse.swt.internal.widgets.MarkupValidator;
import org.eclipse.swt.internal.widgets.listkit.ListLCA;
import org.eclipse.swt.internal.widgets.listkit.ListThemeAdapter;

/**
 * Instances of this class represent a selectable user interface
 * object that displays a list of strings and issues notification
 * when a string is selected.  A list may be single or multi select.
 * <p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>SINGLE, MULTI</dd>
 * <dt><b>Events:</b></dt>
 * <dd>Selection, DefaultSelection</dd>
 * </dl>
 * <p>
 * Note: Only one of SINGLE and MULTI may be specified.
 * </p><p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 * @since 1.0
 */
public class List extends Scrollable {

    private final class ResizeListener extends ControlAdapter {
        @Override
        public void controlResized(ControlEvent event) {
            updateScrollBars();
        }
    }

    private final ListModel model;
    private int focusIndex;
    private transient IListAdapter listAdapter;
    private final ResizeListener resizeListener;
    private int topIndex;
    private boolean hasVScrollBar;
    private boolean hasHScrollBar;
    private BoxDimensions bufferedItemPadding;
    private int customItemHeight;

    /**
     * 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 Widget#checkSubclass
     * @see Widget#getStyle
     */
    public List(Composite parent, int style) {
        super(parent, checkStyle(style));
        model = new ListModel((style & SWT.SINGLE) != 0);
        focusIndex = -1;
        customItemHeight = -1;
        resizeListener = new ResizeListener();
        addControlListener(resizeListener);
    }

    /////////////////////
    // Adaptable override

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getAdapter(Class<T> adapter) {
        if (adapter == IListAdapter.class) {
            if (listAdapter == null) {
                listAdapter = new IListAdapter() {
                    @Override
                    public void setFocusIndex(int focusIndex) {
                        List.this.setFocusIndex(focusIndex);
                    }

                    @Override
                    public Point getItemDimensions() {
                        return List.this.getItemDimensions();
                    }
                };
            }
            return (T) listAdapter;
        }
        if (adapter == WidgetLCA.class) {
            return (T) ListLCA.INSTANCE;
        }
        return super.getAdapter(adapter);
    }

    ///////////////////////////////
    // Methods to get/set selection

    /**
     * Returns an array of <code>String</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 String[] getSelection() {
        checkWidget();
        int[] selectionIndices = model.getSelectionIndices();
        String[] result = new String[selectionIndices.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = model.getItem(selectionIndices[i]);
        }
        return result;
    }

    /**
     * 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 or -1
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - 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();
        return model.getSelectionIndex();
    }

    /**
     * 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();
        return model.getSelectionIndices();
    }

    /**
     * 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 model.getSelectionCount();
    }

    /**
     * Selects the item at the given zero-relative index in the receiver.
     * If the item at the index was already selected, it remains selected.
     * The current selection is first cleared, then the new item is selected.
     * Indices that are out of range are ignored.
     *
     * @param selection 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 List#deselectAll()
     * @see List#select(int)
     */
    // TODO [rh] selection is not scrolled into view (see List.js)
    public void setSelection(int selection) {
        checkWidget();
        model.setSelection(selection);
        updateFocusIndexAfterSelectionChange();
    }

    /**
     * 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.
     *
     * @param selection 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 List#deselectAll()
     * @see List#select(int[])
     */
    public void setSelection(int[] selection) {
        checkWidget();
        model.setSelection(selection);
        updateFocusIndexAfterSelectionChange();
    }

    /**
     * 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.
     *
     * @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 List#deselectAll()
     * @see List#select(int,int)
     */
    public void setSelection(int start, int end) {
        checkWidget();
        model.setSelection(start, end);
        updateFocusIndexAfterSelectionChange();
    }

    /**
     * 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.
     *
     * @param selection the array of items
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the array of items 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 List#deselectAll()
     * @see List#select(int[])
     * @see List#setSelection(int[])
     */
    public void setSelection(String[] selection) {
        checkWidget();
        model.setSelection(selection);
        updateFocusIndexAfterSelectionChange();
    }

    /**
     * Selects the item at the given zero-relative index in the receiver's
     * list.  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 ((style & SWT.SINGLE) != 0) {
            if (index >= 0 && index < model.getItemCount()) {
                model.setSelection(index);
            }
        } else {
            model.addSelection(index);
        }
    }

    /**
     * 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.
     *
     * @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 List#setSelection(int[])
     */
    public void select(int[] indices) {
        checkWidget();
        if (indices == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        int length = indices.length;
        if (length != 0 && ((style & SWT.SINGLE) == 0 || length <= 1)) {
            int i = 0;
            while (i < length) {
                int index = indices[i];
                model.addSelection(index);
                i++;
            }
        }
    }

    /**
     * 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.
     *
     * @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 List#setSelection(int,int)
     */
    public void select(int start, int end) {
        checkWidget();
        if (end >= 0 && start <= end && ((style & SWT.SINGLE) == 0 || start == end)) {
            int count = model.getItemCount();
            if (count != 0 && start < count) {
                int startIndex = Math.max(0, start);
                int endIndex = Math.min(end, count - 1);
                if ((style & SWT.SINGLE) != 0) {
                    model.setSelection(startIndex);
                } else {
                    for (int i = startIndex; i <= endIndex; i++) {
                        model.addSelection(i);
                    }
                }
            }
        }
    }

    /**
     * Selects all of the items in the receiver.
     * <p>
     * If the receiver is single-select, do nothing.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - 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();
        model.selectAll();
        updateFocusIndexAfterSelectionChange();
    }

    /**
     * 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();
        model.deselectAll();
        updateFocusIndexAfterSelectionChange();
    }

    /**
     * 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>
     *
     * @since 1.3
     */
    public void deselect(int index) {
        checkWidget();
        removeFromSelection(index);
    }

    /**
     * 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>
     *
     * @since 1.3
     */
    public void deselect(int start, int end) {
        checkWidget();
        if (start == 0 && end == model.getItemCount() - 1) {
            deselectAll();
        } else {
            int actualStart = Math.max(0, start);
            for (int i = actualStart; i <= end; i++) {
                removeFromSelection(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>
     *
     * @since 1.3
     */
    public void deselect(int[] indices) {
        checkWidget();
        if (indices == null) {
            error(SWT.ERROR_NULL_ARGUMENT);
        }
        for (int i = 0; i < indices.length; i++) {
            removeFromSelection(indices[i]);
        }
    }

    private void removeFromSelection(int index) {
        if (index >= 0 && index < model.getItemCount()) {
            boolean found = false;
            int selection[] = model.getSelectionIndices();
            for (int i = 0; !found && i < selection.length; i++) {
                if (index == selection[i]) {
                    int length = selection.length;
                    int[] newSel = new int[length - 1];
                    System.arraycopy(selection, 0, newSel, 0, i);
                    if (i < length - 1) {
                        System.arraycopy(selection, i + 1, newSel, i, length - i - 1);
                    }
                    selection = newSel;
                    model.setSelection(selection);
                    found = true;
                }
            }
        }
    }

    /**
     * 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 visibility 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();
        boolean result;
        if ((style & SWT.SINGLE) != 0) {
            result = index == getSelectionIndex();
        } else {
            int[] selectionIndices = getSelectionIndices();
            result = false;
            for (int i = 0; !result && i < selectionIndices.length; i++) {
                if (index == selectionIndices[i]) {
                    result = true;
                }
            }
        }
        return result;
    }

    /**
     * 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 topIndex 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>
     *
     * @since 1.3
     */
    public void setTopIndex(int topIndex) {
        checkWidget();
        int count = model.getItemCount();
        if (this.topIndex != topIndex && topIndex >= 0 && topIndex < count) {
            this.topIndex = topIndex;
        }
    }

    /**
     * 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>
     *
     * @since 1.3
     */
    public int getTopIndex() {
        checkWidget();
        return topIndex;
    }

    /**
     * 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>
     *
     * @since 1.3
     */
    public void showSelection() {
        checkWidget();
        int index = getSelectionIndex();
        if (index != -1) {
            int itemCount = getVisibleItemCount();
            if (index < topIndex) {
                // Show item as top item
                setTopIndex(index);
            } else if (itemCount > 0 && index >= topIndex + itemCount) {
                // Show item as last item
                setTopIndex(index - itemCount + 1);
            }
        }
    }

    /**
     * Returns the zero-relative index of the item which currently
     * has the focus in the receiver, or -1 if no item has focus.
     *
     * @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 getFocusIndex() {
        checkWidget();
        return focusIndex;
    }

    ////////////////////////////////
    // Methods to maintain the items

    /**
     * Adds the argument to the end of the receiver's list.
     *
     * @param string the new item
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the string 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 #add(String,int)
     */
    public void add(String string) {
        checkWidget();
        model.add(string);
        updateFocusIndexAfterItemChange();
        updateScrollBars();
    }

    /**
     * Adds the argument to the receiver's list at the given
     * zero-relative index.
     * <p>
     * Note: To add an item at the end of the list, use the
     * result of calling <code>getItemCount()</code> as the
     * index or use <code>add(String)</code>.
     * </p>
     *
     * @param string the new item
     * @param index the index for the item
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
     *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list (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 #add(String)
     */
    public void add(String string, int index) {
        checkWidget();
        model.add(string, index);
        updateFocusIndexAfterItemChange();
        updateScrollBars();
    }

    /**
     * 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();
        model.remove(index);
        updateFocusIndexAfterItemChange();
        adjustTopIndex();
        updateScrollBars();
    }

    /**
     * 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();
        model.remove(start, end);
        updateFocusIndexAfterItemChange();
        adjustTopIndex();
        updateScrollBars();
    }

    /**
     * Removes the items from the receiver 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();
        model.remove(indices);
        updateFocusIndexAfterItemChange();
        adjustTopIndex();
        updateScrollBars();
    }

    /**
     * Searches the receiver's list starting at the first item
     * until an item is found that is equal to the argument,
     * and removes that item from the list.
     *
     * @param string the item to remove
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the string is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the string is not found in the list</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(String string) {
        checkWidget();
        model.remove(string);
        updateFocusIndexAfterItemChange();
        adjustTopIndex();
        updateScrollBars();
    }

    /**
     * Removes all of the items from the receiver.
     * <p>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void removeAll() {
        checkWidget();
        model.removeAll();
        updateFocusIndexAfterItemChange();
        adjustTopIndex();
        updateScrollBars();
    }

    /**
     * Sets the text of the item in the receiver's list at the given
     * zero-relative index to the string argument. This is equivalent
     * to <code>remove</code>'ing the old item at the index, and then
     * <code>add</code>'ing the new item at that index.
     *
     * @param index the index for the item
     * @param string the new text 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>
     *    <li>ERROR_NULL_ARGUMENT - if the string 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 setItem(int index, String string) {
        checkWidget();
        validateMarkup(new String[] { string });
        model.setItem(index, string);
        updateScrollBars();
    }

    /**
     * Sets the receiver's items to be the given array of items.
     *
     * @param items the array of items
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the items array is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if an item in the items 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 setItems(String[] items) {
        checkWidget();
        validateMarkup(items);
        model.setItems(items);
        updateScrollBars();
    }

    /**
     * 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 String getItem(int index) {
        checkWidget();
        return model.getItem(index);
    }

    /**
     * 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 model.getItemCount();
    }

    /**
     * Returns a (possibly empty) array of <code>String</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's list
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - 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[] getItems() {
        checkWidget();
        return model.getItems();
    }

    /**
     * Gets the index of an item.
     * <p>
     * The list is searched starting at 0 until an
     * item is found that is equal to the search item.
     * If no item is found, -1 is returned.  Indexing
     * is zero based.
     *
     * @param string the search item
     * @return the index of the item
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the string 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(String string) {
        checkWidget();
        return indexOf(string, 0);
    }

    /**
     * Searches the receiver's list starting at the given,
     * zero-relative index until an item is found that is equal
     * to the argument, and returns the index of that item. If
     * no item is found or the starting index is out of range,
     * returns -1.
     *
     * @param string the search item
     * @param start the zero-relative index at which to start the search
     * @return the index of the item
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the string 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(String string, int start) {
        checkWidget();
        if (string == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        return model.indexOf(string, start);
    }

    /**
     * Returns the height of the area which would be used to
     * display <em>one</em> of the items in the list.
     *
     * @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();
        int result = customItemHeight;
        if (result == -1) {
            BoxDimensions itemPadding = getItemPadding();
            result = TextSizeUtil.getCharHeight(getFont()) + itemPadding.top + itemPadding.bottom;
        }
        return result;
    }

    /////////////////////////////////////////
    // Listener registration/de-registration

    /**
     * Adds the listener to the collection of listeners who will
     * be notified when the receiver's selection changes, by sending
     * it one of the messages defined in the <code>SelectionListener</code>
     * interface.
     * <p>
     * <code>widgetSelected</code> is called when the selection changes.
     * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked.
     * </p>
     *
     * @param listener the listener which should be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SelectionListener
     * @see #removeSelectionListener
     * @see SelectionEvent
     */
    public void addSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        TypedListener typedListener = new TypedListener(listener);
        addListener(SWT.Selection, typedListener);
        addListener(SWT.DefaultSelection, typedListener);
    }

    /**
     * Removes the listener from the collection of listeners who will
     * be notified when the receiver's selection changes.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SelectionListener
     * @see #addSelectionListener
     */
    public void removeSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        removeListener(SWT.Selection, listener);
        removeListener(SWT.DefaultSelection, listener);
    }

    @Override
    public void setFont(Font font) {
        super.setFont(font);
        updateScrollBars();
    }

    @Override
    boolean isTabGroup() {
        return true;
    }

    @Override
    public void setData(String key, Object value) {
        if (RWT.CUSTOM_ITEM_HEIGHT.equals(key)) {
            setCustomItemHeight(value);
        }
        if (!RWT.MARKUP_ENABLED.equals(key) || !isMarkupEnabledFor(this)) {
            super.setData(key, value);
        }
    }

    /////////////////////////////////////////
    // Widget dimensions

    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        checkWidget();
        int width = getMaxItemWidth();
        int height = getItemHeight() * getItemCount();
        if (width == 0) {
            width = DEFAULT_WIDTH;
        }
        if (height == 0) {
            height = DEFAULT_HEIGHT;
        }
        if (wHint != SWT.DEFAULT) {
            width = wHint;
        }
        if (hHint != SWT.DEFAULT) {
            height = hHint;
        }
        BoxDimensions border = getBorder();
        width += border.left + border.right;
        height += border.top + border.bottom;
        if ((style & SWT.V_SCROLL) != 0) {
            width += getVerticalBar().getSize().x;
        }
        if ((style & SWT.H_SCROLL) != 0) {
            height += getHorizontalBar().getSize().y;
        }
        return new Point(width, height);
    }

    /////////////////////////////////
    // Helping methods for focusIndex

    private void setFocusIndex(int focusIndex) {
        int count = model.getItemCount();
        if (focusIndex == -1 || (focusIndex >= 0 && focusIndex < count)) {
            this.focusIndex = focusIndex;
        }
    }

    private void updateFocusIndexAfterSelectionChange() {
        focusIndex = -1;
        if (model.getItemCount() > 0) {
            if (model.getSelectionIndex() == -1) {
                focusIndex = 0;
            } else {
                focusIndex = model.getSelectionIndices()[0];
            }
        }
    }

    private void updateFocusIndexAfterItemChange() {
        if (model.getItemCount() == 0) {
            focusIndex = -1;
        } else if (model.getSelectionIndex() == -1) {
            focusIndex = model.getItemCount() - 1;
        }
    }

    //////////////////
    // Helping methods

    private static int checkStyle(int style) {
        return checkBits(style, SWT.SINGLE, SWT.MULTI, 0, 0, 0, 0);
    }

    private int getItemWidth(String item) {
        Point extent = stringExtent(getFont(), item, isMarkupEnabledFor(this));
        BoxDimensions itemPadding = getItemPadding();
        return extent.x + itemPadding.left + itemPadding.right;
    }

    private int getMaxItemWidth() {
        int result = 0;
        String[] items = getItems();
        for (int i = 0; i < items.length; i++) {
            int itemWidth = getItemWidth(items[i]);
            result = Math.max(result, itemWidth);
        }
        return result;
    }

    private void adjustTopIndex() {
        int count = model.getItemCount();
        if (count == 0) {
            topIndex = 0;
        } else if (topIndex >= count - 1) {
            topIndex = count - 1;
        }
    }

    final int getVisibleItemCount() {
        int clientHeight = getBounds().height;
        if ((style & SWT.H_SCROLL) != 0) {
            clientHeight -= getHorizontalBar().getSize().y;
        }
        int result = 0;
        if (clientHeight >= 0) {
            int itemHeight = getItemHeight();
            result = clientHeight / itemHeight;
        }
        return result;
    }

    Point getItemDimensions() {
        int width = 0;
        int height = 0;
        if (getItemCount() > 0) {
            int availableWidth = getClientArea().width;
            if ((style & SWT.H_SCROLL) == 0 && isMarkupEnabledFor(this)) {
                width = availableWidth;
            } else {
                width = Math.max(getMaxItemWidth(), availableWidth);
            }
            height = getItemHeight();
        }
        return new Point(width, height);
    }

    private BoxDimensions getItemPadding() {
        if (bufferedItemPadding == null) {
            ListThemeAdapter themeAdapter = (ListThemeAdapter) getAdapter(ThemeAdapter.class);
            bufferedItemPadding = themeAdapter.getItemPadding(this);
        }
        return bufferedItemPadding;
    }

    private void setCustomItemHeight(Object value) {
        if (value == null) {
            customItemHeight = -1;
        } else {
            if (!(value instanceof Integer)) {
                error(SWT.ERROR_INVALID_ARGUMENT);
            }
            int itemHeight = ((Integer) value).intValue();
            if (itemHeight < 0) {
                error(SWT.ERROR_INVALID_RANGE);
            }
            customItemHeight = itemHeight;
        }
    }

    private void validateMarkup(String[] items) {
        if (items != null && isMarkupEnabledFor(this) && !isValidationDisabledFor(this)) {
            for (int i = 0; i < items.length; i++) {
                if (items[i] != null) {
                    MarkupValidator.getInstance().validate(items[i]);
                }
            }
        }
    }

    ///////////////////////////////////////
    // Helping methods - dynamic scrollbars

    boolean hasVScrollBar() {
        return (style & SWT.V_SCROLL) != 0 && hasVScrollBar;
    }

    boolean hasHScrollBar() {
        return (style & SWT.H_SCROLL) != 0 && hasHScrollBar;
    }

    @Override
    int getVScrollBarWidth() {
        int result = 0;
        if (hasVScrollBar()) {
            result = getVerticalBar().getSize().x;
        }
        return result;
    }

    @Override
    int getHScrollBarHeight() {
        int result = 0;
        if (hasHScrollBar()) {
            result = getHorizontalBar().getSize().y;
        }
        return result;
    }

    boolean needsVScrollBar() {
        int availableHeight = getClientArea().height;
        int height = getItemCount() * getItemHeight();
        return height > availableHeight;
    }

    boolean needsHScrollBar() {
        boolean result = false;
        if ((style & SWT.H_SCROLL) != 0) {
            int availableWidth = getClientArea().width;
            int width = getMaxItemWidth();
            result = width > availableWidth;
        }
        return result;
    }

    void updateScrollBars() {
        hasVScrollBar = false;
        hasHScrollBar = needsHScrollBar();
        if (needsVScrollBar()) {
            hasVScrollBar = true;
            hasHScrollBar = needsHScrollBar();
        }
        ScrollBar hScroll = getHorizontalBar();
        if (hScroll != null) {
            hScroll.setVisible(hasHScrollBar);
        }
        ScrollBar vScroll = getVerticalBar();
        if (vScroll != null) {
            vScroll.setVisible(hasVScrollBar);
        }
    }
}