com.microsoft.tfs.client.common.ui.framework.table.TableControl.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.client.common.ui.framework.table.TableControl.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the repository root.

package com.microsoft.tfs.client.common.ui.framework.table;

import java.lang.reflect.Array;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ICheckable;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

import com.microsoft.tfs.client.common.ui.controls.generic.BaseControl;
import com.microsoft.tfs.client.common.ui.framework.helper.ContentProviderAdapter;
import com.microsoft.tfs.client.common.ui.framework.helper.TableViewerSorter;
import com.microsoft.tfs.client.common.ui.framework.layout.GridDataBuilder;
import com.microsoft.tfs.client.common.ui.framework.sizing.ControlSize;
import com.microsoft.tfs.client.common.ui.framework.table.tooltip.TableTooltipLabelManager;
import com.microsoft.tfs.client.common.ui.framework.table.tooltip.TableTooltipLabelProvider;
import com.microsoft.tfs.client.common.ui.framework.validation.CheckboxProviderValidator;
import com.microsoft.tfs.client.common.ui.framework.validation.ElementProviderValidator;
import com.microsoft.tfs.client.common.ui.framework.validation.NumericConstraint;
import com.microsoft.tfs.client.common.ui.framework.validation.SelectionProviderValidator;
import com.microsoft.tfs.client.common.ui.framework.viewer.CheckboxEvent;
import com.microsoft.tfs.client.common.ui.framework.viewer.CheckboxListener;
import com.microsoft.tfs.client.common.ui.framework.viewer.CheckboxProvider;
import com.microsoft.tfs.client.common.ui.framework.viewer.ElementEvent;
import com.microsoft.tfs.client.common.ui.framework.viewer.ElementListener;
import com.microsoft.tfs.client.common.ui.framework.viewer.ElementProvider;
import com.microsoft.tfs.util.Check;
import com.microsoft.tfs.util.listeners.SingleListenerFacade;
import com.microsoft.tfs.util.valid.Validator;

/**
 * <p>
 * {@link TableControl} is a base class for table-based UI controls that use a
 * JFace {@link TableViewer}.
 * </p>
 *
 * <p>
 * The SWT table styles used by {@link TableControl} tables always include
 * {@link SWT#BORDER} (unless style is passed with
 * {@link TableControl#NO_BORDER}), {@link SWT#V_SCROLL}, and
 * {@link SWT#H_SCROLL}. Optionally, the client can pass in the styles
 * {@link SWT#FULL_SELECTION}, {@link SWT#HIDE_SELECTION}, {@link SWT#MULTI}, or
 * {@link SWT#CHECK} which will be used if specified.
 * </p>
 *
 * <p>
 * A {@link TableControl} tracks 3 collections of elements: all elements,
 * selected elements, and checked elements (see {@link ElementCollectionType}).
 * These collections are tracked across the lifetime of the {@link TableControl}
 * . Clients can retrieve the current collections, respond to changes in the
 * collections, and access the collections after the underlying {@link Table}
 * has been disposed. The element collections are strongly-typed - the runtime
 * type of the arrays available from this class is specified by an element type.
 * </p>
 *
 * <p>
 * The selected and checked element collections can be tracked automatically
 * without requiring coordination with the subclass. Unfortunately, the
 * all-elements collection can't. The subclass must call
 * {@link #computeElements()} any time the elements collection may have changed.
 * For instance, if the subclass calls {@link #getViewer()} and sets the input
 * manually, the subclass must follow that with a call to
 * {@link #computeElements()}. Convenience methods on this class to set the
 * input ({@link #setElements(Object[])},{@link #setInput(Object)}, etc.)
 * perform this housekeeping task automatically and should be used when
 * possible.
 * </p>
 *
 * <p>
 * Subclasses should usually provide convenience methods for accessing the
 * element collections that have strongly-typed return value types and argument
 * types. For example, if the element type displayed by a subclass is
 * <code>Name</code>, then the suggested convenience methods are:
 * <ul>
 * <li><code>setNames(Name[])</code>: delegates to {@link #setElements(Object[])}
 * </li>
 * <li><code>Name[] getNames()</code>: delegates to {@link #getElements()}</li>
 * <li><code>setSelectedNames(Name[])</code>: delegates to
 * {@link #setSelectedElements(Object[])}</li>
 * <li><code>setSelectedName(Name)</code>: delegates to
 * {@link #setSelectedElement(Object)}</li>
 * <li><code>Name[] getSelectedNames()</code>: delegates to
 * {@link #getSelectedElements()}</li>
 * <li><code>Name getSelectedName()</code>: delegates to
 * {@link #getSelectedElement()}</li>
 * <li><code>setCheckedNames(Name[])</code>: delegates to
 * {@link #setCheckedElements(Object[])}</li>
 * <li><code>Name[] getCheckedNames()</code>: delegates to
 * {@link #getCheckedElements()}</li>
 * </ul>
 * </p>
 *
 * @see TableViewer
 * @see IPostSelectionProvider
 * @see ElementProvider
 * @see CheckboxProvider
 */
