View | Details | Raw Unified | Return to bug 175735 | Differences between
and this patch

Collapse All | Expand All

(-)src/org/eclipse/jface/examples/databinding/snippets/Snippet028DuplexingObservableValue.java (+309 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2009 Matthew Hall and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Matthew Hall - initial API and implementation (bug 175735)
10
 ******************************************************************************/
11
12
package org.eclipse.jface.examples.databinding.snippets;
13
14
import java.beans.PropertyChangeListener;
15
import java.beans.PropertyChangeSupport;
16
import java.util.Collection;
17
import java.util.Iterator;
18
19
import org.eclipse.core.databinding.DataBindingContext;
20
import org.eclipse.core.databinding.beans.BeanProperties;
21
import org.eclipse.core.databinding.observable.Realm;
22
import org.eclipse.core.databinding.observable.list.IObservableList;
23
import org.eclipse.core.databinding.observable.list.WritableList;
24
import org.eclipse.core.databinding.observable.value.DuplexingObservableValue;
25
import org.eclipse.core.databinding.property.Properties;
26
import org.eclipse.jface.databinding.swt.SWTObservables;
27
import org.eclipse.jface.databinding.swt.WidgetProperties;
28
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
29
import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
30
import org.eclipse.jface.databinding.viewers.ViewerProperties;
31
import org.eclipse.jface.util.Util;
32
import org.eclipse.jface.viewers.TableViewer;
33
import org.eclipse.swt.SWT;
34
import org.eclipse.swt.layout.GridData;
35
import org.eclipse.swt.layout.GridLayout;
36
import org.eclipse.swt.widgets.Display;
37
import org.eclipse.swt.widgets.Label;
38
import org.eclipse.swt.widgets.Shell;
39
import org.eclipse.swt.widgets.Table;
40
import org.eclipse.swt.widgets.TableColumn;
41
import org.eclipse.swt.widgets.Text;
42
43
/**
44
 * @since 3.2
45
 * 
46
 */
47
public class Snippet028DuplexingObservableValue {
48
	protected Shell shell;
49
	private TableViewer viewer;
50
	private Table table;
51
	private Text releaseDate;
52
	private Text title;
53
	private Text director;
54
	private Text writer;
55
56
	/**
57
	 * Launch the application
58
	 * 
59
	 * @param args
60
	 */
61
	public static void main(String[] args) {
62
		try {
63
			Snippet028DuplexingObservableValue window = new Snippet028DuplexingObservableValue();
64
			window.open();
65
		} catch (Exception e) {
66
			e.printStackTrace();
67
		}
68
	}
69
70
	/**
71
	 * Open the window
72
	 */
73
	public void open() {
74
		final Display display = Display.getDefault();
75
		Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() {
76
			public void run() {
77
				createContents();
78
				shell.open();
79
				shell.layout();
80
				while (!shell.isDisposed()) {
81
					if (!display.readAndDispatch())
82
						display.sleep();
83
				}
84
			}
85
		});
86
	}
87
88
	protected void createContents() {
89
		shell = new Shell();
90
		shell.setSize(509, 375);
91
		shell.setText("Snippet028DuplexingObservableValue.java");
92
		final GridLayout gridLayout = new GridLayout();
93
		gridLayout.makeColumnsEqualWidth = true;
94
		gridLayout.numColumns = 4;
95
		shell.setLayout(gridLayout);
96
97
		viewer = new TableViewer(shell, SWT.FULL_SELECTION | SWT.MULTI
98
				| SWT.BORDER);
99
		table = viewer.getTable();
100
		table.setLinesVisible(true);
101
		table.setHeaderVisible(true);
102
		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1));
103
104
		final TableColumn newColumnTableColumn_1 = new TableColumn(table,
105
				SWT.NONE);
106
		newColumnTableColumn_1.setWidth(120);
107
		newColumnTableColumn_1.setText("Movie");
108
109
		final TableColumn newColumnTableColumn = new TableColumn(table,
110
				SWT.NONE);
111
112
		newColumnTableColumn.setWidth(120);
113
		newColumnTableColumn.setText("Release Date");
114
115
		final TableColumn newColumnTableColumn_2 = new TableColumn(table,
116
				SWT.NONE);
117
		newColumnTableColumn_2.setWidth(120);
118
		newColumnTableColumn_2.setText("Director");
119
120
		final TableColumn newColumnTableColumn_3 = new TableColumn(table,
121
				SWT.NONE);
122
		newColumnTableColumn_3.setWidth(120);
123
		newColumnTableColumn_3.setText("Writer");
124
125
		final Label movieLabel = new Label(shell, SWT.NONE);
