org.eclipse.jpt.common.ui.internal.widgets.Pane.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jpt.common.ui.internal.widgets.Pane.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2013 Oracle. 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:
 *     Oracle - initial API and implementation
 ******************************************************************************/
package org.eclipse.jpt.common.ui.internal.widgets;

import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jpt.common.ui.WidgetFactory;
import org.eclipse.jpt.common.ui.internal.WorkbenchTools;
import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper;
import org.eclipse.jpt.common.ui.internal.plugin.JptCommonUiPlugin;
import org.eclipse.jpt.common.ui.internal.swt.ComboModelAdapter;
import org.eclipse.jpt.common.ui.internal.swt.DateTimeModelAdapter;
import org.eclipse.jpt.common.ui.internal.swt.SpinnerModelAdapter;
import org.eclipse.jpt.common.ui.internal.swt.TriStateCheckBoxModelAdapter;
import org.eclipse.jpt.common.ui.internal.swt.bind.SWTBindTools;
import org.eclipse.jpt.common.ui.internal.swt.events.DisposeAdapter;
import org.eclipse.jpt.common.utility.internal.model.value.CompositeBooleanPropertyValueModel;
import org.eclipse.jpt.common.utility.internal.model.value.NullCheckPropertyValueModelWrapper;
import org.eclipse.jpt.common.utility.internal.model.value.PredicatePropertyValueModel;
import org.eclipse.jpt.common.utility.internal.model.value.SimplePropertyValueModel;
import org.eclipse.jpt.common.utility.internal.model.value.StaticPropertyValueModel;
import org.eclipse.jpt.common.utility.internal.predicate.PredicateTools;
import org.eclipse.jpt.common.utility.internal.transformer.TransformerTools;
import org.eclipse.jpt.common.utility.model.Model;
import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent;
import org.eclipse.jpt.common.utility.model.listener.PropertyChangeAdapter;
import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener;
import org.eclipse.jpt.common.utility.model.value.ListValueModel;
import org.eclipse.jpt.common.utility.model.value.ModifiablePropertyValueModel;
import org.eclipse.jpt.common.utility.model.value.PropertyValueModel;
import org.eclipse.jpt.common.utility.transformer.Transformer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DateTime;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.part.PageBook;

/**
 * The abstract definition of a pane which holds a {@link PropertyValueModel}
 * that contains the pane's subject.
 * <p>
 * This class contains convenience methods for building buttons, labels, check
 * boxes, radio buttons, etc.
 * <p>
 * It is possible to easily listen to any property changes coming from the
 * subject, {@link #addPropertyNames(Collection)} specifies which properties
 * are of interest and {@link #propertyChanged(String)} is used to notify the
 * pane when the property has changed.
 * 
 * @see DialogPane
 */
@SuppressWarnings("nls")
public abstract class Pane<T extends Model> {
    /**
     * This will be <code>null</code> for <em>root</em> panes.
     */
    private final Pane<?> parent;

    /**
     * The listener registered with the subject in order to be notified when a
     * property has changed, the property names are determined by
     * {@link #getPropertyNames()}.
     */
    private final PropertyChangeListener aspectChangeListener;

    /**
     * The container of the pane's composite.
     */
    private final Composite container;

    /**
     * Flag used to stop the circular population of widgets.
     */
    private boolean populating;

    /**
     * This listener is registered with the {@link #subjectModel} in order to
     * automatically repopulate this pane when the subject changes.
     */
    private final PropertyChangeListener subjectChangeListener;

    /**
     * The pane's subject.
     */
    private final PropertyValueModel<? extends T> subjectModel;

    /**
     * The widget factory used by the pane and all its descendant panes to
     * create various common widgets.
     * This will be <code>null</code> if the pane has a {@link #parent}.
     */
    private final WidgetFactory widgetFactory;

    /**
     * The resource manager used by the pane and all its descendant panes to
     * allocate resources (images, colors, and fonts).
     * This will be <code>null</code> if the pane has a {@link #parent}.
     */
    private final ResourceManager resourceManager;

    /**
     * The AND of the <em>enabled</em> model passed in via the constructor and
     * the parent pane's <em>enabled</em> model.
     */
    private final PropertyValueModel<Boolean> enabledModel;
    private final PropertyChangeListener enabledModelListener;

    /**
     * A listener that allows us to stop listening to stuff when the control
     * is disposed. (Critical for preventing memory leaks.)
     */
    private final DisposeListener controlDisposeListener;

    /**
     * Construct a pane that uses the specified parent pane's:<ul>
     * <li>subject model
     * <li><em>enabled</em> model
     * </ul>
     */
    protected Pane(Pane<? extends T> parent, Composite parentComposite) {
        this(parent, parent.getSubjectHolder(), parentComposite);
    }

    /**
     * Construct a pane that uses the specified parent pane's:<ul>
     * <li><em>enabled</em> model
     * </ul>
     */
    protected Pane(Pane<?> parent, PropertyValueModel<? extends T> subjectModel, Composite parentComposite) {
        this(parent, subjectModel, buildDefaultEnabledModel(), parentComposite);
    }

    /**
     * Construct a pane that uses the specified parent pane's:<ul>
     * <li>subject model
     * </ul>
     * The specified <em>enabled</em> model will be ANDed with the parent
     * pane's <em>enabled</em> model (i.e. the pane can be <em>enabled</em>
     * only if its parent pane is also <em>enabled</em>).
     */
    protected Pane(Pane<? extends T> parent, Composite parentComposite, PropertyValueModel<Boolean> enabledModel) {
        this(parent, parent.getSubjectHolder(), enabledModel, parentComposite);
    }

    /**
     * Construct a pane that uses the specified subject model and
     * <em>enabled</em> model.
     * <p>
     * The specified <em>enabled</em> model will be ANDed with the parent
     * pane's <em>enabled</em> model (i.e. the pane can be <em>enabled</em>
     * only if its parent pane is also <em>enabled</em>).
     */
    protected Pane(Pane<?> parent, PropertyValueModel<? extends T> subjectModel,
            PropertyValueModel<Boolean> enabledModel, Composite parentComposite) {
        this(parent, subjectModel, enabledModel, parentComposite, null, null);
    }

    /**
     * Construct a <em>root</em> pane with the specified subject model, widget
     * factory, and resource manager.
     * The pane will be <em>disabled</em> whenever the subject is
     * <code>null</code>.
     */
    protected Pane(PropertyValueModel<? extends T> subjectModel, Composite parentComposite,
            WidgetFactory widgetFactory, ResourceManager resourceManager) {
        this(subjectModel, buildIsNotNullModel(subjectModel), parentComposite, widgetFactory, resourceManager);
    }

    /**
     * Construct a <em>root</em> pane with the specified subject model,
     * <em>enabled</em> model, widget factory, and resource manager.
     * <p>
     * The specified <em>enabled</em> model will be ANDed with the parent
     * pane's <em>enabled</em> model (i.e. the pane can be <em>enabled</em>
     * only if its parent pane is also <em>enabled</em>).
     */
    protected Pane(PropertyValueModel<? extends T> subjectModel, PropertyValueModel<Boolean> enabledModel,
            Composite parentComposite, WidgetFactory widgetFactory, ResourceManager resourceManager) {
        this(null, subjectModel, enabledModel, parentComposite, widgetFactory, resourceManager);
    }

    /**
     * This constructor is <code>private</code> so we can enable, but also
     * require, <em>root</em> panes (i.e. panes without parents) to specify the
     * following:<ul>
     * <li>subject model
     * <li>widget factory
     * </ul>
     */
    private Pane(Pane<?> parent, PropertyValueModel<? extends T> subjectModel,
            PropertyValueModel<Boolean> enabledModel, Composite parentComposite, WidgetFactory widgetFactory,
            ResourceManager resourceManager) {
        super();
        if ((subjectModel == null) || (enabledModel == null) || (parentComposite == null)) {
            throw new NullPointerException();
        }
        if (parent == null) {
            if ((widgetFactory == null) || (resourceManager == null)) {
                throw new NullPointerException();
            }
        }
        this.parent = parent;
        this.subjectModel = subjectModel;

        this.enabledModel = andEnabledModel(parent, enabledModel);
        this.enabledModelListener = this.buildEnabledModelListener();
        this.enabledModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.enabledModelListener);