public abstract class TableControl extends BaseControl implements IPostSelectionProvider, CheckboxProvider,
        ElementProvider, ICheckable, TableTooltipLabelProvider {
    /**
     * SWT table styles that will always be used.
     */
    private static int TABLE_STYLES = SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL;

    /**
     * Override the default border.
     */
    public static int NO_BORDER = 1 << 31;

    /**
     * SWT table styles that will only be used if the client passes them in.
     */
    private static int OPTIONAL_TABLE_STYLES = SWT.FULL_SELECTION | SWT.MULTI | SWT.CHECK | SWT.HIDE_SELECTION;

    /**
     * The type of element managed by this {@link TableControl}. Methods that
     * return array data (for example, {@link #getSelectedElements()}) will
     * return arrays that have this component type.
     */
    private final Class elementType;

    /**
     * A key that can be used to persist view data (never <code>null</code>).
     */
    private final String viewDataKey;

    /**
     * The SWT table style that we used when the table was created.
     */
    private final int tableStyles;

    /**
     * The {@link TableViewer} this {@link TableControl} wraps.
     */
    private final TableViewer viewer;

    /**
     * The {@link MenuManager} attached to this {@link TableControl}'s
     * {@link Table}.
     */
    private final MenuManager contextMenu;

    /**
     * An optional text label prompt for the table.
     */
    private Label textLabel = null;

    /**
     * Tracks {@link ISelectionChangedListener}s registered as normal selection
     * changed listeners.
     */
    private final SingleListenerFacade selectionListener = new SingleListenerFacade(
            ISelectionChangedListener.class);

    /**
     * Tracks {@link ISelectionChangedListener}s registered as post-selection
     * changed listeners.
     */
    private final SingleListenerFacade postSelectionListener = new SingleListenerFacade(
            ISelectionChangedListener.class);

    /**
     * Tracks registered {@link CheckboxListener}s.
     */
    private final SingleListenerFacade checkboxListener = new SingleListenerFacade(CheckboxListener.class);

    /**
     * Tracks registered {@link ElementListener}s.
     */
    private final SingleListenerFacade elementListener = new SingleListenerFacade(ElementListener.class);

    /**
     * Tracks registered {@link ICheckStateListener}s.
     */
    private final SingleListenerFacade checkStateListener = new SingleListenerFacade(ICheckStateListener.class);

    /**
     * Tracks registered {@link IDoubleClickListener}s.
     */
    private final SingleListenerFacade doubleClickListener = new SingleListenerFacade(IDoubleClickListener.class);

    /**
     * The {@link DragSource} we create for the table. Never <code>null</code>
     * and disposed in the {@link #widgetDisposed(DisposeEvent)} method.
     */
    private final DragSource dragSource;

    /**
     * If <code>true</code>, we are using {@link #tooltipMouseTrackListener} to
     * provide per-element table tooltips.
     */
    private boolean enableTooltips;

    /**
     * The table tooltip listener.
     */
    private TableTooltipLabelManager tooltipManager;

    /**
     * If <code>true</code>, a check or uncheck should be propogated to all
     * elements in the current selection.
     */
    private boolean checksAffectSelection = true;

    /**
     * The current elements in this {@link TableControl}, or <code>null</code>
     * if there are no elements. The component type of this array is
     * {@link #elementType}.
     */
    private Object[] allElements;

    /**
     * The currently selected elements in this {@link TableControl}, or
     * <code>null</code> if there are no selected elements. The component type
     * of this array is {@link #elementType}.
     */
    private Object[] selectedElements;

    /**
     * The currently checked elements in this {@link TableControl}, or
     * <code>null</code> if there are no checked elements. The component type of
     * this array is {@link #elementType}.
     */
    private Object[] checkedElements;

    /**
     * The lazily created {@link Clipboard}. This field starts off as
     * <code>null</code>, and a clipboard is allocated if needed. This field
     * will be cleaned up in the {@link #widgetDisposed(DisposeEvent)} method,
     * if necessary.
     */
    private Clipboard clipboard;

    /**
     * The transfer types that will be used to copy data to the clipboard. If
     * <code>null</code> or a 0-length array, no data will be copied to the
     * clipboard.
     */
    private Transfer[] clipboardTransferTypes;

    private boolean persistGeometry = true;

    /**
     * <p>
     * Constructs a new {@link TableControl}. See the class documentation for
     * the supported style bits.
     * </p>
     *
     * <p>
     * Subclasses must indicate the runtime type of the element collections
     * managed by this base class by specifying a {@link Class} argument to this
     * constructor. This type is used to determine the component type of the
     * arrays available from public methods on this base class (for example,
     * {@link #getElements()}).
     * </p>
     *
     * <p>
     * Subclasses may optionally pass in a view data key that can be used any
     * time view data needs to be persisted by this {@link TableControl}. If
     * <code>null</code>, a default view data key will be used which is the
     * short class name of the subclass.
     * </p>
     *
     * @param parent
     *        parent {@link Composite}
     * @param style
     *        style bits as described in the class documentation
     * @param elementType
     *        the element type of this {@link TableControl} (must not be
     *        <code>null</code>)
     * @param viewDataKey
     *        a view data key, or <code>null</code> to compute a default key
     */
    protected TableControl(final Composite parent, final int style, final Class elementType, String viewDataKey) {
        super(parent, style);

        Check.notNull(elementType, "elementType"); //$NON-NLS-1$
        if (elementType.isPrimitive()) {
            final String messageFormat = "a TableControl can't be created with the primitive element type [{0}]"; //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, elementType.getName());
            throw new IllegalArgumentException(message);
        }
        this.elementType = elementType;

        if (viewDataKey == null) {
            String className = getClass().getName();
            className = className.substring(className.lastIndexOf('.') + 1);

            String parentName = parent.getClass().getName();
            parentName = parentName.substring(parentName.lastIndexOf('.') + 1);

            String shellName = getShell().getText();
            shellName = shellName.replaceAll("[^a-zA-Z0-9]", ""); //$NON-NLS-1$ //$NON-NLS-2$

            viewDataKey = className + "#" + parentName + "#" + shellName; //$NON-NLS-1$ //$NON-NLS-2$
        }
        this.viewDataKey = viewDataKey;

        final GridLayout layout = new GridLayout();
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        layout.horizontalSpacing = 0;
        layout.verticalSpacing = getVerticalSpacing() / 2;
        setLayout(layout);

        tableStyles = ((style & NO_BORDER) == NO_BORDER)
                ? ((TABLE_STYLES & ~SWT.BORDER) | (OPTIONAL_TABLE_STYLES & (style & ~NO_BORDER)))
                : (TABLE_STYLES | (OPTIONAL_TABLE_STYLES & style));

        final Table table = new Table(this, tableStyles);
        GridDataBuilder.newInstance().hGrab().hFill().vGrab().vFill().applyTo(table);

        if (isCheckboxTable()) {
            viewer = new CheckboxTableViewer(table);
        } else {
            viewer = new TableViewer(table);
        }

        viewer.setUseHashlookup(true);

        contextMenu = createContextMenu(table);

        dragSource = new DragSource(viewer.getControl(), DND.DROP_COPY);
        dragSource.addDragListener(new DragSourceAdapter() {
            @Override
            public void dragSetData(final DragSourceEvent event) {
                TableControl.this.dragSetData(event);
            }
        });

        hookTable(table);

        addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(final DisposeEvent e) {
                TableControl.this.widgetDisposed(e);
            }
        });

        viewer.setCellModifier(new ICellModifier() {
            @Override
            public boolean canModify(final Object element, final String property) {
                return TableControl.this.canModifyElement(element, property);
            }

            @Override
            public Object getValue(final Object element, final String property) {
                return TableControl.this.getValueToModify(element, property);
            }

            @Override
            public void modify(Object element, final String property, final Object value) {
                if (element instanceof TableItem) {
                    element = ((TableItem) element).getData();
                }

                TableControl.this.modifyElement(element, property, value);
            }
        });
        // Must default column properties in case subclass does not define any.
        viewer.setColumnProperties(new String[0]);
    }

    public void setText(final String text) {
        if (textLabel == null && text != null) {
            textLabel = new Label(this, SWT.NONE);
            textLabel.setText(text);
            textLabel.moveAbove(viewer.getTable());
        } else if (textLabel != null && text != null) {
            textLabel.setText(text);
        } else if (textLabel != null && text == null) {
            textLabel.setText(""); //$NON-NLS-1$
        }
    }

    public String getText() {
        if (textLabel == null) {
            return null;
        }

        return textLabel.getText();
    }

    /**
     * Gets the {@link MenuManager} that serves as the context menu for this
     * {@link TableControl}. The context menu is attached to the {@link Table}
     * control available by calling {@link #getTable()}.
     *
     * @return the context menu for this control (never <code>null</code>)
     */
    public MenuManager getContextMenu() {
        return contextMenu;
    }

    /*
     * (non-Javadoc)
     *
     * @seeorg.eclipse.jface.viewers.IPostSelectionProvider#
     * addPostSelectionChangedListener
     * (org.eclipse.jface.viewers.ISelectionChangedListener)
     */
    @Override
    public void addPostSelectionChangedListener(final ISelectionChangedListener listener) {
        postSelectionListener.addListener(listener);
    }

    /*
     * (non-Javadoc)
     *
     * @seeorg.eclipse.jface.viewers.IPostSelectionProvider#
     * removePostSelectionChangedListener
     * (org.eclipse.jface.viewers.ISelectionChangedListener)
     */
    @Override
    public void removePostSelectionChangedListener(final ISelectionChangedListener listener) {
        postSelectionListener.removeListener(listener);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener
     * (org.eclipse.jface.viewers.ISelectionChangedListener)
     */
    @Override
    public void addSelectionChangedListener(final ISelectionChangedListener listener) {
        selectionListener.addListener(listener);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ISelectionProvider#
     * removeSelectionChangedListener
     * (org.eclipse.jface.viewers.ISelectionChangedListener)
     */
    @Override
    public void removeSelectionChangedListener(final ISelectionChangedListener listener) {
        selectionListener.removeListener(listener);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
     */
    @Override
    public ISelection getSelection() {
        if (selectedElements == null) {
            return StructuredSelection.EMPTY;
        }

        /*
         * Note: StructuredSelection's constructor makes a copy of the passed-in
         * array.
         */
        return new StructuredSelection(selectedElements);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse
     * .jface.viewers.ISelection)
     */
    @Override
    public void setSelection(final ISelection selection) {
        viewer.setSelection(selection);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addCheckboxListener(final CheckboxListener listener) {
        throwIfNotCheckboxTable();
        checkboxListener.addListener(listener);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeCheckboxListener(final CheckboxListener listener) {
        throwIfNotCheckboxTable();
        checkboxListener.removeListener(listener);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addElementListener(final ElementListener listener) {
        elementListener.addListener(listener);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeElementListener(final ElementListener listener) {
        elementListener.removeListener(listener);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.microsoft.tfs.client.common.ui.shared.viewer.ElementProvider#
     * getElements ()
     */
    @Override
    public Object[] getElements() {
        return getElementCollection(ElementCollectionType.ALL_ELEMENTS);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.viewers.ICheckable#addCheckStateListener(org.eclipse
     * .jface.viewers.ICheckStateListener)
     */
    @Override
    public void addCheckStateListener(final ICheckStateListener listener) {
        throwIfNotCheckboxTable();
        checkStateListener.addListener(listener);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.eclipse.jface.viewers.ICheckable#removeCheckStateListener(org.eclipse
     * .jface.viewers.ICheckStateListener)
     */
    @Override
    public void removeCheckStateListener(final ICheckStateListener listener) {
        throwIfNotCheckboxTable();
        checkStateListener.removeListener(listener);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ICheckable#getChecked(java.lang.Object)
     */
    @Override
    public boolean getChecked(final Object element) {
        throwIfNotCheckboxTable();
        return ((CheckboxTableViewer) viewer).getChecked(element);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ICheckable#setChecked(java.lang.Object,
     * boolean)
     */
    @Override
    public boolean setChecked(final Object element, final boolean state) {
        throwIfNotCheckboxTable();
        final boolean success = ((CheckboxTableViewer) viewer).setChecked(element, state);

        if (success) {
            /**
             * Programmatically setting check state
             * (CheckboxTableViewer#setChecked()) does not fire a
             * CheckStateChangedEvent. We need to manually trigger a recompute
             * of the checked element set.
             */
            computeCheckedElements(true);
        }

        return success;
    }

    /**
     * @return the number of elements currently contained in this control
     */
    public int getCount() {
        return getElementCollectionCount(ElementCollectionType.ALL_ELEMENTS);
    }

    /**
     * Obtains the currently selected elements in this {@link TableControl}. The
     * component type of the returned array is the element type that was
     * specified at construction time.
     *
     * @return the currently selected elements or an empty array if there are no
     *         currently selected elements (never returns <code>null</code>)
     */
    public Object[] getSelectedElements() {
        return getElementCollection(ElementCollectionType.SELECTED_ELEMENTS);
    }

    /**
     * Obtains the first of the currently selected elements in this
     * {@link TableControl}.
     *
     * @return the first selected element, or <code>null</code> if no elements
     *         are currently selected
     */
    public Object getSelectedElement() {
        if (selectedElements == null || selectedElements.length == 0) {
            return null;
        }
        return selectedElements[0];
    }

    /**
     * Sets the currently selected elements in this {@link TableControl}. Any
     * existing selection is discarded. Any changes in the selected elements
     * will be reported to registered {@link ISelectionChangedListener}s.
     *
     * @param elementsToSelect
     *        the elements that should be selected in this control, or
     *        <code>null</code> if no elements should be selected
     */
    public void setSelectedElements(final Object[] elementsToSelect) {
        ISelection selection;

        if (elementsToSelect == null) {
            selection = StructuredSelection.EMPTY;
        } else {
            selection = new StructuredSelection(elementsToSelect);
        }

        setSelection(selection);
    }

    /**
     * Sets the currently selected element in this {@link TableControl}. Any
     * existing selection is discarded. Any changes in the selected elements
     * will be reported to registered {@link ISelectionChangedListener}s.
     *
     * @param elementToSelect
     *        the element that should be selected in this control, or
     *        <code>null</code> if no element should be selected
     */
    public void setSelectedElement(final Object elementToSelect) {
        ISelection selection;

        if (elementToSelect == null) {
            selection = StructuredSelection.EMPTY;
        } else {
            selection = new StructuredSelection(elementToSelect);
        }

        setSelection(selection);
    }

    /**
     * Sets the currently selected element in this {@link TableControl} to be
     * the first element. Any existing selection is discarded. Any changes in
     * the selected elements will be reported to registered
     * {@link ISelectionChangedListener}s. If there are no elements currently in
     * this {@link TableControl}, this method does nothing.
     */
    public void selectFirst() {
        final Table table = getTable();
        if (table.getItemCount() == 0) {
            return;
        }
        final Object element = table.getItem(0).getData();
        setSelection(new StructuredSelection(element));
    }

    /**
     * Sets every element currently in this {@link TableControl} to be selected.
     * Any existing selection is discarded. Any changes in the selected elements
     * will be reported to registered {@link ISelectionChangedListener}s.
     */
    public void selectAll() {
        final Table table = getTable();

        final TableItem[] items = table.getItems();
        final List elements = new ArrayList();
        for (int i = 0; i < items.length; i++) {
            final Object element = items[i].getData();
            if (!hideElementFromCollections(element)) {
                elements.add(element);
            }
        }
        setSelection(new StructuredSelection(elements));
    }

    /**
     * Unselects all elements in this {@link TableControl}. Any existing
     * selection is discarded. Any changes in the selected elements will be
     * reported to registered {@link ISelectionChangedListener}s.
     */
    public void unselectAll() {
        setSelection(StructuredSelection.EMPTY);
    }

    /**
     * @return the number of elements currently selected
     */
    public int getSelectionCount() {
        return getElementCollectionCount(ElementCollectionType.SELECTED_ELEMENTS);
    }

    /**
     * Obtains the currently checked elements in this {@link TableControl}. The
     * component type of the returned array is the element type that was
     * specified at construction time. If this {@link TableControl} was not
     * constructed with the {@link SWT#CHECK} style, an exception is thrown.
     *
     * @return the currently checked elements or an empty array if there are no
     *         currently checked elements (never returns <code>null</code>)
     */
    @Override
    public Object[] getCheckedElements() {
        return getElementCollection(ElementCollectionType.CHECKED_ELEMENTS);
    }

    /**
     * Sets the currently checked elements in this {@link TableControl}. Any
     * existing check state is discarded. Any changes in the checked elements
     * will be reported to registered {@link CheckboxListener}s. If this
     * {@link TableControl} was not constructed with the {@link SWT#CHECK}
     * style, an exception is thrown.
     *
     * @param elementsToCheck
     *        the elements that should be checked in this control, or
     *        <code>null</code> if no elements should be checked
     */
    public void setCheckedElements(Object[] elementsToCheck) {
        throwIfNotCheckboxTable();

        if (elementsToCheck == null) {
            elementsToCheck = new Object[0];
        }

        ((CheckboxTableViewer) viewer).setCheckedElements(elementsToCheck);

        /**
         * Programmatically setting check state
         * (CheckboxTableViewer#setCheckedElements()) does not fire a
         * CheckStateChangedEvent. We need to manually trigger a recompute of
         * the checked element set.
         */
        computeCheckedElements(true);
    }

    /**
     * Sets all elements currently in this {@link TableControl} to be checked.
     * Any existing check state is discarded. Any changes in the checked
     * elements will be reported to registered {@link CheckboxListener}s. If
     * this {@link TableControl} was not constructed with the {@link SWT#CHECK}
     * style, an exception is thrown.
     */
    public void checkAll() {
        throwIfNotCheckboxTable();
        ((CheckboxTableViewer) viewer).setAllChecked(true);

        /**
         * Programmatically setting check state
         * (CheckboxTableViewer#setAllChecked()) does not fire a
         * CheckStateChangedEvent. We need to manually trigger a recompute of
         * the checked element set.
         */
        computeCheckedElements(true);
    }

    /**
     * Unchecks all elements currently in this {@link TableControl}. Any
     * existing check state is discarded. Any changes in the checked elements
     * will be reported to registered {@link CheckboxListener}s. If this
     * {@link TableControl} was not constructed with the {@link SWT#CHECK}
     * style, an exception is thrown.
     */
    public void uncheckAll() {
        throwIfNotCheckboxTable();
        ((CheckboxTableViewer) viewer).setAllChecked(false);

        /**
         * Programmatically setting check state
         * (CheckboxTableViewer#setAllChecked()) does not fire a
         * CheckStateChangedEvent. We need to manually trigger a recompute of
         * the checked element set.
         */
        computeCheckedElements(true);
    }

    /**
     * Sets the check state of the specified element to checked. Any existing
     * check state of other elements in this {@link TableControl} is preserved.
     * Any changes in the checked elements will be reported to registered
     * {@link CheckboxListener}s. If this {@link TableControl} was not
     * constructed with the {@link SWT#CHECK} style, an exception is thrown.
     *
     * @param element
     *        an element to check (must not be <code>null</code>)
     * @return <code>true</code> if the checked state could be set, and
     *         <code>false</code> otherwise
     */
    public boolean setChecked(final Object element) {
        return setChecked(element, true);
    }

    /**
     * Sets the check state of the specified element to unchecked. Any existing
     * check state of other elements in this {@link TableControl} is preserved.
     * Any changes in the checked elements will be reported to registered
     * {@link CheckboxListener}s. If this {@link TableControl} was not
     * constructed with the {@link SWT#CHECK} style, an exception is thrown.
     *
     * @param element
     *        an element to uncheck (must not be <code>null</code>)
     * @return <code>true</code> if the checked state could be set, and
     *         <code>false</code> otherwise
     */
    public boolean setUnchecked(final Object element) {
        return setChecked(element, false);
    }

    /**
     * @return the number of elements currently checked in this control
     */
    public int getCheckedProjectsCount() {
        return getElementCollectionCount(ElementCollectionType.CHECKED_ELEMENTS);
    }

    /**
     * Adds an {@link IDoubleClickListener} that will be notified when an
     * element in this {@link TableControl} is double-clicked.
     *
     * @param listener
     *        an {@link IDoubleClickListener} to add (must not be
     *        <code>null</code>)
     */
    public void addDoubleClickListener(final IDoubleClickListener listener) {
        doubleClickListener.addListener(listener);
    }

    /**
     * Removes a previously-added {@link IDoubleClickListener} from this
     * {@link TableControl}.
     *
     * @param listener
     *        an {@link IDoubleClickListener} to remove (must not be
     *        <code>null</code>)
     */
    public void removeDoubleClickListener(final IDoubleClickListener listener) {
        doubleClickListener.removeListener(listener);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
     */
    @Override
    public Point computeSize(final int wHint, final int hHint, final boolean changed) {
        return ControlSize.computeCharSize(wHint, hHint, viewer.getControl(), 60, 5);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.swt.widgets.Composite#setFocus()
     */
    @Override
    public boolean setFocus() {
        return viewer.getControl().setFocus();
    }

    /**
     * Sets an {@link ILabelDecorator} for this control to use. Any previous
     * {@link ILabelDecorator} will be ignored. This method is intended for use
     * with the workbench label decoration mechanism.
     *
     * @param labelDecorator
     *        a new {@link ILabelDecorator} to use (must not be
     *        <code>null</code>)
     */
    public void setLabelDecorator(final ILabelDecorator labelDecorator) {
        Check.notNull(labelDecorator, "labelDecorator"); //$NON-NLS-1$

        ILabelProvider labelProvider = (ILabelProvider) viewer.getLabelProvider();

        if (labelProvider instanceof DecoratingLabelProvider) {
            final DecoratingLabelProvider decoratingLabelProvider = (DecoratingLabelProvider) labelProvider;
            labelProvider = decoratingLabelProvider.getLabelProvider();
        }

        labelProvider = new DecoratingLabelProvider(labelProvider, labelDecorator);
        viewer.setLabelProvider(labelDecorator);
    }

    /**
     * @return a {@link Validator} which validates that this
     *         {@link TableControl} contains at least one element
     */
    public Validator getElementsValidator() {
        return new ElementProviderValidator(this, NumericConstraint.ONE_OR_MORE,
                getElementsValidatorErrorMessage());
    }

    /**
     * @return a {@link Validator} which validates that this
     *         {@link TableControl} has at least one selected element
     */
    public Validator getSelectionValidator() {
        return new SelectionProviderValidator(this, NumericConstraint.ONE_OR_MORE,
                getSelectionValidatorErrorMessage());
    }

    /**
     * @return a {@link Validator} which validates that this
     *         {@link TableControl} has exactly one selected element
     */
    public Validator getSingleSelectionValidator() {
        return new SelectionProviderValidator(this, NumericConstraint.EXACTLY_ONE,
                getSingleSelectionValidatorErrorMessage());
    }

    /**
     * @return a {@link Validator} which validates that this
     *         {@link TableControl} has at least one checked element
     */
    public Validator getCheckboxValidator() {
        throwIfNotCheckboxTable();

        return new CheckboxProviderValidator(this, NumericConstraint.ONE_OR_MORE,
                getCheckboxValidatorErrorMessage());
    }

    /**
     * Calls {@link TableViewer#refresh()} on the underlying {@link TableViewer}
     * .
     */
    public void refresh() {
        viewer.refresh();
        computeElements();
    }

    /**
     * @return the SWT {@link Table} that this {@link TableControl} wraps
     */
    public final Table getTable() {
        return viewer.getTable();
    }

    /**
     * @return the JFace {@link TableViewer} that this {@link TableControl}
     *         wraps
     */
    public final TableViewer getViewer() {
        return viewer;
    }

    /**
     * Copies the currently selected elements to the clipboard. The exact format
     * of the clipboard data is specified by the {@link TableControl} subclass.
     * If there are no currently selected elements, or if the
     * {@link TableControl} subclass has not configured clipboard transfer, this
     * method does nothing.
     */
    public void copySelectionToClipboard() {
        final Object[] selectedElements = getSelectedElements();

        if (selectedElements.length == 0) {
            return;
        }

        final Transfer[] transferTypes = clipboardTransferTypes;

        if (transferTypes == null || transferTypes.length == 0) {
            return;
        }

        final Object[] transferData = new Object[transferTypes.length];
        for (int i = 0; i < transferTypes.length; i++) {
            transferData[i] = getTransferData(transferTypes[i], selectedElements);
        }

        getClipboard().setContents(transferData, transferTypes);
    }

    /**
     * Obtains one of the element collections from this {@link TableControl}.
     * The returned array is safe to use (it is a copy of internal data) and is
     * strongly typed to the element type of this {@link TableControl}.
     *
     * @param type
     *        the type of the element collection to return (must not be
     *        <code>null</code>)
     * @return the element collection specified by <code>type</code> (never
     *         <code>null</code>)
     */
    public Object[] getElementCollection(final ElementCollectionType type) {
        final Object[] elements = getElementCollectionInternal(type);

        if (elements == null) {
            return newEmptyArray();
        }

        return elements.clone();
    }

    /**
     * Obtains the count of one of the element collections of this
     * {@link TableControl}.
     *
     * @param type
     *        the type of the element collection to return the count of (must
     *        not be <code>null</code>)
     * @return the count of the element collection specified by
     *         <code>type</code>
     */
    public int getElementCollectionCount(final ElementCollectionType type) {
        final Object[] elements = getElementCollectionInternal(type);

        if (elements == null) {
            return 0;
        }

        return elements.length;
    }

    /**
     * @return the highest index of a selected item, or <code>-1</code> if there
     *         are no selected items
     */
    public int getMaxSelectionIndex() {
        return TableUtils.getMaxSelectionIndex(getTable());
    }

    /**
     * Obtains the element (model object) at the specified index.
     *
     * @throws IllegalArgumentException
     *         if the index is invalid
     *
     * @param index
     *        the index of the item to return
     * @return the element at the specified index
     */
    public Object getElement(final int index) {
        final Table table = getTable();
        final int count = table.getItemCount();
        if (index < 0 || index >= count) {
            final String messageFormat = "index [{0}] is out of range [0,{1}]"; //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, Integer.toString(index), count);
            throw new IllegalArgumentException(message);
        }

        return table.getItem(index).getData();
    }

    /**
     * Initiates cell editing on the specified element. The column that is
     * edited is specified by property name.
     *
     * @param element
     *        the element to edit (must not be <code>null</code>)
     * @param columnPropertyName
     *        the property name of the column to edit (must not be
     *        <code>null</code>)
     */
    public void editElement(final Object element, final String columnPropertyName) {
        final int columnIndex = TableViewerUtils.columnPropertyNameToColumnIndex(columnPropertyName, true, viewer);
        viewer.editElement(element, columnIndex);
    }

    /**
     * Sets the input of this {@link TableControl} to the specified elements
     * array. Subclasses that call this method do not need to follow it with a
     * call to {@link #computeElements()} as this is done automatically. Any
     * existing elements are not preserved. Any changes to the element
     * collections are reported to registered listeners.
     *
     * @param tableElements
     *        an elements array to set as the input of this {@link TableControl}
     *        , or <code>null</code> to clear the table
     */
    protected final void setElements(final Object[] tableElements) {
        setInput(tableElements);
    }

    /**
     * Sets the input of this {@link TableControl}. Subclasses that call this
     * method do not need to follow it with a call to {@link #computeElements()}
     * as this is done automatically. Any existing elements are not preserved.
     * Any changes to the element collections are reported to registered
     * listeners.
     *
     * @param input
     *        a new input for this {@link TableControl}
     */
    protected final void setInput(final Object input) {
        getViewer().setInput(input);
        computeElements();
    }

    protected final void removeElements(final Object[] removeElements) {
        Check.notNull(removeElements, "removeElements"); //$NON-NLS-1$

        final List newElements = new ArrayList();

        for (int i = 0; i < allElements.length; i++) {
            boolean remove = false;

            for (int j = 0; j < removeElements.length; j++) {
                if (allElements[i].equals(removeElements[j])) {
                    remove = true;
                    break;
                }
            }

            if (!remove) {
                newElements.add(allElements[i]);
            }
        }

        setInput(newElements.toArray(new Object[newElements.size()]));
    }

    /**
     * <p>
     * Throws an {@link IllegalStateException} if this {@link TableControl} was
     * not created with the {@link SWT#CHECK} style bit.
     * </p>
     *
     * <p>
     * If this method does not throw, it is safe to cast the result of the
     * {@link #getViewer()} method to a {@link CheckboxTableViewer}.
     * </p>
     */
    protected final void throwIfNotCheckboxTable() {
        if (isCheckboxTable()) {
            return;
        }
        throw new IllegalStateException("This TableControl was not created with the SWT.CHECK style"); //$NON-NLS-1$
    }

    /**
     * Tests whether this {@link TableControl}'s table was created with the
     * {@link SWT#CHECK} style bit. If this method returns <code>true</code>, it
     * is safe to cast the result of the {@link #getViewer()} method to a
     * {@link CheckboxTableViewer}.
     *
     * @return <code>true</code> if this {@link TableControl} was created with
     *         the {@link SWT#CHECK} style bit
     */
    protected final boolean isCheckboxTable() {
        return (tableStyles & SWT.CHECK) != 0;
    }

    /**
     * @return the SWT table styles that were used to create the {@link Table}
     *         this {@link TableControl} wraps
     */
    protected final int getTableStyles() {
        return tableStyles;
    }

    /**
     * Sets whether to propagate checks to the entire selection. If
     * <code>true</code> (the default), when an element is checked or unchecked
     * all other elements in the current selection will also be checked or
     * unchecked.
     *
     * @param checksAffectSelection
     *        <code>true</code> to have the "checks affect entire selection"
     *        behavior
     */
    protected final void setChecksAffectSelection(final boolean checksAffectSelection) {
        this.checksAffectSelection = checksAffectSelection;
    }

    /**
     * @return <code>true</code> if the "checks affect selection" mode is
     *         currently on (see {@link #setChecksAffectSelection(boolean)})
     */
    protected final boolean isChecksAffectSelection() {
        return checksAffectSelection;
    }

    /**
     * A convenience method to set a JFace cell editor for a single column in
     * the {@link TableViewer} that backs this {@link TableControl}. This method
     * can be called multiple times: each call preserves existing cell editors
     * that were previously set for other columns. This method must not be
     * called until the underlying SWT {@link Table}'s columns have been
     * created.
     *
     * @param columnIndex
     *        the column index to set a {@link CellEditor} for
     * @param cellEditor
     *        the {@link CellEditor} to set for the column, or <code>null</code>
     *        for no cell editor
     */
    protected final void setCellEditor(final int columnIndex, final CellEditor cellEditor) {
        CellEditor[] cellEditors = getViewer().getCellEditors();

        if (cellEditors == null) {
            cellEditors = new CellEditor[getTable().getColumnCount()];
            getViewer().setCellEditors(cellEditors);
        }

        if (columnIndex < 0 || columnIndex >= cellEditors.length) {
            final String messageFormat = "the specified column index {0} is out of range"; //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, Integer.toString(columnIndex));
            throw new IllegalArgumentException(message);
        }

        cellEditors[columnIndex] = cellEditor;
    }

    /**
     * A convenience method to get a JFace cell editor for a single column in
     * the {@link TableViewer} that backs this {@link TableControl}.
     *
     * @throws IllegalArgumentException
     *         if no cell editors have been set on the {@link TableViewer}, or
     *         if the specified column index is not valid
     *
     * @param columnIndex
     *        the column index to get a {@link CellEditor} for
     * @return the {@link CellEditor} for the column, or <code>null</code> if
     *         the column does not have a cell editor
     */
    protected final CellEditor getCellEditor(final int columnIndex) {
        final CellEditor[] cellEditors = getViewer().getCellEditors();

        if (cellEditors == null || columnIndex < 0 || columnIndex >= cellEditors.length) {
            final String messageFormat = "the specified column index {0} is out of range"; //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, Integer.toString(columnIndex));
            throw new IllegalArgumentException(message);
        }

        return cellEditors[columnIndex];
    }

    /**
     * A convenience method to set a JFace cell editor for a single column in
     * the {@link TableViewer} that backs this {@link TableControl}. This method
     * can be called multiple times: each call preserves existing cell editors
     * that were previously set for other columns. This method must not be
     * called until the underlying SWT {@link Table}'s columns have been
     * created.
     *
     * @param columnPropertyName
     *        identifies the column to set a {@link CellEditor} for
     * @param cellEditor
     *        the {@link CellEditor} to set for the column, or <code>null</code>
     *        for no cell editor
     */
    protected final void setCellEditor(final String columnPropertyName, final CellEditor cellEditor) {
        final int columnIndex = TableViewerUtils.columnPropertyNameToColumnIndex(columnPropertyName, true,
                getViewer());
        setCellEditor(columnIndex, cellEditor);
    }

    /**
     * A convenience method to get a JFace cell editor for a single column in
     * the {@link TableViewer} that backs this {@link TableControl}.
     *
     * @throws IllegalArgumentException
     *         if no cell editors have been set on the {@link TableViewer}, or
     *         if the specified column property name is not valid
     *
     * @param columnPropertyName
     *        the column property name that identifies the column to get a
     *        {@link CellEditor} for
     * @return the {@link CellEditor} for the column, or <code>null</code> if
     *         the column does not have a cell editor
     */
    protected final CellEditor getCellEditor(final String columnPropertyName) {
        final int columnIndex = TableViewerUtils.columnPropertyNameToColumnIndex(columnPropertyName, true,
                getViewer());
        return getCellEditor(columnIndex);
    }

    /**
     * Sets up the JFace {@link TableViewer} used by this {@link TableControl}
     * to have a default label provider. The default label provider delegates to
     * the {@link #getColumnImage(Object, int)} and
     * {@link #getColumnText(Object, int)}. Subclasses should override those
     * methods if they are using the default label provider.
     */
    protected final void setUseDefaultLabelProvider() {
        viewer.setLabelProvider(new DefaultLabelProvider() {
            @Override
            public Image getColumnImage(final Object element, final int columnIndex) {
                return TableControl.this.getColumnImage(element, columnIndex);
            }

            @Override
            public String getColumnText(final Object element, final int columnIndex) {
                final String s = TableControl.this.getColumnText(element, columnIndex);
                return (s != null ? s : ""); //$NON-NLS-1$
            }

            @Override
            public Color getForeground(final Object element) {
                return TableControl.this.getForegroundColor(element);
            }

            @Override
            public Color getBackground(final Object element) {
                return TableControl.this.getBackgroundColor(element);
            }
        });
    }

    /**
     * Sets up the JFace {@link TableViewer} used by this {@link TableControl}
     * to have a default content provider. The default content provider assumes
     * that the input {@link Object} is an array. The elements returned from the
     * input {@link Object} is simply the input {@link Object} itself, cast to
     * an array.
     */
    protected final void setUseDefaultContentProvider() {
        viewer.setContentProvider(new ContentProviderAdapter() {
            @Override
            public Object[] getElements(final Object inputElement) {
                return (Object[]) inputElement;
            }
        });
    }

    /**
     * Sets up the JFace {@link TableViewer} used by this {@link TableControl}
     * to have a default sorter. The default sorter is an instance of
     * {@link TableViewerSorter}. This method should be called after the
     * underlying {@link Table} has been configured and has had
     * {@link TableColumn}s added to it.
     */
    protected final void setUseDefaultSorter() {
        viewer.setSorter(new TableViewerSorter(viewer));
    }

    /**
     * Sets up the JFace {@link TableViewer} used by this {@link TableControl}
     * to have a default label provider, content provider, and sorter. This
     * method should be called after the underlying {@link Table} has been
     * configured and has had {@link TableColumn}s added to it. This is a
     * convenience method and is equivalent to calling the following methods:
     * <ul>
     * <li>{@link #setUseDefaultLabelProvider()}</li>
     * <li>{@link #setUseDefaultContentProvider()}</li>
     * <li>{@link #setUseDefaultSorter()}</li>
     * </ul>
     */
    protected final void setUseViewerDefaults() {
        setUseDefaultLabelProvider();
        setUseDefaultContentProvider();
        setUseDefaultSorter();
    }

    /**
     * An optional convenience method method to configure the underlying SWT
     * {@link Table} for this {@link TableControl}.
     *
     * @param headerVisible
     *        <code>true</code> if the table's header should be visible
     * @param linesVisible
     *        <code>true</code> if the table's gridlines should be visible
     * @param columnData
     *        an array of {@link TableColumnData}, one for each column to be
     *        created (must not be <code>null</code>)
     */
    protected final void setupTable(final boolean headerVisible, final boolean linesVisible,
            final TableColumnData[] columnData) {
        TableViewerUtils.setupTableViewer(viewer, headerVisible, linesVisible, getViewDataKey(), columnData);
    }

    /**
     * Set the specified tooltips on the corresponding table column headers.
     *
     * @param tooltips
     *        The list of tooltips. Each item in the list corresponds to a
     *        column at the same index. Use a <code>null</code> value to
     *        indicate a tooltip should be omitted for the column. This list can
     *        be shorter than the corresponding number of columns, in which case
     *        tooltips are not defined for those columns.
     */
    protected final void setTableColumnHeaderTooltips(final String[] tooltips) {
        final TableColumn[] columns = viewer.getTable().getColumns();
        for (int i = 0; i < columns.length; i++) {
            if (i >= tooltips.length) {
                break;
            }

            if (tooltips[i] != null) {
                columns[i].setToolTipText(tooltips[i]);
            }
        }
    }

    /**
     * Sets whether to enable per-item tooltips for this {@link TableControl}
     * (default is <code>false</code>). If <code>true</code>, the
     * {@link #getTooltipText(Object, int)} method will be called to obtain new
     * tooltip text each time the mouse hovers over an element.
     *
     * @param enableTooltips
     *        <code>true</code> to enable the per-element tooltip behavior
     */
    protected final void setEnableTooltips(final boolean enableTooltips) {
        setEnableTooltips(enableTooltips, false);
    }

    /**
     * Sets whether to enable per-item or per-cell tooltips for this
     * {@link TableControl} (default is <code>false</code>). If
     * <code>true</code>, then {@link #getTooltipText(Object, int)} method will
     * be called to obtain new tooltip text each time the mouse hovers over an
     * element.
     *
     * @param enableTooltips
     *        <code>true</code> to enable the per-element tooltip behavior
     * @param supportCellTooltips
     *        <code>true</code> to enable the per-cell tooltip behavior
     */
    protected final void setEnableTooltips(final boolean enableTooltips, final boolean supportCellTooltips) {
        if (this.enableTooltips == enableTooltips) {
            return;
        }

        this.enableTooltips = enableTooltips;

        if (enableTooltips) {
            tooltipManager = new TableTooltipLabelManager(getTable(), this, supportCellTooltips);
            tooltipManager.addTooltipManager();
        } else {
            if (tooltipManager != null) {
                tooltipManager.removeTooltipManager();
                tooltipManager = null;
            }
        }
    }

    /**
     * Called to set the clipboard transfer types for this {@link TableControl}.
     * The clipboard transfer types are initially <code>null</code>. The
     * {@link #copySelectionToClipboard()} method does not do anything until at
     * least one clipboard transfer type has been specified by the subclass
     * calling this method. Once clipboard transfer types have been specified,
     * the {@link #copySelectionToClipboard()} method calls the
     * {@link #getTransferData(Transfer, Object)} method for each transfer type
     * and element in the selection. If you call this method to specify
     * clipboard transfer types, you should also override
     * {@link #getTransferData(Transfer, Object)} to return data for each type.
     *
     * @param transferTypes
     *        the clipboard transfer types to use, or <code>null</code> to
     *        disable clipboard support for this {@link TableControl}
     */
    protected final void setClipboardTransferTypes(final Transfer[] transferTypes) {
        clipboardTransferTypes = transferTypes;
    }

    /**
     * Called to set a single clipboard transfer type for this
     * {@link TableControl}. The clipboard transfer types are initially
     * <code>null</code>. The {@link #copySelectionToClipboard()} method does
     * not do anything until a clipboard transfer type has been specified by the
     * subclass calling this method. Once clipboard transfer types have been
     * specified, the {@link #copySelectionToClipboard()} method calls the
     * {@link #getTransferData(Transfer, Object)} method for each transfer type
     * and element in the selection. If you call this method to specify
     * clipboard transfer types, you should also override
     * {@link #getTransferData(Transfer, Object)} to return data for each type.
     *
     * @param transferType
     *        the clipboard transfer type to use, or <code>null</code> to
     *        disable clipboard support for this {@link TableControl}
     */
    protected final void setClipboardTransferType(final Transfer transferType) {
        setClipboardTransferTypes(new Transfer[] { transferType });
    }

    /**
     * Called to set the drag transfer types for this {@link TableControl}. The
     * drag transfer types are initially unset. Once drag types have been set,
     * drag operations will call {@link #getTransferData(Transfer, Object[])}
     * with a transfer type specified here and the selected elements. If you
     * call this method to specify drag transfer types, you should also override
     * {@link #getTransferData(Transfer, Object[])} to return data for each
     * type.
     *
     * @param transferTypes
     *        the drag transfer types to use, or <code>null</code> to disable
     *        drag support for this {@link TableControl}
     */
    protected final void setDragTransferTypes(Transfer[] transferTypes) {
        if (transferTypes == null) {
            transferTypes = new Transfer[0];
        }
        dragSource.setTransfer(transferTypes);
    }

    /**
     * Obtains a {@link Clipboard}. Subclasses must not dispose of the clipboard
     * - it is disposed by the {@link TableControl} base class.
     *
     * @return a {@link Clipboard} (never <code>null</code>)
     */
    protected final Clipboard getClipboard() {
        if (clipboard == null) {
            clipboard = new Clipboard(getDisplay());
        }

        return clipboard;
    }

    /**
     * <p>
     * This method computes the current elements contained in this
     * {@link TableControl} and caches them. The contained elements can be
     * retrieved by calling {@link #getElements()}. As a result of calling this
     * method, any registered {@link ElementListener}s are notified of a change
     * in the all-elements collection.
     * </p>
     *
     * <p>
     * This method <b>must</b> be called by subclasses whenever the subclass
     * performs an operation that may modify the set of elements contained in
     * this control. For example, if the subclass calls {@link #getViewer()} and
     * manually sets the input element, it must subsequently call this method.
     * </p>
     */
    protected final void computeElements() {
        final TableItem[] items = getTable().getItems();
        final Object[] elements = new Object[items.length];
        for (int i = 0; i < items.length; i++) {
            elements[i] = items[i].getData();
        }

        allElements = computeElementCollectionInternal(elements, ElementCollectionType.ALL_ELEMENTS);

        /*
         * In order that the selection and checked items are correctly
         * represented in this class when the element listener fires, we have to
         * compute them before we fire the event, but we don't want these other
         * computations to fire their events.
         */
        computeSelectedElements(false);

        if (isCheckboxTable()) {
            computeCheckedElements(false);
        }

        notifyElementListeners();
    }

    /**
     * <p>
     * This method computes the currently selected elements in this
     * {@link TableControl} and caches them. The selected elements can be
     * retrieved by calling {@link #getSelectedElements()}. As a result of
     * calling this method, any registered {@link ISelectionChangedListener}s
     * are notified of a change in the selected elements collection.
     * </p>
     *
     * <p>
     * Normally, there is no reason for subclasses to call this method. Changes
     * to the selected elements are detected automatically by this base class.
     * </p>
     */
    protected final void computeSelectedElements(final boolean fireEvent) {
        final IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
        final Object[] elements = selection.toArray();

        selectedElements = computeElementCollectionInternal(elements, ElementCollectionType.SELECTED_ELEMENTS);

        if (fireEvent) {
            notifySelectionChangedListeners();
        }
    }

    /**
     * <p>
     * This method computes the currently checked elements in this
     * {@link TableControl} and caches them. The checked elements can be
     * retrieved by calling {@link #getCheckedElements()}. As a result of
     * calling this method, any registered {@link CheckboxListener}s are
     * notified of a change in the checked elements collection.
     * </p>
     *
     * <p>
     * Normally, there is no reason for subclasses to call this method. Changes
     * to the checked elements are detected automatically by this base class.
     * </p>
     */
    protected final void computeCheckedElements(final boolean fireEvent) {
        final Object[] elements = ((CheckboxTableViewer) viewer).getCheckedElements();

        checkedElements = computeElementCollectionInternal(elements, ElementCollectionType.CHECKED_ELEMENTS);

        if (fireEvent) {
            notifyCheckboxListeners();
        }
    }

    /**
     * Called to compute an element collection. This method gives subclasses a
     * chance to filter or modify the element collections. The default behavior
     * is to call {@link #hideElementFromCollections(Object)} for each candidate
     * element. All candidates for which that method returns <code>false</code>
     * will be added to the element collection.
     *
     * @param candidates
     *        the candidate elements (never <code>null</code>)
     * @param type
     *        the element collection type that is being built (never
     *        <code>null</code>)
     * @return a {@link List} containing elements that should be in the
     *         collection (must not be <code>null</code>)
     */
    protected List computeElementCollection(final Object[] candidates, final ElementCollectionType type) {
        final List list = new ArrayList();
        for (int i = 0; i < candidates.length; i++) {
            if (!hideElementFromCollections(candidates[i])) {
                list.add(candidates[i]);
            }
        }
        return list;
    }

    /**
     * Called by the default implementation of
     * {@link #computeElementCollection(Object[], ElementCollectionType)} to
     * allow subclasses to filter certain elements from all element collections.
     * The default behavior is to return <code>false</code>, which means that no
     * elements will be filtered from the collections.
     *
     * @param element
     *        an element being considered for inclusion in an element collection
     *        (never <code>null</code>)
     * @return <code>true</code> if the element should be hidden from the
     *         collection
     */
    protected boolean hideElementFromCollections(final Object element) {
        return false;
    }

    /**
     * Notifies any registered {@link ElementListener}s of a change in the
     * all-elements collection. Normally, there is no reason for subclasses to
     * call this method.
     */
    protected final void notifyElementListeners() {
        final ElementEvent elementEvent = new ElementEvent(this, getElements());
        ((ElementListener) elementListener.getListener()).elementsChanged(elementEvent);
    }

    /**
     * Notifies any registered {@link ISelectionChangedListener}s of a change in
     * the selected elements collection. Normally, there is no reason for
     * subclasses to call this method.
     */
    protected final void notifySelectionChangedListeners() {
        final SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
        final ISelectionChangedListener listener = (ISelectionChangedListener) selectionListener.getListener();
        listener.selectionChanged(event);
    }

    /**
     * Notifies any registered post-{@link ISelectionChangedListener}s of a
     * change in the selected elements collection. Normally, there is no reason
     * for subclasses to call this method.
     */
    protected final void notifyPostSelectionChangedListeners() {
        final SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
        final ISelectionChangedListener listener = (ISelectionChangedListener) postSelectionListener.getListener();
        listener.selectionChanged(event);
    }

    /**
     * Notifies any registered {@link CheckboxListener}s of a change in the
     * checked elements collection. Normally, there is no reason for subclasses
     * to call this method.
     */
    protected final void notifyCheckboxListeners() {
        final CheckboxEvent checkboxEvent = new CheckboxEvent(this, getCheckedElements());
        ((CheckboxListener) checkboxListener.getListener()).checkedElementsChanged(checkboxEvent);
    }

    /**
     * Notifies any registered {@link ICheckStateListener}s of a change in the
     * check state of the specified element. Normally, there is no reason for
     * subclasses to call this method.
     *
     * @param element
     *        the element to notify listeners about (must not be
     *        <code>null</code>)
     * @param checked
     *        the check state of the element
     */
    protected final void notifyCheckStateListeners(final Object element, final boolean checked) {
        final ICheckStateListener listener = (ICheckStateListener) checkStateListener.getListener();
        listener.checkStateChanged(new CheckStateChangedEvent(this, element, checked));
    }

    /**
     * Notifies any registered {@link IDoubleClickListener}s of a double-click
     * of the specified element. Normally, there is no reason for subclasses to
     * call this method.
     *
     * @param element
     *        the element to notify listeners about (must not be
     *        <code>null</code>)
     */
    protected final void notifyDoubleClickListeners(final Object element) {
        final IDoubleClickListener listener = (IDoubleClickListener) doubleClickListener.getListener();
        listener.doubleClick(new DoubleClickEvent(getViewer(), new StructuredSelection(element)));
    }

    /**
     * @return a view data key that can be used to persist any view data (
     *         <code>null</code> to not persist any settings)
     */
    protected final String getViewDataKey() {
        if (!persistGeometry) {
            return null;
        }

        return viewDataKey;
    }

    protected void setOptionPersistGeometry(final boolean persistGeometry) {
        this.persistGeometry = persistGeometry;
    }

    protected boolean getOptionPersistGeometry() {
        return persistGeometry;
    }

    /**
     * <p>
     * Called to get transfer data for the specified transfer type and selected
     * elements in this {@link TableControl}. This method should be overridden
     * by the subclass to enable clipboard support for this {@link TableControl}
     * . In addition, the subclass must call
     * {@link #setClipboardTransferTypes(Transfer[])} to specify the transfer
     * types that this method will be called with. This method is called by
     * {@link #copySelectionToClipboard()}.
     * </p>
     *
     * <p>
     * <b>Important</b>: the subclass must not modify the selected elements
     * array.
     * </p>
     *
     * @param transferType
     *        the transfer type to get transfer data for, never
     *        <code>null</code>
     * @param selectedElements
     *        the selected elements to get the transfer data for, never
     *        <code>null</code>
     * @return transfer data for the selected elements
     */
    protected Object getTransferData(final Transfer transferType, final Object[] selectedElements) {
        return null;
    }

    /**
     * <p>
     * This method is invoked if the "default" label provider is used (by
     * calling {@link #setUseDefaultLabelProvider()}). The default
     * implementation of this method is to delegate to the column property-based
     * {@link #getColumnText(Object, String)} method.
     * </p>
     *
     * <p>
     * Subclasses should override this method if they are using the default
     * label provider but are not using column property names.
     * </p>
     *
     * @param element
     *        the element to get text for
     * @param columnIndex
     *        the index of the column to get text for
     * @return the column text, or <code>null</code> for no text in the column
     *         for this element
     */
    protected String getColumnText(final Object element, final int columnIndex) {
        return getColumnText(element, TableViewerUtils.columnIndexToColumnProperty(columnIndex, getViewer()));
    }

    /**
     * <p>
     * This method is invoked if the "default" label provider is used (by
     * calling {@link #setUseDefaultLabelProvider()}) and the subclass does not
     * override the column index-based {@link #getColumnText(Object, int)}
     * method. The default implementation of this method is to return
     * <code>null</code>, indicating no column text.
     * </p>
     *
     * <p>
     * Subclasses should override this method if they are using the default
     * label provider and are using column property names (which are preferred
     * over column indices).
     * </p>
     *
     * @param element
     *        the element to get text for
     * @param columnPropertyName
     *        the column property name of the column to get text for
     * @return the column text, or <code>null</code> for no text in the column
     *         for this element
     */
    protected String getColumnText(final Object element, final String columnPropertyName) {
        return null;
    }

    /**
     * <p>
     * This method is invoked if the "default" label provider is used (by
     * calling {@link #setUseDefaultLabelProvider()}). The default
     * implementation of this method is to delegate to the column property-based
     * {@link #getColumnImage(Object, String)} method.
     * </p>
     *
     * <p>
     * Subclasses should override this method if they are using the default
     * label provider but are not using column property names.
     * </p>
     *
     * @param element
     *        the element to get an image for
     * @param columnIndex
     *        the index of the column to get an image for
     * @return the column image, or <code>null</code> for no image in the column
     *         for this element
     */
    protected Image getColumnImage(final Object element, final int columnIndex) {
        return getColumnImage(element, TableViewerUtils.columnIndexToColumnProperty(columnIndex, getViewer()));
    }

    /**
     * <p>
     * This method is invoked if the "default" label provider is used (by
     * calling {@link #setUseDefaultLabelProvider()}) and the subclass does not
     * override the column index-based {@link #getColumnImage(Object, int)}
     * method. The default implementation of this method is to return
     * <code>null</code>, indicating no column image.
     * </p>
     *
     * <p>
     * Subclasses should override this method if they are using the default
     * label provider and are using column property names (which are preferred
     * over column indices).
     * </p>
     *
     * @param element
     *        the element to get an image for
     * @param columnIndex
     *        the index of the column to get an image for
     * @return the column image, or <code>null</code> for no image in the column
     *         for this element
     */
    protected Image getColumnImage(final Object element, final String columnPropertyName) {
        return null;
    }

    /**
     * <p>
     * This method is invoked if the "default" label provider is used (by
     * calling {@link #setUseDefaultLabelProvider()}). The default
     * implementation of this method is to return <code>null</code>, indicating
     * that the default foreground color should be used to decorate the object.
     * </p>
     *
     * <p>
     * Subclasses can override to provide a non-default foreground color for
     * elements in the table.
     * </p>
     *
     * @param element
     *        the element a color is being requested for
     * @return the foreground color for the element, or <code>null</code> to use
     *         the default foreground color
     */
    protected Color getForegroundColor(final Object element) {
        return null;
    }

    /**
     * <p>
     * This method is invoked if the "default" label provider is used (by
     * calling {@link #setUseDefaultLabelProvider()}). The default
     * implementation of this method is to return <code>null</code>, indicating
     * that the default background color should be used to decorate the object.
     * </p>
     *
     * <p>
     * Subclasses can override to provide a non-default background color for
     * elements in the table.
     * </p>
     *
     * @param element
     *        the element a color is being requested for
     * @return the background color for the element, or <code>null</code> to use
     *         the default background color
     */
    protected Color getBackgroundColor(final Object element) {
        return null;
    }

    /**
     * Called to get a per-element tooltip for this {@link TableControl}. Will
     * only be called if a subclass calls {@link #setEnableTooltips(boolean)}
     * with <code>true</code>.
     *
     * @param element
     *        an element currently in this {@link TableControl}
     * @return tooltip text for the element or <code>null</code> for no tooltip
     */
    @Override
    public String getTooltipText(final Object element, final int columnIndex) {
        return null;
    }

    /**
     * @return an error message for use with the {@link Validator} returned by
     *         {@link #getElementsValidator()}, or <code>null</code> for no
     *         specific error message
     */
    protected String getElementsValidatorErrorMessage() {
        return null;
    }

    /**
     * @return an error message for use with the {@link Validator} returned by
     *         {@link #getSelectionValidator()}, or <code>null</code> for no
     *         specific error message
     */
    protected String getSelectionValidatorErrorMessage() {
        return null;
    }

    /**
     * @return an error message for use with the {@link Validator} returned by
     *         {@link #getSingleSelectionValidator()}, or <code>null</code> for
     *         no specific error message
     */
    protected String getSingleSelectionValidatorErrorMessage() {
        return null;
    }

    /**
     * @return an error message for use with the {@link Validator} returned by
     *         {@link #getCheckboxValidator()}, or <code>null</code> for no
     *         specific error message
     */
    protected String getCheckboxValidatorErrorMessage() {
        return null;
    }

    /**
     * Called when this {@link TableControl} is disposed. This is an empty
     * convenience method, intended for overriding by the subclass.
     */
    protected void onDisposed() {

    }

    /**
     * Called when the selection has changed in the {@link TableViewer} backing
     * this {@link TableControl}. The base class implementation calls
     * {@link #computeSelectedElements()}.
     *
     * @param event
     *        the {@link SelectionChangedEvent}
     */
    protected void onSelectionChanged(final SelectionChangedEvent event) {
        computeSelectedElements(true);
    }

    /**
     * Called when the selection has changed (post-selection) in the
     * {@link TableViewer} backing this {@link TableControl}. The base class
     * implementation calls {@link #notifyPostSelectionChangedListeners()}.
     *
     * @param event
     *        the {@link SelectionChangedEvent}
     */
    protected void onPostSelectionChanged(final SelectionChangedEvent event) {
        notifyPostSelectionChangedListeners();
    }

    /**
     * Called when a {@link KeyEvent} occurs on the {@link Table} backing this
     * {@link TableControl}. The base class currently does nothing - subclasses
     * should still invoke super.onKeyPressed() in case functionality is added
     * to the base class in the future.
     *
     * @param e
     *        the {@link KeyEvent}
     */
    protected void onKeyPressed(final KeyEvent e) {
    }

    /**
     * Called when a {@link CheckStateChangedEvent} occurs on the
     * {@link CheckboxTableViewer} backing this {@link TableControl}.
     *
     * @param event
     *        the {@link CheckStateChangedEvent}
     */
    protected void onCheckStateChanged(final CheckStateChangedEvent event) {
        final boolean checked = event.getChecked();

        if (isChecksAffectSelection()) {
            /*
             * propagate checked state to all elements in the current selection
             */
            viewer.getTable().setRedraw(false);
            final IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
            if (selection.size() == viewer.getTable().getItemCount()) {
                ((CheckboxTableViewer) viewer).setAllChecked(checked);
            } else {
                /*
                 * Make sure the element being checked is part of the selection.
                 * Otherwise, we ignore the selected elements. Avoids the case
                 * where you have some elements selected, and check a
                 * non-selected element. The checkstate of the selected elements
                 * should NOT change in this case.
                 */
                boolean checkedElementInSelection = false;

                for (final Iterator it = selection.iterator(); it.hasNext();) {
                    if (it.next().equals(event.getElement())) {
                        checkedElementInSelection = true;
                        break;
                    }
                }

                if (checkedElementInSelection) {
                    for (final Iterator it = selection.iterator(); it.hasNext();) {
                        ((CheckboxTableViewer) viewer).setChecked(it.next(), checked);
                    }
                }
            }
            viewer.getTable().setRedraw(true);
        }

        computeCheckedElements(true);

        final Object element = event.getElement();
        if (!hideElementFromCollections(element)) {
            notifyCheckStateListeners(element, event.getChecked());
        }
    }

    /**
     * Called when a {@link DoubleClickEvent} occurs on the {@link TableViewer}
     * backing this {@link TableControl}.
     *
     * @param event
     *        the {@link DoubleClickEvent}
     */
    protected void onDoubleClick(final DoubleClickEvent event) {
        final Object element = ((IStructuredSelection) event.getSelection()).getFirstElement();
        if (!hideElementFromCollections(element)) {
            notifyDoubleClickListeners(element);
        }
    }

    /**
     * Called by the {@link ICellModifier#modify(Object, String, Object)} method
     * of the {@link ICellModifier} that is set on the underlying
     * {@link TableViewer} by default. Subclasses can override to support cell
     * editing.
     *
     * @param element
     *        the element that has been edited (never <code>null</code>)
     * @param columnPropertyName
     *        the identifier of the column that has been edited (never
     *        <code>null</code>)
     * @param value
     *        the cell-editor-specific value
     */
    protected void modifyElement(final Object element, final String columnPropertyName, final Object value) {

    }

    /**
     * Called by the {@link ICellModifier#getValue(Object, String)} method of
     * the {@link ICellModifier} that is set on the underlying
     * {@link TableViewer} by default. Subclasses can override to support cell
     * editing.
     *
     * @param element
     *        the element that is being edited (never <code>null</code>)
     * @param columnPropertyName
     *        the identifier of the column that is being edited (never
     *        <code>null</code>)
     * @return the cell-editor-specific value
     */
    protected Object getValueToModify(final Object element, final String columnPropertyName) {
        return null;
    }

    /**
     * Called by the {@link ICellModifier#canModify(Object, String)} method of
     * the {@link ICellModifier} that is set on the underlying
     * {@link TableViewer} by default. Subclasses can override to support cell
     * editing. If subclasses override, they should also install
     * {@link CellEditor}s by calling {@link #setCellEditor(String, CellEditor)}
     * for each column that can be edited. The default implementation of this
     * method returns <code>false</code>, effectively disabling cell editing.
     *
     * @param element
     *        the candidate element for cell editing (never <code>null</code>)
     * @param columnPropertyName
     *        the identifier of the candidate column for cell editing (never
     *        <code>null</code>)
     * @return <code>true</code> if cell editing should be enabled for the
     *         specified element and the specified column
     */
    protected boolean canModifyElement(final Object element, final String columnPropertyName) {
        return false;
    }

    /**
     * Computes an element collection by invoking the subclass hook
     * {@link #computeElementCollection(Object[], ElementCollectionType)}.
     *
     * @param candidates
     *        the candidate elements
     * @param type
     *        the element collection type
     * @return <code>null</code> if no elements are in the collection, or a
     *         typesafe array of elements if there is at least one element in
     *         the collection
     */
    private Object[] computeElementCollectionInternal(final Object[] candidates, final ElementCollectionType type) {
        final List list = computeElementCollection(candidates, type);

        if (list == null || list.size() == 0) {
            return null;
        } else {
            return list.toArray(newElementArray(list.size()));
        }
    }

    /**
     * Returns one of the internal element collections. No safe copy is made of
     * the returned collection.
     *
     * @param type
     *        the type of collection to return (must not be <code>null</code>)
     * @return the internal collection, or <code>null</code> if the internal
     *         collection is currently empty
     */
    private Object[] getElementCollectionInternal(final ElementCollectionType type) {
        Check.notNull(type, "type"); //$NON-NLS-1$

        if (ElementCollectionType.CHECKED_ELEMENTS == type) {
            throwIfNotCheckboxTable();
        }

        if (ElementCollectionType.ALL_ELEMENTS == type) {
            return allElements;
        }

        if (ElementCollectionType.SELECTED_ELEMENTS == type) {
            return selectedElements;
        }

        if (ElementCollectionType.CHECKED_ELEMENTS == type) {
            return checkedElements;
        }

        final String messageFormat = "unknown element collection type: {0}"; //$NON-NLS-1$
        final String message = MessageFormat.format(messageFormat, type);
        throw new IllegalArgumentException(message);
    }

    /**
     * @return a new, empty array of the component type specified by
     *         {@link #elementType}
     */
    private Object[] newEmptyArray() {
        return newElementArray(0);
    }

    /**
     * Creates anew array of the component type specified by
     * {@link #elementType}.
     *
     * @param length
     *        the length of the returned array
     * @return an element array
     */
    private Object[] newElementArray(final int length) {
        return (Object[]) Array.newInstance(elementType, length);
    }

    /**
     * Creates a new context menu (JFace {@link MenuManager}) on the specified
     * control. The {@link MenuManager} is set to use the "remove all when
     * shown" style. No listeners are added to the {@link MenuManager} by this
     * method.
     *
     * @param control
     *        the control to place the menu on
     * @return the JFace {@link MenuManager}
     */
    private MenuManager createContextMenu(final Control control) {
        final MenuManager menuManager = new MenuManager("#popup"); //$NON-NLS-1$
        menuManager.setRemoveAllWhenShown(true);
        control.setMenu(menuManager.createContextMenu(control));
        return menuManager;
    }

    /**
     * Called by the drag listener we've attached to this table's
     * {@link DragSource}.
     */
    private void dragSetData(final DragSourceEvent event) {
        final Transfer[] transferTypes = dragSource.getTransfer();
        for (int i = 0; i < transferTypes.length; i++) {
            if (transferTypes[i].isSupportedType(event.dataType)) {
                final Object[] elements = getSelectedElements();
                final Object transferData = getTransferData(transferTypes[i], elements);
                event.data = transferData;
                break;
            }
        }
    }

    /**
     * Hooks the SWT {@link Table} by adding appropriate listeners (selection,
     * checkbox, etc).
     *
     * @param table
     *        the SWT table to hook
     */
    private void hookTable(final Table table) {
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(final SelectionChangedEvent event) {
                TableControl.this.onSelectionChanged(event);
            }
        });

        viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(final SelectionChangedEvent event) {
                TableControl.this.onPostSelectionChanged(event);
            }
        });

        viewer.addDoubleClickListener(new IDoubleClickListener() {
            @Override
            public void doubleClick(final DoubleClickEvent event) {
                TableControl.this.onDoubleClick(event);
            }
        });

        table.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(final KeyEvent e) {
                TableControl.this.onKeyPressed(e);
            }
        });

        if (isCheckboxTable()) {
            final CheckboxTableViewer checkboxTableViewer = (CheckboxTableViewer) viewer;
            checkboxTableViewer.addCheckStateListener(new ICheckStateListener() {
                @Override
                public void checkStateChanged(final CheckStateChangedEvent event) {
                    TableControl.this.onCheckStateChanged(event);
                }
            });
        }
    }

    /**
     * Called when this {@link TableControl} is disposed. We first call the
     * subclass hook method ({@link #onDisposed()}), then clean up private
     * objects.
     */
    private void widgetDisposed(final DisposeEvent e) {
        onDisposed();

        if (clipboard != null) {
            clipboard.dispose();
            clipboard = null;
        }

        dragSource.dispose();
    }

    private static abstract class DefaultLabelProvider extends LabelProvider
            implements ITableLabelProvider, IColorProvider {
    }
}