Bug 564010 - Add property versions of WorkbenchObservables methods

And move implementation of the new property classes to an internal
package.

This is done to prepare for the deprecation of WorkbenchObservables.

Change-Id: I271aee33ff8f99031dfc970c7702ffe6db244bae
Signed-off-by: Jens Lidestrom <jens@lidestrom.se>
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkbenchPreferenceConstants.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkbenchPreferenceConstants.java
index 1c46b90..911217c 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkbenchPreferenceConstants.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkbenchPreferenceConstants.java
@@ -388,7 +388,7 @@
 	 *
 	 * @deprecated No longer in use. Use swt-corner-radius CSS property to override
 	 *             when round or square corners are desired.
-	 * @since 3.119
+	 * @since 3.120
 	 */
 	@Deprecated
 	String USE_ROUND_TABS = "USE_ROUND_TABS"; //$NON-NLS-1$
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkingSetUpdater2.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkingSetUpdater2.java
index b91d4ba..dd1ee5b 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkingSetUpdater2.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkingSetUpdater2.java
@@ -19,7 +19,7 @@
  * <code>IWorkingSetUpdater2</code> can be used to restore the content of a
  * working set, if the working set content shouldn't be persisted by the framework.
  *
- * @since 3.119
+ * @since 3.120
  */
 public interface IWorkingSetUpdater2 extends IWorkingSetUpdater {
 
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/databinding/WorkbenchObservables.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/databinding/WorkbenchObservables.java
index 71b4493..a04a75b 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/databinding/WorkbenchObservables.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/databinding/WorkbenchObservables.java
@@ -16,10 +16,6 @@
  *******************************************************************************/
 package org.eclipse.ui.databinding;
 
-import org.eclipse.core.databinding.observable.Diffs;
-import org.eclipse.core.databinding.observable.IObservable;
-import org.eclipse.core.databinding.observable.Realm;
-import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
 import org.eclipse.core.databinding.observable.value.ComputedValue;
 import org.eclipse.core.databinding.observable.value.IObservableValue;
 import org.eclipse.core.runtime.Assert;
@@ -28,15 +24,10 @@
 import org.eclipse.ui.IEditorInput;
 import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.IEditorReference;
-import org.eclipse.ui.IPageListener;
-import org.eclipse.ui.IPartListener2;
 import org.eclipse.ui.IPartService;
-import org.eclipse.ui.IPropertyListener;
 import org.eclipse.ui.ISelectionService;
-import org.eclipse.ui.IWindowListener;
 import org.eclipse.ui.IWorkbench;
 import org.eclipse.ui.IWorkbenchPage;
-import org.eclipse.ui.IWorkbenchPartConstants;
 import org.eclipse.ui.IWorkbenchPartReference;
 import org.eclipse.ui.IWorkbenchWindow;
 import org.eclipse.ui.databinding.typed.WorkbenchProperties;
@@ -119,44 +110,7 @@
 	 */
 	public static IObservableValue<IWorkbenchWindow> observeActiveWorkbenchWindow(IWorkbench workbench) {
 		Assert.isNotNull(workbench);
-		return new ListeningValue<IWorkbenchWindow>() {
-			private final IWindowListener listener = new IWindowListener() {
-				@Override
-				public void windowActivated(IWorkbenchWindow window) {
-					protectedSetValue(window);
-				}
-
-				@Override
-				public void windowDeactivated(IWorkbenchWindow window) {
-					if (window == doGetValue()) {
-						protectedSetValue(null);
-					}
-				}
-
-				@Override
-				public void windowClosed(IWorkbenchWindow window) {
-				}
-
-				@Override
-				public void windowOpened(IWorkbenchWindow window) {
-				}
-			};
-
-			@Override
-			protected void startListening() {
-				workbench.addWindowListener(listener);
-			}
-
-			@Override
-			protected void stopListening() {
-				workbench.removeWindowListener(listener);
-			}
-
-			@Override
-			protected IWorkbenchWindow calculate() {
-				return workbench.getActiveWorkbenchWindow();
-			}
-		};
+		return WorkbenchProperties.activeWindow().observe(workbench);
 	}
 
 	/**
@@ -169,40 +123,7 @@
 	 */
 	public static IObservableValue<IWorkbenchPage> observeActiveWorkbenchPage(IWorkbenchWindow window) {
 		Assert.isNotNull(window);
-		return new ListeningValue<IWorkbenchPage>() {
-			private final IPageListener listener = new IPageListener() {
-				@Override
-				public void pageActivated(IWorkbenchPage page) {
-					protectedSetValue(page);
-				}
-
-				@Override
-				public void pageClosed(IWorkbenchPage page) {
-					if (page == doGetValue()) {
-						protectedSetValue(null);
-					}
-				}
-
-				@Override
-				public void pageOpened(IWorkbenchPage page) {
-				}
-			};
-
-			@Override
-			protected void startListening() {
-				window.addPageListener(listener);
-			}
-
-			@Override
-			protected void stopListening() {
-				window.removePageListener(listener);
-			}
-
-			@Override
-			protected IWorkbenchPage calculate() {
-				return window.getActivePage();
-			}
-		};
+		return WorkbenchProperties.activePage().observe(window);
 	}
 
 	/**
@@ -216,60 +137,7 @@
 	 */
 	public static IObservableValue<IWorkbenchPartReference> observeActivePart(IPartService partService) {
 		Assert.isNotNull(partService);
-		return new ListeningValue<IWorkbenchPartReference>() {
-			private final IPartListener2 listener = new IPartListener2() {
-				@Override
-				public void partActivated(IWorkbenchPartReference partRef) {
-					protectedSetValue(partRef);
-				}
-
-				@Override
-				public void partDeactivated(IWorkbenchPartReference partRef) {
-					if (partRef == doGetValue()) {
-						protectedSetValue(null);
-					}
-				}
-
-				@Override
-				public void partBroughtToTop(IWorkbenchPartReference partRef) {
-				}
-
-				@Override
-				public void partClosed(IWorkbenchPartReference partRef) {
-				}
-
-				@Override
-				public void partOpened(IWorkbenchPartReference partRef) {
-				}
-
-				@Override
-				public void partHidden(IWorkbenchPartReference partRef) {
-				}
-
-				@Override
-				public void partVisible(IWorkbenchPartReference partRef) {
-				}
-
-				@Override
-				public void partInputChanged(IWorkbenchPartReference partRef) {
-				}
-			};
-
-			@Override
-			protected void startListening() {
-				partService.addPartListener(listener);
-			}
-
-			@Override
-			protected void stopListening() {
-				partService.removePartListener(listener);
-			}
-
-			@Override
-			protected IWorkbenchPartReference calculate() {
-				return partService.getActivePartReference();
-			}
-		};
+		return WorkbenchProperties.activePartReference().observe(partService);
 	}
 
 	/**
@@ -299,131 +167,6 @@
 	 */
 	public static IObservableValue<IEditorInput> observeEditorInput(IEditorPart editor) {
 		Assert.isNotNull(editor);
-		return new ListeningValue<IEditorInput>() {
-			private final IPropertyListener listener = (Object source, int propId) -> {
-				if (propId == IWorkbenchPartConstants.PROP_INPUT) {
-					protectedSetValue(editor.getEditorInput());
-				}
-			};
-
-			@Override
-			protected void startListening() {
-				editor.addPropertyListener(listener);
-			}
-
-			@Override
-			protected void stopListening() {
-				editor.removePropertyListener(listener);
-			}
-
-			@Override
-			protected IEditorInput calculate() {
-				return editor.getEditorInput();
-			}
-		};
-	}
-
-	/**
-	 * A base class for creating observable values that track the state of a
-	 * non-{@link IObservable} objects.
-	 */
-	private abstract static class ListeningValue<T> extends AbstractObservableValue<T> {
-		private T value;
-		private boolean isListening;
-		private volatile boolean hasListeners;
-
-		@Override
-		protected final T doGetValue() {
-			// The value is not kept up to date when we are not listening.
-			if (isListening) {
-				return value;
-			}
-			return calculate();
-		}
-
-		/**
-		 * Sets the value. Must be invoked in the {@link Realm} of the observable.
-		 * Subclasses must call this method instead of {@link #setValue} or
-		 * {@link #doSetValue}.
-		 *
-		 * @param value the value to set
-		 */
-		protected final void protectedSetValue(T value) {
-			checkRealm();
-			if (!isListening)
-				throw new IllegalStateException();
-			if (this.value != value) {
-				fireValueChange(Diffs.createValueDiff(this.value, this.value = value));
-			}
-		}
-
-		@Override
-		protected final void firstListenerAdded() {
-			if (getRealm().isCurrent()) {
-				startListeningInternal();
-			} else {
-				getRealm().asyncExec(() -> {
-					if (hasListeners && !isListening) {
-						startListeningInternal();
-					}
-				});
-			}
-			hasListeners = true;
-			super.firstListenerAdded();
-		}
-
-		@Override
-		protected final void lastListenerRemoved() {
-			if (getRealm().isCurrent()) {
-				stopListeningInternal();
-			} else {
-				getRealm().asyncExec(() -> {
-					if (!hasListeners && isListening) {
-						stopListeningInternal();
-					}
-				});
-			}
-			hasListeners = false;
-			super.lastListenerRemoved();
-		}
-
-		private void startListeningInternal() {
-			isListening = true;
-			value = calculate();
-			startListening();
-		}
-
-		private void stopListeningInternal() {
-			isListening = false;
-			value = null;
-			stopListening();
-		}
-
-		/**
-		 * Subclasses must override this method to attach listeners to the
-		 * non-{@link IObservable} objects the state of which is tracked by this
-		 * observable.
-		 */
-		protected abstract void startListening();
-
-		/**
-		 * Subclasses must override this method to detach listeners from the
-		 * non-{@link IObservable} objects the state of which is tracked by this
-		 * observable.
-		 */
-		protected abstract void stopListening();
-
-		/**
-		 * Subclasses must override this method to provide the object's value that will
-		 * be used when the value is not set explicitly by {@link #doSetValue(Object)}.
-		 *
-		 * @return the object's value
-		 */
-		protected abstract T calculate();
-
-		@Override
-		public Object getValueType() {
-			return null;
-		}
+		return WorkbenchProperties.editorInput().observe(editor);
 	}
 }
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/databinding/typed/WorkbenchProperties.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/databinding/typed/WorkbenchProperties.java
index ce24631..db3ae9a 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/databinding/typed/WorkbenchProperties.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/databinding/typed/WorkbenchProperties.java
@@ -14,12 +14,26 @@
  *******************************************************************************/
 package org.eclipse.ui.databinding.typed;
 