126
		movieLabel.setText("Movie");
127
128
		final Label directorLabel = new Label(shell, SWT.NONE);
129
		directorLabel.setLayoutData(new GridData());
130
		directorLabel.setText("Release Date");
131
132
		final Label producerLabel = new Label(shell, SWT.NONE);
133
		producerLabel.setText("Director");
134
135
		final Label scoreLabel = new Label(shell, SWT.NONE);
136
		scoreLabel.setText("Writer");
137
138
		title = new Text(shell, SWT.BORDER);
139
		final GridData gd_title = new GridData(SWT.FILL, SWT.CENTER, true,
140
				false);
141
		title.setLayoutData(gd_title);
142
143
		releaseDate = new Text(shell, SWT.BORDER);
144
		final GridData gd_releaseDate = new GridData(SWT.FILL, SWT.CENTER,
145
				true, false);
146
		releaseDate.setLayoutData(gd_releaseDate);
147
148
		director = new Text(shell, SWT.BORDER);
149
		final GridData gd_director = new GridData(SWT.FILL, SWT.CENTER, true,
150
				false);
151
		director.setLayoutData(gd_director);
152
153
		writer = new Text(shell, SWT.BORDER);
154
		final GridData gd_writer = new GridData(SWT.FILL, SWT.CENTER, true,
155
				false);
156
		writer.setLayoutData(gd_writer);
157
158
		bindUI();
159
	}
160
161
	// Minimal JavaBeans support
162
	public static abstract class AbstractModelObject {
163
		private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
164
				this);
165
166
		public void addPropertyChangeListener(PropertyChangeListener listener) {
167
			propertyChangeSupport.addPropertyChangeListener(listener);
168
		}
169
170
		public void addPropertyChangeListener(String propertyName,
171
				PropertyChangeListener listener) {
172
			propertyChangeSupport.addPropertyChangeListener(propertyName,
173
					listener);
174
		}
175
176
		public void removePropertyChangeListener(PropertyChangeListener listener) {
177
			propertyChangeSupport.removePropertyChangeListener(listener);
178
		}
179
180
		public void removePropertyChangeListener(String propertyName,
181
				PropertyChangeListener listener) {
182
			propertyChangeSupport.removePropertyChangeListener(propertyName,
183
					listener);
184
		}
185
186
		protected void firePropertyChange(String propertyName, Object oldValue,
187
				Object newValue) {
188
			propertyChangeSupport.firePropertyChange(propertyName, oldValue,
189
					newValue);
190
		}
191
	}
192
193
	public static class MovieInfo extends AbstractModelObject {
194
		private String title;
195
		private String releaseDate;
196
		private String director;
197
		private String writer;
198
199
		public MovieInfo(String title, String releaseDate, String director,
200
				String writer) {
201
			this.title = title;
202
			this.releaseDate = releaseDate;
203
			this.director = director;
204
			this.writer = writer;
205
		}
206
207
		public String getTitle() {
208
			return title;
209
		}
210
211
		public void setTitle(String title) {
212
			firePropertyChange("title", this.title, this.title = title);
213
		}
214
215
		public String getReleaseDate() {
216
			return releaseDate;
217
		}
218
219
		public void setReleaseDate(String releaseDate) {
220
			firePropertyChange("releaseDate", this.releaseDate,
221
					this.releaseDate = releaseDate);
222
		}
223
224
		public String getDirector() {
225
			return director;
226
		}
227
228
		public void setDirector(String director) {
229
			firePropertyChange("director", this.director,
230
					this.director = director);
231
		}
232
233
		public String getWriter() {
234
			return writer;
235
		}
236
237
		public void setWriter(String writer) {
238
			firePropertyChange("writer", this.writer, this.writer = writer);
239
		}
240
	}
