Skip to content
Snippets Groups Projects
Commit 3368aa7e authored by Simon Barner's avatar Simon Barner
Browse files

- 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
parent 5108ba3c
No related branches found
No related tags found
No related merge requests found
......@@ -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);
}
}
......@@ -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 {
......
......@@ -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);
}
}
......@@ -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;
}
}
......@@ -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);
......
......@@ -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();
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment