| package org.eclipse.jface.internal.databinding.provisional.swt; |
| |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import org.eclipse.core.databinding.observable.AbstractObservable; |
| import org.eclipse.core.databinding.observable.ChangeEvent; |
| import org.eclipse.core.databinding.observable.IChangeListener; |
| import org.eclipse.core.databinding.observable.IObservable; |
| import org.eclipse.core.databinding.observable.ObservableTracker; |
| import org.eclipse.core.databinding.observable.Realm; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| |
| /** |
| * This is a provisional class. Please leave feedback, good or bad, in bug |
| * 419415 so it can be decided what to promote to general API. |
| * <P> |
| * A composite that automatically tracks and registers listeners on its |
| * dependencies as long as all of its dependencies are {@link IObservable} |
| * objects. The tracking is done while the child controls for this composite are |
| * being created. So when any of the dependencies change, the child controls are |
| * re-created. |
| * <P> |
| * Child controls are re-used if possible. To do this, the code that creates the |
| * child controls must not create the controls directly. Instead a control |
| * creator is instantiated for each 'type' of control. If a control creator is |
| * called to create a control then it will first search the existing unused |
| * controls for a control that was previously created by that control creator |
| * (or a control creator that 'equals' it). A new control is only created if |
| * none exist. If the re-used control needs to be moved in the child control |
| * order then that is handled by this class. |
| * <P> |
| * Any change to one of the observable dependencies causes the child controls to |
| * be re-built. |
| * <p> |
| * Example: The composite contains a 'name' control, it contains a 'company |
| * name' control only if isCompany (being of type IObservableValue<Boolean>) is |
| * set. |
| * </p> |
| * |
| * <pre> |
| * class MyComposite extends UpdatableComposite { |
| * ControlCreator labelCreator = new LabelCreator(this); |
| * ControlCreator companyNameControlCreator = new CompanyNameControlCreator(this); |
| * ControlCreator genderComboCreator = new GenderComboCreator(this); |
| * |
| * MyComposite(Composite parent, int style) { |
| * super(parent, style); |
| * |
| * // IMPORTANT: Constructors must always call this. The constructor in the super class |
| * // cannot do this for you because trackControls calls createControls which may use fields in this class |
| * // that will not have been initialized when the super constructor is called. |
| * trackControls(); |
| * } |
| * |
| * @Override |
| * protected void createControls() { |
| * Label label1 = labelCreator.create(); |
| * label1.setText("Name:"); |
| * |
| * nameControlCreator.create(); |
| * |
| * if (isCompany.getValue()) { |
| * Label label2 = labelCreator.create(); |
| * label2.setText("Company Name:"); |
| * |
| * companyNameControlCreator.create(); |
| * } else { |
| * Label label3 = labelCreator.create(); |
| * label3.setText("Gender:"); |
| * |
| * genderComboCreator.create(); |
| * } |
| * } |
| * </pre> |
| * |
| * The implementation of the label creator might look something like this: |
| * |
| * <pre> |
| * public class PropertyLabelCreator extends ControlCreator<Label> { |
| * |
| * public PropertyLabelCreator(PropertiesComposite updatingComposite) { |
| * super(updatingComposite); |
| * } |
| * |
| * @Override |
| * public Label createControl() { |
| * final CLabel label = new Label(composite, SWT.NONE); |
| * label.setBackground(composite.getBackground()); |
| * return label; |
| * } |
| * } |
| * </pre> |
| * |
| * if isCompany is toggled on and off, you will see the second line of controls |
| * switch back and forth. The 'Name:' label is the first control and will be |
| * re-used. Likewise the 'name' control will be re-used. The labels for 'Company |
| * Name:' and for 'Sex:' are both created by the same ControlCreator instance |
| * and therefore will be re-used. The call to setText, made in the user's code, |
| * will toggle the label text between "Company Name:" and "Sex:". The last |
| * control will be created and disposed as 'isCompany' is toggled. |
| * <P> |
| * You often have a choice to make as to whether two controls share the same |
| * creator. For example, for a combo box, you can have a separate control |
| * creator for each combo. The values listed in the drop-down are set when the |
| * control is first created. If the control is re-used then the drop-down values |
| * will already be populated. The second option is to have a single creator for |
| * many different combo boxes that may appear in the composite and that may all |
| * have different lists of options in the drop-down. In that case a control that |
| * was created for one use may be re-used for a different use with different |
| * values in the drop-down. The user code in CreateControls must then set the |
| * drop-down values after the control is 'created' (being sure to remove |
| * previous values). |
| * |
| * @author Nigel Westbury |
| */ |
| public abstract class UpdatingComposite extends Composite { |
| |
| /** |
| * key in the user data map in each control. The value in the map will be |
| * the control creator that created this control. |
| */ |
| public final String key = "org.eclipse.databinding.controlcreator"; //$NON-NLS-1$ |
| |
| private boolean dirty = true; |
| |
| /** |
| * Array of observables this computed value depends on. This field has a |
| * value of <code>null</code> if we are not currently listening. |
| */ |
| private IObservable[] dependencies = null; |
| |
| /** |
| * Set to a non-null list only while controls are being re-built, this being |
| * the list of controls available for re-use and must be in order |
| */ |
| List<Control> remainder = null; |
| |
| private class ControlSetObservable extends AbstractObservable { |
| private ControlSetObservable(Realm realm) { |
| super(realm); |
| } |
| |
| public boolean isStale() { |
| return false; |
| } |
| |
| @Override |
| public void fireChange() { |
| super.fireChange(); |
| } |
| } |
| |
| /** |
| * Inner class that implements interfaces that we don't want to expose as |
| * public API. Each interface could have been implemented using a separate |
| * anonymous class, but we combine them here to reduce the memory overhead |
| * and number of classes. |
| * |
| * <p> |
| * The Runnable calls computeValue and stores the result in cachedValue. |
| * </p> |
| * |
| * <p> |
| * The IChangeListener stores each observable in the dependencies list. This |
| * is registered as the listener when calling ObservableTracker, to detect |
| * every observable that is used by computeValue. |
| * </p> |
| * |
| * <p> |
| * The IChangeListener is attached to every dependency. |
| * </p> |
| * |
| */ |
| private class PrivateChangeInterface implements IChangeListener { |
| public void handleChange(ChangeEvent event) { |
| makeDirty(); |
| } |
| } |
| |
| /** |
| * This runnable updates the controls in the composite. It will create, |
| * dispose, and move around controls as necessary to get the list of child |
| * controls of the composite into their new required state. This method is |
| * called initially from the constructor of this composite and thereafter |
| * when any of the tracked dependencies change. |
| * <P> |
| * This code is in a runnable because it is called by the dependency |
| * tracking in <code>ObservableTracker</code>. |
| */ |
| private class PrivateRunnableInterface implements Runnable { |
| public void run() { |
| remainder = new LinkedList<Control>(); |
| remainder.addAll(Arrays.asList(getChildren())); |
| |
| createControls(); |
| |
| /* |
| * Remove any controls left. |
| */ |
| for (Control control : remainder) { |
| remove(control); |
| } |
| } |
| } |
| |
| private IChangeListener privateChangeInterface = new PrivateChangeInterface(); |
| private Runnable privateRunnableInterface = new PrivateRunnableInterface(); |
| |
| private ControlSetObservable computeSizeObservable = new ControlSetObservable( |
| Realm.getDefault()); |
| |
| public UpdatingComposite(Composite parent, int style) { |
| super(parent, style); |
| |
| addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| stopListening(); |
| } |
| }); |
| } |
| |
| protected final void makeDirty() { |
| if (!dirty) { |
| dirty = true; |
| |
| stopListening(); |
| |
| /* |
| * Start the runnable asynchronously, so it runs after all the |
| * observables have been updated. |
| */ |
| Display.getCurrent().asyncExec(new Runnable() { |
| public void run() { |
| /* |
| * Although we stop listening to the dependencies when this |
| * composite is disposed, there is a small window in which |
| * the composite may be disposed before we get back onto the |
| * UI thread here. |
| */ |
| if (isDisposed()) { |
| return; |
| } |
| |
| createAndTrackControls(); |
| |
| /* |
| * Having added and removed controls, this composite now |
| * needs to be laid out. The children are okay because they |
| * should be laid out when they are created or re-used, so |
| * passing <code>false</code> is fine. |
| */ |
| layout(false); |
| |
| /* |
| * Assume that the preferred size has changed. Parent |
| * composites may need to re-layout their controls whenever |
| * the size of this control changes. |
| */ |
| computeSizeObservable.fireChange(); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * This line will do the following: |
| * <UL> |
| * <LI>Run the <code>createControls</code> method</LI> |
| * <LI>While doing so, add any observable that is touched to the |
| * dependencies list</LI> |
| * </UL> |
| */ |
| protected void createAndTrackControls() { |
| IObservable[] newDependencies = ObservableTracker.runAndMonitor( |
| privateRunnableInterface, privateChangeInterface, null); |
| |
| dependencies = newDependencies; |
| |
| dirty = false; |
| } |
| |
| /** |
| * This method is called when a control is no longer needed. This default |
| * implementation will dispose the control. |
| * <P> |
| * Override this if the layout allows controls to be left hidden. |
| * |
| * @param control |
| */ |
| private void remove(Control control) { |
| // First tell the control creator that this control |
| // is going away. |
| ControlCreator<?> creator = (ControlCreator<?>) control.getData(key); |
| creator.remove(control); |
| |
| control.dispose(); |
| } |
| |
| /** |
| * |
| */ |
| private void stopListening() { |
| // Stop listening for dependency changes. |
| if (dependencies != null) { |
| for (int i = 0; i < dependencies.length; i++) { |
| IObservable observable = dependencies[i]; |
| |
| observable.removeChangeListener(privateChangeInterface); |
| } |
| dependencies = null; |
| } |
| } |
| |
| /** |
| * This method is called while getters are being tracked. However we want to |
| * track only getters on observables that were used to determine which |
| * controls are created and setting properties on those controls outside of |
| * the control creator. We really don't want to track getters that are used |
| * while creating the control. For example it may be that |
| * |
| * @param controlCreator |
| * @return the control, which may be either re-used or just created by the |
| * control creator |
| */ |
| <T extends Control> T create(ControlCreator<T> controlCreator) { |
| ObservableTracker.setIgnore(true); |
| try { |
| T matchingControl = null; |
| for (Control control : remainder) { |
| if (controlCreator.equals(control.getData(key))) { |
| matchingControl = controlCreator.typeControl(control); |
| break; |
| } |
| } |
| |
| if (matchingControl != null) { |
| /* |
| * Move this control to be before any other remaining controls. |
| */ |
| if (matchingControl != remainder.get(0)) { |
| matchingControl.moveAbove(remainder.get(0)); |
| } |
| remainder.remove(matchingControl); |
| } else { |
| matchingControl = controlCreator.createControl(); |
| matchingControl.setData(key, controlCreator); |
| controlCreator.controls.add(matchingControl); |
| /* |
| * Move this control to be before any other remaining controls. |
| */ |
| if (!remainder.isEmpty()) { |
| matchingControl.moveAbove(remainder.get(0)); |
| } |
| } |
| return matchingControl; |
| } finally { |
| ObservableTracker.setIgnore(false); |
| } |
| } |
| |
| /** |
| * The implementation of this method creates the child controls in the |
| * composite. |
| * <P> |
| * DO NOT create controls directly in this composite. You must use control |
| * creators to create all child controls. Be aware that this method will be |
| * called multiple times as dependencies change. |
| * <P> |
| * DO use tracked getters to get values, otherwise the UI will not |
| * automatically update as the value changes. If the getter for a value is |
| * not a tracked getter then create an observable for the value and then get |
| * the value from the observable (the observable you created would then be |
| * automatically added as a dependency). |
| */ |
| abstract protected void createControls(); |
| |
| /** |
| * Overrides the implementation from the super class to make it a tracked |
| * getter. |
| * <P> |
| * This has nothing much to do with the purpose of this class. It's just |
| * that computeSize should always be a tracked getter because that allows |
| * the parent composite to adjust its layout accordingly. |
| * |
| * @TrackedGetter |
| */ |
| @Override |
| public Point computeSize(int wHint, int hHint, boolean changed) { |
| ObservableTracker.getterCalled(computeSizeObservable); |
| return super.computeSize(wHint, hHint, changed); |
| } |
| } |