blob: 643371e6d8d5d957bc91c297144debb2c84fca06 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2012 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.core.runtime.Assert;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jpt.common.ui.WidgetFactory;
import org.eclipse.jpt.common.ui.internal.Tracing;
import org.eclipse.jpt.common.ui.internal.listeners.SWTPropertyChangeListenerWrapper;
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.util.ControlAligner;
import org.eclipse.jpt.common.ui.internal.util.LabeledButton;
import org.eclipse.jpt.common.ui.internal.util.LabeledControlUpdater;
import org.eclipse.jpt.common.ui.internal.util.SWTUtil;
import org.eclipse.jpt.common.ui.internal.utility.swt.SWTTools;
import org.eclipse.jpt.common.utility.internal.NonNullBooleanTransformer;
import org.eclipse.jpt.common.utility.internal.StringConverter;
import org.eclipse.jpt.common.utility.internal.model.value.CompositeBooleanPropertyValueModel;
import org.eclipse.jpt.common.utility.internal.model.value.SimplePropertyValueModel;
import org.eclipse.jpt.common.utility.internal.model.value.TransformationPropertyValueModel;
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.PropertyChangeListener;
import org.eclipse.jpt.common.utility.model.value.ListValueModel;
import org.eclipse.jpt.common.utility.model.value.PropertyValueModel;
import org.eclipse.jpt.common.utility.model.value.ModifiablePropertyValueModel;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyListener;
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.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
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.PlatformUI;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.help.IWorkbenchHelpSystem;
import org.eclipse.ui.part.PageBook;
/**
* The abstract definition of a pane which holds onto a <code>PropertyValueModel</code>
* that contains the subject of this pane.
* <p>
* It also contains convenience methods for building buttons, labels, check
* boxes, and radio buttons, etc.
* <p>
* It is possible to easily listen to any property changes coming from the
* subject, {@link #addPropertyNames(Collection)} is specify which properties
* are of interest and {@link #propertyChanged(String)} is used to notify the
* pane when the property has changed.
*
* @see FormPane
* @see DialogPane
*
* @version 2.0
* @since 2.0
*/
@SuppressWarnings("nls")
public abstract class Pane<T extends Model>
{
/**
* The listener registered with the subject in order to be notified when a
* property has changed, the property names are determined by
* {@link #propertyNames()}.
*/
private PropertyChangeListener aspectChangeListener;
/**
* The container of this composite.
*/
private final Composite container;
/**
* The aligner responsible to align the left controls.
*/
private ControlAligner leftControlAligner;
/**
* Flag used to stop the circular population of widgets.
*/
private boolean populating;
/**
* The aligner responsible to align the left controls.
*/
private ControlAligner rightControlAligner;
/**
* This listener is registered with the subject holder in order to
* automatically repopulate this pane with the new subject.
*/
private PropertyChangeListener subjectChangeListener;
/**
* The subject of this pane.
*/
private PropertyValueModel<T> subjectHolder;
/**
* The collection of registered sub-panes will be automatically notified
* when listeners need to be engaged or disengaged or when to populate its
* widgets.
*/
private Collection<Pane<?>> subPanes;
/**
* The factory used to create various common widgets.
*/
private WidgetFactory widgetFactory;
/**
* The collection of <code>Control</code>s that are displayed in this pane,
* which will have their enablement state updated when
* {@link #enableWidgets(boolean)} is called.
*/
private ArrayList<Control> managedWidgets;
/**
* The collection of <code>Pane</code>s that are displayed in this pane,
* which will have their enablement state updated when
* {@link #enableWidgets(boolean)} is called.
*/
private ArrayList<Pane<?>> managedSubPanes;
/**
* This enabled model is used to store the pane's base enablement state.
* If API is called to set the pane enabled, this model gets updated. If the pane is thereby
* fully enabled (controller enabled model is also in agreement) the pane's widgets are set
* enabled.
* @see #getCombinedEnabledModel()
*/
private final ModifiablePropertyValueModel<Boolean> baseEnabledModel
= new SimplePropertyValueModel<Boolean>(Boolean.TRUE);
/**
* This enabled model is used to define the pane's enablement as controlled by other widgets,
* tests, etc. (for example a radio button)
* If this model is changed, and the pane is thereby fully enabled (base enabled model is also
* in agreement) the pane's widgets are set enabled.
* @see #getCombinedEnabledModel()
*/
private PropertyValueModel<Boolean> controllerEnabledModel;
/**
* The "and" combination of {@link #baseEnabledModel} and {@link #controllerEnabledModel}
*/
private PropertyValueModel<Boolean> combinedEnabledModel;
private PropertyChangeListener combinedEnabledModelListener;
/**
* Creates a new <code>Pane</code>.
*
* @param parentPane The parent pane of this one
* @param parent The parent container
*
* @category Constructor
*/
protected Pane(
Pane<? extends T> parentPane,
Composite parent) {
this(parentPane, parent, true);
}
/**
* Creates a new <code>Pane</code>.
*
* @param parentPane The parent container of this one
* @param parent The parent container
* @param widgetFactory The factory used to create various widgets
* @param automaticallyAlignWidgets <code>true</code> to make the widgets
* this pane aligned with the widgets of the given parent pane;
* <code>false</code> to not align them
*
* @category Constructor
*/
protected Pane(
Pane<? extends T> parentPane,
Composite parent,
boolean automaticallyAlignWidgets) {
this(
parentPane,
parentPane.getSubjectHolder(),
parent,
automaticallyAlignWidgets);
}
/**
* Creates a new <code>Pane</code>.
*
* @param parentPane The parent container of this one
* @param parent The parent container
* @param widgetFactory The factory used to create various widgets
* @param automaticallyAlignWidgets <code>true</code> to make the widgets
* this pane aligned with the widgets of the given parent pane;
* <code>false</code> to not align them
*
* @category Constructor
*/
protected Pane(
Pane<? extends T> parentPane,
Composite parent,
boolean automaticallyAlignWidgets,
boolean parentManagePane) {
this(
parentPane,
parentPane.getSubjectHolder(),
parent,
automaticallyAlignWidgets,
parentManagePane);
}
/**
* Creates a new <code>Pane</code>.
*
* @param parentPane The parent container of this one
* @param subjectHolder The holder of this pane's subject
* @param parent The parent container
*
* @category Constructor
*/
protected Pane(
Pane<?> parentPane,
PropertyValueModel<? extends T> subjectHolder,
Composite parent) {
this(parentPane, subjectHolder, parent, true);
}
protected Pane(
Pane<?> parentPane,
PropertyValueModel<? extends T> subjectHolder,
Composite parent,
PropertyValueModel<Boolean> enabledModel) {
this(parentPane, subjectHolder, parent, true, enabledModel);
}
/**
* Creates a new <code>Pane</code>.
*
* @param parentPane The parent container of this one
* @param subjectHolder The holder of this pane's subject
* @param parent The parent container
* @param widgetFactory The factory used to create various widgets
* @param automaticallyAlignWidgets <code>true</code> to make the widgets
* this pane aligned with the widgets of the given parent pane;
* <code>false</code> to not align them
*
* @category Constructor
*/
protected Pane(
Pane<?> parentPane,
PropertyValueModel<? extends T> subjectHolder,
Composite parent,
boolean automaticallyAlignWidgets) {
this(parentPane, subjectHolder, parent, automaticallyAlignWidgets, true);
}
protected Pane(
Pane<?> parentPane,
PropertyValueModel<? extends T> subjectHolder,
Composite parent,
boolean automaticallyAlignWidgets,
PropertyValueModel<Boolean> enabledModel) {
this(parentPane, subjectHolder, parent, automaticallyAlignWidgets, true, enabledModel);
}
/**
* Creates a new <code>Pane</code>.
*
* @param parentPane The parent container of this one
* @param subjectHolder The holder of this pane's subject
* @param parent The parent container
* @param widgetFactory The factory used to create various widgets
* @param automaticallyAlignWidgets <code>true</code> to make the widgets
* this pane aligned with the widgets of the given parent pane;
* <code>false</code> to not align them
* @param parentManagePane <code>true</code> to have the parent pane manage
* the enabled state of this pane
*
* @category Constructor
*/
protected Pane(
Pane<?> parentPane,
PropertyValueModel<? extends T> subjectHolder,
Composite parent,
boolean automaticallyAlignWidgets,
boolean parentManagePane) {
this(subjectHolder, parent, parentPane.getWidgetFactory());
this.initialize(parentPane, automaticallyAlignWidgets, parentManagePane);
}
protected Pane(
Pane<?> parentPane,
PropertyValueModel<? extends T> subjectHolder,
Composite parent,
boolean automaticallyAlignWidgets,
boolean parentManagePane,
PropertyValueModel<Boolean> enabledModel) {
this(subjectHolder, parent, parentPane.getWidgetFactory());
this.initialize(parentPane, automaticallyAlignWidgets, parentManagePane);
this.initializeEnabledModel(enabledModel);
}
/**
* Creates a new <code>Pane</code>.
*
* @param subjectHolder The holder of this pane's subject
* @param parent The parent container
* @param widgetFactory The factory used to create various common widgets
*
* @category Constructor
*/
protected Pane(
PropertyValueModel<? extends T> subjectHolder,
Composite parent,
WidgetFactory widgetFactory) {
super();
this.initialize(subjectHolder, widgetFactory);
this.container = this.addContainer(parent);
this.initializeLayout(this.container);
this.engageSubjectHolder();
this.engageListeners(getSubject());
this.populate();
}
// ********** initialization **********
@SuppressWarnings("unchecked")
private void initialize(
PropertyValueModel<? extends T> subjectHolder,
WidgetFactory widgetFactory) {
Assert.isNotNull(subjectHolder, "The subject holder cannot be null");
this.subjectHolder = (PropertyValueModel<T>) subjectHolder;
this.widgetFactory = widgetFactory;
this.subPanes = new ArrayList<Pane<?>>();
this.managedWidgets = new ArrayList<Control>();
this.managedSubPanes = new ArrayList<Pane<?>>();
this.leftControlAligner = new ControlAligner();
this.rightControlAligner = new ControlAligner();
this.subjectChangeListener = this.buildSubjectChangeListener();
this.aspectChangeListener = this.buildAspectChangeListener();
this.initialize();
}
protected void initialize() {
// do nothing by default
}
/**
* Registers this pane with the parent pane.
*
* @param parentPane The parent pane
* @param automaticallyAlignWidgets <code>true</code> to make the widgets
* this pane aligned with the widgets of the given parent pane;
* <code>false</code> to not align them
* @param parentManagePane <code>true</code> to have the parent pane manage
* the enabled state of this pane
*
* @category Initialization
*/
private void initialize(
Pane<?> parentPane,
boolean automaticallyAlignWidgets,
boolean parentManagePane) {
// Register this pane with the parent pane, it will call the methods
// automatically (engageListeners(), disengageListeners(), populate(),
// dispose(), etc)
parentPane.registerSubPane(this);
if (parentManagePane) {
parentPane.manageSubPane(this);
}
// Align the left and right controls with the controls from the parent
// pane
if (automaticallyAlignWidgets) {
parentPane.addAlignLeft(this);
parentPane.addAlignRight(this);
}
}
private void initializeEnabledModel(PropertyValueModel<Boolean> enabledModel) {
this.controllerEnabledModel = enabledModel;
this.combinedEnabledModel =
CompositeBooleanPropertyValueModel.and(this.baseEnabledModel, this.controllerEnabledModel);
this.combinedEnabledModelListener = buildCombinedEnabledModelListener();
this.combinedEnabledModel.addPropertyChangeListener(
PropertyValueModel.VALUE, this.combinedEnabledModelListener);
enableWidgets_(getCombinedEnablement());
}
private PropertyChangeListener buildCombinedEnabledModelListener() {
return new SWTPropertyChangeListenerWrapper(buildControllerEnabledModelListener_());
}
private PropertyChangeListener buildControllerEnabledModelListener_() {
return new PropertyChangeListener() {
@SuppressWarnings("unchecked")
public void propertyChanged(PropertyChangeEvent e) {
Pane.this.controllerEnablementChanged();
}
};
}
/**
* Initializes the layout of this pane.
*
* @param container The parent container
*
* @category Layout
*/
protected abstract void initializeLayout(Composite container);
private void manageWidget(Control control) {
if (this.managedWidgets.contains(control)) {
throw new IllegalStateException();
}
this.managedWidgets.add(control);
}
private void manageSubPane(Pane<?> subPane) {
if (this.managedSubPanes.contains(subPane)) {
throw new IllegalStateException();
}
this.managedSubPanes.add(subPane);
}
/**
* Adds the given pane's widgets (those that were registered with its
* left <code>ControlAligner</code>) to this pane's left
* <code>ControlAligner</code> so that their width can be adjusted to have
* the width of the widest widget.
*
* @param pane The pane containing the widgets to add
*
* @category Layout
*/
protected final void addAlignLeft(Pane<?> container) {
this.leftControlAligner.add(container.leftControlAligner);
}
/**
* Adds the given control to the collection of widgets that have their width
* adjust with the width of the widest widget. The left alignment is usually
* used for labels.
*
* @param pane The pane to add
*
* @category Layout
*/
protected final void addAlignLeft(Control control) {
this.leftControlAligner.add(control);
}
/**
* Adds the given pane's widgets (those that were registered with its
* right <code>ControlAligner</code>) to this pane's right
* <code>ControlAligner</code> so that their width can be adjusted to have
* the width of the widest widget.
*
* @param pane The pane containing the widgets to add
*
* @category Layout
*/
protected final void addAlignRight(Pane<?> container) {
this.rightControlAligner.add(container.rightControlAligner);
}
/**
* Adds the given control to the collection of widgets that have their width
* adjust with the width of the widest widget. The left alignment is usually
* used for buttons.
*
* @param pane The pane to add
*
* @category Layout
*/
protected final void addAlignRight(Control control) {
this.rightControlAligner.add(control);
}
/**
* Adds the given pane's controls (those that were registered for
* alignment) from this pane.
*
* @param pane The pane containing the widgets to add for
* alignment
*
* @category Layout
*/
protected final void addPaneForAlignment(Pane<?> container) {
addAlignLeft(container);
addAlignRight(container);
}
/**
* 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);
}
/**
* 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.manageWidget(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.widgetFactory.createButton(container, text);
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
buttonAction.run();
}
});
if (helpId != null) {
getHelpSystem().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 <code>Section</code> that can be collapsed. 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
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
protected final Composite addCollapsibleSection(
Composite container,
String sectionText) {
return this.addCollapsibleSection(
container,
sectionText,
new SimplePropertyValueModel<Boolean>(Boolean.FALSE));
}
/**
* Creates a new <code>Section</code> that can be collapsed. 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
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
protected final Composite addCollapsibleSection(Composite container,
String sectionText,
String description) {
return this.addCollapsibleSection(
container,
sectionText,
description,
new SimplePropertyValueModel<Boolean>(Boolean.FALSE)
);
}
/**
* 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 type The type of section to create
* @param expandedStateHolder The holder of the boolean that will dictate
* when to expand or collapse the section
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
private Composite addCollapsibleSection(Composite container,
String sectionText,
int type,
PropertyValueModel<Boolean> expandedStateHolder) {
return addCollapsibleSection(container, sectionText, null, type, expandedStateHolder);
}
/**
* 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 provided
* @param type The type of section to create
* @param expandedStateHolder The holder of the boolean that will dictate
* when to expand or collapse the section
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
private Composite addCollapsibleSection(Composite container,
String sectionText,
String description,
int type,
PropertyValueModel<Boolean> expandedStateHolder) {
Composite subPane = this.addSection(
container,
sectionText,
description,
ExpandableComposite.TWISTIE | type
);
Section section = (Section) subPane.getParent();
expandedStateHolder.addPropertyChangeListener(
PropertyValueModel.VALUE,
buildExpandedStateChangeListener(section)
);
section.setExpanded(
expandedStateHolder.getValue() != null ? expandedStateHolder.getValue() : true
);
return subPane;
}
/**
* 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 expandedStateHolder The holder of the boolean that will dictate
* when to expand or collapse the section
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
protected final Composite addCollapsibleSection(Composite container,
String sectionText,
PropertyValueModel<Boolean> expandedStateHolder) {
return this.addCollapsibleSection(
container,
sectionText,
ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE,
expandedStateHolder
);
}
/**
* 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
* @param expandedStateHolder The holder of the boolean that will dictate
* when to expand or collapse the section
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
protected final Composite addCollapsibleSection(Composite container,
String sectionText,
String description,
PropertyValueModel<Boolean> expandedStateHolder) {
return this.addCollapsibleSection(
container,
sectionText,
description,
ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE,
expandedStateHolder
);
}
/**
* Creates a new <code>Section</code>. A sub-pane is automatically added as
* its client which can be typed cast directly as a <code>Composite</code>.
*
* @param container The container of the new widget
* @param sectionText The text of the new section
* @param expandedStateHolder The holder of the boolean that will dictate
* when to expand or collapse the section
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
protected final Composite addCollapsibleSubSection(Composite container,
String sectionText,
PropertyValueModel<Boolean> expandedStateHolder) {
return this.addCollapsibleSection(
container,
sectionText,
SWT.NULL,
expandedStateHolder
);
}
/**
* 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.manageWidget(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.widgetFactory.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,
StringConverter<V> stringConverter) {
Combo combo = this.addCombo(container);
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,
StringConverter<V> 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,
StringConverter<V> 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;
}
/**
* 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 addContainer(Composite parent) {
return this.addSubPane(parent);
}
protected final <V> Combo addEditableCombo(
Composite container,
ListValueModel<V> listHolder,
ModifiablePropertyValueModel<V> selectedItemHolder,
StringConverter<V> stringConverter,
PropertyValueModel<Boolean> enabledModel) {
Combo combo = this.addUnmanagedEditableCombo(container, listHolder, selectedItemHolder, stringConverter);
this.controlEnabledState(enabledModel, combo);
return combo;
}
protected final Combo addEditableCombo(
Composite container) {
Combo combo = this.widgetFactory.createEditableCombo(container);
combo.setLayoutData(getFieldGridData());
this.manageWidget(combo);
return combo;
}
/**
* 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,
StringConverter<V> stringConverter) {
Combo combo = this.addEditableCombo(container);
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;
}
private PropertyChangeListener buildExpandedStateChangeListener(final Section section) {
return new SWTPropertyChangeListenerWrapper(buildExpandedStateChangeListener_(section));
}
private PropertyChangeListener buildExpandedStateChangeListener_(final Section section) {
return new PropertyChangeListener() {
public void propertyChanged(final PropertyChangeEvent e) {
Boolean value = (Boolean) e.getNewValue();
if (value == null) {
value = Boolean.TRUE;
}
section.setExpanded(value);
}
};
}
/**
* 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.widgetFactory.createHyperlink(parent, text);
this.manageWidget(link);
link.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
Hyperlink hyperLink = (Hyperlink) e.widget;
if (hyperLink.isEnabled()) {
hyperLinkAction.run();
}
}
});
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 = addUnmanagedLabel(container, labelText);
manageWidget(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.widgetFactory.createLabel(container, labelText);
}
/**
* Creates a new container that will have a non-editable combo labeled with
* the given text.
*
* @param container The parent container
* @param labelText The text of the label
* @param listHolder The <code>ListValueHolder</code>
* @param selectedItemHolder The holder of the selected item
* @param rightControl The control shown to the right of the main widget
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The container of the label and the given center control
*
* @category Layout
*/
protected final <V> Combo addLabeledCombo(Composite container,
String labelText,
ListValueModel<V> listHolder,
ModifiablePropertyValueModel<V> selectedItemHolder,
StringConverter<V> stringConverter,
Control rightControl,
String helpId) {
Combo combo = this.addCombo(
container,
listHolder,
selectedItemHolder,
stringConverter
);
this.addLabeledComposite(
container,
labelText,
(combo.getParent() != container) ? combo.getParent() : combo,
rightControl,
helpId
);
return combo;
}
/**
* Creates a new container that will have a non-editable combo labeled with
* the given text.
*
* @param container The parent container
* @param labelText The text of the label
* @param listHolder The <code>ListValueHolder</code>
* @param selectedItemHolder The holder of the selected item
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The container of the label and the given center control
*
* @category Layout
*/
protected final <V> Combo addLabeledCombo(Composite container,
String labelText,
ListValueModel<V> listHolder,
ModifiablePropertyValueModel<V> selectedItemHolder,
StringConverter<V> stringConverter,
String helpId) {
return this.addLabeledCombo(
container,
labelText,
listHolder,
selectedItemHolder,
stringConverter,
null,
helpId
);
}
/**
* Creates a new container that will have the given center control labeled
* with the given label.
*
* @param container The parent container
* @param leftControl The widget shown to the left of the main widget
* @param centerControl The main widget
* @param rightControl The control shown to the right of the main widget
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The container of the label and the given center control
*
* @category Layout
*/
protected final Composite addLabeledComposite(Composite container,
Control leftControl,
Control centerControl,
Control rightControl,
String helpId) {
// Container for the label and main composite
container = this.addSubPane(container, 3, 0, 0, 0, 0);
// Left control
GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.BEGINNING;
gridData.grabExcessHorizontalSpace = false;
leftControl.setLayoutData(gridData);
// Re-parent the left control to the new sub pane
leftControl.setParent(container);
this.addAlignLeft(leftControl);
// Re-parent the center control to the new sub pane
centerControl.setParent(container);
// Register the help id for the center control
if (helpId != null) {
getHelpSystem().setHelp(centerControl, helpId);
}
// Right control
if (rightControl == null) {
Composite spacer = this.addPane(container);
spacer.setLayout(this.buildSpacerLayout());
rightControl = spacer;
}
else {
rightControl.setParent(container);
// Register the help id for the right control
if (helpId != null) {
getHelpSystem().setHelp(rightControl, helpId);
}
}
gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL_HORIZONTAL;
gridData.grabExcessHorizontalSpace = false;
rightControl.setLayoutData(gridData);
this.addAlignRight(rightControl);
return container;
}
/**
* Creates a new container that will have the given center control labeled
* with the given label.
*
* @param container The parent container
* @param label The label used to describe the center control
* @param centerControl The main widget
* @param helpId The topic help ID to be registered for the given center
* control
* @return The container of the label and the given center control
*
* @category Layout
*/
protected final Composite addLabeledComposite(Composite container,
Control label,
Control centerControl,
String helpId) {
return this.addLabeledComposite(
container,
label,
centerControl,
null,
helpId
);
}
/**
* Creates a new container that will have the given center composite labeled
* with the given label text.
*
* @param container The parent container
* @param labelText The text to label the main composite
* @param centerPane The main widget
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The container of the label and the given center control
*
* @category Layout
*/
protected final Composite addLabeledComposite(Composite container,
String labelText,
Pane<?> centerPane,
String helpId) {
return this.addLabeledComposite(
container,
labelText,
centerPane.getControl(),
helpId
);
}
/**
* Creates a new container that will have the given center composite labeled
* with the given label text.
*
* @param container The parent container
* @param labelText The text to label the main composite
* @param centerControl The main widget
* @return The container of the label and the given center control
*
* @category Layout
*/
protected final Composite addLabeledComposite(Composite container,
String labelText,
Control centerControl) {
return this.addLabeledComposite(
container,
labelText,
centerControl,
null,
null
);
}
/**
* Creates a new container that will have the given center composite labeled
* with the given label text.
*
* @param container The parent container
* @param labelText The text to label the main composite
* @param centerControl The main widget
* @param rightControl The control shown to the right of the main widget
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The container of the label and the given center control
*
* @category Layout
*/
protected final Composite addLabeledComposite(Composite container,
String labelText,
Control centerControl,
Control rightControl,
String helpId) {
return this.addLabeledComposite(
container,
this.addLabel(container, labelText),
centerControl,
rightControl,
helpId
);
}
/**
* Creates a new container that will have the given center composite labeled
* with the given label text.
*
* @param container The parent container
* @param labelText The text to label the main composite
* @param centerControl The main widget
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The container of the label and the given center control
*
* @category Layout
*/
protected final Composite addLabeledComposite(Composite container,
String labelText,
Control centerControl,
String helpId) {
Label label = this.addLabel(container, labelText);
return this.addLabeledComposite(
container,
label,
centerControl,
helpId
);
}
/**
* Creates a new container that will have the given center control labeled
* with the given label.
*
* @param container The parent container
* @param leftControl The widget shown to the left of the main widget
* @param centerControl The main widget
* @param rightControl The control shown to the right of the main widget
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The newly created <code>CCombo</code>
*
* @category Layout
*/
protected final Combo addLabeledEditableCombo(Composite container,
String labelText,
ModifyListener comboListener,
Control rightControl,
String helpId) {
Combo combo = this.addEditableCombo(container);
combo.addModifyListener(comboListener);
this.addLabeledComposite(
container,
labelText,
(combo.getParent() != container) ? combo.getParent() : combo,
rightControl,
helpId
);
return combo;
}
/**
* Creates a new container that will have an editable combo labeled with the
* given text.
*
* @param container The parent container
* @param labelText The text of the label
* @param comboListener The listener that will be notified when the selection
* changes
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The newly created <code>CCombo</code>
*
* @category Layout
*/
protected final Combo addLabeledEditableCombo(Composite container,
String labelText,
ModifyListener comboListener,
String helpId) {
return this.addLabeledEditableCombo(
container,
labelText,
comboListener,
null,
helpId
);
}
/**
* Creates a new container that will have the given center control labeled
* with the given label.
*
* @param container The parent container
* @param leftControl The widget shown to the left of the main widget
* @param centerControl The main widget
* @param labelProvider The provider responsible to convert the combo's items
* into human readable strings
* @param rightControl The control shown to the right of the main widget
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The newly created <code>CCombo</code>
*
* @category Layout
*/
protected final Combo addLabeledEditableComboViewer(Composite container,
String labelText,
ModifyListener comboListener,
ILabelProvider labelProvider,
Control rightControl,
String helpId) {
ComboViewer comboViewer = this.addEditableComboViewer(
container,
labelProvider
);
Combo combo = comboViewer.getCombo();
combo.addModifyListener(comboListener);
this.addLabeledComposite(
container,
labelText,
(combo.getParent() != container) ? combo.getParent() : combo,
rightControl,
helpId
);
return combo;
}
/**
* Creates a new container that will have an editable combo labeled with the
* given text.
*
* @param container The parent container
* @param labelText The text of the label
* @param comboListener The listener that will be notified when the selection
* changes
* @param labelProvider The provider responsible to convert the combo's items
* into human readable strings
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The newly created <code>CCombo</code>
*
* @category Layout
*/
protected final Combo addLabeledEditableComboViewer(Composite container,
String labelText,
ModifyListener comboListener,
ILabelProvider labelProvider,
String helpId) {
return this.addLabeledEditableComboViewer(
container,
labelText,
comboListener,
labelProvider,
null,
helpId
);
}
/**
* Creates a new container that will have an editable combo labeled with the
* given text.
*
* @param container The parent container
* @param labelText The text of the label
* @param listHolder The <code>ListValueHolder</code>
* @param selectedItemHolder The holder of the selected item
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The newly created <code>CCombo</code>
*
* @category Layout
*/
protected final <V> Combo addLabeledEditableCombo(Composite container,
String labelText,
ListValueModel<V> listHolder,
ModifiablePropertyValueModel<V> selectedItemHolder,
String helpId) {
return this.addLabeledEditableCombo(
container,
labelText,
listHolder,
selectedItemHolder,
StringConverter.Default.<V>instance(),
null,
helpId
);
}
/**
* Creates a new container that will have the given center control labeled
* with the given label.
*
* @param container The parent container
* @param labelText The text of the label
* @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
* @param rightControl The control shown to the right of the main widget
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The newly created <code>Combo</code>
*
* @category Layout
*/
protected final <V> Combo addLabeledEditableCombo(Composite container,
String labelText,
ListValueModel<V> listHolder,
ModifiablePropertyValueModel<V> selectedItemHolder,
StringConverter<V> stringConverter,
Control rightControl,
String helpId) {
Combo combo = this.addEditableCombo(
container,
listHolder,
selectedItemHolder,
stringConverter
);
this.addLabeledComposite(
container,
labelText,
(combo.getParent() != container) ? combo.getParent() : combo,
rightControl,
helpId
);
return combo;
}
/**
* Creates a new container that will have an editable combo labeled with the
* given text.
*
* @param container The parent container
* @param labelText The text of the label
* @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
* @param helpId The topic help ID to be registered for the given center
* composite
* @return The newly created <code>Combo</code>
*
* @category Layout
*/
protected final <V> Combo addLabeledEditableCombo(Composite container,
String labelText,
ListValueModel<V> listHolder,
ModifiablePropertyValueModel<V> selectedItemHolder,
StringConverter<V> stringConverter,
String helpId) {
return this.addLabeledEditableCombo(
container,
labelText,
listHolder,
selectedItemHolder,
stringConverter,
null,
helpId
);
}
/**
* Creates a new container that will have a text field as the center control
* labeled with the given label.
*
* @param container The parent container
* @param labelText The text area's label
* @param textHolder The holder of the text field's input
* @param lineCount The number of lines the text area should display
* @param helpId The topic help ID to be registered for the text field
* @return The newly created <code>Text</code>
*
* @category Layout
*/
protected final Text addLabeledMultiLineText(Composite container,
String labelText,
ModifiablePropertyValueModel<String> textHolder,
int lineCount,
String helpId) {
Text text = this.addMultiLineText(container, textHolder, lineCount);
container = this.addLabeledComposite(
container,
labelText,
text,
helpId
);
int textHeight = text.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
// Specify the number of lines the text area should display
GridData gridData = (GridData) text.getLayoutData();
gridData.heightHint = text.getLineHeight() * lineCount;
// Move the label to the top of its cell
Control label = container.getChildren()[0];
int labelHeight = label.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
gridData = (GridData) label.getLayoutData();
gridData.verticalAlignment = SWT.TOP;
gridData.verticalIndent += (Math.abs(textHeight - labelHeight) / 2);
return text;
}
/**
* Creates a new container that will have a text field as the center control
* labeled with the given label.
*
* @param container The parent container
* @param textHolder The holder of the text field's input
* @return The newly created <code>Text</code>
*
* @category Layout
*/
protected final Text addLabeledPasswordText(Composite container,
String labelText,
ModifiablePropertyValueModel<String> textHolder) {
return this.addLabeledPasswordText(
container,
labelText,
textHolder,
null
);
}
/**
* Creates a new container that will have a text field as the center control
* labeled with the given label.
*
* @param container The parent container
* @param labelText The text field's label
* @param rightComponent The component to be placed to the right of the text
* field
* @param textHolder The holder of the text field's input
* @param helpId The topic help ID to be registered for the text field
* @return The newly created <code>Text</code>
*
* @category Layout
*/
protected final Text addLabeledPasswordText(Composite container,
String labelText,
ModifiablePropertyValueModel<String> textHolder,
Control rightControl,
String helpId) {
Text text = this.addPasswordText(container, textHolder);
this.addLabeledComposite(
container,
labelText,
text,
rightControl,
helpId
);
return text;
}
/**
* Creates a new container that will have a text field as the center control
* labeled with the given label.
*
* @param container The parent container
* @param textHolder The holder of the text field's input
* @param helpId The topic help ID to be registered for the text field
* @return The newly created <code>Text</code>
*
* @category Layout
*/
protected final Text addLabeledPasswordText(Composite container,
String labelText,
ModifiablePropertyValueModel<String> textHolder,
String helpId) {
return this.addLabeledPasswordText(
container,
labelText,
textHolder,
null,
helpId
);
}
/**
* Creates a new spinner.
*
* @param parent The parent container
* @param labelText The label's text
* @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 rightControl The widget to be placed to the right of spinner
* @param helpId The topic help ID to be registered for the spinner
* @return The newly created <code>Spinner</code>
*
* @category Layout
*/
protected final Spinner addLabeledSpinner(Composite parent,
String labelText,
ModifiablePropertyValueModel<Integer> numberHolder,
int defaultValue,
int minimumValue,
int maximumValue,
Control rightControl,
String helpId) {
Spinner spinner = this.addSpinner(
parent,
numberHolder,
defaultValue,
minimumValue,
maximumValue,
helpId
);
Label label = addLabel(parent, labelText);
addLabeledComposite(
parent,
label,
(spinner.getParent() != parent) ? spinner.getParent() : spinner,
rightControl,
helpId
);
GridData gridData = (GridData) spinner.getLayoutData();
gridData.horizontalAlignment = GridData.BEGINNING;
return spinner;
}
/**
* 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.manageWidget(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.widgetFactory.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) {
getHelpSystem().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.manageWidget(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.widgetFactory.createDateTime(parent, SWT.TIME);
DateTimeModelAdapter.adapt(hoursHolder, minutesHolder, secondsHolder, dateTime);
if (helpId != null) {
getHelpSystem().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,
StringConverter<V> stringConverter) {
Combo combo = addUnmanagedEditableCombo(container);
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) {
Combo combo = this.widgetFactory.createEditableCombo(container);
combo.setLayoutData(getFieldGridData());
return combo;
}
/**
* Creates a new container that will have a text field as the center control
* labeled with the given label.
*
* @param container The parent container
* @param textHolder The holder of the text field's input
* @return The newly created <code>Text</code>
*
* @category Layout
*/
protected final Text addLabeledText(Composite container,
String labelText,
ModifiablePropertyValueModel<String> textHolder) {
return this.addLabeledText(container, labelText, textHolder, null);
}
/**
* Creates a new container that will have a text field as the center control
* labeled with the given label.
*
* @param container The parent container
* @param labelText The text field's label
* @param rightComponent The component to be placed to the right of the text
* field
* @param textHolder The holder of the text field's input
* @param helpId The topic help ID to be registered for the text field
* @return The newly created <code>Text</code>
*
* @category Layout
*/
protected final Text addLabeledText(Composite container,
String labelText,
ModifiablePropertyValueModel<String> textHolder,
Control rightComponent,
String helpId) {
Text text = this.addText(container, textHolder);
this.addLabeledComposite(
container,
labelText,
text,
rightComponent,
helpId
);
return text;
}
/**
*
* Creates a new container that will have a text field as the center control
* labeled with the given label.
*
* @param container The parent container
* @param labelText The text field's label
* @param rightComponent The component to be placed to the right of the text
* field
* @param textHolder The holder of the text field's input
* @param helpId The topic help ID to be registered for the text field
* @return The newly created <code>Text</code>
*
* @category Layout
*/
protected final Text addLabeledText(Composite container,
Label label,
ModifiablePropertyValueModel<String> textHolder,
Control rightComponent,
String helpId) {
Text text = this.addText(container, textHolder);
this.addLabeledComposite(
container,
label,
text,
rightComponent,
helpId
);
return text;
}
/**
* Creates a new container that will have a text field as the center control
* labeled with the given label.
*
* @param container The parent container
* @param textHolder The holder of the text field's input
* @param helpId The topic help ID to be registered for the text field
* @return The newly created <code>Text</code>
*
* @category Layout
*/
protected final Text addLabeledText(Composite container,
String labelText,
ModifiablePropertyValueModel<String> textHolder,
String helpId) {
return this.addLabeledText(
container,
labelText,
textHolder,
null,
helpId
);
}
/**
* Creates a new container that will have a text field as the center control
* labeled with the given label.
*
* @param container The parent container
* @param textHolder The holder of the text field's input
* @param helpId The topic help ID to be registered for the text field
* @return The newly created <code>Text</code>
*
* @category Layout
*/
protected final Text addLabeledText(Composite container,
Label label,
ModifiablePropertyValueModel<String> textHolder,
String helpId) {
return this.addLabeledText(
container,
label,
textHolder,
null,
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 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.manageWidget(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.widgetFactory.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) {
getHelpSystem().setHelp(list, helpId);
}
return list;
}
/**
* Creates a new lable expanding on multiple lines.
*
* @param parent The parent container
* @param labelText The label's text
*
* @category Layout
*/
protected final FormText addMultiLineLabel(Composite container,
String labelText) {
FormText label = this.widgetFactory.createMultiLineLabel(container, labelText);
manageWidget(label);
return label;
}
/**
* 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.widgetFactory.createMultiLineText(container);
text.setLayoutData(getFieldGridData());
this.manageWidget(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(container, lineCount, text, text.getLineHeight());
if (helpId != null) {
getHelpSystem().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 textHolder 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> textHolder,
int lineCount,
String helpId) {
Text text = this.addMultiLineText(container, lineCount, helpId);
SWTTools.bind(textHolder, 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(Composite container,
int lineCount,
Control text,
int lineHeight) {
int textHeight = text.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
// 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;
// Move the label to the top of its cell
Control label = container.getChildren()[0];
int labelHeight = label.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
gridData = (GridData) label.getLayoutData();
gridData.verticalAlignment = SWT.TOP;
gridData.verticalIndent += (Math.abs(textHeight - labelHeight) / 2);
}
/**
* 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.widgetFactory.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_HORIZONTAL));
return container;
}
/**
* 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 addPasswordText(Composite container,
ModifiablePropertyValueModel<String> textHolder) {
Text text = this.addPasswordText(container);
SWTTools.bind(textHolder, text);
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 addPasswordText(Composite container) {
Text text = this.widgetFactory.createPasswordText(container);
text.setLayoutData(getFieldGridData());
this.manageWidget(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.widgetFactory.createPushButton(parent, buttonText);
manageWidget(button);
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
buttonAction.run();
}
});
button.setLayoutData(new GridData());
if (helpId != null) {
getHelpSystem().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
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
protected final Composite addSection(Composite container,
String sectionText) {
return this.addSection(
container,
sectionText,
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 type The type of section to create
* @param expandedStateHolder The holder of the boolean that will dictate
* when to expand or collapse the section
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
private Composite addSection(Composite container,
String sectionText,
int type) {
return this.addSection(container, sectionText, null, type);
}
/**
* 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
* @param expandedStateHolder The holder of the boolean that will dictate
* 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.widgetFactory.createSection(container, type | ((description != null) ? Section.DESCRIPTION : SWT.NULL));
section.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
section.setText(sectionText);
section.marginWidth = 0;
section.marginHeight = 0;
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 the layout responsible to compute the size of the spacer created
* for the right control when none was given. The spacer helps to align all
* the right controls.
*
* @category Layout
*/
private Layout buildSpacerLayout() {
return new Layout() {
@Override
protected Point computeSize(Composite composite,
int widthHint,
int heightHint,
boolean flushCache) {
return new Point(widthHint, heightHint);
}
@Override
protected void layout(Composite composite, boolean flushCache) {
GridData data = (GridData) composite.getLayoutData();
composite.setBounds(0, 0, data.widthHint, data.heightHint);
}
};
}
private PropertyChangeListener buildSubjectChangeListener() {
return new SWTPropertyChangeListenerWrapper(this.buildSubjectChangeListener_());
}
private PropertyChangeListener buildSubjectChangeListener_() {
return new PropertyChangeListener() {
@SuppressWarnings("unchecked")
public void propertyChanged(PropertyChangeEvent e) {
Pane.this.subjectChanged((T) e.getOldValue(), (T) e.getNewValue());
}
};
}
/**
* 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 <code>Section</code>. A sub-pane is automatically added as
* its client which can be typed cast directly as a <code>Composite</code>.
*
* @param container The container of the new widget
* @param sectionText The text of the new section
* @return The <code>Section</code>'s sub-pane
*
* @category Layout
*/
protected final Composite addSubSection(Composite container,
String sectionText) {
return this.addCollapsibleSubSection(
container,
sectionText,
new SimplePropertyValueModel<Boolean>(Boolean.TRUE)
);
}
/**
* 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.manageWidget(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.widgetFactory.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) {
getHelpSystem().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.manageWidget(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.widgetFactory.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) {
getHelpSystem().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) {
getHelpSystem().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 textHolder 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> textHolder,
String helpId) {
Text text = this.addText(container, helpId);
SWTTools.bind(textHolder, 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 textHolder 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> textHolder,
String helpId) {
Text text = this.addUnmanagedText(container, helpId);
SWTTools.bind(textHolder, 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.widgetFactory.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) {
getHelpSystem().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 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 addUnmanagedToggleButton(
Composite parent,
String buttonText,
ModifiablePropertyValueModel<Boolean> booleanHolder,
String helpId,
int toggleButtonType) {
Button button;
if (toggleButtonType == SWT.PUSH) {
button = this.widgetFactory.createPushButton(parent, buttonText);
}
else if (toggleButtonType == SWT.RADIO) {
button = this.widgetFactory.createRadioButton(parent, buttonText);
}
else if (toggleButtonType == SWT.CHECK) {
button = this.widgetFactory.createCheckBox(parent, buttonText);
}
else {
button = this.widgetFactory.createButton(parent, buttonText);
}
button.setLayoutData(new GridData());
SWTTools.bind(booleanHolder, button);
if (helpId != null) {
getHelpSystem().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.manageWidget(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 = new TriStateCheckBox(
parent,
text,
this.getWidgetFactory()
);
this.manageWidget(checkBox.getCheckBox());
TriStateCheckBoxModelAdapter.adapt(
booleanHolder,
checkBox
);
if (helpId != null) {
getHelpSystem().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 stringHolder 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> stringHolder,
String helpId) {
TriStateCheckBox checkBox = this.addTriStateCheckBox(
parent,
text,
booleanHolder,
helpId
);
new LabeledControlUpdater(
new LabeledButton(checkBox.getCheckBox()),
stringHolder
);
return checkBox;
}
/**
* Requests this pane to populate its widgets with the subject's values.
*
* @category Populate
*/
protected void doPopulate() {
this.log(Tracing.UI_LAYOUT, " ->doPopulate()");
}
private void controlEnabledState(PropertyValueModel<Boolean> booleanModel, Control... controls) {
this.controlEnabledState_(this.wrapEnabledModel(booleanModel), controls);
}
/**
* Assume the "enabled" models can return null (which is typical with aspect
* adapters etc.).
*/
private PropertyValueModel<Boolean> wrapEnabledModel(PropertyValueModel<Boolean> booleanModel) {
return new TransformationPropertyValueModel<Boolean, Boolean>(booleanModel, NonNullBooleanTransformer.FALSE);
}
private void controlEnabledState_(PropertyValueModel<Boolean> booleanModel, Control... controls) {
SWTTools.controlEnabledState(this.andEnabledModel(booleanModel), controls);
}
private PropertyValueModel<Boolean> getCombinedEnabledModel() {
return (this.combinedEnabledModel != null) ? this.combinedEnabledModel : this.baseEnabledModel;
}
private boolean getCombinedEnablement() {
Boolean enabled = getCombinedEnabledModel().getValue();
return (enabled == null) ? true : enabled.booleanValue();
}
private PropertyValueModel<Boolean> andEnabledModel(PropertyValueModel<Boolean> booleanModel) {
return CompositeBooleanPropertyValueModel.and(getCombinedEnabledModel(), booleanModel);
}
protected void controllerEnablementChanged() {
enableWidgets_(getCombinedEnablement());
}
/**
* Changes the enablement state of the widgets of this pane.
*
* @param enabled <code>true</code> to enable the widgets or <code>false</code>
* to disable them
*
* @category Layout
*/
public void enableWidgets(boolean enabled) {
this.baseEnabledModel.setValue(Boolean.valueOf(enabled));
enableWidgets_(getCombinedEnablement());
}
private void enableWidgets_(boolean enabled) {
if (! this.container.isDisposed()) {
for (Control control : this.managedWidgets) {
control.setEnabled(enabled);
}
for (Pane<?> subPane : this.managedSubPanes) {
subPane.enableWidgets(enabled);
}
}
}
private void engageSubjectHolder() {
this.subjectHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener);
}
/**
* engage the specified subject
*/
protected void engageListeners(T subject) {
if (subject != null) {
this.engageListeners_(subject);
}
}
/**
* specified subject is not null
*/
protected void engageListeners_(T subject) {
this.log(Tracing.UI_LAYOUT, " ->engageListeners_(" + subject + ')');
for (String propertyName : this.getPropertyNames()) {
subject.addPropertyChangeListener(propertyName, this.aspectChangeListener);
}
}
/**
* disengage the specified subject
*/
protected void disengageListeners(T subject) {
if (subject != null) {
this.disengageListeners_(subject);
}
}
/**
* specified subject is not null
*/
protected void disengageListeners_(T subject) {
this.log(Tracing.UI_LAYOUT, " ->disengageListeners_(" + subject + ')');
for (String propertyName : this.getPropertyNames()) {
subject.removePropertyChangeListener(propertyName, this.aspectChangeListener);
}
}
private void disengageSubjectHolder() {
this.subjectHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.subjectChangeListener);
}
/**
* Returns the main <code>Control</code> of this pane.
*
* @return The main container
*
* @category Layout
*/
public Composite getControl() {
return this.container;
}
/**
* Returns the subject holder used by this pane.
*
* @return The holder of the subject
*
* @category Populate
*/
protected final PropertyValueModel<T> getSubjectHolder() {
return this.subjectHolder;
}
/**
* Returns the factory responsible for creating the widgets.
*
* @return The factory used by this pane to create the widgets
*
* @category Layout
*/
protected final WidgetFactory getWidgetFactory() {
return this.widgetFactory;
}
/**
* Returns the margin taken by a group box, which is the number of pixel the
* group box border and its margin takes before displaying its widgets plus
* 5 pixels since the widgets inside of the group box and the border should
* have that extra 5 pixels.
*
* @return The width taken by the group box border with its margin
*
* @category Layout
*/
protected final int getGroupBoxMargin() {
Group group = this.widgetFactory.createGroup(SWTUtil.getShell(), "");
Rectangle clientArea = group.getClientArea();
group.dispose();
return clientArea.x + 5;
}
/**
* Returns the helps system.
*
* @return The platform's help system
*
* @category Helper
*/
protected final IWorkbenchHelpSystem getHelpSystem() {
return PlatformUI.getWorkbench().getHelpSystem();
}
/**
* Determines whether
*
* @return
*
* @category Populate
*/
protected final boolean isPopulating() {
return this.populating;
}
/**
* Logs the given message if the <code>Tracing.DEBUG_LAYOUT</code> is enabled.
*
* @param flag
* @param message The logging message
*/
protected void log(String flag, String message) {
if (flag.equals(Tracing.UI_LAYOUT) && Tracing.booleanDebugOption(Tracing.UI_LAYOUT)) {
this.log(message);
}
}
protected void log(String message) {
Class<?> thisClass = this.getClass();
String className = thisClass.getSimpleName();
if (thisClass.isAnonymousClass()) {
className = className.substring(0, className.indexOf('$'));
className += "->" + thisClass.getSuperclass().getSimpleName();
}
Tracing.log(className + ": " + message);
}
/**
* Notifies this pane to populate itself using the subject's information.
*
* @category Populate
*/
private void populate() {
if (!this.container.isDisposed()) {
this.log(Tracing.UI_LAYOUT, "populate()");
this.repopulate();
}
}
/**
* Notifies the subject's property associated with the given property name
* has changed.
*
* @param propertyName The property name associated with the property change
*
* @category Populate
*/
protected void propertyChanged(String propertyName) {
}
/**
* Returns the list of names to listen for properties changing from the
* subject.
*
* @return A non-<code>null</code> list of property names
*
* @category Populate
*/
protected Collection<String> getPropertyNames() {
ArrayList<String> propertyNames = new ArrayList<String>();
addPropertyNames(propertyNames);
return propertyNames;
}
/**
* Removes the given pane's widgets (those that were registered with
* its left <code>ControlAligner</code>) from this pane's left
* <code>ControlAligner</code> so that their width will no longer be adjusted
* with the width of the widest widget.
*
* @param pane The pane containing the widgets to remove
*
* @category Layout
*/
protected final void removeAlignLeft(Pane<?> pane) {
this.leftControlAligner.remove(pane.leftControlAligner);
}
/**
* Removes the given control from the collection of widgets that are aligned
* to have the same width when they are shown to the left side of the 3
* widget colums.
*
* @param pane The pane to remove, its width will no longer be
* ajusted to be the width of the longest widget
*
* @category Layout
*/
protected final void removeAlignLeft(Control control) {
this.leftControlAligner.remove(control);
}
/**
* Removes the given pane's widgets (those that were registered with
* its right <code>ControlAligner</code>) from this pane's right
* <code>ControlAligner</code> so that their width will no longer be adjusted
* with the width of the widest widget.
*
* @param pane The pane containing the widgets to remove
*
* @category Layout
*/
protected final void removeAlignRight(Pane<?> pane) {
this.rightControlAligner.remove(pane.rightControlAligner);
}
/**
* Removes the given control from the collection of widgets that are aligned
* to have the same width when they are shown to the right side of the 3
* widget colums.
*
* @param pane The pane to remove, its width will no longer be
* ajusted to be the width of the longest widget
*
* @category Layout
*/
protected final void removeAlignRight(Control control) {
this.rightControlAligner.remove(control);
}
/**
* Removes the given pane's controls (those that were registered for
* alignment) from this pane.
*
* @param pane The pane containing the widgets that no longer
* requires their width adjusted with the width of the longest widget
*
* @category Layout
*/
protected final void removePaneForAlignment(Pane<?> pane) {
removeAlignLeft(pane);
removeAlignRight(pane);
}
/**
* This method is called (perhaps internally) when this needs to repopulate
* but the object of interest has not changed.
*
* @category Populate
*/
protected final void repopulate() {
this.log(Tracing.UI_LAYOUT, " ->repopulate()");
// Populate this pane
try {
setPopulating(true);
doPopulate();
}
finally {
setPopulating(false);
}
// Ask the sub-panes to repopulate themselves
for (Pane<?> subPane : this.subPanes) {
subPane.repopulate();
}
}
/**
* 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.
*
* @param populating
*
* @category Populate
*/
protected final void setPopulating(boolean populating) {
this.populating = populating;
}
/**
* Either show or hides this pane.
*
* @param visible The new visibility state
*/
public void setVisible(boolean visible) {
if (!this.container.isDisposed()) {
this.container.setVisible(visible);
}
}
/**
* Returns the nearest <code>Shell</code> displaying the main widget of this
* pane.
*
* @return The nearest window displaying this pane
*/
public final Shell getShell() {
return this.container.getShell();
}
/**
* Returns the subject of this pane.
*
* @return The subject if this pane was not disposed; <code>null</code>
* if it was
*
* @category Populate
*/
public T getSubject() {
return this.subjectHolder.getValue();
}
/**
* The subject has changed, disconnects any listeners from the old subject
* and connects those listeners onto the new subject.
*
* @param oldsubject The old subject or <code>null</code> if none was set
* @param newSubject The new subject or <code>null</code> if none needs to be
* set
*
* @category Populate
*/
protected final void subjectChanged(T oldSubject, T newSubject) {
if (!this.container.isDisposed()) {
this.log(Tracing.UI_LAYOUT, "subjectChanged()");
this.disengageListeners(oldSubject);
this.repopulate();
this.engageListeners(newSubject);
}
}
/**
* Registers another <code>Pane</code> with this one so it can
* be automatically notified about certain events such as engaging or
* disengaging the listeners, etc.
*
* @param subPane The sub-pane to register
*
* @category Controller
*/
protected final void registerSubPane(Pane<?> subPane) {
this.subPanes.add(subPane);
}
/**
* Unregisters the given <code>Pane</code> from this one so it
* can no longer be automatically notified about certain events such as
* engaging or disengaging the listeners, etc.
*
* @param subPane The sub-pane to unregister
*
* @category Controller
*/
protected final void unregisterSubPane(Pane<?> subPane) {
this.subPanes.remove(subPane);
}
private void updatePane(String propertyName) {
if (!isPopulating() && !this.container.isDisposed()) {
this.populating = true;
try {
propertyChanged(propertyName);
}
finally {
this.populating = false;
}
}
}
public void dispose() {
this.log(Tracing.UI_LAYOUT, "dispose()");
// Dispose this pane
this.disengageListeners(getSubject());
this.disengageSubjectHolder();
if (this.combinedEnabledModel != null && this.combinedEnabledModelListener != null) {
this.combinedEnabledModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.combinedEnabledModelListener);
}
this.leftControlAligner.dispose();
this.rightControlAligner.dispose();
// Ask the sub-panes to dispose themselves
for (Pane<?> subPane : this.subPanes) {
subPane.dispose();
}
}
}