        this.widgetFactory = widgetFactory;
        this.resourceManager = (resourceManager == null) ? null : new LocalResourceManager(resourceManager);

        this.aspectChangeListener = this.buildAspectChangeListener();

        this.initialize();

        if (this.addsComposite()) {
            this.container = this.addComposite(parentComposite);
            this.initializeLayout(this.container);
        } else {
            this.container = null;
            this.initializeLayout(parentComposite);
        }
        this.controlDisposeListener = this.buildControlDisposeListener();
        this.getControl().addDisposeListener(this.controlDisposeListener);

        this.subjectChangeListener = this.buildSubjectChangeListener();
        this.subjectModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener);

        this.engageListeners(getSubject());
        this.populate();
    }

    // ********** enabled model **********

    /**
     * Return an <em>enabled</em> model that will result in the pane's
     * <em>enabled</em> state always matching that of its parent pane.
     */
    private static PropertyValueModel<Boolean> buildDefaultEnabledModel() {
        return new StaticPropertyValueModel<Boolean>(Boolean.TRUE);
    }

    /**
     * Return a {@link Boolean} value model that will return
     * {@link Boolean#TRUE} if the value of the specified value model is
     * <em>not</em> <code>null</code>;
     * {@link Boolean#FALSE} if the value <em>is</em> <code>null</code>.
     */
    protected static PropertyValueModel<Boolean> buildIsNotNullModel(PropertyValueModel<?> valueModel) {
        return new PredicatePropertyValueModel<Object>(valueModel, PredicateTools.isNotNull());
    }

    /**
     * Convenience method for sub-classes.
     * Wrap the pane's {@link #subjectModel} in a {@link #buildIsNotNullModel(PropertyValueModel)};
     * i.e. a model that returns whether the subject is <code>null</code>.
     */
    protected PropertyValueModel<Boolean> buildSubjectIsNotNullModel() {
        return buildIsNotNullModel(this.subjectModel);
    }

    /**
     * Return a {@link Boolean} value model that will return the AND of the
     * value of the <em>enabled</em> model of the specified (typically parent) pane
     * and the value of the specified <em>enabled</em> model.
     * <p>
     * This is useful for a pane that is <em>enabled</em> when both its parent
     * pane is <em>enabled</em> <em>and</em> the pane's model indicates the
     * pane should be <em>enabled</em>.
     */
    @SuppressWarnings("unchecked")
    private static PropertyValueModel<Boolean> andEnabledModel(Pane<?> pane,
            PropertyValueModel<Boolean> enabledModel) {
        enabledModel = buildNonNullModel(enabledModel);
        // NB: we fetch private state from the pane
        return (pane == null) ? enabledModel
                : CompositeBooleanPropertyValueModel.and(pane.enabledModel, enabledModel);
    }

    /**
     * Return a {@link Boolean} value model that will return the value of the
     * specified {@link Boolean} value model if it is <em>not</em>
     * <code>null</code>;
     * {@link Boolean#FALSE} if the value is <code>null</code>.
     * <p>
     * This is useful for <em>enabled</em> models that might return <code>null</code>
     * (which is typical with aspect adapters etc.).
     */
    private static PropertyValueModel<Boolean> buildNonNullModel(PropertyValueModel<Boolean> booleanModel) {
        return new NullCheckPropertyValueModelWrapper<Boolean>(booleanModel, Boolean.FALSE);
    }

    // ********** initialization **********

    private PropertyChangeListener buildEnabledModelListener() {
        return new EnabledModelListener();
    }

    /* CU private */ class EnabledModelListener extends PropertyChangeAdapter {
        @Override
        public void propertyChanged(PropertyChangeEvent event) {
            Pane.this.enabledModelChanged(((Boolean) event.getOldValue()).booleanValue(),
                    ((Boolean) event.getNewValue()).booleanValue());
        }
    }

    protected void enabledModelChanged(@SuppressWarnings("unused") boolean oldEnabled,
            @SuppressWarnings("unused") boolean newEnabled) {
        // NOP
    }

    private PropertyChangeListener buildSubjectChangeListener() {
        return new SWTPropertyChangeListenerWrapper(this.buildSubjectChangeListener_());
    }

    private PropertyChangeListener buildSubjectChangeListener_() {
        return new SubjectChangeListener();
    }

    /* CU private */ class SubjectChangeListener extends PropertyChangeAdapter {
        @Override
        @SuppressWarnings("unchecked")
        public void propertyChanged(PropertyChangeEvent e) {
            Pane.this.subjectChanged((T) e.getOldValue(), (T) e.getNewValue());
        }
    }

    /**
     * Initialize the pane's models. This method is called before the pane's
     * UI widget is built in {@link #initializeLayout(Composite)}.
     */
    protected void initialize() {
        // do nothing by default
    }

    /**
     * Build the pane's UI widget in the specified composite, using
     * the models built in {@link #initialize()}.
     */
    protected abstract void initializeLayout(Composite parentComposite);

    private DisposeListener buildControlDisposeListener() {
        return new ControlDisposeListener();
    }

    /* CU private */ class ControlDisposeListener extends DisposeAdapter {
        @Override
        public void widgetDisposed(DisposeEvent event) {
            Pane.this.controlDisposed();
        }
    }

    /**
     * Adds any property names to the given collection in order to be notified
     * when the actual property changes in the subject.
     *
     * @param propertyNames The collection of property names to register with the
     * subject
     */
    protected void addPropertyNames(Collection<String> propertyNames) {
    }

    private PropertyChangeListener buildAspectChangeListener() {
        return new SWTPropertyChangeListenerWrapper(buildAspectChangeListener_());
    }

    private PropertyChangeListener buildAspectChangeListener_() {
        return new PropertyChangeListener() {
            public void propertyChanged(PropertyChangeEvent e) {
                //subject() could have changed or is null because of the possibility of
                //"jumping" on the UI thread here and a selection change occuring
                if (e.getSource() == getSubject()) {
                    updatePane(e.getPropertyName());
                }
            }
        };
    }

    /**
     * Creates a new button using the given information.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param buttonAction The action to be invoked when the button is pressed
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    protected final Button addButton(Composite container, String text, final Runnable buttonAction) {

        return this.addButton(container, text, null, buttonAction);
    }

    protected final Button addButton(Composite container, String text, final Runnable buttonAction,
            PropertyValueModel<Boolean> enabledModel) {

        return this.addButton(container, text, null, buttonAction, enabledModel);
    }

    /**
     * Creates a new unmanaged <code>Button</code> widget.  Unmanaged means
     * that this Pane will not handle the enabling/disabling of this widget.
     * The owning object will handle it with its own PaneEnabler or ControlEnabler.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param buttonAction The action to be invoked when the button is pressed
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    protected final Button addUnmanagedButton(Composite container, String text, final Runnable buttonAction) {

        return this.addUnmanagedButton(container, text, null, buttonAction);
    }

    /**
     * Creates a new button using the given information.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param helpId The topic help ID to be registered for the new check box
     * @param buttonAction The action to be invoked when the button is pressed
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    protected final Button addButton(Composite container, String text, String helpId, final Runnable buttonAction) {

        Button button = addUnmanagedButton(container, text, helpId, buttonAction);
        this.controlEnabledState(button);

        return button;
    }

    protected final Button addButton(Composite container, String text, String helpId, final Runnable buttonAction,
            PropertyValueModel<Boolean> enabledModel) {

        Button button = addUnmanagedButton(container, text, helpId, buttonAction);
        this.controlEnabledState(enabledModel, button);

        return button;
    }

    /**
     * Creates a new unmanaged <code>Button</code> widget.  Unmanaged means
     * that this Pane will not handle the enabling/disabling of this widget.
     * The owning object will handle it with its own PaneEnabler or ControlEnabler.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param helpId The topic help ID to be registered for the new check box
     * @param buttonAction The action to be invoked when the button is pressed
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    private Button addUnmanagedButton(Composite container, String text, String helpId,
            final Runnable buttonAction) {

        Button button = this.getWidgetFactory().createButton(container, text);
        button.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                buttonAction.run();
            }
        });

        if (helpId != null) {
            this.setHelp(button, helpId);
        }

        GridData gridData = new GridData();
        gridData.grabExcessHorizontalSpace = false;
        gridData.horizontalAlignment = GridData.FILL;
        button.setLayoutData(gridData);

        return button;
    }

    /**
     * This layout will leave space for decorations on widgets.
     * Whether decorated or not, all of the widgets need the same indent
      * so that they align properly.
     */
    protected GridData getFieldGridData() {
        int margin = FieldDecorationRegistry.getDefault().getMaximumDecorationWidth();
        GridData data = new GridData();
        data.horizontalAlignment = SWT.FILL;
        data.widthHint = IDialogConstants.ENTRY_FIELD_WIDTH + margin;
        data.horizontalIndent = margin;
        data.grabExcessHorizontalSpace = true;
        return data;
    }

    /**
     * Creates a new check box using the given information.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param booleanHolder The holder of the selection state
     * @param helpId The topic help ID to be registered for the new check box
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    protected final Button addCheckBox(Composite parent, String buttonText,
            ModifiablePropertyValueModel<Boolean> booleanHolder, String helpId) {

        return this.addToggleButton(parent, buttonText, booleanHolder, helpId, SWT.CHECK);
    }

    protected final Button addCheckBox(Composite parent, String buttonText,
            ModifiablePropertyValueModel<Boolean> booleanHolder, String helpId,
            PropertyValueModel<Boolean> enabledModel) {

        Button button = this.addUnmanagedToggleButton(parent, buttonText, booleanHolder, helpId, SWT.CHECK);
        this.controlEnabledState(enabledModel, button);
        return button;
    }

    /**
     * Creates a new non-editable <code>Combo</code>.
     *
     * @param container The parent container
     * @return The newly created <code>Combo</code>
     *
     * @category Layout
     */
    protected final Combo addCombo(Composite container) {
        Combo combo = this.addUnmanagedCombo(container);
        this.controlEnabledState(combo);
        return combo;
    }

    /**
     * Creates a new non-editable <code>Combo</code>.
     *
     * @param container The parent container
     * @return The newly created <code>Combo</code>
     *
     * @category Layout
     */
    protected final Combo addCombo(Composite container, String helpId) {
        Combo combo = this.addUnmanagedCombo(container);

        if (helpId != null) {
            this.setHelp(combo, helpId);
        }

        this.controlEnabledState(combo);
        return combo;
    }

    /**
     * Creates a new non-editable <code>Combo</code>.
     *
     * @param container The parent container
     * @return The newly created <code>Combo</code>
     *
     * @category Layout
     */
    private Combo addUnmanagedCombo(Composite container) {
        Combo combo = this.getWidgetFactory().createCombo(container);
        combo.setLayoutData(getFieldGridData());
        return combo;
    }

    /**
     * Creates a new non-editable <code>Combo</code>.
     *
     * @param container The parent container
     * @param listHolder The <code>ListValueHolder</code>
     * @param selectedItemHolder The holder of the selected item
     * @param stringConverter The converter responsible to transform each item
     * into a string representation
     * @return The newly created <code>Combo</code>
     *
     * @category Layout
     */
    protected final <V> Combo addCombo(Composite container, ListValueModel<V> listHolder,
            ModifiablePropertyValueModel<V> selectedItemHolder, Transformer<V, String> stringConverter,
            String helpId) {

        Combo combo = this.addCombo(container, helpId);

        ComboModelAdapter.adapt(listHolder, selectedItemHolder, combo, stringConverter);

        return combo;
    }

    /**
     * Creates a new non-editable <code>Combo</code>.
     *
     * @param container The parent container
     * @param listHolder The <code>ListValueHolder</code>
     * @param selectedItemHolder The holder of the selected item
     * @param stringConverter The converter responsible to transform each item
     * into a string representation
     * @return The newly created <code>Combo</code>
     *
     * @category Layout
     */
    private <V> Combo addUnmanagedCombo(Composite container, ListValueModel<V> listHolder,
            ModifiablePropertyValueModel<V> selectedItemHolder, Transformer<V, String> stringConverter) {

        Combo combo = this.addUnmanagedCombo(container);

        ComboModelAdapter.adapt(listHolder, selectedItemHolder, combo, stringConverter);

        return combo;
    }

    protected final <V> Combo addCombo(Composite container, ListValueModel<V> listHolder,
            ModifiablePropertyValueModel<V> selectedItemHolder, Transformer<V, String> stringConverter,
            PropertyValueModel<Boolean> enabledModel) {

        Combo combo = this.addUnmanagedCombo(container, listHolder, selectedItemHolder, stringConverter);
        this.controlEnabledState(enabledModel, combo);
        return combo;
    }

    /**
     * Creates a new <code>ComboViewer</code> using a <code>Combo</code>.
     *
     * @param container The parent container
     * @param labelProvider The provider responsible to convert the combo's items
     * into human readable strings
     * @return The newly created <code>ComboViewer</code>
     *
     * @category Layout
     */
    protected final ComboViewer addComboViewer(Composite container, IBaseLabelProvider labelProvider) {

        Combo combo = this.addCombo(container);
        ComboViewer viewer = new ComboViewer(combo);
        viewer.setLabelProvider(labelProvider);
        return viewer;
    }

    protected final ComboViewer addComboViewer(Composite container, IBaseLabelProvider labelProvider,
            String helpId) {

        Combo combo = this.addCombo(container, helpId);
        ComboViewer viewer = new ComboViewer(combo);
        viewer.setLabelProvider(labelProvider);
        return viewer;
    }

    /**
     * Creates the main container of this pane. The layout and layout data are
     * automatically set.
     *
     * @param parent The parent container
     * @return The newly created <code>Composite</code> that will holds all the
     * widgets created by this pane through {@link #initializeLayout(Composite)}
     *
     * @category Layout
     */
    protected Composite addComposite(Composite parent) {
        return this.addSubPane(parent);
    }

    /**
     * Return whether this Pane should add a Composite. Using this
     * to reduce the number of SWT Controls (USER handles in windows) created.
     * Typically you would return false if the Pane is for only 1 widget. In this case
     * you need to override {@link #getControl()} to return the appropriate Control
     */
    protected boolean addsComposite() {
        return true;
    }

    protected final <V> Combo addEditableCombo(Composite container, ListValueModel<V> listHolder,
            ModifiablePropertyValueModel<V> selectedItemHolder, Transformer<V, String> stringConverter,
            PropertyValueModel<Boolean> enabledModel) {

        return this.addEditableCombo(container, listHolder, selectedItemHolder, stringConverter, enabledModel,
                null);
    }

    protected final <V> Combo addEditableCombo(Composite container, ListValueModel<V> listHolder,
            ModifiablePropertyValueModel<V> selectedItemHolder, Transformer<V, String> stringConverter,
            PropertyValueModel<Boolean> enabledModel, String helpId) {

        Combo combo = this.addUnmanagedEditableCombo(container, listHolder, selectedItemHolder, stringConverter,
                helpId);
        this.controlEnabledState(enabledModel, combo);
        return combo;
    }

    protected final Combo addEditableCombo(Composite container) {
        return this.addEditableCombo(container, null);
    }

    protected final Combo addEditableCombo(Composite container, String helpId) {
        Combo combo = this.getWidgetFactory().createEditableCombo(container);

        if (helpId != null) {
            this.setHelp(combo, helpId);
        }

        combo.setLayoutData(getFieldGridData());
        this.controlEnabledState(combo);
        return combo;
    }

    protected final <V> Combo addEditableCombo(Composite container, ListValueModel<V> listHolder,
            ModifiablePropertyValueModel<V> selectedItemHolder) {

        return this.addEditableCombo(container, listHolder, selectedItemHolder,
                TransformerTools.<V>objectToStringTransformer(), (String) null);
    }

    /**
     * Creates a new editable <code>Combo</code>.
     *
     * @param container The parent container
     * @param listHolder The <code>ListValueHolder</code>
     * @param selectedItemHolder The holder of the selected item
     * @param stringConverter The converter responsible to transform each item
     * into a string representation
     * @return The newly created <code>Combo</code>
     *
     * @category Layout
     */
    protected final <V> Combo addEditableCombo(Composite container, ListValueModel<V> listHolder,
            ModifiablePropertyValueModel<V> selectedItemHolder, Transformer<V, String> stringConverter,
            String helpId) {

        Combo combo = this.addEditableCombo(container, helpId);

        ComboModelAdapter.adapt(listHolder, selectedItemHolder, combo, stringConverter);

        return combo;
    }

    /**
     * Creates a new editable <code>ComboViewer</code> using a <code>Combo</code>.
     *
     * @param container The parent container
     * @param labelProvider The provider responsible to convert the combo's items
     * into human readable strings
     * @return The newly created <code>ComboViewer</code>
     *
     * @category Layout
     */
    protected final ComboViewer addEditableComboViewer(Composite container, IBaseLabelProvider labelProvider) {

        Combo combo = this.addEditableCombo(container);
        ComboViewer viewer = new ComboViewer(combo);
        viewer.setLabelProvider(labelProvider);
        return viewer;
    }

    /**
     * Creates a new <code>Hyperlink</code> that will invoked the given
     * <code>Runnable</code> when selected. The given action is always invoked
     * from the UI thread.
     *
     * @param parent The parent container
     * @param text The hyperlink's text
     * @param hyperLinkAction The action to be invoked when the link was selected
     * return The newly created <code>Hyperlink</code>
     *
     * @category Layout
     */
    protected final Hyperlink addHyperlink(Composite parent, String text, final Runnable hyperLinkAction) {

        Hyperlink link = this.getWidgetFactory().createHyperlink(parent, text);
        this.controlEnabledState(link);

        link.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {

                Hyperlink hyperLink = (Hyperlink) e.widget;

                if (hyperLink.isEnabled()) {
                    hyperLinkAction.run();
                }
            }
        });

        return link;
    }

    protected final Hyperlink addHyperlink(Composite parent, String text) {

        Hyperlink link = this.getWidgetFactory().createHyperlink(parent, text);
        this.controlEnabledState(link);

        return link;
    }

    /**
     * Creates a new label using the given information.
     *
     * @param parent The parent container
     * @param labelText The label's text
     *
     * @category Layout
     */
    protected final Label addLabel(Composite container, String labelText) {

        Label label = this.addUnmanagedLabel(container, labelText);
        this.controlEnabledState(label);
        return label;
    }

    protected final Label addLabel(Composite container, String labelText,
            PropertyValueModel<Boolean> enabledModel) {
        Label label = this.addUnmanagedLabel(container, labelText);
        this.controlEnabledState(enabledModel, label);
        return label;
    }

    /**
     * Creates a new unmanaged <code>Label</code> widget.  Unmanaged means
     * that this Pane will not handle the enabling/disabling of this widget.
     * The owning object will handle it with its own PaneEnabler or ControlEnabler.
     *
     * @param parent The parent container
     * @param labelText The label's text
     *
     * @category Layout
     */
    private Label addUnmanagedLabel(Composite container, String labelText) {

        return this.getWidgetFactory().createLabel(container, labelText);
    }

    /**
     * Creates a new managed spinner. Managed means that this Pane will
     * handle enabling/disabling of this widget if a PaneEnabler is used.
     *
     * @param parent The parent container
     * @param numberHolder The holder of the integer value
     * @param defaultValue The value shown when the holder has <code>null</code>
     * @param minimumValue The minimum value that the spinner will allow
     * @param maximumValue The maximum value that the spinner will allow
     * @param helpId The topic help ID to be registered for the new button
     * @return The newly created <code>Spinner</code>
     *
     * @category Layout
     */
    protected final Spinner addSpinner(Composite parent, ModifiablePropertyValueModel<Integer> numberHolder,
            int defaultValue, int minimumValue, int maximumValue, String helpId) {

        Spinner spinner = addUnmanagedSpinner(parent, numberHolder, defaultValue, minimumValue, maximumValue,
                helpId);
        this.controlEnabledState(spinner);
        return spinner;
    }

    /**
     * Creates a new unmanaged spinner.  Unmanaged means that this Pane will
     * not handle the enabling/disabling of this widget.  The owning object will handle
     * it with its own PaneEnabler or ControlEnabler.
     *
     * @param parent The parent container
     * @param numberHolder The holder of the integer value
     * @param defaultValue The value shown when the holder has <code>null</code>
     * @param minimumValue The minimum value that the spinner will allow
     * @param maximumValue The maximum value that the spinner will allow
     * @param helpId The topic help ID to be registered for the new button
     * @return The newly created <code>Spinner</code>
     *
     * @category Layout
     */
    private Spinner addUnmanagedSpinner(Composite parent, ModifiablePropertyValueModel<Integer> numberHolder,
            int defaultValue, int minimumValue, int maximumValue, String helpId) {

        Spinner spinner = this.getWidgetFactory().createSpinner(parent);
        spinner.setMinimum(minimumValue);
        spinner.setMaximum(maximumValue);
        GridData gridData = getFieldGridData();
        gridData.grabExcessHorizontalSpace = false;
        spinner.setLayoutData(gridData);

        SpinnerModelAdapter.adapt(numberHolder, spinner, defaultValue);

        if (helpId != null) {
            this.setHelp(spinner, helpId);
        }

        return spinner;
    }

    /**
     * Creates a new managed DateTime of type SWT.TIME.  Managed means that this Pane will
     * handle enabling/disabling of this widget if a PaneEnabler is used.
     *
     * @param parent The parent container
     * @param hoursHolder The holder of the hours integer value
     * @param minutesHolder The holder of the minutes integer value
     * @param secondsHolder The holder of the seconds integer value
     * @param helpId The topic help ID to be registered for the new dateTime
     * @return The newly created <code>DateTime</code>
     *
     * @category Layout
     */
    protected final DateTime addDateTime(Composite parent, ModifiablePropertyValueModel<Integer> hoursHolder,
            ModifiablePropertyValueModel<Integer> minutesHolder,
            ModifiablePropertyValueModel<Integer> secondsHolder, String helpId) {

        DateTime dateTime = this.addUnmanagedDateTime(parent, hoursHolder, minutesHolder, secondsHolder, helpId);
        this.controlEnabledState(dateTime);

        return dateTime;
    }

    protected final DateTime addDateTime(Composite parent, ModifiablePropertyValueModel<Integer> hoursHolder,
            ModifiablePropertyValueModel<Integer> minutesHolder,
            ModifiablePropertyValueModel<Integer> secondsHolder, String helpId,
            PropertyValueModel<Boolean> enabledModel) {
        DateTime dateTime = this.addUnmanagedDateTime(parent, hoursHolder, minutesHolder, secondsHolder, helpId);
        this.controlEnabledState(enabledModel, dateTime);
        return dateTime;
    }

    /**
     * Creates a new unmanaged DateTime of type SWT.TIME.  Unmanaged means that this Pane will
     * not handle the enabling/disabling of this widget.  The owning object will handle
     * it with its own PaneEnabler or ControlEnabler.
     *
     * @param parent The parent container
     * @param hoursHolder The holder of the hours integer value
     * @param minutesHolder The holder of the minutes integer value
     * @param secondsHolder The holder of the seconds integer value
     * @param helpId The topic help ID to be registered for the new dateTime
     * @return The newly created <code>DateTime</code>
     *
     * @category Layout
     */
    private DateTime addUnmanagedDateTime(Composite parent, ModifiablePropertyValueModel<Integer> hoursHolder,
            ModifiablePropertyValueModel<Integer> minutesHolder,
            ModifiablePropertyValueModel<Integer> secondsHolder, String helpId) {

        DateTime dateTime = this.getWidgetFactory().createDateTime(parent, SWT.TIME);

        DateTimeModelAdapter.adapt(hoursHolder, minutesHolder, secondsHolder, dateTime);

        if (helpId != null) {
            this.setHelp(dateTime, helpId);
        }

        return dateTime;
    }

    /**
     * Creates a new editable <code>Combo</code>.
     *
     * @param container The parent container
     * @param listHolder The <code>ListValueHolder</code>
     * @param selectedItemHolder The holder of the selected item
     * @param stringConverter The converter responsible to transform each item
     * into a string representation
     * @return The newly created <code>CCombo</code>
     *
     * @category Layout
     */
    private <V> Combo addUnmanagedEditableCombo(Composite container, ListValueModel<V> listHolder,
            ModifiablePropertyValueModel<V> selectedItemHolder, Transformer<V, String> stringConverter,
            String helpId) {

        Combo combo = addUnmanagedEditableCombo(container, helpId);

        ComboModelAdapter.adapt(listHolder, selectedItemHolder, combo, stringConverter);

        return combo;
    }

    /**
     * Creates a new editable <code>Combo</code>.
     *
     * @param container The parent container
     * @return The newly created <code>CCombo</code>
     *
     * @category Layout
     */
    private Combo addUnmanagedEditableCombo(Composite container, String helpId) {

        Combo combo = this.getWidgetFactory().createEditableCombo(container);
        combo.setLayoutData(getFieldGridData());

        if (helpId != null) {
            this.setHelp(container, helpId);
        }

        return combo;
    }

    /**
     * Creates a new list and notify the given selection holder when the
     * selection changes. If the selection count is different than one than the
     * holder will receive <code>null</code>.
     *
     * @param container The parent container
     * @param helpId The topic help ID to be registered for the new radio button
     * @return The newly created <code>List</code>
     *
     * @category Layout
     */
    protected final List addList(Composite container, String helpId) {

        return this.addList(container, new SimplePropertyValueModel<String>(), helpId);
    }

    /**
     * Creates a new list and notify the given selection holder when the
     * selection changes. If the selection count is different than one than the
     * holder will receive <code>null</code>.
     *
     * @param container The parent container
     * @param selectionHolder The holder of the unique selected item
     * @param helpId The topic help ID to be registered for the new radio button
     * @return The newly created <code>List</code>
     *
     * @category Layout
     */
    protected final List addList(Composite container, ModifiablePropertyValueModel<String> selectionHolder,
            String helpId) {

        List list = this.addUnmanagedList(container, selectionHolder, helpId);
        this.controlEnabledState(list);

        return list;
    }

    /**
     * Creates a new unmanaged list and notify the given selection holder when the
     * selection changes. If the selection count is different than one than the
     * holder will receive <code>null</code>.
     * Unmanaged means that this Pane will not handle the enabling/disabling of this widget.
     * The owning object will handle it with its own PaneEnabler or ControlEnabler.
     *
     * @param container The parent container
     * @param selectionHolder The holder of the unique selected item
     * @param helpId The topic help ID to be registered for the new radio button
     * @return The newly created <code>List</code>
     *
     * @category Layout
     */
    private List addUnmanagedList(Composite container, ModifiablePropertyValueModel<String> selectionHolder,
            String helpId) {

        List list = this.getWidgetFactory().createList(container,
                SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI);

        list.addSelectionListener(buildSelectionListener(selectionHolder));
        list.setLayoutData(new GridData(GridData.FILL_BOTH));

        if (helpId != null) {
            this.setHelp(list, helpId);
        }

        return list;
    }

    /**
     * Creates a new <code>Text</code> widget that has multiple lines.
     *
     * @param container The parent container
     * @return The newly created <code>Text</code> widget
     *
     */
    protected final Text addMultiLineText(Composite container) {

        Text text = this.getWidgetFactory().createMultiLineText(container);
        text.setLayoutData(getFieldGridData());
        this.controlEnabledState(text);

        return text;
    }

    /**
     * Creates a new <code>Text</code> widget that has multiple lines.
     *
     * @param container The parent container
     * @param lineCount The number of lines the text area should display
     * @param helpId The topic help ID to be registered for the new text
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    protected final Text addMultiLineText(Composite container, int lineCount, String helpId) {

        Text text = this.addMultiLineText(container);
        adjustMultiLineTextLayout(lineCount, text, text.getLineHeight());

        if (helpId != null) {
            this.setHelp(text, helpId);
        }

        return text;
    }

    /**
     * Creates a new <code>Text</code> widget that has multiple lines.
     *
     * @param container The parent container
     * @param textHolder The holder of the text field's input
     * @param lineCount The number of lines the text area should display
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    protected final Text addMultiLineText(Composite container, ModifiablePropertyValueModel<String> textHolder,
            int lineCount) {

        return this.addMultiLineText(container, textHolder, lineCount, null);
    }

    /**
     * Creates a new <code>Text</code> widget that has multiple lines.
     *
     * @param container The parent container
     * @param textModel The holder of the text field's input
     * @param helpId The topic help ID to be registered for the new text
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    protected final Text addMultiLineText(Composite container, ModifiablePropertyValueModel<String> textModel,
            int lineCount, String helpId) {

        Text text = this.addMultiLineText(container, lineCount, helpId);
        SWTBindTools.bind(textModel, text);
        return text;
    }

    /**
     * Adjusts the layout of the given container so that the text control has the correct amount of
     * lines by default.
     */
    protected final void adjustMultiLineTextLayout(int lineCount, Control text, int lineHeight) {

        // Specify the number of lines the text area should display
        GridData gridData = (GridData) text.getLayoutData();
        if (gridData == null) {
            gridData = this.getFieldGridData();
            text.setLayoutData(gridData);
        }
        gridData.heightHint = lineHeight * lineCount;
    }

    /**
     * Creates a new <code>PageBook</code> and set the proper layout and layout
     * data.
     *
     * @param container The parent container
     * @return The newly created <code>PageBook</code>
     *
     * @category Layout
     */
    protected final PageBook addPageBook(Composite container) {

        PageBook pageBook = new PageBook(container, SWT.NULL);
        pageBook.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        return pageBook;
    }

    /**
     * Creates a new container without specifying any layout manager.
     *
     * @param container The parent of the new container
     * @return The newly created <code>Composite</code>
     *
     * @category Layout
     */
    protected final Composite addPane(Composite parent) {
        return this.getWidgetFactory().createComposite(parent);
    }

    /**
     * Creates a new container using the given layout manager.
     *
     * @param parent The parent of the new container
     * @param layout The layout manager of the new container
     * @return The newly created container
     *
     * @category Layout
     */
    protected final Composite addPane(Composite container, Layout layout) {

        container = this.addPane(container);
        container.setLayout(layout);
        container.setLayoutData(new GridData(GridData.FILL_BOTH));
        return container;
    }

    /**
     * Creates a new <code>Text</code> widget.
     *
     * @param container The parent container
     * @param textModel The holder of the text field's input
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    protected final Text addPasswordText(Composite container, ModifiablePropertyValueModel<String> textModel) {

        Text text = this.addPasswordText(container);
        SWTBindTools.bind(textModel, text);

        return text;
    }

    /**
     * Creates a new <code>Text</code> widget.
     *
     * @param container The parent container
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    protected final Text addPasswordText(Composite container) {

        Text text = this.getWidgetFactory().createPasswordText(container);
        text.setLayoutData(getFieldGridData());

        this.controlEnabledState(text);
        return text;
    }

    /**
     * Creates a new push button using the given information.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param buttonAction The action to be invoked when the button is pressed
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    protected final Button addPushButton(Composite parent, String buttonText, final Runnable buttonAction) {

        return this.addPushButton(parent, buttonText, null, buttonAction);
    }

    /**
     * Creates a new push button using the given information.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param buttonAction The action to be invoked when the button is pressed
     * @param helpId The topic help ID to be registered for the new radio button
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    protected final Button addPushButton(Composite parent, String buttonText, String helpId,
            final Runnable buttonAction) {

        Button button = this.getWidgetFactory().createPushButton(parent, buttonText);
        controlEnabledState(button);
        button.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                buttonAction.run();
            }
        });

        button.setLayoutData(new GridData());

        if (helpId != null) {
            this.setHelp(button, helpId);
        }

        return button;
    }

    /**
     * Creates a new check box using the given information.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param booleanHolder The holder of the selection state
     * @param helpId The topic help ID to be registered for the new radio button
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    protected final Button addRadioButton(Composite parent, String buttonText,
            ModifiablePropertyValueModel<Boolean> booleanHolder, String helpId) {

        return this.addToggleButton(parent, buttonText, booleanHolder, helpId, SWT.RADIO);
    }

    /**
     * Creates a new <code>Section</code>. A sub-pane is automatically added as
     * its client and is the returned <code>Composite</code>.
     *
     * @param container The container of the new widget
     * @param sectionText The text of the new section
     * @param description The section's description
     * @return The <code>Section</code>'s sub-pane
     *
     * @category Layout
     */
    protected final Composite addSection(Composite container, String sectionText, String description) {

        return this.addSection(container, sectionText, description, ExpandableComposite.TITLE_BAR);
    }

    /**
     * Creates a new <code>Section</code>. A sub-pane is automatically added as
     * its client and is the returned <code>Composite</code>.
     *
     * @param container The container of the new widget
     * @param sectionText The text of the new section
     * @param description The section's description or <code>null</code> if none
     * was provider
     * @param type The type of section to create
     * when to expand or collapse the section
     * @return The <code>Section</code>'s sub-pane
     *
     * @category Layout
     */
    private Composite addSection(Composite container, String sectionText, String description, int type) {

        Section section = this.getWidgetFactory().createSection(container,
                type | ((description != null) ? Section.DESCRIPTION : SWT.NULL));
        section.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        section.setText(sectionText);

        if (description != null) {
            section.setDescription(description);
        }

        Composite subPane = this.addSubPane(section);
        section.setClient(subPane);

        return subPane;
    }

    private SelectionListener buildSelectionListener(final ModifiablePropertyValueModel<String> selectionHolder) {
        return new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                List list = (List) e.widget;
                String[] selectedItems = list.getSelection();
                if ((selectedItems == null) || (selectedItems.length != 1)) {
                    selectionHolder.setValue(null);
                } else {
                    selectionHolder.setValue(selectedItems[0]);
                }
            }
        };
    }

    /**
     * Creates a new <code>Composite</code> used as a sub-pane.
     *
     * @param container The parent container
     * @return The newly created <code>Composite</code> used as a sub-pane
     *
     * @category Layout
     */
    protected final Composite addSubPane(Composite container) {
        return this.addSubPane(container, 0);
    }

    /**
     * Creates a new <code>Composite</code> used as a sub-pane.
     *
     * @param container The parent container
     * @param topMargin The extra spacing to add at the top of the pane
     * @return The newly created <code>Composite</code> used as a sub-pane
     *
     * @category Layout
     */
    protected final Composite addSubPane(Composite container, int topMargin) {
        return this.addSubPane(container, topMargin, 0);
    }

    /**
     * Creates a new <code>Composite</code> used as a sub-pane.
     *
     * @param container The parent container
     * @param topMargin The extra spacing to add at the top of the pane
     * @param leftMargin The extra spacing to add to the left of the pane
     * @return The newly created <code>Composite</code> used as a sub-pane
     *
     * @category Layout
     */
    protected final Composite addSubPane(Composite container, int topMargin, int leftMargin) {

        return this.addSubPane(container, topMargin, leftMargin, 0, 0);
    }

    /**
     * Creates a new <code>Composite</code> used as a sub-pane, the new widget
     * will have its layout and layout data already initialized, the layout will
     * be a <code>GridLayout</code> with 1 column.
     *
     * @param container The parent container
     * @param topMargin The extra spacing to add at the top of the pane
     * @param leftMargin The extra spacing to add to the left of the pane
     * @param bottomMargin The extra spacing to add at the bottom of the pane
     * @param rightMargin The extra spacing to add to the right of the pane
     * @return The newly created <code>Composite</code> used as a sub-pane
     *
     * @category Layout
     */
    protected final Composite addSubPane(Composite container, int topMargin, int leftMargin, int bottomMargin,
            int rightMargin) {

        return this.addSubPane(container, 1, topMargin, leftMargin, bottomMargin, rightMargin);
    }

    /**
     * Creates a new <code>Composite</code> used as a sub-pane, the new widget
     * will have its layout and layout data already initialized, the layout will
     * be a <code>GridLayout</code> with 1 column.
     *
     * @param container The parent container
     * @param topMargin The extra spacing to add at the top of the pane
     * @param leftMargin The extra spacing to add to the left of the pane
     * @param bottomMargin The extra spacing to add at the bottom of the pane
     * @param rightMargin The extra spacing to add to the right of the pane
     * @return The newly created <code>Composite</code> used as a sub-pane
     *
     * @category Layout
     */
    protected final Composite addSubPane(Composite container, int columnCount, int topMargin, int leftMargin,
            int bottomMargin, int rightMargin) {

        GridLayout layout = new GridLayout(columnCount, false);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.marginTop = topMargin;
        layout.marginLeft = leftMargin;
        layout.marginBottom = bottomMargin;
        layout.marginRight = rightMargin;

        container = this.addPane(container, layout);

        return container;
    }

    /**
     * Creates a new table.
     *
     * @param container The parent container
     * @param style The style to apply to the table
     * @param helpId The topic help ID to be registered for the new table or
     * <code>null</code> if no help ID is required
     * @return The newly created <code>Table</code>
     *
     * @category Layout
     */
    protected final Table addTable(Composite container, int style, String helpId) {

        Table table = addUnmanagedTable(container, style, helpId);
        this.controlEnabledState(table);

        return table;
    }

    /**
     * Creates a new unmanaged table.  Unmanaged means that this Pane will
     * not handle the enabling/disabling of this widget.  The owning object will handle
     * it with its own PaneEnabler or ControlEnabler.
     *
     * @param container The parent container
     * @param style The style to apply to the table
     * @param helpId The topic help ID to be registered for the new table or
     * <code>null</code> if no help ID is required
     * @return The newly created <code>Table</code>
     *
     * @category Layout
     */
    protected final Table addUnmanagedTable(Composite container, int style, String helpId) {

        Table table = this.getWidgetFactory().createTable(container, style);
        table.setHeaderVisible(true);
        table.setLinesVisible(true);

        GridData gridData = new GridData(GridData.FILL_BOTH);
        gridData.heightHint = table.getItemHeight() * 4;
        table.setLayoutData(gridData);

        if (helpId != null) {
            this.setHelp(table, helpId);
        }

        return table;
    }

    /**
     * Creates a new table.
     *
     * @param container The parent container
     * @param helpId The topic help ID to be registered for the new table or
     * <code>null</code> if no help ID is required
     * @return The newly created <code>Table</code>
     *
     * @category Layout
     */
    protected final Table addTable(Composite container, String helpId) {

        return this.addTable(container, SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION | SWT.MULTI, helpId);
    }

    /**
     * Creates a new unmanaged table.  Unmanaged means that this Pane will
     * not handle the enabling/disabling of this widget.  The owning object will handle
     * it with its own PaneEnabler or ControlEnabler.
     *
     * @param container The parent container
     * @param helpId The topic help ID to be registered for the new table or
     * <code>null</code> if no help ID is required
     * @return The newly created <code>Table</code>
     *
     * @category Layout
     */
    protected final Table addUnmanagedTable(Composite container, String helpId) {

        return this.addUnmanagedTable(container, SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION | SWT.MULTI,
                helpId);
    }

    /**
     * Creates a new managed <code>Text</code> widget.
     *
     * @param container The parent container
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    protected final Text addText(Composite container) {
        Text text = this.addUnmanagedText(container);
        this.controlEnabledState(text);
        return text;
    }

    /**
     * Creates a new unmanaged <code>Text</code> widget.  Unmanaged means
     * that this Pane will not handle the enabling/disabling of this widget.
     * The owning object will handle it with its own PaneEnabler or ControlEnabler.
     *
     * @param container The parent container
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    private Text addUnmanagedText(Composite container) {
        Text text = this.getWidgetFactory().createText(container);
        text.setLayoutData(getFieldGridData());
        return text;
    }

    /**
     * Creates a new <code>Text</code> widget.
     *
     * @param container The parent container
     * @param helpId The topic help ID to be registered for the new text
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    protected final Text addText(Composite container, String helpId) {

        Text text = this.addText(container);

        if (helpId != null) {
            this.setHelp(text, helpId);
        }

        return text;
    }

    /**
     * Creates a new unmanaged <code>Text</code> widget.  Unmanaged means
     * that this Pane will not handle the enabling/disabling of this widget.
     * The owning object will handle it with its own PaneEnabler or ControlEnabler.
     *
     * @param container The parent container
     * @param helpId The topic help ID to be registered for the new text
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    private Text addUnmanagedText(Composite container, String helpId) {

        Text text = this.addUnmanagedText(container);

        if (helpId != null) {
            this.setHelp(text, helpId);
        }

        return text;
    }

    /**
     * Creates a new <code>Text</code> widget.
     *
     * @param container The parent container
     * @param textHolder The holder of the text field's input
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    protected final Text addText(Composite container, ModifiablePropertyValueModel<String> textHolder) {

        return this.addText(container, textHolder, null);
    }

    /**
     * Creates a new <code>Text</code> widget.
     *
     * @param container The parent container
     * @param textModel The holder of the text field's input
     * @param helpId The topic help ID to be registered for the new text
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    protected final Text addText(Composite container, ModifiablePropertyValueModel<String> textModel,
            String helpId) {

        Text text = this.addText(container, helpId);
        SWTBindTools.bind(textModel, text);

        return text;
    }

    protected final Text addText(Composite container, ModifiablePropertyValueModel<String> textHolder,
            String helpId, PropertyValueModel<Boolean> enabledModel) {
        Text text = this.addUnmanagedText(container, textHolder, helpId);
        this.controlEnabledState(enabledModel, text);
        return text;
    }

    /**
     * Creates a new unmanaged <code>Text</code> widget.  Unmanaged means
     * that this Pane will not handle the enabling/disabling of this widget.
     * The owning object will handle it with its own PaneEnabler or ControlEnabler.
     *
     * @param container The parent container
     * @param textModel The holder of the text field's input
     * @param helpId The topic help ID to be registered for the new text
     * @return The newly created <code>Text</code> widget
     *
     * @category Layout
     */
    private Text addUnmanagedText(Composite container, ModifiablePropertyValueModel<String> textModel,
            String helpId) {

        Text text = this.addUnmanagedText(container, helpId);
        SWTBindTools.bind(textModel, text);

        return text;
    }

    /**
     * Creates a new container with a titled border.
     *
     * @param title The text of the titled border
     * @param container The parent container
     * @return The newly created <code>Composite</code> with a titled border
     *
     * @category Layout
     */
    protected final Group addTitledGroup(Composite container, String title) {
        return this.addTitledGroup(container, title, null);
    }

    /**
     * Creates a new container with a titled border.
     *
     * @param title The text of the titled border
     * @param container The parent container
     * @param helpId The topic help ID to be registered for the new group
     * @return The newly created <code>Composite</code> with a titled border
     *
     * @category Layout
     */
    protected final Group addTitledGroup(Composite container, String title, String helpId) {

        return addTitledGroup(container, title, 1, helpId);
    }

    /**
     * Creates a new container with a titled border.
     *
     * @param title The text of the titled border
     * @param container The parent container
     * @param helpId The topic help ID to be registered for the new group
     * @return The newly created <code>Composite</code> with a titled border
     *
     * @category Layout
     */
    protected final Group addTitledGroup(Composite container, String title, int columnCount, String helpId) {

        Group group = this.getWidgetFactory().createGroup(container, title);
        //manageWidget(group); TODO unsure if I want to manage groups,
        //also should probably rename this addUnmanagedTitledPane
        group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        GridLayout layout = new GridLayout(columnCount, false);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.marginTop = 5;
        layout.marginLeft = 5;
        layout.marginBottom = 5;
        layout.marginRight = 5;
        group.setLayout(layout);

        if (helpId != null) {
            this.setHelp(group, helpId);
        }

        return group;
    }

    /**
     * Creates a new unmanaged new toggle button (radio button or check box).
     * Unmanaged means  that this Pane will not handle the enabling/disabling
     * of this widget. The owning object will handle it with its own PaneEnabler
     * or ControlEnabler.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param booleanModel The holder of the selection state
     * @param helpId The topic help ID to be registered for the new button
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    private Button addUnmanagedToggleButton(Composite parent, String buttonText,
            ModifiablePropertyValueModel<Boolean> booleanModel, String helpId, int toggleButtonType) {

        Button button;

        if (toggleButtonType == SWT.PUSH) {
            button = this.getWidgetFactory().createPushButton(parent, buttonText);
        } else if (toggleButtonType == SWT.RADIO) {
            button = this.getWidgetFactory().createRadioButton(parent, buttonText);
        } else if (toggleButtonType == SWT.CHECK) {
            button = this.getWidgetFactory().createCheckBox(parent, buttonText);
        } else {
            button = this.getWidgetFactory().createButton(parent, buttonText);
        }

        button.setLayoutData(new GridData());
        SWTBindTools.bind(booleanModel, button);

        if (helpId != null) {
            this.setHelp(button, helpId);
        }

        return button;
    }

    /**
     * Creates a new toggle button (radio button or check box) using the given
     * information.
     *
     * @param parent The parent container
     * @param buttonText The button's text
     * @param booleanHolder The holder of the selection state
     * @param helpId The topic help ID to be registered for the new button
     * @return The newly created <code>Button</code>
     *
     * @category Layout
     */
    private Button addToggleButton(Composite parent, String buttonText,
            ModifiablePropertyValueModel<Boolean> booleanHolder, String helpId, int toggleButtonType) {

        Button button = addUnmanagedToggleButton(parent, buttonText, booleanHolder, helpId, toggleButtonType);
        this.controlEnabledState(button);
        return button;
    }

    /**
     * Creates a new check box that can have 3 selection states (selected,
     * unselected and partially selected.
     *
     * @param parent The parent container
     * @param text The button's text
     * @param booleanHolder The holder of the boolean value where <code>null</code>
     * means partially selected
     * @param helpId The topic help ID to be registered for the new check box
     * @return The newly created <code>TriStateCheckBox</code>
     *
     * @category Layout
     */
    protected final TriStateCheckBox addTriStateCheckBox(Composite parent, String text,
            ModifiablePropertyValueModel<Boolean> booleanHolder, String helpId) {

        TriStateCheckBox checkBox = this.addUnmanagedTriStateCheckBox(parent, text, booleanHolder, helpId);

        this.controlEnabledState(checkBox.getCheckBox());

        return checkBox;
    }

    protected final TriStateCheckBox addUnmanagedTriStateCheckBox(Composite parent, String text,
            ModifiablePropertyValueModel<Boolean> booleanHolder, String helpId) {

        TriStateCheckBox checkBox = new TriStateCheckBox(parent, text, this.getWidgetFactory());

        TriStateCheckBoxModelAdapter.adapt(booleanHolder, checkBox);

        if (helpId != null) {
            this.setHelp(checkBox.getCheckBox(), helpId);
        }

        return checkBox;
    }

    /**
     * Creates a new check box that can have 3 selection states (selected,
     * unselected and partially selected.
     *
     * @param parent The parent container
     * @param text The button's text
     * @param booleanHolder The holder of the boolean value where <code>null</code>
     * means partially selected
     * @param textModel The holder of the string to put in parenthesis after
     * the check box's text when it is partially selected
     * @param helpId The topic help ID to be registered for the new check box
     * @return The newly created <code>TriStateCheckBox</code>
     *
     * @category Layout
     */
    protected final TriStateCheckBox addTriStateCheckBoxWithDefault(Composite parent, String text,
            ModifiablePropertyValueModel<Boolean> booleanHolder, PropertyValueModel<String> textModel,
            String helpId) {

        TriStateCheckBox checkBox = this.addTriStateCheckBox(parent, text, booleanHolder, helpId);

        SWTBindTools.bindTextLabel(textModel, checkBox.getCheckBox());

        return checkBox;
    }

    protected final TriStateCheckBox addTriStateCheckBoxWithDefault(Composite parent, String text,
            ModifiablePropertyValueModel<Boolean> booleanHolder, PropertyValueModel<String> textModel,
            PropertyValueModel<Boolean> enabledModel, String helpId) {

        TriStateCheckBox checkBox = this.addUnmanagedTriStateCheckBox(parent, text, booleanHolder, helpId);

        this.controlEnabledState(enabledModel, checkBox.getCheckBox());

        SWTBindTools.bindTextLabel(textModel, checkBox.getCheckBox());

        return checkBox;
    }

    /**
     * Requests this pane to populate its widgets with the subject's values.
     *
     * @category Populate
     */
    protected void doPopulate() {
        JptCommonUiPlugin.instance().trace(TRACE_OPTION, "doPopulate");
    }

    // ********** enabled models **********

    protected boolean isEnabled() {
        return this.enabledModel.getValue().booleanValue();
    }

    /**
     * Control the <em>enabled</em> state of the specified controls with the
     * pane's {@link #enabledModel}.
     * <p>
     * Use {@link #controlEnabledState(PropertyValueModel, Control...)} if the
     * controls might be disabled when the pane is enabled.
     */
    protected void controlEnabledState(Control... controls) {
        SWTBindTools.controlEnabledState(this.enabledModel, controls);
    }

    /**
     * Use the specified boolean model to determine the <em>enabled</em>
     * state of the specified controls (i.e. when the <em>pane</em> is enabled).
     * If the specified boolean model returns <code>null</code> (which is
     * typical of aspect adapters), the controls will be disabled.
     * <p>
     * Use {@link #controlEnabledState(Control...)} if the
     * controls are only enabled when the pane is enabled.
     */
    protected void controlEnabledState(PropertyValueModel<Boolean> controlsEnabledModel, Control... controls) {
        SWTBindTools.controlEnabledState(this.andEnabledModel(controlsEnabledModel), controls);
    }

    /**
     * AND the specified boolean model with the pane's {@link #enabledModel},
     * resulting in an <em>enabled</em> model that can only be <code>true</code>
     * when the pane as a whole is enabled.
     */
    private PropertyValueModel<Boolean> andEnabledModel(PropertyValueModel<Boolean> booleanModel) {
        return andEnabledModel(this, booleanModel);
    }

    // ********** subject listeners **********

    /**
     * Engage the specified subject
     */
    private void engageListeners(T subject) {
        if (subject != null) {
            this.engageListeners_(subject);
        }
    }

    /**
     * Pre-condition: the specified subject is not <code>null</code>
     */
    protected void engageListeners_(T subject) {
        JptCommonUiPlugin.instance().trace(TRACE_OPTION, "engageListeners_({0})", subject);

        for (String propertyName : this.getPropertyNames()) {
            subject.addPropertyChangeListener(propertyName, this.aspectChangeListener);
        }
    }

    /**
     * Disengage the specified subject
     */
    private void disengageListeners(T subject) {
        if (subject != null) {
            this.disengageListeners_(subject);
        }
    }

    /**
     * Pre-condition: the specified subject is not <code>null</code>
     */
    protected void disengageListeners_(T subject) {
        JptCommonUiPlugin.instance().trace(TRACE_OPTION, "disengageListeners_({0})", subject);

        for (String propertyName : this.getPropertyNames()) {
            subject.removePropertyChangeListener(propertyName, this.aspectChangeListener);
        }
    }

    /**
     * Returns the main <code>Composite</code> of this pane.
     *
     * @return The main container
     *
     * @category Layout
     */
    public Control getControl() {
        if (!addsComposite()) {
            throw new IllegalStateException("Must override getControl() if addsComposite() returns false");
        }
        return this.container;
    }

    /**
     * Returns the subject holder used by this pane.
     *
     * @return The holder of the subject
     *
     * @category Populate
     */
    protected final PropertyValueModel<? extends T> getSubjectHolder() {
        return this.subjectModel;
    }

    /**
     * If the pane is a <em>root</em> pane, return its widget factory;
     * otherwise return the pane's parent's widget factory.
     */
    protected final WidgetFactory getWidgetFactory() {
        return (this.parent == null) ? this.widgetFactory : this.parent.getWidgetFactory();
    }

    /**
     * If the pane is a <em>root</em> pane, return its resource manager;
     * otherwise return the pane's parent's resource manager.
     */
    public final ResourceManager getResourceManager() {
        return (this.parent == null) ? this.resourceManager : this.parent.getResourceManager();
    }

    protected final void setHelp(Control control, String contextID) {
        WorkbenchTools.setHelp(control, contextID);
    }

    protected final boolean isPopulating() {
        return this.populating;
    }

    /**
     * Notifies this pane to populate itself using the subject's information.
     *
     * @category Populate
     */
    private void populate() {
        if (!this.getControl().isDisposed()) {
            JptCommonUiPlugin.instance().trace(TRACE_OPTION, "populate");
            this.repopulate();
        }
    }

    /**
     * The subject's specified property has changed.
     */
    protected void propertyChanged(@SuppressWarnings("unused") String propertyName) {
        // NOP
    }

    /**
     * Return the names of the subject's properties we listen to here and notify
     * via calls to {@link #propertyChanged(String)}.
     */
    private Collection<String> getPropertyNames() {
        ArrayList<String> propertyNames = new ArrayList<String>();
        this.addPropertyNames(propertyNames);
        return propertyNames;
    }

    /**
     * This method is called (perhaps internally) when this needs to repopulate
     * but the object of interest has not changed.
     */
    protected final void repopulate() {
        JptCommonUiPlugin.instance().trace(TRACE_OPTION, "repopulate");

        try {
            this.setPopulating(true);
            this.doPopulate();
        } finally {
            this.setPopulating(false);
        }
    }

    /**
     * Sets the internal flag that is used to determine whether the pane is being
     * populated or not. During population, it is required to not update the
     * widgets when the model is updated nor to update the model when the widgets
     * are being synchronized with the model's values.
     */
    protected final void setPopulating(boolean populating) {
        this.populating = populating;
    }

    /**
     * Either show or hides this pane.
     */
    public void setVisible(boolean visible) {
        if (this.container != null && !this.container.isDisposed()) {
            this.container.setVisible(visible);
        }
    }

    /**
     * @see Control#getShell()
     */
    public final Shell getShell() {
        return this.getControl().getShell();
    }

    /**
     * Return the pane's subject.
     */
    public T getSubject() {
        return this.subjectModel.getValue();
    }

    /**
     * The pane's subject has changed. Disconnect any listeners from the old
     * subject and connect those listeners to the new subject.
     */
    /* CU private */ final void subjectChanged(T oldSubject, T newSubject) {
        if (!this.getControl().isDisposed()) {
            JptCommonUiPlugin.instance().trace(TRACE_OPTION, "subjectChanged({0}, {1})", oldSubject, newSubject);
            this.disengageListeners(oldSubject);
            this.repopulate();
            this.engageListeners(newSubject);
        }
    }

    private void updatePane(String propertyName) {
        if (!isPopulating() && !this.getControl().isDisposed()) {
            this.populating = true;

            try {
                propertyChanged(propertyName);
            } finally {
                this.populating = false;
            }
        }
    }

    protected void controlDisposed() {
        // the control is not yet "disposed" when we receive this event
        // so we can still remove our listeners
        JptCommonUiPlugin.instance().trace(TRACE_OPTION, "control disposed");

        this.disengageListeners(getSubject());

        this.subjectModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener);

        this.enabledModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.enabledModelListener);
        this.getControl().removeDisposeListener(this.controlDisposeListener);
        if (this.parent == null) {
            this.resourceManager.dispose();
        }
    }

    private static final String TRACE_OPTION = Pane.class.getSimpleName();
}