diff options
| author | Jens Lidestrom | 2020-06-05 21:46:44 +0000 |
|---|---|---|
| committer | Jens Lideström | 2020-06-19 11:18:21 +0000 |
| commit | ed4c96aea6a0d1e77b8a5b9e8793144ddda2ded0 (patch) | |
| tree | b91003eb5f3738eedb7bdab4e22a025a368a3e1c | |
| parent | 90384b2caf1e05a0906142c13cf1aad5948a3957 (diff) | |
| download | eclipse.platform.ui-ed4c96aea6a0d1e77b8a5b9e8793144ddda2ded0.tar.gz eclipse.platform.ui-ed4c96aea6a0d1e77b8a5b9e8793144ddda2ded0.tar.xz eclipse.platform.ui-ed4c96aea6a0d1e77b8a5b9e8793144ddda2ded0.zip | |
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>
14 files changed, 824 insertions, 265 deletions
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 1c46b90917c..911217c07e9 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 @@ public interface IWorkbenchPreferenceConstants { * * @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 b91d4bad186..dd1ee5b273a 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 @@ import org.eclipse.core.runtime.IAdaptable; * <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 71b449311cb..a04a75b7d28 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.core.runtime.Platform; 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 class WorkbenchObservables { */ 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 class WorkbenchObservables { */ 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 class WorkbenchObservables { */ 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 class WorkbenchObservables { */ 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 ce24631916d..db3ae9a1c02 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 @@ public class WorkbenchProperties { 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 00000000000..5e6ff7fa29c --- /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 00000000000..4a92e534187 --- /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 00000000000..b2c278b922b --- /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 00000000000..6716db25add --- /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 00000000000..b9371dd2abb --- /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 33a2a2123da..526c4b35f26 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 @@ Manifest-Version: 1.0 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 41b5507167d..aa0f58105d2 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 21b02de901c..f4a073f8bb2 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.api.StartupTest; 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 @@ import org.junit.runners.Suite; 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 00000000000..c4619351419 --- /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 a2300c40afa..2a53ecf60d7 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 @@ Require-Bundle: org.eclipse.core.resources, 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, |
