Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 5cc07d4b901850ca620e8bbeb37061e7e2c97124 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
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.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
 * dsependencies 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. 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 {
 * 		LabelCreator labelCreator = new LabelCreator(this);
 * 		LabelCreator labelCreator = new LabelCreator(this);
 * 
 * 	&#064;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("Sex:");
 * 					
 * 			sexComboCreator.create();
 * 		}
 * 	}
 * </pre>
 * 
 * The implementation of the label creator might look something like this:
 * 
 * <pre>
 * public class PropertyLabelCreator extends ControlCreator&lt;Label&gt; {
 * 
 * 	public PropertyLabelCreator(PropertiesComposite updatingComposite) {
 * 		super(updatingComposite);
 * 	}
 * 
 * 	&#064;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.
	 */
	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);
			}

			layout(true);
		}
	}

	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);
		/*
		 * We must wait until after the derived class fields have been
		 * initialized before attempting to create the child controls.
		 */
		// Display.getCurrent().asyncExec(new Runnable() {
		// public void run() {
		trackControls();
		// }
		// });
	}

	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() {
					trackControls();
					computeSizeObservable.fireChange();
				}
			});
		}
	}

	/**
	 * This line will do the following:
	 * <UL>
	 * <LI>Run the calculate method</LI>
	 * <LI>While doing so, add any observable that is touched to the
	 * dependencies list</LI>
	 * </UL>
	 */
	protected void trackControls() {
		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;
		}
	}

	<T extends Control> T create(ControlCreator<T> controlCreator) {

		T matchingControl = null;
		for (Control control : remainder) {
			if (control.getData(key) == controlCreator) {
				matchingControl = controlCreator.typeControl(control);
				break;
			}
		}

		if (matchingControl != null) {
			/*
			 * Move this control to be before any other remaining controls.
			 */
			if (matchingControl != remainder.get(0)) {
				matchingControl.moveBelow(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.moveBelow(remainder.get(0));
			}
		}

		return matchingControl;
	}

	/**
	 * 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);
	}
}

Back to the top