Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2014-09-16 16:05:45 +0000
committerChristian W. Damus2014-09-16 22:23:42 +0000
commitd2783ef746f0ccaf3ef083f004b9784fe37ccf07 (patch)
treed9fae22c1ede18db2bdc168f137883c120883983 /plugins/views
parent8f380f19a66c1d08f627b02d0ff53e690b8c7f57 (diff)
downloadorg.eclipse.papyrus-d2783ef746f0ccaf3ef083f004b9784fe37ccf07.tar.gz
org.eclipse.papyrus-d2783ef746f0ccaf3ef083f004b9784fe37ccf07.tar.xz
org.eclipse.papyrus-d2783ef746f0ccaf3ef083f004b9784fe37ccf07.zip
444227: Property view broken for associations
https://bugs.eclipse.org/bugs/show_bug.cgi?id=444227 The ViewEditor must be able to repeat sections for list values, such as the multiple ends of an association. To that end, the DisplayEngine now supports proxy sections that combine the same section instance that needs to be repeated with an arbitrary discriminator that distinguishes the repeated occurrences. This allows the display engine to reuse controls for the same repetition of the same section. Also, because multiple selections are presented as observables that are dynamic proxies wrapping a MultipleObservableValue, the ViewEditor cannot rely on that class's API to access the multiple objects that were selected. The API required by the ViewEditor is factored out into an interface that the proxy then automatically implements, as usual in the delegating observables mechanism. Change-Id: I88345c23f898100bd109bab2eccfd60d4b098323
Diffstat (limited to 'plugins/views')
-rw-r--r--plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/DefaultDisplayEngine.java56
-rw-r--r--plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/SectionDiscriminator.java108
-rw-r--r--plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/TabModel.java37
-rw-r--r--plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/AbstractPropertyEditor.java3
-rw-r--r--plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/ViewEditor.java140
5 files changed, 313 insertions, 31 deletions
diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/DefaultDisplayEngine.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/DefaultDisplayEngine.java
index c7698d07407..7c8293417b7 100644
--- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/DefaultDisplayEngine.java
+++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/DefaultDisplayEngine.java
@@ -10,6 +10,7 @@
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
* Christian W. Damus (CEA) - Use URIs to support non-URL-compatible storage (CDO)
* Christian W. Damus (CEA) - bug 417409
+ * Christian W. Damus (CEA) - bug 444227
*
*****************************************************************************/
package org.eclipse.papyrus.views.properties.runtime;
@@ -193,7 +194,7 @@ public class DefaultDisplayEngine implements DisplayEngine {
}
protected DataSource getDataSource(Section section) {
- return displayedSections.get(section.getTab().getId(), section.getName());
+ return displayedSections.get(section);
}
/**
@@ -202,21 +203,18 @@ public class DefaultDisplayEngine implements DisplayEngine {
* @return the previously-recorded data source, if any, for this {@code section} which has now been displaced
*/
protected DataSource addDataSource(Section section, DataSource dataSource) {
- return displayedSections.put(section.getTab().getId(), section.getName(), dataSource);
+ return displayedSections.put(section, dataSource);
}
- protected void addControl(Section section, Control control) {
- final String tabID = section.getTab().getId();
- final String sectionID = section.getName();
-
- controls.put(tabID, sectionID, control);
+ protected void addControl(final Section section, Control control) {
+ controls.put(section, control);
control.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
// Perhaps the tabbed properties view is disposing a tab that is not shown by the new selection
- displayedSections.remove(tabID, sectionID);
- controls.remove(tabID, sectionID);
+ displayedSections.remove(section);
+ controls.remove(section);
}
});
}
@@ -307,4 +305,44 @@ public class DefaultDisplayEngine implements DisplayEngine {
}
layout(parent);
}
+
+ /**
+ * Creates a proxy for a {@code section} that makes it distinct from other occurrences of the same section, according to some
+ * arbitrary {@code disciminator}.
+ *
+ * @param section
+ * a section to be repeated with unique discriminators
+ * @param discriminator
+ * this {@code section}'s discriminator value
+ *
+ * @return the proxy instance combining the identity of the {@code section} with its unique {@code discriminator}
+ */
+ public static Section discriminate(Section section, Object discriminator) {
+ if (section == null) {
+ throw new IllegalArgumentException("null section");
+ }
+ if (discriminator == null) {
+ throw new IllegalArgumentException("null discriminator");
+ }
+ if (SectionDiscriminator.isDiscriminated(section)) {
+ throw new IllegalArgumentException("section already has a discriminator");
+ }
+
+ return SectionDiscriminator.discriminate(section, discriminator);
+ }
+
+ /**
+ * Obtains the discriminator for a {@code section} proxy, if it is a proxy.
+ *
+ * @param section
+ * a section that is repeated with unique discriminators
+ * @return this {@code section}'s discriminator value, or {@code null} if it is a singleton (non-proxy) section
+ */
+ public static Object getDiscriminator(Section section) {
+ if (section == null) {
+ throw new IllegalArgumentException("null section");
+ }
+
+ return SectionDiscriminator.getDiscriminator(section);
+ }
}
diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/SectionDiscriminator.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/SectionDiscriminator.java
new file mode 100644
index 00000000000..07dba74905e
--- /dev/null
+++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/SectionDiscriminator.java
@@ -0,0 +1,108 @@
+/*****************************************************************************
+ * Copyright (c) 2014 CEA LIST and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * CEA LIST - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.views.properties.runtime;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.papyrus.views.properties.contexts.Section;
+
+/**
+ * A proxy for {@link Section}s that distinguishes multiple occurrences of the same instance via an arbitrary discriminator.
+ */
+class SectionDiscriminator implements InvocationHandler {
+ private static final Class<?>[] PROXY_INTERFACES = { Section.class, EObject.class, InternalEObject.class };
+
+ private final Section section;
+ private final Object discriminator;
+
+ SectionDiscriminator(Section section, Object discriminator) {
+ this.section = section;
+ this.discriminator = discriminator;
+ }
+
+ static Section discriminate(Section section, Object discriminator) {
+ return (Section) Proxy.newProxyInstance(section.getClass().getClassLoader(), PROXY_INTERFACES, new SectionDiscriminator(section, discriminator));
+ }
+
+ static Object getDiscriminator(Section section) {
+ Object result = null;
+
+ if (Proxy.isProxyClass(section.getClass())) {
+ InvocationHandler handler = Proxy.getInvocationHandler(section);
+ if (handler instanceof SectionDiscriminator) {
+ result = ((SectionDiscriminator) handler).discriminator;
+ }
+ }
+
+ return result;
+ }
+
+ static boolean isDiscriminated(Section section) {
+ boolean result = false;
+
+ if ((section != null) && Proxy.isProxyClass(section.getClass())) {
+ InvocationHandler handler = Proxy.getInvocationHandler(section);
+ result = handler instanceof SectionDiscriminator;
+ }
+
+ return result;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Object result = null;
+
+ if (method.getDeclaringClass() == Object.class) {
+ // Don't delegate equals(), hashCode(), and monitor protocol
+ if (method.getName().equals("equals")) { //$NON-NLS-1$
+ if ((args[0] != null) && Proxy.isProxyClass(args[0].getClass())) {
+ result = this.equals(Proxy.getInvocationHandler(args[0]));
+ } else {
+ result = proxy == args[0];
+ }
+ } else if (method.getName().equals("hashCode")) { //$NON-NLS-1$
+ result = this.hashCode();
+ } else {
+ result = method.invoke(this, args);
+ }
+ } else {
+ result = method.invoke(section, args);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ boolean result = obj == this;
+ if (!result && (obj instanceof SectionDiscriminator)) {
+ SectionDiscriminator other = (SectionDiscriminator) obj;
+ result = other.section == this.section && other.discriminator.equals(this.discriminator);
+ }
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + discriminator.hashCode();
+ result = prime * result + section.hashCode();
+ return result;
+ }
+
+}
diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/TabModel.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/TabModel.java
index 678993b3e3f..4159a28e7e6 100644
--- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/TabModel.java
+++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/TabModel.java
@@ -21,9 +21,13 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.eclipse.papyrus.views.properties.contexts.Section;
+
/**
* An encapsulation of the hierarchical tab structure of the property sheets managed by a {@link DisplayEngine}.
+ * It supports distinction of repeated {@code Section}s via proxies that combine section instances with
+ * arbitrary {@linkplain SectionDiscriminator discriminators}.
*/
class TabModel<V> {
@@ -43,6 +47,10 @@ class TabModel<V> {
return Collections.unmodifiableSet(result);
}
+ public V get(Section section) {
+ return model.get(new Path(section));
+ }
+
public V get(String tabID, String sectionID) {
return model.get(new Path(tabID, sectionID));
}
@@ -60,10 +68,18 @@ class TabModel<V> {
return Collections.unmodifiableList(result);
}
+ public V put(Section section, V value) {
+ return model.put(new Path(section), value);
+ }
+
public V put(String tabID, String sectionID, V value) {
return model.put(new Path(tabID, sectionID), value);
}
+ public V remove(Section section) {
+ return model.remove(new Path(section));
+ }
+
public V remove(String tabID, String sectionID) {
return model.remove(new Path(tabID, sectionID));
}
@@ -105,12 +121,19 @@ class TabModel<V> {
final String sectionID;
- Path(String tabID, String sectionID) {
+ final Object discriminator;
+
+ Path(String tabID, String sectionID, Object discriminator) {
checkWildcard(tabID);
checkWildcard(sectionID);
this.tabID = tabID;
this.sectionID = sectionID;
+ this.discriminator = (discriminator == null) ? WILDCARD : discriminator;
+ }
+
+ Path(String tabID, String sectionID) {
+ this(tabID, sectionID, WILDCARD);
}
/**
@@ -121,6 +144,14 @@ class TabModel<V> {
this.tabID = tabID;
this.sectionID = WILDCARD;
+ this.discriminator = WILDCARD;
+ }
+
+ /**
+ * Create a path for a specific section.
+ */
+ Path(Section section) {
+ this(section.getTab().getId(), section.getName(), SectionDiscriminator.getDiscriminator(section));
}
static void checkWildcard(String id) {
@@ -152,10 +183,10 @@ class TabModel<V> {
}
Path other = (Path) obj;
- return equals(tabID, other.tabID) && equals(sectionID, other.sectionID);
+ return equals(tabID, other.tabID) && equals(sectionID, other.sectionID) && equals(discriminator, other.discriminator);
}
- private static boolean equals(String anID, String anotherID) {
+ private static boolean equals(Object anID, Object anotherID) {
// Deliberately testing for identity of non-interned string
return (anID == WILDCARD) || (anotherID == WILDCARD) || anID.equals(anotherID);
}
diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/AbstractPropertyEditor.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/AbstractPropertyEditor.java
index 5c006213a09..d6f0fa7a712 100644
--- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/AbstractPropertyEditor.java
+++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/AbstractPropertyEditor.java
@@ -11,6 +11,7 @@
* Thibault Le Ouay t.leouay@sherpa-eng.com - Add binding implementation
* Christian W. Damus (CEA) - bug 417409
* Christian W. Damus (CEA) - bug 443417
+ * Christian W. Damus (CEA) - bug 444227
*
*****************************************************************************/
package org.eclipse.papyrus.views.properties.widgets;
@@ -235,7 +236,7 @@ public abstract class AbstractPropertyEditor implements IChangeListener, Customi
*/
protected void applyReadOnly(boolean readOnly) {
AbstractEditor editor = getEditor();
- if (editor != null) {
+ if ((editor != null) && !editor.isDisposed()) {
editor.setReadOnly(readOnly);
}
}
diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/ViewEditor.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/ViewEditor.java
index 624d35d67ae..c61609a6501 100644
--- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/ViewEditor.java
+++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/widgets/ViewEditor.java
@@ -9,12 +9,14 @@
* Contributors:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
* Christian W. Damus (CEA) - bug 443417
+ * Christian W. Damus (CEA) - bug 444227
*
*****************************************************************************/
package org.eclipse.papyrus.views.properties.widgets;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -23,7 +25,7 @@ import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.papyrus.infra.tools.databinding.MultipleObservableValue;
+import org.eclipse.papyrus.infra.tools.databinding.IMultipleObservableValue;
import org.eclipse.papyrus.infra.widgets.editors.AbstractEditor;
import org.eclipse.papyrus.views.properties.Activator;
import org.eclipse.papyrus.views.properties.contexts.Context;
@@ -38,10 +40,14 @@ import org.eclipse.papyrus.views.properties.runtime.DisplayEngine;
import org.eclipse.papyrus.views.properties.widgets.layout.PropertiesLayout;
import org.eclipse.papyrus.views.properties.xwt.XWTSection;
import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.ScrollBar;
/**
* An Editor for displaying a whole property {@link View} on a sub-object.
@@ -56,7 +62,7 @@ public class ViewEditor extends AbstractPropertyEditor {
private Composite self;
- private Map<Section, XWTSection> sections = new HashMap<Section, XWTSection>();
+ private Map<Section, EditorSection> sections = new HashMap<Section, EditorSection>();
private DisplayEngine displayEngine;
@@ -122,7 +128,16 @@ public class ViewEditor extends AbstractPropertyEditor {
public void dataSourceChanged(DataSourceChangedEvent event) {
// The data source's selection changed. Re-display our nested sections, if appropriate
- checkInput();
+ if ((self != null) && !self.isDisposed()) {
+ self.getDisplay().asyncExec(new Runnable() {
+
+ public void run() {
+ if (!self.isDisposed()) {
+ checkInput();
+ }
+ }
+ });
+ }
}
};
}
@@ -198,25 +213,82 @@ public class ViewEditor extends AbstractPropertyEditor {
displayEngine = new DefaultDisplayEngine(false);
}
+ // We need to be able to repeat sections, so use an arbitrary discriminator to
+ // present the same section multiple times as distinct sections
+ int index = 0;
if (observable instanceof IObservableValue) {
IObservableValue observableValue = (IObservableValue) observable;
- if (observableValue instanceof MultipleObservableValue) {
- MultipleObservableValue multipleObservable = (MultipleObservableValue) observableValue;
- display(displayEngine, multipleObservable.getObservedValues(), view);
+ if (observableValue instanceof IMultipleObservableValue) {
+ IMultipleObservableValue multipleObservable = (IMultipleObservableValue) observableValue;
+ display(displayEngine, multipleObservable.getObservedValues(), view, index++);
} else {
Object value = observableValue.getValue();
- display(displayEngine, value, view);
+ display(displayEngine, value, view, index++);
}
} else if (observable instanceof IObservableList) {
IObservableList observableList = (IObservableList) observable;
for (Object value : observableList) {
- display(displayEngine, value, view);
+ display(displayEngine, value, view, index++);
+ }
+ }
+
+ // Any repeated sections that we had created for a previous selection and no longer need must be destroyed
+ purgeUnusedSections(index);
+
+ // A hack to force the containing scroll pane, if any (we expect to have one in the property sheet), to
+ // recompute its client area and scrollbars
+ for (Composite next = self; (next != null); next = next.getParent()) {
+ if (next.getParent() instanceof ScrolledComposite) {
+ final ScrolledComposite scrolled = (ScrolledComposite) next.getParent();
+ next.layout();
+ scrolled.layout();
+ scrolled.getDisplay().asyncExec(new Runnable() {
+
+ public void run() {
+ resizeScrolledComposite(scrolled);
+ }
+ });
}
}
updateControls();
}
+ private void purgeUnusedSections(int maxDiscriminator) {
+ for (Iterator<Section> iter = sections.keySet().iterator(); iter.hasNext();) {
+ Section section = iter.next();
+ Object discriminator = DefaultDisplayEngine.getDiscriminator(section);
+ if ((discriminator instanceof Number) && (((Number) discriminator).intValue() >= maxDiscriminator)) {
+ sections.get(section).dispose();
+ iter.remove();
+ }
+ }
+ }
+
+ /**
+ * Recompute the size of a {@code scrolled} composite's client area and adjust its scroll bars accordingly.
+ *
+ * @param scrolled
+ * a scrolled composite to force to adapt to a new layout
+ */
+ private void resizeScrolledComposite(ScrolledComposite scrolled) {
+ Point sizeConstraint = scrolled.getContent().getSize();
+ sizeConstraint = scrolled.getContent().computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ scrolled.setMinSize(sizeConstraint);
+
+ Rectangle clientArea = scrolled.getClientArea();
+
+ ScrollBar vbar = scrolled.getVerticalBar();
+ if (vbar != null) {
+ vbar.setPageIncrement(clientArea.height - 5);
+ }
+
+ ScrollBar hbar = scrolled.getHorizontalBar();
+ if (hbar != null) {
+ hbar.setPageIncrement(clientArea.width - 5);
+ }
+ }
+
/**
* Displays the given view in the display engine, with the given object.
*
@@ -229,8 +301,8 @@ public class ViewEditor extends AbstractPropertyEditor {
* @param view
* The view to display
*/
- protected void display(DisplayEngine display, Object data, View view) {
- display(display, Collections.singletonList(data), view);
+ protected void display(DisplayEngine display, Object data, View view, Object discriminator) {
+ display(display, Collections.singletonList(data), view, discriminator);
}
/**
@@ -245,19 +317,20 @@ public class ViewEditor extends AbstractPropertyEditor {
* @param view
* The view to display
*/
- protected void display(DisplayEngine display, List<Object> selectedElements, View view) {
+ protected void display(DisplayEngine display, List<Object> selectedElements, View view, Object discriminator) {
for (Section section : view.getSections()) {
- XWTSection xwtSection = sections.get(section);
- if (xwtSection == null) {
- xwtSection = new XWTSection(section, view, display);
- sections.put(section, xwtSection);
- xwtSection.createControls(new Composite(self, SWT.NONE), null);
+ // Distinguish this occurrence of the section
+ section = DefaultDisplayEngine.discriminate(section, discriminator);
+
+ EditorSection editorSection = sections.get(section);
+ if (editorSection == null) {
+ editorSection = new EditorSection(new XWTSection(section, view, display));
+ sections.put(section, editorSection);
}
ISelection selection = new StructuredSelection(selectedElements);
- xwtSection.setInput(null, selection);
- xwtSection.refresh();
+ editorSection.setInput(selection);
}
}
@@ -301,4 +374,35 @@ public class ViewEditor extends AbstractPropertyEditor {
public Control getControl() {
return self;
}
+
+ //
+ // Nested types
+ //
+
+ /**
+ * An encapsulation of an XWT section with the composite that contains it within the {@link ViewEditor}'s parent composite.
+ */
+ private class EditorSection {
+ private final XWTSection xwt;
+ private final Composite sectionComposite;
+
+ EditorSection(XWTSection xwt) {
+ this.xwt = xwt;
+ this.sectionComposite = new Composite(self, SWT.NONE);
+
+ xwt.createControls(sectionComposite, null);
+ }
+
+ void dispose() {
+ if (!sectionComposite.isDisposed()) {
+ xwt.dispose();
+ sectionComposite.dispose();
+ }
+ }
+
+ void setInput(ISelection selection) {
+ xwt.setInput(null, selection);
+ xwt.refresh();
+ }
+ }
}

Back to the top