From ec9e7064748f00f1cbaea710103064b1738356b8 Mon Sep 17 00:00:00 2001 From: Christian W. Damus Date: Thu, 22 May 2014 22:44:04 -0400 Subject: 417409: [Performances - Properties view] Delay in UI when reorganizing diagram layout. https://bugs.eclipse.org/bugs/show_bug.cgi?id=417409 Make property sheet views reusable, with updating of the bound selection when the selection changes to another element that shows the same views. This employs new capability of the DataSource to update the selection that it encapsulates, pushing the new selection into the ModelElements that it creates, using a new delegating observable framework. Property sheet controls are re-used on a per-tab basis. Because of the new delegation pattern introduced here, we need to be able to ensure that delegate observables are disposed of when they are no longer needed. This includes not only the delegates of the new DelegatingObservables, but also the delegates of MultipleObservableValue and similar aggregates. As these delegates can be shared amongst multiple wrappers of different kinds, we use a simple reference counting scheme to ensure that observables are not disposed while they are still in use. This averts the exceptions discovered in multi-observable (multiple selection) scenarios on a previous iteration of this patch set. Change-Id: Ide8f3fcea4228083a68bc9d5d39dc5a50217af62 --- .../AbstractEMFModelElementFactory.java | 41 +++++ .../modelelement/AbstractModelElement.java | 98 ++++++++++-- .../modelelement/AbstractModelElementFactory.java | 40 +++++ .../AnnotationModelElementFactory.java | 18 ++- .../modelelement/CompositeModelElement.java | 70 ++++++++- .../views/properties/modelelement/DataSource.java | 57 +++++-- .../modelelement/DataSourceChangedEvent.java | 32 ++++ .../properties/modelelement/DataSourceFactory.java | 11 +- .../modelelement/EMFModelElementFactory.java | 10 +- .../modelelement/IDataSourceListener.java | 30 ++++ .../PreferencesModelElementFactory.java | 21 ++- .../properties/runtime/DefaultDisplayEngine.java | 95 +++++++++--- .../papyrus/views/properties/runtime/TabModel.java | 165 +++++++++++++++++++++ .../properties/widgets/AbstractPropertyEditor.java | 59 +++++++- .../papyrus/views/properties/xwt/XWTSection.java | 18 ++- .../views/properties/xwt/XWTSectionDescriptor.java | 34 ++++- .../views/properties/xwt/XWTTabDescriptor.java | 39 ++++- 17 files changed, 774 insertions(+), 64 deletions(-) create mode 100644 plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractEMFModelElementFactory.java create mode 100644 plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractModelElementFactory.java create mode 100644 plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSourceChangedEvent.java create mode 100644 plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/IDataSourceListener.java create mode 100644 plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/TabModel.java (limited to 'plugins/views') diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractEMFModelElementFactory.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractEMFModelElementFactory.java new file mode 100644 index 00000000000..4e53be287f0 --- /dev/null +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractEMFModelElementFactory.java @@ -0,0 +1,41 @@ +/***************************************************************************** + * Copyright (c) 2010, 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: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - bug 417409 + * + *****************************************************************************/ +package org.eclipse.papyrus.views.properties.modelelement; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.papyrus.infra.emf.utils.EMFHelper; + +/** + * A partial implementation of a ModelElementFactory for creating {@link EMFModelElement}s + * + * @author Camille Letavernier + * + */ +public abstract class AbstractEMFModelElementFactory extends AbstractModelElementFactory { + + @Override + protected void updateModelElement(T modelElement, Object newSourceElement) { + EObject eObject = EMFHelper.getEObject(newSourceElement); + if(eObject == null) { + throw new IllegalArgumentException("Cannot resolve EObject selection: " + newSourceElement); + } + + updateEMFModelElement(modelElement, eObject); + } + + public static void updateEMFModelElement(EMFModelElement modelElement, EObject newEObject) { + modelElement.source = newEObject; + modelElement.domain = EMFHelper.resolveEditingDomain(newEObject); + } +} diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractModelElement.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractModelElement.java index a69f51490b8..d316c2c8678 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractModelElement.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractModelElement.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2011 CEA LIST. + * Copyright (c) 2011, 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 @@ -9,15 +9,24 @@ * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation * Thibault Le Ouay t.leouay@sherpa-eng.com - Add binding implementation + * Christian W. Damus (CEA) - bug 417409 + * *****************************************************************************/ package org.eclipse.papyrus.views.properties.modelelement; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; +import org.eclipse.core.databinding.observable.DisposeEvent; +import org.eclipse.core.databinding.observable.IDisposeListener; import org.eclipse.core.databinding.observable.IObservable; import org.eclipse.core.databinding.validation.IValidator; import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.papyrus.infra.tools.databinding.DelegatingObservableValue; +import org.eclipse.papyrus.infra.tools.databinding.IDelegatingObservable; +import org.eclipse.papyrus.infra.tools.databinding.ReferenceCountedObservable; import org.eclipse.papyrus.infra.widgets.creation.ReferenceValueFactory; import org.eclipse.papyrus.infra.widgets.providers.EmptyContentProvider; import org.eclipse.papyrus.infra.widgets.providers.IStaticContentProvider; @@ -29,7 +38,7 @@ import org.eclipse.papyrus.views.properties.creation.PropertyEditorFactory; * * @author Camille Letavernier */ -public abstract class AbstractModelElement implements ModelElement { +public abstract class AbstractModelElement implements ModelElement, IDataSourceListener { /** * The DataSource owning this ModelElement @@ -38,11 +47,20 @@ public abstract class AbstractModelElement implements ModelElement { private final Map observables = new HashMap(); - + private IDisposeListener observableDisposeListener; + + AbstractModelElementFactory factory; + /** * Constructor. */ protected AbstractModelElement() { + super(); + } + + @SuppressWarnings("unchecked") + void setFactory(AbstractModelElementFactory factory) { + this.factory = (AbstractModelElementFactory)factory; } public IStaticContentProvider getContentProvider(String propertyPath) { @@ -74,7 +92,41 @@ public abstract class AbstractModelElement implements ModelElement { } public void setDataSource(DataSource source) { - this.dataSource = source; + if(this.dataSource != source) { + if(this.dataSource != null) { + this.dataSource.removeDataSourceListener(this); + } + + this.dataSource = source; + + if(this.dataSource != null) { + this.dataSource.addDataSourceListener(this); + } + } + } + + public final void dataSourceChanged(DataSourceChangedEvent event) { + if(event.getDataSource() == dataSource) { + // The data source changed. Update for the new selection + IStructuredSelection selection = dataSource.getSelection(); + if(selection.isEmpty()) { + factory.updateModelElement(this, null); + } else if(selection.size() == 1) { + factory.updateModelElement(this, selection.getFirstElement()); + } else { + updateMultipleSelection(selection); + } + + // Update our observables + for(Map.Entry next : observables.entrySet()) { + IDelegatingObservable wrapper = ((IDelegatingObservable)next.getValue()); + wrapper.setDelegate(doGetObservable(next.getKey())); + } + } + } + + void updateMultipleSelection(IStructuredSelection selection) { + throw new IllegalArgumentException("multiple selection"); //$NON-NLS-1$ } /** @@ -100,6 +152,10 @@ public abstract class AbstractModelElement implements ModelElement { if(!observables.containsKey(propertyPath)) { IObservable observable = doGetObservable(propertyPath); if(observable != null) { + // Wrap it so that we may replace the delegate as needed + observable = DelegatingObservableValue.wrap(observable); + observable.addDisposeListener(getObservableDisposeListener()); + ReferenceCountedObservable.Util.retain(observable); observables.put(propertyPath, observable); } } @@ -118,15 +174,39 @@ public abstract class AbstractModelElement implements ModelElement { public void dispose() { for(IObservable observable : observables.values()) { - observable.dispose(); + if(observableDisposeListener != null) { + // Don't let the listener concurrently modify the map in case releasing triggers dispose + observable.removeDisposeListener(observableDisposeListener); + } + + ReferenceCountedObservable.Util.release(observable); } + observables.clear(); + observableDisposeListener = null; } - - public IValidator getValidator(String propertyPath){ + + public IValidator getValidator(String propertyPath) { return null; } - - + private IDisposeListener getObservableDisposeListener() { + if(observableDisposeListener == null) { + observableDisposeListener = new IDisposeListener() { + + public void handleDispose(DisposeEvent event) { + // Remove this property + for(Iterator> entries = observables.entrySet().iterator(); entries.hasNext();) { + if(entries.next().getValue() == event.getObservable()) { + entries.remove(); + break; + } + } + } + }; + } + + return observableDisposeListener; + } + } diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractModelElementFactory.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractModelElementFactory.java new file mode 100644 index 00000000000..b9517cfee18 --- /dev/null +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AbstractModelElementFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.views.properties.modelelement; + +import org.eclipse.papyrus.views.properties.contexts.DataContextElement; + + +/** + * This is the AbstractModelElementFactory type. Enjoy. + */ +public abstract class AbstractModelElementFactory implements ModelElementFactory { + + protected AbstractModelElementFactory() { + super(); + } + + public final ModelElement createFromSource(Object sourceElement, DataContextElement context) { + AbstractModelElement result = doCreateFromSource(sourceElement, context); + + if(result != null) { + result.setFactory(this); + } + + return result; + } + + protected abstract T doCreateFromSource(Object sourceElement, DataContextElement context); + + protected abstract void updateModelElement(T modelElement, Object newSourceElement); +} diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AnnotationModelElementFactory.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AnnotationModelElementFactory.java index 3dd13364b61..8d84047ace7 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AnnotationModelElementFactory.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/AnnotationModelElementFactory.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2011 CEA LIST. + * Copyright (c) 2011, 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 @@ -8,6 +8,8 @@ * * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - bug 417409 + * *****************************************************************************/ package org.eclipse.papyrus.views.properties.modelelement; @@ -23,9 +25,10 @@ import org.eclipse.papyrus.views.properties.contexts.DataContextElement; * * @author Camille Letavernier */ -public class AnnotationModelElementFactory implements ModelElementFactory { +public class AnnotationModelElementFactory extends AbstractModelElementFactory { - public ModelElement createFromSource(Object sourceElement, DataContextElement context) { + @Override + protected AnnotationModelElement doCreateFromSource(Object sourceElement, DataContextElement context) { EObject source = EMFHelper.getEObject(sourceElement); if(source == null) { Activator.log.warn("Unable to resolve the selected element to an EObject"); //$NON-NLS-1$ @@ -40,4 +43,13 @@ public class AnnotationModelElementFactory implements ModelElementFactory { return new AnnotationModelElement((EModelElement)source, domain, context.getName()); } + @Override + protected void updateModelElement(AnnotationModelElement modelElement, Object newSourceElement) { + EObject eObject = EMFHelper.getEObject(newSourceElement); + if(!(eObject instanceof EModelElement)) { + throw new IllegalArgumentException("Cannot resolve EModelElement selection: " + newSourceElement); + } + modelElement.source = (EModelElement)eObject; + modelElement.domain = EMFHelper.resolveEditingDomain(eObject); + } } diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/CompositeModelElement.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/CompositeModelElement.java index 0b4573a085f..73e046102df 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/CompositeModelElement.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/CompositeModelElement.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2010 CEA LIST. + * Copyright (c) 2010, 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 @@ -8,15 +8,20 @@ * * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - bug 417409 + * *****************************************************************************/ package org.eclipse.papyrus.views.properties.modelelement; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import org.eclipse.core.databinding.observable.IObservable; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.papyrus.infra.tools.databinding.AggregatedObservable; import org.eclipse.papyrus.infra.tools.databinding.MultipleObservableValue; import org.eclipse.papyrus.infra.widgets.providers.EmptyContentProvider; @@ -34,6 +39,15 @@ import org.eclipse.papyrus.infra.widgets.providers.IStaticContentProvider; */ public class CompositeModelElement extends AbstractModelElement { + private final BoundModelElementFactory subModelElementFactory; + + + public CompositeModelElement(BoundModelElementFactory subModelElementFactory) { + super(); + + this.subModelElementFactory = subModelElementFactory; + } + @Override public IObservable doGetObservable(String propertyPath) { @@ -69,6 +83,47 @@ public class CompositeModelElement extends AbstractModelElement { return observableComposite; } + @Override + void updateMultipleSelection(IStructuredSelection selection) { + ListIterator subElements = elements.listIterator(); + Iterator newSourceElements = selection.iterator(); + + // Re-use existing sub-elements, just updating them + while(newSourceElements.hasNext() && subElements.hasNext()) { + ModelElement nextSubElement = subElements.next(); + if(nextSubElement instanceof AbstractModelElement) { + // Can reuse it + AbstractModelElement reusable = (AbstractModelElement)nextSubElement; + reusable.factory.updateModelElement(reusable, newSourceElements.next()); + } else { + // Replace it + nextSubElement.dispose(); + + ModelElement newSubElement = subModelElementFactory.createModelElement(newSourceElements.next()); + if(newSubElement != null) { + subElements.set(newSubElement); + } else { + // TODO: Report a warning? + subElements.remove(); + } + } + } + + // And create new ones if necessary + while(newSourceElements.hasNext()) { + ModelElement newSubElement = subModelElementFactory.createModelElement(newSourceElements.next()); + if(newSubElement != null) { + subElements.add(newSubElement); + } // TODO: Else report a warning? + } + + // And destroy any unneeded sub-elements + while(subElements.hasNext()) { + subElements.next().dispose(); + subElements.remove(); + } + } + /** * Adds a sub-model element to this CompositeModelElement * @@ -185,4 +240,17 @@ public class CompositeModelElement extends AbstractModelElement { public List getSubElements() { return elements; } + + // + // Nested types + // + + /** + * Protocol for a factory that a {@link CompositeModelElement} uses to create sub-elements for a multiple + * selection. It binds all of the necessary context so that the only input is a selected source element. + */ + public interface BoundModelElementFactory { + + ModelElement createModelElement(Object sourceElement); + } } diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSource.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSource.java index 362835a029d..23368e23579 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSource.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSource.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2010 CEA LIST. + * Copyright (c) 2010, 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 @@ -9,18 +9,19 @@ * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation * Thibault Le Ouay t.leouay@sherpa-eng.com - Add binding implementation + * Christian W. Damus (CEA) - bug 417409 + * *****************************************************************************/ package org.eclipse.papyrus.views.properties.modelelement; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.IObservable; import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.papyrus.infra.widgets.creation.ReferenceValueFactory; @@ -50,8 +51,10 @@ import org.eclipse.papyrus.views.properties.contexts.View; */ public class DataSource implements IChangeListener { - private Set changeListeners = new HashSet(); + private final ListenerList changeListeners = new ListenerList(ListenerList.IDENTITY); + private final ListenerList dataSourceListeners = new ListenerList(ListenerList.IDENTITY); + private View view; private IStructuredSelection selection; @@ -61,6 +64,7 @@ public class DataSource implements IChangeListener { /** * Constructs a new DataSource from the given view and selection * + * @param realm * @param view * @param selection * @@ -189,13 +193,37 @@ public class DataSource implements IChangeListener { public void removeChangeListener(IChangeListener listener) { changeListeners.remove(listener); } + + public void addDataSourceListener(IDataSourceListener listener) { + dataSourceListeners.add(listener); + } + + public void removeDataSourceListener(IDataSourceListener listener) { + dataSourceListeners.remove(listener); + } public void handleChange(ChangeEvent event) { - //The set of listeners may change during the update. - Set listeners = new HashSet(changeListeners); - - for(IChangeListener listener : listeners) { - listener.handleChange(event); + Object[] listeners = changeListeners.getListeners(); + for(int i = 0; i < listeners.length; i++) { + try { + ((IChangeListener)listeners[i]).handleChange(event); + } catch (Exception e) { + Activator.log.error("Uncaught exception in observable change listener.", e); //$NON-NLS-1$ + } + } + } + + protected void fireDataSourceChanged() { + Object[] listeners = dataSourceListeners.getListeners(); + if(listeners.length > 0) { + DataSourceChangedEvent event = new DataSourceChangedEvent(this); + for(int i = 0; i < listeners.length; i++) { + try { + ((IDataSourceListener)listeners[i]).dataSourceChanged(event); + } catch (Exception e) { + Activator.log.error("Uncaught exception in data-source listener.", e); //$NON-NLS-1$ + } + } } } @@ -212,6 +240,17 @@ public class DataSource implements IChangeListener { public IStructuredSelection getSelection() { return selection; } + + /** + * @param selection the selection to set + */ + public void setSelection(IStructuredSelection selection) { + if(!selection.equals(this.selection)) { + this.selection = selection; + + fireDataSourceChanged(); + } + } /** * @param propertyPath diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSourceChangedEvent.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSourceChangedEvent.java new file mode 100644 index 00000000000..f102926cb9a --- /dev/null +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSourceChangedEvent.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.views.properties.modelelement; + +import java.util.EventObject; + + +/** + * This is the DataSourceChangedEvent type. Enjoy. + */ +public class DataSourceChangedEvent extends EventObject { + + private static final long serialVersionUID = 1L; + + DataSourceChangedEvent(DataSource source) { + super(source); + } + + public DataSource getDataSource() { + return (DataSource)getSource(); + } +} diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSourceFactory.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSourceFactory.java index 0a36400fdf8..9dc30680fcd 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSourceFactory.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/DataSourceFactory.java @@ -9,6 +9,7 @@ * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation * Christian W. Damus (CEA) - bug 435103 + * Christian W. Damus (CEA) - bug 417409 * *****************************************************************************/ package org.eclipse.papyrus.views.properties.modelelement; @@ -107,12 +108,18 @@ public class DataSourceFactory { * @return The model element corresponding to the given contextElement and * selection */ - private ModelElement createModelElement(DataContextElement contextElement, IStructuredSelection selection) { + private ModelElement createModelElement(final DataContextElement contextElement, IStructuredSelection selection) { if(selection.size() == 1) { // Single Selection ModelElement modelElement = createFromSource(selection.getFirstElement(), contextElement); return modelElement; } else { // MultiSelection - CompositeModelElement composite = new CompositeModelElement(); + // Bind the context element in a factory for the composite to create sub-elements + CompositeModelElement composite = new CompositeModelElement(new CompositeModelElement.BoundModelElementFactory() { + + public ModelElement createModelElement(Object sourceElement) { + return createFromSource(sourceElement, contextElement); + } + }); Iterator it = selection.iterator(); while(it.hasNext()) { diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/EMFModelElementFactory.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/EMFModelElementFactory.java index 8877aca5120..6559d7eff55 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/EMFModelElementFactory.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/EMFModelElementFactory.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2010 CEA LIST. + * Copyright (c) 2010, 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 @@ -8,6 +8,8 @@ * * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - bug 417409 + * *****************************************************************************/ package org.eclipse.papyrus.views.properties.modelelement; @@ -23,9 +25,10 @@ import org.eclipse.papyrus.views.properties.contexts.DataContextElement; * @author Camille Letavernier * */ -public class EMFModelElementFactory implements ModelElementFactory { +public class EMFModelElementFactory extends AbstractEMFModelElementFactory { - public ModelElement createFromSource(Object sourceElement, DataContextElement context) { + @Override + protected EMFModelElement doCreateFromSource(Object sourceElement, DataContextElement context) { EObject source = EMFHelper.getEObject(sourceElement); if(source == null) { Activator.log.warn("Unable to resolve the selected element to an EObject"); //$NON-NLS-1$ @@ -35,5 +38,4 @@ public class EMFModelElementFactory implements ModelElementFactory { EditingDomain domain = EMFHelper.resolveEditingDomain(source); return new EMFModelElement(source, domain); } - } diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/IDataSourceListener.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/IDataSourceListener.java new file mode 100644 index 00000000000..9e5b9a4b6d2 --- /dev/null +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/IDataSourceListener.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.views.properties.modelelement; + +import java.util.EventListener; + +import org.eclipse.core.databinding.observable.IObservable; + + +/** + * A listener protocol for changes to a {@link DataSource} (especially its selection). Data sources are not {@linkplain IObservable observables} + * because that would confuse the XWT bindings framework. + * + * @see DataSource + * @see DataSource#addDataSourceListener(IDataSourceListener) + */ +public interface IDataSourceListener extends EventListener { + + void dataSourceChanged(DataSourceChangedEvent event); +} diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/PreferencesModelElementFactory.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/PreferencesModelElementFactory.java index 0aa2db099db..e771d4e3141 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/PreferencesModelElementFactory.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/modelelement/PreferencesModelElementFactory.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2012 CEA LIST. + * Copyright (c) 2012, 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 @@ -8,16 +8,31 @@ * * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - bug 417409 + * *****************************************************************************/ package org.eclipse.papyrus.views.properties.modelelement; +import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.papyrus.views.properties.contexts.DataContextElement; +import org.eclipse.ui.preferences.ScopedPreferenceStore; -public class PreferencesModelElementFactory implements ModelElementFactory { +public class PreferencesModelElementFactory extends AbstractModelElementFactory { - public ModelElement createFromSource(Object sourceElement, DataContextElement context) { + @Override + protected PreferencesModelElement doCreateFromSource(Object sourceElement, DataContextElement context) { return new PreferencesModelElement(context); } + @Override + protected void updateModelElement(PreferencesModelElement modelElement, Object newSourceElement) { + if(!(newSourceElement instanceof DataContextElement)) { + throw new IllegalArgumentException("Cannot resolve DataContextElement selection: " + newSourceElement); + } + + DataContextElement context = (DataContextElement)newSourceElement; + modelElement.context = context; + modelElement.store = new ScopedPreferenceStore(InstanceScope.INSTANCE, context.getName()); + } } 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 dc5360b25b5..a55521e39fb 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 @@ -15,10 +15,10 @@ package org.eclipse.papyrus.views.properties.runtime; import java.net.URL; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -40,6 +40,8 @@ import org.eclipse.papyrus.xwt.ILoadingContext; import org.eclipse.papyrus.xwt.IXWTLoader; import org.eclipse.papyrus.xwt.XWT; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; @@ -55,12 +57,14 @@ public class DefaultDisplayEngine implements DisplayEngine { private ILoadingContext loadingContext = new DefaultLoadingContext(getClass().getClassLoader()); - private Set displayedSections = new HashSet(); + private Map currentTabs = new HashMap(); - private Set controls = new HashSet(); + private TabModel displayedSections = new TabModel(); + + private TabModel controls = new TabModel(); private boolean allowDuplicate; - + private Object xmlCache; /** @@ -84,7 +88,6 @@ public class DefaultDisplayEngine implements DisplayEngine { } public List getTabDescriptors(Set views) { - Map result = new LinkedHashMap(); Set selectedSections = new HashSet(); @@ -116,20 +119,37 @@ public class DefaultDisplayEngine implements DisplayEngine { } } - disposeControls(); - return new LinkedList(result.values()); + for(Map.Entry next : result.entrySet()) { + XWTTabDescriptor existing = currentTabs.get(next.getKey()); + if((existing != null) && !existing.equals(next.getValue())) { + // Will have to rebuild this tab + disposeControls(next.getKey()); + } + } + + currentTabs = result; + + return new ArrayList(result.values()); } /** - * Disposes the controls created by this DisplayEngine + * Disposes the controls created by this DisplayEngine for the specified tab ID. * This should not dispose the engine itself, which can be reused. */ - protected void disposeControls() { - for(Control control : controls) { + protected void disposeControls(String tabID) { + for(Control control : this.controls.remove(tabID)) { control.dispose(); } - displayedSections.clear(); - controls.clear(); + + for(DataSource dataSource : displayedSections.remove(tabID)) { + dataSource.dispose(); + } + } + + protected void disposeControls() { + for(String next : new ArrayList(controls.tabIDs())) { + disposeControls(next); + } } /** @@ -138,7 +158,7 @@ public class DefaultDisplayEngine implements DisplayEngine { public void dispose() { disposeControls(); } - + /** * Invalidates any caches that I may have because the displayed property UI contexts, constraints, or views have * changed in some way. @@ -153,21 +173,54 @@ public class DefaultDisplayEngine implements DisplayEngine { return null; } - if(!allowDuplicate && displayedSections.contains(section.getName())) { + DataSource existing = getDataSource(section); + if(!allowDuplicate && (existing != null)) { + // Update the data source and fire the bindings + existing.setSelection(source.getSelection()); + return null; } Control control = createSection(parent, section, loadXWTFile(section), source); - displayedSections.add(section.getName()); + addDataSource(section, source); if(control != null) { - controls.add(control); + addControl(section, control); } return control; } + protected DataSource getDataSource(Section section) { + return displayedSections.get(section.getTab().getId(), section.getName()); + } + + /** + * Adds a new {@code dataSource} for a property {@code section}. + * + * @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); + } + + protected void addControl(Section section, Control control) { + final String tabID = section.getTab().getId(); + final String sectionID = section.getName(); + + controls.put(tabID, sectionID, 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); + } + }); + } + public void refreshSection(Composite parent, Section section, DataSource source) { for(Control control : parent.getChildren()) { control.dispose(); @@ -175,10 +228,10 @@ public class DefaultDisplayEngine implements DisplayEngine { Control control = createSection(parent, section, loadXWTFile(section), source); - displayedSections.add(section.getName()); + addDataSource(section, source); if(control != null) { - controls.add(control); + addControl(section, control); } } @@ -198,7 +251,7 @@ public class DefaultDisplayEngine implements DisplayEngine { try { ResourceSet rset = section.eResource().getResourceSet(); URL url = new URL(null, sectionFile.toString(), new EMFURLStreamHandler(rset.getURIConverter())); - + Map options = new HashMap(); options.put(IXWTLoader.CONTAINER_PROPERTY, parent); options.put(IXWTLoader.DATACONTEXT_PROPERTY, source); @@ -208,11 +261,11 @@ public class DefaultDisplayEngine implements DisplayEngine { if(control != null) { control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - controls.add(control); + addControl(section, control); } } catch (Exception ex) { Activator.log.error("Error while loading " + section.getSectionFile(), ex); //$NON-NLS-1$ - disposeControls(); + disposeControls(section.getTab().getId()); Label label = new Label(parent, SWT.NONE); label.setText("An error occured in the property view. The file " + section.getSectionFile() + " could not be loaded"); //$NON-NLS-1$ //$NON-NLS-2$ } 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 new file mode 100644 index 00000000000..da005579afb --- /dev/null +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/runtime/TabModel.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2014 CEA 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: + * Christian W. Damus (CEA) - Initial API and implementation + * + */ +package org.eclipse.papyrus.views.properties.runtime; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * An encapsulation of the hierarchical tab structure of the property sheets managed by a {@link DisplayEngine}. + */ +class TabModel { + + private final Map model = new HashMap(); + + TabModel() { + super(); + } + + public Set tabIDs() { + Set result = new HashSet(); + + for(Path next : model.keySet()) { + result.add(next.tabID); + } + + return Collections.unmodifiableSet(result); + } + + public V get(String tabID, String sectionID) { + return model.get(new Path(tabID, sectionID)); + } + + public Collection get(String tabID) { + List result = new ArrayList(4); + + Path key = new Path(tabID); + for(Map.Entry next : model.entrySet()) { + if(next.getKey().equals(key)) { + result.add(next.getValue()); + } + } + + return Collections.unmodifiableList(result); + } + + public V put(String tabID, String sectionID, V value) { + return model.put(new Path(tabID, sectionID), value); + } + + public V remove(String tabID, String sectionID) { + return model.remove(new Path(tabID, sectionID)); + } + + public Collection remove(String tabID) { + List result = new ArrayList(4); + + Path key = new Path(tabID); + for(V next = model.remove(key); next != null; next = model.remove(key)) { + result.add(next); + } + + return Collections.unmodifiableList(result); + } + + public Collection clear() { + List result = new ArrayList(model.values()); + + model.clear(); + + return Collections.unmodifiableList(result); + } + + @Override + public String toString() { + return String.format("TabModel%s", model.toString()); //$NON-NLS-1$ + } + + // + // Nested types + // + + final static class Path { + + // Do not intern this string! Deliberately constructing a new String instance + private static final String WILDCARD = new String("*"); //$NON-NLS-1$ + + final String tabID; + + final String sectionID; + + Path(String tabID, String sectionID) { + checkWildcard(tabID); + checkWildcard(sectionID); + + this.tabID = tabID; + this.sectionID = sectionID; + } + + /** + * Create a wildcard path for all sections in a tab. + */ + Path(String tabID) { + checkWildcard(tabID); + + this.tabID = tabID; + this.sectionID = WILDCARD; + } + + static void checkWildcard(String id) { + // Deliberately testing for identity of non-interned string + if((id == null) || (id == WILDCARD)) { + throw new IllegalArgumentException("Attempt to create a wildcard path explicitly"); //$NON-NLS-1$ + } + } + + @Override + public String toString() { + return String.format("(%s, %s)", tabID, sectionID); //$NON-NLS-1$ + } + + @Override + public int hashCode() { + // This isn't great for hash-map performance, but wildcards have to have the same hash as the keys they + // match, otherwise using them to access the map won't work + return tabID.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(!(obj instanceof Path)) { + return false; + } + + Path other = (Path)obj; + return equals(tabID, other.tabID) && equals(sectionID, other.sectionID); + } + + private static boolean equals(String anID, String 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 6c452ce7b4f..7e9fe1961cf 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 @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2010 CEA LIST. + * Copyright (c) 2010, 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 @@ -9,6 +9,8 @@ * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation * Thibault Le Ouay t.leouay@sherpa-eng.com - Add binding implementation + * Christian W. Damus (CEA) - bug 417409 + * *****************************************************************************/ package org.eclipse.papyrus.views.properties.widgets; @@ -27,6 +29,8 @@ import org.eclipse.papyrus.views.properties.Activator; import org.eclipse.papyrus.views.properties.contexts.Context; import org.eclipse.papyrus.views.properties.contexts.Property; import org.eclipse.papyrus.views.properties.modelelement.DataSource; +import org.eclipse.papyrus.views.properties.modelelement.DataSourceChangedEvent; +import org.eclipse.papyrus.views.properties.modelelement.IDataSourceListener; import org.eclipse.papyrus.views.properties.runtime.ConfigurationManager; import org.eclipse.papyrus.views.properties.util.PropertiesUtil; import org.eclipse.swt.events.DisposeEvent; @@ -52,6 +56,8 @@ public abstract class AbstractPropertyEditor implements IChangeListener, Customi * The DataSource representing the semantic objects */ protected DataSource input; + + private IDataSourceListener dataSourceListener; protected boolean readOnly = false; @@ -83,6 +89,7 @@ public abstract class AbstractPropertyEditor implements IChangeListener, Customi protected IValidator modelValidator; protected IConverter targetToModelConverter; + /** * Indicates if the editor's label should be displayed */ @@ -299,8 +306,22 @@ public abstract class AbstractPropertyEditor implements IChangeListener, Customi * @param input */ public void setInput(DataSource input) { - this.input = input; - checkInput(); + final DataSource oldInput = this.input; + if(input != oldInput) { + if(oldInput != null) { + oldInput.removeDataSourceListener(getDataSourceListener()); + } + + this.input = input; + + if(input != null) { + input.addDataSourceListener(getDataSourceListener()); + } + + // Only do this after attaching our listener so that it will be ahead of + // any ModelElements created for properties + checkInput(); + } } /** @@ -565,4 +586,36 @@ public abstract class AbstractPropertyEditor implements IChangeListener, Customi return modelValidator; } + private IDataSourceListener getDataSourceListener() { + if(dataSourceListener == null) { + dataSourceListener = new IDataSourceListener() { + + public void dataSourceChanged(DataSourceChangedEvent event) { + // The data source's selection changed. Update my validator or clear it if now there is none + IObservableValue observable = AbstractPropertyEditor.this.observableValue; + + if((observable != null) && (modelValidator != null) && (valueEditor != null) && !valueEditor.isDisposed()) { + modelValidator = null; + + // First, clear the validator to disable validation + valueEditor.setStrategies(); + valueEditor.setModelValidator(null); + + // Then re-enable to later when ready for user input + observable.getRealm().asyncExec(new Runnable() { + + public void run() { + if((valueEditor != null) && !valueEditor.isDisposed()) { + valueEditor.setStrategies(); + valueEditor.setModelValidator(getValidator()); + } + } + }); + } + } + }; + } + + return dataSourceListener; + } } diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTSection.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTSection.java index 074caeaf9e0..c282fcc67aa 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTSection.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTSection.java @@ -9,6 +9,7 @@ * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation * Christian W. Damus (CEA) - bug 435420 + * Christian W. Damus (CEA) - bug 417409 * *****************************************************************************/ package org.eclipse.papyrus.views.properties.xwt; @@ -116,15 +117,18 @@ public class XWTSection extends AbstractPropertySection implements IChangeListen } private void setSource(DataSource source) { - if(this.source != null) { - this.source.removeChangeListener(this); - this.source.dispose(); - } + final DataSource oldSource = this.source; + + if(oldSource != source) { + if(oldSource != null) { + oldSource.removeChangeListener(this); + } - this.source = source; + this.source = source; - if(section.getConstraints().size() > 0) { - source.addChangeListener(this); + if(section.getConstraints().size() > 0) { + source.addChangeListener(this); + } } } diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTSectionDescriptor.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTSectionDescriptor.java index 17161e129b9..1907ddffdb4 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTSectionDescriptor.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTSectionDescriptor.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2010 CEA LIST. + * Copyright (c) 2010, 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 @@ -8,6 +8,8 @@ * * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - bug 417409 + * *****************************************************************************/ package org.eclipse.papyrus.views.properties.xwt; @@ -73,5 +75,35 @@ public class XWTSectionDescriptor extends AbstractSectionDescriptor { return true; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + System.identityHashCode(display); + result = prime * result + System.identityHashCode(section); + result = prime * result + System.identityHashCode(view); + return result; + } + + /** + * XWT section descriptors are equal if they have the same (identical) references to the section and view from the property-sheet model + * and are associated with the same display engine. + */ + @Override + public boolean equals(Object obj) { + boolean result; + + if(this == obj) { + result = true; + } else if((obj == null) || (obj.getClass() != this.getClass())) { + result = false; + } else { + XWTSectionDescriptor other = (XWTSectionDescriptor)obj; + + result = (other.section == this.section) && (other.view == this.view) && (other.display == this.display); + } + + return result; + } } diff --git a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTTabDescriptor.java b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTTabDescriptor.java index d4a4b84f240..0c7ed473d80 100644 --- a/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTTabDescriptor.java +++ b/plugins/views/properties/org.eclipse.papyrus.views.properties/src/org/eclipse/papyrus/views/properties/xwt/XWTTabDescriptor.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2010 CEA LIST. + * Copyright (c) 2010, 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 @@ -8,9 +8,13 @@ * * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - bug 417409 + * *****************************************************************************/ package org.eclipse.papyrus.views.properties.xwt; +import java.util.List; + import org.eclipse.papyrus.views.properties.Activator; import org.eclipse.papyrus.views.properties.contexts.Section; import org.eclipse.papyrus.views.properties.contexts.Tab; @@ -104,4 +108,37 @@ public class XWTTabDescriptor extends AbstractTabDescriptor { public int getPriority() { return tab.getPriority(); } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + List sectionDescriptors = getSectionDescriptors(); + result = prime * result + ((tab == null) ? 0 : tab.hashCode()); + result = prime * result + ((sectionDescriptors == null) ? 0 : sectionDescriptors.hashCode()); + return result; + } + + /** + * XWT tab descriptors are equal if they have the same ID and an equal list (in order) of section descriptors. + */ + @Override + public boolean equals(Object obj) { + boolean result; + + if(this == obj) { + result = true; + } else if((obj == null) || (obj.getClass() != this.getClass())) { + result = false; + } else { + XWTTabDescriptor other = (XWTTabDescriptor)obj; + + result = (other.getId() == null) ? this.getId() == null : (other.getId().equals(this.getId())); + if(result) { + result = (other.getSectionDescriptors() == null) ? this.getSectionDescriptors() == null : other.getSectionDescriptors().equals(this.getSectionDescriptors()); + } + } + + return result; + } } -- cgit v1.2.3