241
242
	private void bindUI() {
243
		IObservableList movies = new WritableList();
244
		movies.add(new MovieInfo("Cloverfield", "January 18, 2008",
245
				"Matt Reeves", "Drew Goddard"));
246
		movies.add(new MovieInfo("Iron Man", "May 2, 2008", "Jon Favreau",
247
				"Mark Fergus"));
248
		movies.add(new MovieInfo(
249
				"Indiana Jones and the Kingdom of the Crystal Skull",
250
				"May 22, 2008", "Steven Spielberg", "Drunken Lemurs"));
251
		movies.add(new MovieInfo("Get Smart", "June 20, 2008", "Peter Segal",
252
				"Tom J. Astle"));
253
		movies.add(new MovieInfo("Wanted", "June 27, 2008",
254
				"Timur Bekmambetov", "Michael Brandt"));
255
		movies.add(new MovieInfo("Wall-E", "June 27, 2008", "Andrew Stanton",
256
				"Andrew Stanton"));
257
		movies.add(new MovieInfo("The Dark Knight", "July 18, 2008",
258
				"Christopher Nolan", "David S. Goyer"));
259
		movies.add(new MovieInfo("007: Quantum of Solace", "October 31, 2008",
260
				"Marc Forster", "Robert Wade"));
261
		movies.add(new MovieInfo("Valkyrie", "December 25, 2008",
262
				"Bryan Singer", "Christopher McQuarrie"));
263
264
		ObservableListContentProvider cp = new ObservableListContentProvider();
265
		viewer.setContentProvider(cp);
266
		viewer.setLabelProvider(new ObservableMapLabelProvider(Properties
267
				.observeEach(cp.getKnownElements(), BeanProperties
268
						.values(new String[] { "title", "releaseDate",
269
								"director", "writer" }))));
270
		viewer.setInput(movies);
271
272
		DataBindingContext dbc = new DataBindingContext();
273
274
		IObservableList selections = ViewerProperties.multipleSelection()
275
				.observe(viewer);
276
		dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(title),
277
				new StringDuplexingObservableValue(BeanProperties
278
						.value("title").observeDetail(selections)));
279
		dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(releaseDate),
280
				new StringDuplexingObservableValue(BeanProperties.value(
281
						"releaseDate").observeDetail(selections)));
282
		dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(director),
283
				new StringDuplexingObservableValue(BeanProperties.value(
284
						"director").observeDetail(selections)));
285
		dbc.bindValue(WidgetProperties.text(SWT.Modify).observe(writer),
286
				new StringDuplexingObservableValue(BeanProperties.value(
287
						"writer").observeDetail(selections)));
288
	}
289
290
	private static class StringDuplexingObservableValue extends
291
			DuplexingObservableValue {
292
293
		public StringDuplexingObservableValue(IObservableList target) {
294
			super(target);
295
		}
296
297
		protected Object coalesceElements(Collection elements) {
298
			Iterator it = elements.iterator();
299
			if (!it.hasNext())
300
				return "";
301
			Object first = it.next();
302
			while (it.hasNext()) {
303
				if (!Util.equals(first, it.next()))
304
					return "<Multiple values>";
305
			}
306
			return first;
307
		}
308
	}
309
}
(-)src/org/eclipse/jface/tests/databinding/BindingTestSuite.java (-1 / +3 lines)
Lines 14-20 Link Here
14
 *     Matthew Hall - bugs 210115, 212468, 212223, 206839, 208858, 208322,
14
 *     Matthew Hall - bugs 210115, 212468, 212223, 206839, 208858, 208322,
15
 *                    212518, 215531, 221351, 184830, 213145, 218269, 239015,
15
 *                    212518, 215531, 221351, 184830, 213145, 218269, 239015,
16
 *                    237703, 237718, 222289, 247394, 233306, 247647, 254524,
16
 *                    237703, 237718, 222289, 247394, 233306, 247647, 254524,
17
 *                    246103, 249992, 256150, 256543, 262269
17
 *                    246103, 249992, 256150, 256543, 262269, 175735
18
 *     Ovidio Mallo - bug 237163, bug 235195
18
 *     Ovidio Mallo - bug 237163, bug 235195
19
 *******************************************************************************/
19
 *******************************************************************************/
20
package org.eclipse.jface.tests.databinding;
20
package org.eclipse.jface.tests.databinding;
Lines 65-70 Link Here
65
import org.eclipse.core.tests.databinding.observable.value.AbstractVetoableValueTest;
65
import org.eclipse.core.tests.databinding.observable.value.AbstractVetoableValueTest;
66
import org.eclipse.core.tests.databinding.observable.value.ComputedValueTest;
66
import org.eclipse.core.tests.databinding.observable.value.ComputedValueTest;
67
import org.eclipse.core.tests.databinding.observable.value.DecoratingObservableValueTest;
67
import org.eclipse.core.tests.databinding.observable.value.DecoratingObservableValueTest;
68
import org.eclipse.core.tests.databinding.observable.value.DuplexingObservableValueTest;
68
import org.eclipse.core.tests.databinding.observable.value.SelectObservableValueTest;
69
import org.eclipse.core.tests.databinding.observable.value.SelectObservableValueTest;
69
import org.eclipse.core.tests.databinding.observable.value.WritableValueTest;
70
import org.eclipse.core.tests.databinding.observable.value.WritableValueTest;
70
import org.eclipse.core.tests.databinding.util.PolicyTest;
71
import org.eclipse.core.tests.databinding.util.PolicyTest;
Lines 268-273 Link Here
268
		addTestSuite(AbstractVetoableValueTest.class);