+import org.eclipse.core.databinding.property.Properties;
 import org.eclipse.core.databinding.property.list.IListProperty;
 import org.eclipse.core.databinding.property.value.IValueProperty;
 import org.eclipse.core.runtime.IAdapterManager;
 import org.eclipse.core.runtime.Platform;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IPageService;
+import org.eclipse.ui.IPartService;
 import org.eclipse.ui.ISelectionService;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPartReference;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.internal.databinding.ActivePageProperty;
+import org.eclipse.ui.internal.databinding.ActivePartProperty;
+import org.eclipse.ui.internal.databinding.ActiveWindowProperty;
 import org.eclipse.ui.internal.databinding.AdaptedValueProperty;
+import org.eclipse.ui.internal.databinding.EditorInputProperty;
 import org.eclipse.ui.internal.databinding.MultiSelectionProperty;
 import org.eclipse.ui.internal.databinding.SingleSelectionProperty;
 
@@ -175,4 +189,70 @@
 		return new MultiSelectionProperty<>(partId, postSelection, elementType);
 	}
 
+	/**
+	 * Returns a property for observing the active window of a workbench. The value
+	 * is null if there is no active window.
+	 *
+	 * @return the property
+	 * @see IWorkbench#getActiveWorkbenchWindow
+	 * @see IWorkbench#addWindowListener
+	 * @since 3.120
+	 */
+	public static <S extends IWorkbench> IValueProperty<S, IWorkbenchWindow> activeWindow() {
+		return new ActiveWindowProperty<>();
+	}
+
+	/**
+	 * Returns a property for observing the active page of a workbench window. The
+	 * value is null if there is no active page.
+	 *
+	 * @return the property
+	 * @see IWorkbenchWindow#getActivePage
+	 * @see IWorkbenchWindow#addPageListener
+	 * @since 3.120
+	 */
+	public static <S extends IPageService> IValueProperty<S, IWorkbenchPage> activePage() {
+		return new ActivePageProperty<>();
+	}
+
+	/**
+	 * Returns a property for observing the active part reference of a part service.
+	 * The value is null if there is no active part.
+	 *
+	 * @return the property
+	 * @see IPartService#getActivePart
+	 * @see IPartService#addPartListener
+	 * @since 3.120
+	 */
+	public static <S extends IPartService> IValueProperty<S, IWorkbenchPartReference> activePartReference() {
+		return new ActivePartProperty<>();
+	}
+
+	/**
+	 * Returns a property for observing the active part reference of a part service,
+	 * casted to {@link IEditorReference}. The value is null if the active part is
+	 * not an {@code IEditorReference}. Note that this value is different from
+	 * {@link IWorkbenchPage#getActiveEditor}.
+	 *
+	 * @return the property
+	 * @see IPartService#getActivePart
+	 * @see IPartService#addPartListener
+	 * @since 3.120
+	 */
+	public static <S extends IPartService> IValueProperty<S, IEditorReference> activePartAsEditorReference() {
+		return WorkbenchProperties.<S>activePartReference().value(Properties.convertedValue(IEditorReference.class,
+						part -> part instanceof IEditorReference ? (IEditorReference) part : null));
+	}
+
+	/**
+	 * Returns a property for observing the editor input an editor part.
+	 *
+	 * @return the property
+	 * @see IEditorPart#getEditorInput
+	 * @see IEditorPart#addPropertyListener
+	 * @since 3.120
+	 */
+	public static <S extends IEditorPart> IValueProperty<S, IEditorInput> editorInput() {
+		return new EditorInputProperty<>();
+	}
 }
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ActivePageProperty.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ActivePageProperty.java
new file mode 100644
index 0000000..5e6ff7f
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ActivePageProperty.java
@@ -0,0 +1,74 @@
+package org.eclipse.ui.internal.databinding;
+
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.property.value.ValueProperty;
+import org.eclipse.ui.IPageListener;
+import org.eclipse.ui.IPageService;
+import org.eclipse.ui.IWorkbenchPage;
+
+/**
+ * A property for observing the active {@link IWorkbenchPage} of a
+ * {@link IPageService}.
+ *
+ * @param <S> the source type
+ */
+public class ActivePageProperty<S extends IPageService> extends ValueProperty<S, IWorkbenchPage> {
+	@Override
+	public Object getValueType() {
+		return IWorkbenchPage.class;
+	}
+
+	@Override
+	protected IWorkbenchPage doGetValue(S source) {
+		return source.getActivePage();
+	}
+
+	@Override
+	protected void doSetValue(S source, IWorkbenchPage value) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public IObservableValue<IWorkbenchPage> observe(Realm realm, S source) {
+		return new ListeningValue<IWorkbenchPage>(realm) {
+			private final IPageListener listener = new IPageListener() {
+				@Override
+				public void pageActivated(IWorkbenchPage page) {
+					protectedSetValue(page);
+				}
+
+				@Override
+				public void pageClosed(IWorkbenchPage page) {
+					if (page == doGetValue()) {
+						protectedSetValue(null);
+					}
+				}
+
+				@Override
+				public void pageOpened(IWorkbenchPage page) {
+				}
+			};
+
+			@Override
+			protected void startListening() {
+				source.addPageListener(listener);
+			}
+
+			@Override
+			protected void stopListening() {
+				source.removePageListener(listener);
+			}
+
+			@Override
+			protected IWorkbenchPage calculate() {
+				return ActivePageProperty.this.getValue(source);
+			}
+
+			@Override
+			public Object getValueType() {
+				return ActivePageProperty.this.getValueType();
+			}
+		};
+	}
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ActivePartProperty.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ActivePartProperty.java
new file mode 100644
index 0000000..4a92e53
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ActivePartProperty.java
@@ -0,0 +1,70 @@
+package org.eclipse.ui.internal.databinding;
+
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.property.value.ValueProperty;
+import org.eclipse.ui.IPartListener2;
+import org.eclipse.ui.IPartService;
+import org.eclipse.ui.IWorkbenchPartReference;
+
+/**
+ * A property for observing the active {@link IWorkbenchPartReference} of a
+ * {@link IPartService}.
+ *
+ * @param <S> the source type
+ */
+public class ActivePartProperty<S extends IPartService> extends ValueProperty<S, IWorkbenchPartReference> {
+	@Override
+	public Object getValueType() {
+		return IWorkbenchPartReference.class;
+	}
+
+	@Override
+	protected IWorkbenchPartReference doGetValue(S source) {
+		return source.getActivePartReference();
+	}
+
+	@Override
+	protected void doSetValue(S source, IWorkbenchPartReference value) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public IObservableValue<IWorkbenchPartReference> observe(Realm realm, S source) {
+		return new ListeningValue<IWorkbenchPartReference>(realm) {
+			private final IPartListener2 listener = new IPartListener2() {
+				@Override
+				public void partActivated(IWorkbenchPartReference partRef) {
+					protectedSetValue(partRef);
+				}
+
+				@Override
+				public void partDeactivated(IWorkbenchPartReference partRef) {
+					if (partRef == doGetValue()) {
+						protectedSetValue(null);
+					}
+				}
+			};
+
+			@Override
+			protected void startListening() {
+				source.addPartListener(listener);
+			}
+
+			@Override
+			protected void stopListening() {
+				source.removePartListener(listener);
+			}
+
+			@Override
+			protected IWorkbenchPartReference calculate() {
+				return ActivePartProperty.this.getValue(source);
+			}
+
+			@Override
+			public Object getValueType() {
+				return ActivePartProperty.this.getValueType();
+			}
+		};
+	}
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ActiveWindowProperty.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ActiveWindowProperty.java
new file mode 100644
index 0000000..b2c278b
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ActiveWindowProperty.java
@@ -0,0 +1,78 @@
+package org.eclipse.ui.internal.databinding;
+
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.property.value.ValueProperty;
+import org.eclipse.ui.IWindowListener;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchWindow;
+
+/**
+ * A property for observing the active {@link IWorkbenchWindow} of a
+ * {@link IWorkbench}.
+ *
+ * @param <S> the source type
+ */
+public class ActiveWindowProperty<S extends IWorkbench> extends ValueProperty<S, IWorkbenchWindow> {
+	@Override
+	public Object getValueType() {
+		return IWorkbenchWindow.class;
+	}
+
+	@Override
+	protected IWorkbenchWindow doGetValue(S source) {
+		return source.getActiveWorkbenchWindow();
+	}
+
+	@Override
+	protected void doSetValue(S source, IWorkbenchWindow value) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public IObservableValue<IWorkbenchWindow> observe(Realm realm, S source) {
+		return new ListeningValue<IWorkbenchWindow>(realm) {
+			private final IWindowListener listener = new IWindowListener() {
+				@Override
+				public void windowActivated(IWorkbenchWindow window) {
+					protectedSetValue(window);
+				}
+
+				@Override
+				public void windowDeactivated(IWorkbenchWindow window) {
+					if (window == doGetValue()) {
+						protectedSetValue(null);
+					}
+				}
+
+				@Override
+				public void windowClosed(IWorkbenchWindow window) {
+				}
+
+				@Override
+				public void windowOpened(IWorkbenchWindow window) {
+				}
+			};
+
+			@Override
+			protected void startListening() {
+				source.addWindowListener(listener);
+			}
+
+			@Override
+			protected void stopListening() {
+				source.removeWindowListener(listener);
+			}
+
+			@Override
+			protected IWorkbenchWindow calculate() {
+				return ActiveWindowProperty.this.getValue(source);
+			}
+
+			@Override
+			public Object getValueType() {
+				return ActiveWindowProperty.this.getValueType();
+			}
+		};
+	}
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/EditorInputProperty.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/EditorInputProperty.java
new file mode 100644
index 0000000..6716db2
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/EditorInputProperty.java
@@ -0,0 +1,62 @@
+package org.eclipse.ui.internal.databinding;
+
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.property.value.ValueProperty;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IPropertyListener;
+import org.eclipse.ui.IWorkbenchPartConstants;
+
+/**
+ * A property for observing the {@link IEditorInput} of and {@link IEditorPart}.
+ *
+ * @param <S> the source type
+ */
+public class EditorInputProperty<S extends IEditorPart> extends ValueProperty<S, IEditorInput> {
+	@Override
+	public Object getValueType() {
+		return IEditorInput.class;
+	}
+
+	@Override
+	protected IEditorInput doGetValue(S source) {
+		return source.getEditorInput();
+	}
+
+	@Override
+	protected void doSetValue(S source, IEditorInput value) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public IObservableValue<IEditorInput> observe(Realm realm, S source) {
+		return new ListeningValue<IEditorInput>(realm) {
+			private final IPropertyListener listener = (Object s, int propId) -> {
+				if (propId == IWorkbenchPartConstants.PROP_INPUT) {
+					protectedSetValue(source.getEditorInput());
+				}
+			};
+
+			@Override
+			protected void startListening() {
+				source.addPropertyListener(listener);
+			}
+
+			@Override
+			protected void stopListening() {
+				source.removePropertyListener(listener);
+			}
+
+			@Override
+			protected IEditorInput calculate() {
+				return EditorInputProperty.this.getValue(source);
+			}
+
+			@Override
+			public Object getValueType() {
+				return EditorInputProperty.this.getValueType();
+			}
+		};
+	}
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ListeningValue.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ListeningValue.java
new file mode 100644
index 0000000..b9371dd
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/databinding/ListeningValue.java
@@ -0,0 +1,112 @@
+package org.eclipse.ui.internal.databinding;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IObservable;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
+
+/**
+ * A base class for creating observable values that track the state of a
+ * non-{@link IObservable} objects.
+ *
+ * @param <T> the value type
+ */
+abstract class ListeningValue<T> extends AbstractObservableValue<T> {
+	private T value;
+	private boolean isListening;
+	private volatile boolean hasListeners;
+
+	public ListeningValue(Realm realm) {
+		super(realm);
+	}
+
+	@Override
+	protected final T doGetValue() {
+		// The value is not kept up to date when we are not listening.
+		if (isListening) {
+			return value;
+		}
+		return calculate();
+	}
+
+	/**
+	 * Sets the value. Must be invoked in the {@link Realm} of the observable.
+	 * Subclasses must call this method instead of {@link #setValue} or
+	 * {@link #doSetValue}.
+	 *
+	 * @param value the value to set
+	 */
+	protected final void protectedSetValue(T value) {
+		checkRealm();
+		if (!isListening)
+			throw new IllegalStateException();
+		if (this.value != value) {
+			fireValueChange(Diffs.createValueDiff(this.value, this.value = value));
+		}
+	}
+
+	@Override
+	protected final void firstListenerAdded() {
+		if (getRealm().isCurrent()) {
+			startListeningInternal();
+		} else {
+			getRealm().asyncExec(() -> {
+				if (hasListeners && !isListening) {
+					startListeningInternal();
+				}
+			});
+		}
+		hasListeners = true;
+		super.firstListenerAdded();
+	}
+
+	@Override
+	protected final void lastListenerRemoved() {
+		if (getRealm().isCurrent()) {
+			stopListeningInternal();
+		} else {
+			getRealm().asyncExec(() -> {
+				if (!hasListeners && isListening) {
+					stopListeningInternal();
+				}
+			});
+		}
+		hasListeners = false;
+		super.lastListenerRemoved();
+	}
+
+	private void startListeningInternal() {
+		isListening = true;
+		value = calculate();
+		startListening();
+	}
+
+	private void stopListeningInternal() {
+		isListening = false;
+		value = null;
+		stopListening();
+	}
+
+	/**
+	 * Subclasses must override this method to attach listeners to the
+	 * non-{@link IObservable} objects the state of which is tracked by this
+	 * observable.
+	 */
+	protected abstract void startListening();
+
+	/**
+	 * Subclasses must override this method to detach listeners from the
+	 * non-{@link IObservable} objects the state of which is tracked by this
+	 * observable.
+	 */
+	protected abstract void stopListening();
+
+	/**
+	 * Subclasses must override this method to provide the object's value that will
+	 * be used when the value is not set explicitly by
+	 * {@link AbstractObservableValue#doSetValue(Object)}.
+	 *
+	 * @return the object's value
+	 */
+	protected abstract T calculate();
+}
diff --git a/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF
index 33a2a21..526c4b3 100644
--- a/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.ui.workbench; singleton:=true
-Bundle-Version: 3.119.100.qualifier
+Bundle-Version: 3.120.0.qualifier
 Bundle-ClassPath: .
 Bundle-Activator: org.eclipse.ui.internal.WorkbenchPlugin
 Bundle-ActivationPolicy: lazy
diff --git a/bundles/org.eclipse.ui.workbench/pom.xml b/bundles/org.eclipse.ui.workbench/pom.xml
index 41b5507..aa0f581 100644
--- a/bundles/org.eclipse.ui.workbench/pom.xml
+++ b/bundles/org.eclipse.ui.workbench/pom.xml
@@ -20,7 +20,7 @@
   </parent>
   <groupId>org.eclipse.ui</groupId>
   <artifactId>org.eclipse.ui.workbench</artifactId>
-  <version>3.119.100-SNAPSHOT</version>
+  <version>3.120.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 
   <properties>
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java
index 21b02de..f4a073f 100644
--- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java
@@ -25,6 +25,7 @@
 import org.eclipse.ui.tests.commands.CommandsTestSuite;
 import org.eclipse.ui.tests.concurrency.ConcurrencyTestSuite;
 import org.eclipse.ui.tests.contexts.ContextsTestSuite;
+import org.eclipse.ui.tests.databinding.WorkbenchDatabindingTest;
 import org.eclipse.ui.tests.datatransfer.DataTransferTestSuite;
 import org.eclipse.ui.tests.decorators.DecoratorsTestSuite;
 import org.eclipse.ui.tests.dialogs.FilteredResourcesSelectionDialogTestSuite;
@@ -100,6 +101,7 @@
 	IntroTestSuite.class,
 	MultiEditorTestSuite.class,
 	OpenSystemInPlaceEditorTest.class,
+	WorkbenchDatabindingTest.class,
 })
 public class UiTestSuite {
 }
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/databinding/WorkbenchDatabindingTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/databinding/WorkbenchDatabindingTest.java
new file mode 100644
index 0000000..c461935
--- /dev/null
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/databinding/WorkbenchDatabindingTest.java
@@ -0,0 +1,337 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Jens Lidestrom and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Jens Lidestrom - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.tests.databinding;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.databinding.observable.value.ValueDiff;
+import org.eclipse.core.databinding.property.value.IValueProperty;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IPageListener;
+import org.eclipse.ui.IPageService;
+import org.eclipse.ui.IPartListener2;
+import org.eclipse.ui.IPartService;
+import org.eclipse.ui.IPropertyListener;
+import org.eclipse.ui.IWindowListener;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPartReference;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.databinding.typed.WorkbenchProperties;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests for {@link WorkbenchProperties}.
+ */
+public class WorkbenchDatabindingTest {
+	private static TestRealm realm;
+
+	@BeforeClass
+	public static void setup() {
+		realm = new TestRealm();
+	}
+
+	@Test
+	public void testActiveWindow() {
+		IWorkbench workbench = mock(IWorkbench.class);
+		IWorkbenchWindow win1 = mock(IWorkbenchWindow.class);
+		IWorkbenchWindow win2 = mock(IWorkbenchWindow.class);
+
+		when(workbench.getActiveWorkbenchWindow()) //
+				.thenReturn(win1, win1, win2) // One initial value, on when listener is added, one after change
+				.thenThrow(new AssertionError());
+
+		AtomicReference<IWindowListener> listener = new AtomicReference<>();
+		doAnswer(inv -> {
+			listener.set((IWindowListener) inv.getArgument(0));
+			return null;
+		}).when(workbench).addWindowListener(any());
+
+		List<ValueChangeEvent<?>> events = new ArrayList<>();
+
+		IObservableValue<IWorkbenchWindow> value = WorkbenchProperties.activeWindow().observe(workbench);
+
+		// Check initial value
+		assertSame(win1, value.getValue());
+
+		IValueChangeListener<Object> changeListener = events::add;
+		value.addValueChangeListener(changeListener);
+
+		// Update using listener
+		listener.get().windowActivated(win2);
+
+		assertSame(win2, value.getValue());
+		assertEquals(1, events.size());
+		ValueDiff<?> diff = events.get(0).diff;
+		assertEquals(win1, diff.getOldValue());
+		assertEquals(win2, diff.getNewValue());
+
+		// Other window is deactivated
+		listener.get().windowDeactivated(win1);
+
+		assertSame(win2, value.getValue());
+		assertEquals(1, events.size());
+
+		// Active window is deactivated
+		listener.get().windowDeactivated(win2);
+
+		assertNull(value.getValue());
+		assertEquals(2, events.size());
+
+		value.removeValueChangeListener(changeListener);
+		value.dispose();
+	}
+
+	@Test
+	public void testActiveWindowValueType() {
+		IObservableValue<IWorkbenchWindow> value = WorkbenchProperties.activeWindow().observe(mock(IWorkbench.class));
+		assertEquals(IWorkbenchWindow.class, value.getValueType());
+		assertEquals(IWorkbenchWindow.class, WorkbenchProperties.activeWindow().getValueType());
+	}
+
+	@Test
+	public void testActivePage() {
+		IWorkbenchWindow window = mock(IWorkbenchWindow.class);
+		IWorkbenchPage page1 = mock(IWorkbenchPage.class);
+		IWorkbenchPage page2 = mock(IWorkbenchPage.class);
+
+		when(window.getActivePage()) //
+				.thenReturn(page1, page1, page2) // One initial value, on when listener is added, one after change
+				.thenThrow(new AssertionError());
+
+		AtomicReference<IPageListener> listener = new AtomicReference<>();
+		doAnswer(inv -> {
+			listener.set((IPageListener) inv.getArgument(0));
+			return null;
+		}).when(window).addPageListener(any());
+
+		List<ValueChangeEvent<?>> events = new ArrayList<>();
+
+		IObservableValue<IWorkbenchPage> value = WorkbenchProperties.activePage().observe(window);
+
+		// Check initial value
+		assertSame(page1, value.getValue());
+
+		IValueChangeListener<Object> changeListener = events::add;
+		value.addValueChangeListener(changeListener);
+
+		// Update using listener
+		listener.get().pageActivated(page2);
+
+		assertSame(page2, value.getValue());
+		assertEquals(1, events.size());
+		ValueDiff<?> diff = events.get(0).diff;
+		assertEquals(page1, diff.getOldValue());
+		assertEquals(page2, diff.getNewValue());
+
+		// Other page is closed
+		listener.get().pageClosed(page1);
+
+		assertSame(page2, value.getValue());
+		assertEquals(1, events.size());
+
+		// Active page is closed
+		listener.get().pageClosed(page2);
+
+		assertNull(value.getValue());
+		assertEquals(2, events.size());
+
+		value.removeValueChangeListener(changeListener);
+		value.dispose();
+	}
+
+	@Test
+	public void testActivePageValueType() {
+		IObservableValue<IWorkbenchPage> value = WorkbenchProperties.activePage().observe(mock(IPageService.class));
+		assertEquals(IWorkbenchPage.class, value.getValueType());
+		assertEquals(IWorkbenchPage.class, WorkbenchProperties.activePage().getValueType());
+	}
+
+	@Test
+	public void testActivePartReference() {
+		IPartService service = mock(IPartService.class);
+		IWorkbenchPartReference part1 = mock(IWorkbenchPartReference.class);
+		IWorkbenchPartReference part2 = mock(IWorkbenchPartReference.class);
+
+		when(service.getActivePartReference()) //
+				.thenReturn(part1, part1, part2) // One initial value, on when listener is added, one after change
+				.thenThrow(new AssertionError());
+
+		AtomicReference<IPartListener2> listener = new AtomicReference<>();
+		doAnswer(inv -> {
+			listener.set((IPartListener2) inv.getArgument(0));
+			return null;
+		}).when(service).addPartListener(any(IPartListener2.class));
+
+		List<ValueChangeEvent<?>> events = new ArrayList<>();
+
+		IObservableValue<IWorkbenchPartReference> value = WorkbenchProperties.activePartReference().observe(service);
+
+		// Check initial value
+		assertSame(part1, value.getValue());
+
+		IValueChangeListener<Object> changeListener = events::add;
+		value.addValueChangeListener(changeListener);
+
+		// Update using listener
+		listener.get().partActivated(part2);
+
+		assertSame(part2, value.getValue());
+		assertEquals(1, events.size());
+		ValueDiff<?> diff = events.get(0).diff;
+		assertEquals(part1, diff.getOldValue());
+		assertEquals(part2, diff.getNewValue());
+
+		// Other part is deactivated
+		listener.get().partDeactivated(part1);
+
+		assertSame(part2, value.getValue());
+		assertEquals(1, events.size());
+
+		// Active part is deactivated
+		listener.get().partDeactivated(part2);
+
+		assertNull(value.getValue());
+		assertEquals(2, events.size());
+
+		value.removeValueChangeListener(changeListener);
+		value.dispose();
+	}
+
+	@Test
+	public void testActivePartReferenceValueType() {
+		IObservableValue<IWorkbenchPartReference> value = WorkbenchProperties.activePartReference().observe(mock(IPartService.class));
+		assertEquals(IWorkbenchPartReference.class, value.getValueType());
+		assertEquals(IWorkbenchPartReference.class, WorkbenchProperties.activePartReference().getValueType());
+	}
+
+	/**
+	 * {@link WorkbenchProperties#activeEditorReference} is implemented using
+	 * {@link WorkbenchProperties#activePartReference}, so we only need to verify
+	 * that the conversion works.
+	 */
+	@Test
+	public void testActiveEditorReference() {
+		IPartService service = mock(IPartService.class);
+		IWorkbenchPartReference part = mock(IWorkbenchPartReference.class);
+		IEditorReference editor = mock(IEditorReference.class);
+
+		when(service.getActivePartReference()) //
+				.thenReturn(part, editor) // One initial value, on when listener is added, one after change
+				.thenThrow(new AssertionError());
+
+		IValueProperty<IPartService, IEditorReference> prop = WorkbenchProperties.activePartAsEditorReference();
+
+		assertNull(prop.getValue(service));
+		assertSame(editor, prop.getValue(service));
+	}
+
+	@Test
+	public void testActiveEditorReferenceValueType() {
+		IObservableValue<IEditorReference> value = WorkbenchProperties.activePartAsEditorReference().observe(mock(IPartService.class));
+		assertEquals(IEditorReference.class, value.getValueType());
+		assertEquals(IEditorReference.class, WorkbenchProperties.activePartAsEditorReference().getValueType());
+	}
+
+	/**
+	 * {@link WorkbenchProperties#activeEditorReference} is implemented using
+	 * {@link WorkbenchProperties#activePartReference}, so we only need to verify
+	 * that the conversion works.
+	 */
+	@Test
+	public void testEditorInput() {
+		IEditorPart editor = mock(IEditorPart.class);
+		IEditorInput input1 = mock(IEditorInput.class);
+		IEditorInput input2 = mock(IEditorInput.class);
+
+		when(editor.getEditorInput()) //
+				.thenReturn(input1, input1, input2) // One initial value, on when listener is added, one after change
+				.thenThrow(new AssertionError());
+
+		AtomicReference<IPropertyListener> listener = new AtomicReference<>();
+		doAnswer(inv -> {
+			listener.set((IPropertyListener) inv.getArgument(0));
+			return null;
+		}).when(editor).addPropertyListener(any(IPropertyListener.class));
+
+		List<ValueChangeEvent<?>> events = new ArrayList<>();
+
+		IObservableValue<IEditorInput> value = WorkbenchProperties.editorInput().observe(editor);
+
+		// Check initial value
+		assertSame(input1, value.getValue());
+
+		IValueChangeListener<Object> changeListener = events::add;
+		value.addValueChangeListener(changeListener);
+
+		// Update using listener
+		listener.get().propertyChanged(editor, IEditorPart.PROP_INPUT);
+
+		assertSame(input2, value.getValue());
+		assertEquals(1, events.size());
+		ValueDiff<?> diff = events.get(0).diff;
+		assertEquals(input1, diff.getOldValue());
+		assertEquals(input2, diff.getNewValue());
+
+		value.removeValueChangeListener(changeListener);
+		value.dispose();
+	}
+
+	@Test
+	public void testEditorInputValueType() {
+		IObservableValue<IEditorInput> value = WorkbenchProperties.editorInput().observe(mock(IEditorPart.class));
+		assertEquals(IEditorInput.class, value.getValueType());
+		assertEquals(IEditorInput.class, WorkbenchProperties.editorInput().getValueType());
+	}
+
+	@AfterClass
+	public static void teardown() {
+		realm.restore();
+	}
+
+	private static class TestRealm extends Realm {
+		private Realm old;
+
+		public TestRealm() {
+			setDefault(this);
+		}
+
+		@Override
+		public boolean isCurrent() {
+			return true;
+		}
+
+		public void restore() {
+			setDefault(old);
+		}
+	}
+}
diff --git a/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF
index a2300c4..2a53ecf 100644
--- a/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.ui.tests/META-INF/MANIFEST.MF
@@ -23,6 +23,7 @@
  org.eclipse.core.filesystem,
  org.eclipse.core.databinding,
  org.eclipse.core.databinding.beans,
+ org.eclipse.core.databinding.property,
  org.eclipse.jface.databinding,
  org.eclipse.ui.navigator.resources,
  org.eclipse.core.runtime,