blob: 0ade7fc0ba16322e235c57354b81821a04ff53ba [file] [log] [blame]
/*******************************************************************************
* 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 2.0, which accompanies this distribution
* and is available at https://www.eclipse.org/legal/epl-2.0/.
*
* 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.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.bindings.SWTBindingTools;
import org.eclipse.jpt.common.ui.internal.swt.events.DisposeAdapter;
import org.eclipse.jpt.common.ui.internal.swt.listeners.SWTListenerTools;
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 SWTListenerTools.wrap(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 SWTListenerTools.wrap(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(label);
return label;
}
protected final Label addLabel(
Composite container,
String labelText,
PropertyValueModel<Boolean> enabledModel
) {
Label label = this.addUnmanagedLabel(container, labelText);
this.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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.bindEnabledState(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);
SWTBindingTools.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);
SWTBindingTools.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.bindEnabledState(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);
bindEnabledState(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.bindEnabledState(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.bindEnabledState(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);
SWTBindingTools.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.bindEnabledState(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);
SWTBindingTools.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());
SWTBindingTools.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.bindEnabledState(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.bindEnabledState(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
);
SWTBindingTools.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.bindEnabledState(enabledModel, checkBox.getCheckBox());
SWTBindingTools.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 #bindEnabledState(PropertyValueModel, Control...)} if the
* controls might be disabled when the pane is enabled.
*/
protected void bindEnabledState(Control... controls) {
SWTBindingTools.bindEnabledState(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 #bindEnabledState(Control...)} if the
* controls are only enabled when the pane is enabled.
*/
protected void bindEnabledState(PropertyValueModel<Boolean> controlsEnabledModel, Control... controls) {
SWTBindingTools.bindEnabledState(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();
}