269
		addTestSuite(AbstractVetoableValueTest.class);
269
		addTestSuite(ComputedValueTest.class);
270
		addTestSuite(ComputedValueTest.class);
270
		addTest(DecoratingObservableValueTest.suite());
271
		addTest(DecoratingObservableValueTest.suite());
272
		addTestSuite(DuplexingObservableValueTest.class);
271
		addTest(SelectObservableValueTest.suite());
273
		addTest(SelectObservableValueTest.suite());
272
		addTest(WritableValueTest.suite());
274
		addTest(WritableValueTest.suite());
273
275
(-)src/org/eclipse/core/tests/databinding/observable/value/DuplexingObservableValueTest.java (+85 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2009 Matthew Hall and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Matthew Hall - initial API and implementation (bug 173735)
10
 *******************************************************************************/
11
12
package org.eclipse.core.tests.databinding.observable.value;
13
14
import java.util.ArrayList;
15
import java.util.Collection;
16
import java.util.Iterator;
17
18
import org.eclipse.core.databinding.observable.list.IObservableList;
19
import org.eclipse.core.databinding.observable.list.WritableList;
20
import org.eclipse.core.databinding.observable.value.DuplexingObservableValue;
21
import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase;
22
import org.eclipse.jface.util.Util;
23
24
/**
25
 * @since 1.0
26
 * 
27
 */
28
public class DuplexingObservableValueTest extends AbstractDefaultRealmTestCase {
29
	private IObservableList list;
30
	private DuplexingObservableValue observable;
31
32
	protected void setUp() throws Exception {
33
		super.setUp();
34
		list = new WritableList(new ArrayList(), String.class);
35
	}
36
37
	public void testValueType_InheritFromTargetList() throws Exception {
38
		observable = new DuplexingObservableValue(list) {
39
			protected Object coalesceElements(Collection elements) {
40
				return null;
41
			}
42
		};
43
		assertEquals(
44
				"value type should be the element type of the target list",
45
				String.class, observable.getValueType());
46
	}
47
48
	public void testValueType_ProvidedInConstructor() throws Exception {
49
		observable = new DuplexingObservableValue(list, Object.class) {
50
			protected Object coalesceElements(Collection elements) {
51
				return null;
52
			}
53
		};
54
		assertEquals("value type should be the type passed to constructor",
55
				Object.class, observable.getValueType());
56
	}
57
58
	public void test_getValue() throws Exception {
59
		observable = new DuplexingObservableValue(list) {
60
			protected Object coalesceElements(Collection elements) {
61
				Iterator it = elements.iterator();
62
				if (!it.hasNext())
63
					return null;
64
				Object first = it.next();
65
				while (it.hasNext()) {
66
					Object next = it.next();
67
					if (!Util.equals(first, next))
68
						return "<Multiple Values>";
69
				}
70
				return first;
71
			}
72
		};
73
		assertNull(observable.getValue());
74
		list.add("42");
75
		assertEquals("Value should be \"42\"", "42", observable.getValue());
76
		list.add("42");
77
		assertEquals("Value should be \"42\"", "42", observable.getValue());
78
		list.add("watermelon");
79
		assertEquals("<Multiple Values>", observable.getValue());
80
		list.remove(2);
81
		assertEquals("Value should be \"42\"", "42", observable.getValue());
82
		list.clear();
83
		assertNull(observable.getValue());
84
	}
85
}
(-)src/org/eclipse/core/databinding/observable/value/DuplexingObservableValue.java (+196 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2009 Matthew Hall and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Matthew Hall - initial API and implementation (bug 175735)
10
 ******************************************************************************/
11
12
package org.eclipse.core.databinding.observable.value;
13
14
import java.util.Collection;
15
16
import org.eclipse.core.databinding.observable.ChangeEvent;
17
import org.eclipse.core.databinding.observable.IChangeListener;
18
import org.eclipse.core.databinding.observable.IStaleListener;
19
import org.eclipse.core.databinding.observable.StaleEvent;
20
import org.eclipse.core.databinding.observable.list.IObservableList;
21
22
/**
23
 * @since 1.2
24
 */
25
public abstract class DuplexingObservableValue extends AbstractObservableValue {
26
	private IObservableList target;
27
	private final Object valueType;
28
29
	private boolean dirty = true;
30
	private boolean updating = false;
31
	private Object cachedValue = null; // applicable only while hasListener()
32
33
	private PrivateInterface privateInterface;
34
35
	/**
36
	 * @param target
37
	 */
38
	public DuplexingObservableValue(IObservableList target) {
39
		this(target, target.getElementType());
40
	}
41
42
	/**
43
	 * @param target
44
	 * @param valueType
45
	 */
46
	public DuplexingObservableValue(IObservableList target, Object valueType) {
47
		super(target.getRealm());
48
		this.target = target;
49
		this.valueType = valueType;
50
	}
51
52
	private class PrivateInterface implements IChangeListener, IStaleListener {
53
		public void handleChange(ChangeEvent event) {
54
			if (!updating)
55
				makeDirty();
56
		}
57
58
		public void handleStale(StaleEvent staleEvent) {
59
			if (!dirty) {
60
				fireStale();
61
			}
62
		}
63
	}
64
65
	protected void firstListenerAdded() {
66
		if (privateInterface == null)
67
			privateInterface = new PrivateInterface();
68
		target.addChangeListener(privateInterface);
69
		target.addStaleListener(privateInterface);
70
	}
71
72
	protected void lastListenerRemoved() {
73
		target.removeChangeListener(privateInterface);
74
		target.removeStaleListener(privateInterface);
75
	}
76
77
	protected final void makeDirty() {
78
		if (hasListeners() && !dirty) {
79
			dirty = true;
80
81
			// copy the old value
82
			final Object oldValue = cachedValue;
83
			// Fire the "dirty" event. This implementation recomputes the new
84
			// value lazily.
85
			fireValueChange(new ValueDiff() {
86
				public Object getOldValue() {
87
					return oldValue;
88
				}
89
90
				public Object getNewValue() {
91
					return getValue();
92
				}
93
			});
94
		}
95
	}
96
97
	public boolean isStale() {
98
		getValue();
99
		return target.isStale();
100
	}
101
102
	protected Object doGetValue() {
103
		if (!hasListeners())
104
			return coalesceElements(target);
105
106
		if (dirty) {
107
			cachedValue = coalesceElements(target);
108
			dirty = false;
109
			if (target.isStale())
110
				fireStale();
111
		}
112
113
		return cachedValue;
114
	}
115
116
	protected abstract Object coalesceElements(Collection elements);
117
118
	protected void doSetValue(Object value) {
119
		final Object oldValue = cachedValue;
120
121
		boolean wasUpdating = updating;
122
		try {
123
			updating = true;
124
			for (int i = 0; i < target.size(); i++)
125
				target.set(i, value);
126
		} finally {
127
			updating = wasUpdating;
128
		}
129
130
		// Fire the "dirty" event. This implementation recomputes the new
131
		// value lazily.
132
		if (hasListeners()) {
133
			fireValueChange(new ValueDiff() {
134
				public Object getOldValue() {
135
					return oldValue;
136
				}
137
138
				public Object getNewValue() {
139
					return getValue();
140
				}
141
			});
142
		}
143
	}
144
145
	public Object getValueType() {
146
		return valueType;
147
	}
148
149
	public synchronized void addChangeListener(IChangeListener listener) {
150
		super.addChangeListener(listener);
151
		// If somebody is listening, we need to make sure we attach our own
152
		// listeners
153
		computeValueForListeners();
154
	}
155
156
	public synchronized void addValueChangeListener(
157
			IValueChangeListener listener) {
158
		super.addValueChangeListener(listener);
159
		// If somebody is listening, we need to make sure we attach our own
160
		// listeners
161
		computeValueForListeners();
162
	}
163
164
	/**
165
	 * Some clients just add a listener and expect to get notified even if they
166
	 * never called getValue(), so we have to call getValue() ourselves here to
167
	 * be sure. Need to be careful about realms though, this method can be
168
	 * called outside of our realm. See also bug 198211. If a client calls this
169
	 * outside of our realm, they may receive change notifications before the
170
	 * runnable below has been executed. It is their job to figure out what to
171
	 * do with those notifications.
172
	 */
173
	private void computeValueForListeners() {
174
		getRealm().exec(new Runnable() {
175
			public void run() {
176
				// We are not currently listening.
177
				if (hasListeners()) {
178
					// But someone is listening for changes. Call getValue()
179
					// to make sure we start listening to the observables we
180
					// depend on.
181
					getValue();
182
				}
183
			}
184
		});
185
	}
186
187
	public synchronized void dispose() {
188
		if (privateInterface != null && target != null) {
189
			target.removeChangeListener(privateInterface);
190
			target.removeStaleListener(privateInterface);
191
		}
192
		target = null;
193
		privateInterface = null;
194
		super.dispose();
195
	}
196
}

Return to bug 175735