From 3368aa7e0322472b66edc489aef4e0a4d613e3e4 Mon Sep 17 00:00:00 2001 From: Simon Barner <barner@fortiss.org> Date: Wed, 3 Sep 2014 14:18:49 +0000 Subject: [PATCH] - ComboBoxEditingSupport / value providers - Enable selection of values for non-enum attributes from a combo box. The following options are available: - Fix set of values - Dynamically managed set of values. The user may either choose from the existing values for a given annotation type, or enter a new one. Deleting existing choices is also supported. - AnnotationViewPartBase: Also watch specifications in order to ensure update of admissible choices (see code for detailed comments) - GenericAnnotationView: Set viewer input before construction of columns in order to enable aggregation of admissible values refs 1841 --- .../ComboBoxEditingSupport.java | 26 +++- ...leEStructuralFeatureValueProviderBase.java | 21 ++++ .../SingleEnumAttributeValueProviderBase.java | 2 +- .../valueprovider/ValueProviderBase.java | 111 +++++++++++++++++- .../view/AnnotationViewPartBase.java | 34 ++++-- .../view/GenericAnnotationView.java | 8 +- 6 files changed, 181 insertions(+), 21 deletions(-) diff --git a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/editingsupport/ComboBoxEditingSupport.java b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/editingsupport/ComboBoxEditingSupport.java index 2ffc31f8c..a4f1156c1 100644 --- a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/editingsupport/ComboBoxEditingSupport.java +++ b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/editingsupport/ComboBoxEditingSupport.java @@ -17,13 +17,14 @@ $Id$ +--------------------------------------------------------------------------*/ package org.fortiss.tooling.base.ui.annotation.editingsupport; -import java.util.List; +import java.util.Collection; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ComboBoxViewerCellEditor; import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.swt.custom.CCombo; import org.eclipse.swt.widgets.Composite; import org.fortiss.tooling.base.model.element.IAnnotatedSpecification; @@ -40,6 +41,9 @@ public class ComboBoxEditingSupport extends AnnotationEditingSupportBase { /** Combo box cell editor */ private ComboBoxViewerCellEditor cellEditor = null; + /** Flag if this {@link ComboBoxEditingSupport} is editable. */ + boolean isEditable; + /** * Constructor. * @@ -49,15 +53,19 @@ public class ComboBoxEditingSupport extends AnnotationEditingSupportBase { * Annotation class handled by this editing support * @param values * Values for the combo box + * @param isEditable + * Flag if combo box should be editable, i.e. if it is possible to create new values */ public ComboBoxEditingSupport(ColumnViewer viewer, - Class<? extends IAnnotatedSpecification> clazz, List<String> values) { + Class<? extends IAnnotatedSpecification> clazz, Collection<String> values, + boolean isEditable) { super(viewer, clazz); + this.isEditable = isEditable; cellEditor = new ComboBoxViewerCellEditor((Composite)getViewer().getControl()); cellEditor.setLabelProvider(new LabelProvider()); cellEditor.setContentProvider(new ArrayContentProvider()); cellEditor.setInput(values); - cellEditor.getViewer().getCCombo().setEditable(false); + cellEditor.getViewer().getCCombo().setEditable(isEditable); } /** {@inheritDoc} */ @@ -65,4 +73,16 @@ public class ComboBoxEditingSupport extends AnnotationEditingSupportBase { protected CellEditor getCellEditor(Object element) { return cellEditor; } + + /** {@inheritDoc} */ + @Override + protected void setValue(Object element, Object value) { + if(isEditable && value == null) { + // New values, i.e values that have been entered into the text input field, and have not + // been selected from the combo box are indicated by a null value. Hence, fetch the text + // from the corresponding UI control. + value = ((CCombo)cellEditor.getControl()).getText(); + } + super.setValue(element, value); + } } diff --git a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/SingleEStructuralFeatureValueProviderBase.java b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/SingleEStructuralFeatureValueProviderBase.java index b9168f139..a7776ad11 100644 --- a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/SingleEStructuralFeatureValueProviderBase.java +++ b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/SingleEStructuralFeatureValueProviderBase.java @@ -94,9 +94,22 @@ public abstract class SingleEStructuralFeatureValueProviderBase<T extends IAnnot } /** {@inheritDoc} */ + @SuppressWarnings("unchecked") @Override public <U> void setAnnotationValue(U value, T specification) throws Exception { if(value instanceof String) { + // In case, the input choice is presented in dynamically managed combo box (i.e., where + // new value choices can be entered by the user, the empty String indicates that the + // current value should be deleted, and a neighboring value from the set should be used. + // In case the last occurrence of a value choice has been removed, it will be no longer + // be offered in the combo box. + if(((String)value).isEmpty()) { + String rval = getAnnotationValueAndUpdateInputChoice((String)value, specification); + if(rval != null) { + value = (U)rval; + } + } + setAnnotationValueFromString((String)value, specification); } else { specification.eSet(structuralFeature, value); @@ -106,6 +119,14 @@ public abstract class SingleEStructuralFeatureValueProviderBase<T extends IAnnot /** {@inheritDoc} */ @Override public void setAnnotationValue(String value, T specification) throws Exception { + // See comment in setAnnotationValue(U value, T specification) + if((value == null || value.isEmpty())) { + String rval = getAnnotationValueAndUpdateInputChoice(value, specification); + if(rval != null) { + value = rval; + } + } + if(structuralFeature instanceof EAttribute) { setAnnotationValueFromString(value, specification); } else { diff --git a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/SingleEnumAttributeValueProviderBase.java b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/SingleEnumAttributeValueProviderBase.java index 491b128fa..4ce5139a6 100644 --- a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/SingleEnumAttributeValueProviderBase.java +++ b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/SingleEnumAttributeValueProviderBase.java @@ -83,6 +83,6 @@ public abstract class SingleEnumAttributeValueProviderBase<T extends IAnnotatedS enumValues.add(e.getName()); } - return new ComboBoxEditingSupport(viewer, clazz, enumValues); + return new ComboBoxEditingSupport(viewer, clazz, enumValues, false); } } diff --git a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/ValueProviderBase.java b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/ValueProviderBase.java index e281dd030..d94fb7b38 100644 --- a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/ValueProviderBase.java +++ b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/valueprovider/ValueProviderBase.java @@ -17,9 +17,14 @@ $Id$ +--------------------------------------------------------------------------*/ package org.fortiss.tooling.base.ui.annotation.valueprovider; +import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EFactory; import org.eclipse.emf.ecore.EObject; @@ -28,7 +33,10 @@ import org.eclipse.jface.viewers.EditingSupport; import org.fortiss.tooling.base.model.element.IAnnotatedSpecification; import org.fortiss.tooling.base.model.element.IModelElement; import org.fortiss.tooling.base.model.element.IModelElementSpecification; +import org.fortiss.tooling.base.ui.annotation.AnnotationEntry; +import org.fortiss.tooling.base.ui.annotation.editingsupport.ComboBoxEditingSupport; import org.fortiss.tooling.base.ui.annotation.editingsupport.TextEditingSupport; +import org.fortiss.tooling.kernel.utils.EcoreUtils; /** * Base class for {@link IAnnotationValueProvider}s. @@ -52,6 +60,21 @@ public abstract class ValueProviderBase<T extends IAnnotatedSpecification> imple */ protected final EFactory annotationFactory; + /** + * Fixed input choice to be offered in a combo box. The semantics of this field is as follows: + * <ul> + * <li> {@code fixedInputChoice == null} -> do not use fixed values, provide text input cell.</li> + * <li> {@code !fixedInputChoice.isEmpty()} -> provide a fixed set of choices in an input box. + * For enum types, see {@link SingleEnumAttributeValueProviderBase}</li> + * <li> {@code fixedInputChoice.isEmpty()} -> dynamically manage the set of provided values, i.e. + * the combo box is editable and enables the user to add new values and remove existing choices + * (by removing all instances of them).</li> + */ + private List<String> fixedInputChoice; + + /** Current input choice, computed according to the initialization of {@link #fixedInputChoice}. */ + private Collection<String> currentInputChoice; + /** * Constructs a new {@link IAnnotationValueProvider} for an annotation specified in a given * {@link EClass}, and constructed using a given {@link EFactory}. @@ -109,8 +132,17 @@ public abstract class ValueProviderBase<T extends IAnnotatedSpecification> imple public EditingSupport createEditingSupport(ColumnViewer viewer, Class<? extends IAnnotatedSpecification> clazz, String instanceKey) { - // Default to text editing cell. - return new TextEditingSupport(viewer, clazz); + initializeInputChoice(viewer, clazz); + + // Input is not restricted -> free editing in text cell + if(currentInputChoice == null) { + return new TextEditingSupport(viewer, clazz); + } + + // Input is restricted to concrete set of values + // (use SingleEnumAttributeValueProviderBase for Enum types!) + return new ComboBoxEditingSupport(viewer, clazz, currentInputChoice, + fixedInputChoice.isEmpty()); } /** @@ -180,4 +212,79 @@ public abstract class ValueProviderBase<T extends IAnnotatedSpecification> imple return false; } + /** Sets the {@link #fixedInputChoice} field. */ + public void setFixedInputChoice(List<String> fixedInputChoice) { + this.fixedInputChoice = fixedInputChoice; + } + + /** + * Initializes the {@link #currentInputChoice} field (called during the construction of the + * editing support in {@link #createEditingSupport(ColumnViewer, Class)}). + */ + @SuppressWarnings("unchecked") + private void initializeInputChoice(ColumnViewer viewer, + Class<? extends IAnnotatedSpecification> clazz) { + // Input choice is not set, or it is restricted to concrete set of values + // (use SingleEnumAttributeValueProviderBase for Enum types!) + if(fixedInputChoice == null || !fixedInputChoice.isEmpty()) { + currentInputChoice = fixedInputChoice; + + } else if(!(viewer.getInput() instanceof Set<?>) || ((Set<?>)viewer.getInput()).isEmpty() || + !(((Set<?>)viewer.getInput()).iterator().next() instanceof AnnotationEntry)) { + currentInputChoice = null; + + } else { + + AnnotationEntry annotationEntry = + ((Set<AnnotationEntry>)viewer.getInput()).iterator().next(); + + EObject root = annotationEntry.getModelElement(); + while(root.eContainer() != null) { + root = root.eContainer(); + } + + currentInputChoice = new TreeSet<String>(); + for(T specification : (EList<T>)EcoreUtils.getChildrenWithType(root, clazz)) { + currentInputChoice.add(getAnnotationValue(specification).toString()); + } + } + } + + /** + * Updates the current input choice, and returns the actual value. Called from + * {@link #setAnnotationValue(String, IAnnotatedSpecification)}. In particular, this + * method handles the removal of input choices from if these are dynamically managed. + */ + protected String getAnnotationValueAndUpdateInputChoice(String value, T specification) { + if((value == null || value.isEmpty()) && currentInputChoice != null && + !currentInputChoice.isEmpty()) { + // Only one entry left: do not remove + if(currentInputChoice.size() == 1) { + return currentInputChoice.iterator().next(); + } + + // 1st entry is to be removed: Choose 2nd entry as new value + String currentValue = getAnnotationValue(specification); + if(currentInputChoice.iterator().next().equals(currentValue)) { + Iterator<String> iter = currentInputChoice.iterator(); + iter.next(); + String rval = iter.next(); + currentInputChoice.remove(currentValue); + return rval; + } + + // Otherwise, return the entry just before the entry to be removed. + String rval = null; + for(Iterator<String> iter = currentInputChoice.iterator(); iter.hasNext();) { + String s = iter.next(); + if(s != null && s.equals(currentValue)) { + currentInputChoice.remove(currentValue); + return rval; + } + rval = s; + } + } + return null; + } + } diff --git a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/view/AnnotationViewPartBase.java b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/view/AnnotationViewPartBase.java index ddc5a1c69..fc4edd5f9 100644 --- a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/view/AnnotationViewPartBase.java +++ b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/view/AnnotationViewPartBase.java @@ -32,6 +32,7 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.part.ViewPart; +import org.fortiss.tooling.base.model.element.IAnnotatedSpecification; import org.fortiss.tooling.base.model.element.IModelElement; import org.fortiss.tooling.base.ui.annotation.AnnotationEntry; import org.fortiss.tooling.base.ui.annotation.IAnnotationValueService; @@ -62,7 +63,7 @@ public abstract class AnnotationViewPartBase extends ViewPart implements ISelect * Set of {@link IModelElement}s for which the {@link #changeListener} has been installed to * watch for model changes (to trigger an update of this {@link AnnotationViewPartBase}). */ - Set<IModelElement> watchedModelElements = new HashSet<IModelElement>(); + Set<EObject> watchedModelElements = new HashSet<EObject>(); /** * {@link Adapter} to watch for the addition, removal or value change of model elements @@ -77,15 +78,19 @@ public abstract class AnnotationViewPartBase extends ViewPart implements ISelect notification.getEventType() == Notification.SET) { Object notifier = notification.getNotifier(); - if((notifier instanceof IModelElement) && - (IPersistencyService.INSTANCE - .getTopLevelElementFor((IModelElement)notifier) != null)) { + if((notifier instanceof EObject) && + (IPersistencyService.INSTANCE.getTopLevelElementFor((EObject)notifier) != null)) { // Ignore update requests for elements that are not hook to a top-level element, // e.g. SET events that are fired when a component is removed (before the actual // REMOVE event). Otherwise, the AnnotationValueService would create a NPE // during the preparation of the context via // KernelModelElementUtils.runAsCommand(). - update((IModelElement)notifier); + + if(notifier instanceof IModelElement) { + update((IModelElement)notifier); + } else if(notifier instanceof IAnnotatedSpecification) { + update(((IAnnotatedSpecification)notifier).getSpecificationOf()); + } } } } @@ -129,28 +134,33 @@ public abstract class AnnotationViewPartBase extends ViewPart implements ISelect rootElements.add(rootElement); } - // Determine all IModelElements below the current root elements - Set<IModelElement> currentWatchedModelElements = new HashSet<IModelElement>(); + // Determine all IModelElements, and their specifications below the current root elements. + // Specifications must be watched, too, since the state of an editing support for a given + // specification can depend on the state of one or more other specifications. + Set<EObject> currentWatchedModelElements = new HashSet<EObject>(); for(EObject rootElement : rootElements) { for(EObject modelElement : EcoreUtils.getChildrenWithType(rootElement, IModelElement.class)) { - currentWatchedModelElements.add((IModelElement)modelElement); + currentWatchedModelElements.add(modelElement); + currentWatchedModelElements.addAll(((IModelElement)modelElement) + .getSpecifications()); } if(rootElement instanceof IModelElement) { - currentWatchedModelElements.add((IModelElement)rootElement); + currentWatchedModelElements.add(rootElement); + currentWatchedModelElements + .addAll(((IModelElement)rootElement).getSpecifications()); } } // Add change listeners to IModelElements that are about to appear in the view - Set<IModelElement> tmpWatchedModelElements = - new HashSet<IModelElement>(currentWatchedModelElements); + Set<EObject> tmpWatchedModelElements = new HashSet<EObject>(currentWatchedModelElements); tmpWatchedModelElements.removeAll(watchedModelElements); for(EObject modelElement : tmpWatchedModelElements) { modelElement.eAdapters().add(changeListener); } // Remove change listeners IModelElements that are about to disappear from the view - tmpWatchedModelElements = new HashSet<IModelElement>(watchedModelElements); + tmpWatchedModelElements = new HashSet<EObject>(watchedModelElements); tmpWatchedModelElements.removeAll(currentWatchedModelElements); for(EObject modelElement : tmpWatchedModelElements) { modelElement.eAdapters().remove(changeListener); diff --git a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/view/GenericAnnotationView.java b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/view/GenericAnnotationView.java index 379f0524c..1b8c50517 100644 --- a/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/view/GenericAnnotationView.java +++ b/org.fortiss.tooling.base.ui/trunk/src/org/fortiss/tooling/base/ui/annotation/view/GenericAnnotationView.java @@ -377,6 +377,11 @@ public class GenericAnnotationView extends AnnotationViewPartBase { // Minimize flickering while updating the table tableViewer.getTable().setRedraw(false); + // Input must be set before the construction of the columns, since the editing support for a + // given cell might need to query the contents of other model elements (e.g., in order to + // aggregate a set of admissible choices for its value). + tableViewer.setInput(annotationEntries); + /* * Sorted set of {@link ColumnHandle} used to instantiate this {@link * GenericAnnotationView}'s columns in the right order. @@ -433,9 +438,6 @@ public class GenericAnnotationView extends AnnotationViewPartBase { // Update the view tableViewer.getTable().setRedraw(true); - if(!tableViewer.getTable().isDisposed()) { - tableViewer.setInput(annotationEntries); - } tableViewer.refresh(); } -- GitLab