| /** |
| * Copyright (c) 2008, 2013 itemis AG 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: |
| * itemis AG - Initial API and implementation |
| * Lorenzo Bettini, Francesco Guidieri - refactoring for EmfParsley |
| * |
| */ |
| package org.eclipse.emf.parsley.composite; |
| |
| import java.util.List; |
| |
| import org.eclipse.core.databinding.Binding; |
| import org.eclipse.core.databinding.observable.value.IObservableValue; |
| import org.eclipse.emf.databinding.EMFDataBindingContext; |
| import org.eclipse.emf.databinding.EMFProperties; |
| import org.eclipse.emf.databinding.edit.EMFEditProperties; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.EcorePackage; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.edit.command.SetCommand; |
| import org.eclipse.emf.edit.domain.EditingDomain; |
| import org.eclipse.emf.parsley.EmfParsleyActivator; |
| import org.eclipse.emf.parsley.EmfParsleyConstants; |
| import org.eclipse.emf.parsley.edit.IEditingStrategy; |
| import org.eclipse.emf.parsley.edit.TextUndoRedo; |
| import org.eclipse.emf.parsley.internal.databinding.DataBindingHelper; |
| import org.eclipse.emf.parsley.runtime.util.PolymorphicDispatcherExtensions; |
| import org.eclipse.emf.parsley.ui.provider.ComboViewerLabelProvider; |
| import org.eclipse.emf.parsley.ui.provider.FeatureLabelCaptionProvider; |
| import org.eclipse.emf.parsley.util.DatabindingUtil; |
| import org.eclipse.emf.parsley.util.FeatureHelper; |
| import org.eclipse.emf.parsley.widgets.IWidgetFactory; |
| import org.eclipse.jface.bindings.keys.KeyStroke; |
| import org.eclipse.jface.bindings.keys.ParseException; |
| import org.eclipse.jface.databinding.viewers.ViewersObservables; |
| import org.eclipse.jface.fieldassist.ContentProposalAdapter; |
| import org.eclipse.jface.fieldassist.ControlDecoration; |
| import org.eclipse.jface.fieldassist.FieldDecoration; |
| import org.eclipse.jface.fieldassist.FieldDecorationRegistry; |
| import org.eclipse.jface.fieldassist.SimpleContentProposalProvider; |
| import org.eclipse.jface.fieldassist.TextContentAdapter; |
| import org.eclipse.jface.viewers.ArrayContentProvider; |
| import org.eclipse.jface.viewers.ComboViewer; |
| import org.eclipse.jface.viewers.ILabelProvider; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.KeyListener; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.DateTime; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Text; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.Iterables; |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import com.google.inject.name.Named; |
| |
| /** |
| * |
| * Creates Control for an {@link EStructuralFeature} |
| * |
| * @author Dennis Huebner initial code |
| * @author Lorenzo Bettini refactoring for EMF Parsley |
| * @author Francesco Guidieri added validation support |
| * |
| */ |
| public abstract class AbstractControlFactory implements IWidgetFactory { |
| |
| private static final int GRID_DATA_HORIZONTAL_INDENT = 10; |
| |
| private static final String OBSERVE_PREFIX = "observe_"; |
| |
| private static final String CONTROL_PREFIX = "control_"; |
| |
| @Inject |
| @Named(EmfParsleyConstants.CONTENT_ASSIST_SHORTCUT) |
| private String contentAssistShortcut; |
| |
| @Inject |
| private Provider<ILabelProvider> labelProviderProvider; |
| |
| @Inject |
| private Provider<ComboViewerLabelProvider> comboViewerLabelProviderProvider; |
| |
| @Inject |
| private FeatureHelper featureHelper; |
| |
| @Inject |
| private ProposalCreator proposalCreator; |
| |
| @Inject |
| private DataBindingHelper dataBindingHelper; |
| |
| private EObject owner; |
| private Resource resource; |
| private EditingDomain domain; |
| private EMFDataBindingContext edbc; |
| |
| /** |
| * This will be created by the abstract method {@link #createWidgetFactory()} |
| */ |
| private IWidgetFactory widgetFactory; |
| |
| /** |
| * This will be created by the abstract method |
| * {@link #createFeatureLabelCaptionProvider()} |
| */ |
| private FeatureLabelCaptionProvider featureLabelCaptionProvider; |
| |
| private boolean readonly = false; |
| |
| public static final String EOBJECT_KEY = EcorePackage.Literals.EOBJECT |
| .getName(); |
| public static final String ESTRUCTURALFEATURE_KEY = EcorePackage.Literals.ESTRUCTURAL_FEATURE |
| .getName(); |
| |
| public AbstractControlFactory() { |
| |
| } |
| |
| protected EObject getOwner() { |
| return owner; |
| } |
| |
| protected EditingDomain getEditingDomain() { |
| return domain; |
| } |
| |
| protected EMFDataBindingContext getDataBindingContext() { |
| return edbc; |
| } |
| |
| protected Resource getResource() { |
| if (resource == null) { |
| resource = owner.eResource(); |
| } |
| return resource; |
| } |
| |
| /** |
| * Concrete implementation should create a {@link IWidgetFactory} according |
| * to the specific widgets (e.g., for dialogs or forms). |
| * |
| * @return the concrete implementation |
| */ |
| protected abstract IWidgetFactory createWidgetFactory(); |
| |
| /** |
| * Concrete implementation should create a |
| * {@link FeatureLabelCaptionProvider} according to the specific widgets |
| * (e.g., for dialogs or forms). |
| * |
| * @return the concrete implementation |
| */ |
| protected abstract FeatureLabelCaptionProvider createFeatureLabelCaptionProvider(); |
| |
| public boolean isReadonly() { |
| return readonly; |
| } |
| |
| public void setReadonly(boolean readonly) { |
| this.readonly = readonly; |
| } |
| |
| private ILabelProvider createLabelProvider() { |
| return labelProviderProvider.get(); |
| } |
| |
| private ILabelProvider createComboViewerLabelProvider() { |
| return comboViewerLabelProviderProvider.get(); |
| } |
| |
| /** |
| * Initializes this factory for creating {@link Control}s with |
| * Data Binding. |
| * |
| * The passed {@link EditingDomain} can be null; in that case |
| * Data Binding will be implemented through {@link EMFProperties}, instead |
| * of {@link EMFEditProperties}. If the {@link EditingDomain} is null |
| * views and editors will not be notified about changes to the passed |
| * {@link EObject}. This is useful when you want to create {@link Control}s |
| * that act on a copy of the original object (see also {@link IEditingStrategy}). |
| * |
| * @param domain |
| * @param owner |
| * @param parent |
| * @see IEditingStrategy |
| */ |
| public void init(EditingDomain domain, EObject owner, Composite parent) { |
| widgetFactory = createWidgetFactory(); |
| init(parent); |
| featureLabelCaptionProvider = createFeatureLabelCaptionProvider(); |
| this.edbc = new EMFDataBindingContext(); |
| this.domain = domain; |
| this.owner = owner; |
| } |
| |
| /** |
| * Creates a caption label and a {@link Control} for the passed {@link EStructuralFeature} |
| * of the {@link EObject} handled by this factory. |
| * |
| * @param feature the {@link EStructuralFeature} for the creation |
| */ |
| public void createEditingField(EStructuralFeature feature) { |
| featureLabelCaptionProvider.getLabel(getParent(), owner, feature); |
| create(feature); |
| } |
| |
| /** |
| * Creates a {@link Control} for the passed {@link EStructuralFeature} |
| * of the {@link EObject} handled by this factory, using polymorphic dispatch. |
| * |
| * @param feature the {@link EStructuralFeature} for the creation of control |
| * @return a {@link Control} |
| */ |
| public Control create(EStructuralFeature feature) { |
| return create(feature, true); |
| } |
| |
| /** |
| * Creates a {@link Control} for the passed {@link EStructuralFeature} |
| * of the {@link EObject} handled by this factory, using polymorphic dispatch, if |
| * specified in the argument withPolymorphicDispatch. |
| * |
| * @param feature |
| * @param withPolymorphicDispatch |
| * @return |
| */ |
| public Control create(EStructuralFeature feature, boolean withPolymorphicDispatch) { |
| Control control = null; |
| |
| if (withPolymorphicDispatch) { |
| control = polymorphicCreateControl(feature); |
| } |
| |
| if (control == null) { |
| if (feature.isMany()) { |
| control = createAndBindList(feature, withPolymorphicDispatch); |
| } else { |
| control = createAndBindValue(feature, withPolymorphicDispatch); |
| } |
| setupControl(feature, control); |
| } |
| |
| registerUndoRedo(control); |
| |
| return control; |
| } |
| |
| /** |
| * Creates a {@link Control} for the passed {@link EStructuralFeature} |
| * of the {@link EObject} handled by this factory, using the default |
| * implementation, that is, without using polymorphic dispatch. |
| * |
| * @param feature the {@link EStructuralFeature} for the creation of control |
| * @return a {@link Control} |
| */ |
| public Control createDefaultControl(EStructuralFeature feature) { |
| return create(feature, false); |
| } |
| |
| protected KeyListener registerUndoRedo(Control control) { |
| if (control instanceof Text) { |
| return new TextUndoRedo((Text) control); |
| } |
| return null; |
| } |
| |
| @SuppressWarnings("rawtypes") |
| protected Control createAndBindList(final EStructuralFeature feature, boolean withPolymorphicDispatch) { |
| IObservableValue source = createFeatureObserveable(feature, withPolymorphicDispatch); |
| |
| ControlObservablePair retValAndTargetPair = createControlForList(feature, withPolymorphicDispatch); |
| Control retVal = retValAndTargetPair.getControl(); |
| IObservableValue target = retValAndTargetPair.getObservableValue(); |
| |
| Binding binding = bindValue(feature, target, source); |
| binding.updateModelToTarget(); |
| return retVal; |
| } |
| |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| protected IObservableValue createFeatureObserveable(final EStructuralFeature feature, boolean withPolymorphicDispatch) { |
| if (withPolymorphicDispatch) { |
| IObservableValue source = polymorphicCreateObserveable(domain, feature); |
| if (source != null) { |
| return source; |
| } |
| } |
| if (domain != null) { |
| return EMFEditProperties.value(domain, feature).observe(owner); |
| } else { |
| return EMFProperties.value(feature).observe(owner); |
| } |
| } |
| |
| @SuppressWarnings("rawtypes") |
| protected ControlObservablePair createControlForList( |
| final EStructuralFeature feature, boolean withPolymorphicDispatch) { |
| if (withPolymorphicDispatch) { |
| ControlObservablePair result = polymorphicGetObservableControl(feature); |
| if (result != null) { |
| return result; |
| } |
| } |
| |
| MultipleFeatureControl mfc = new MultipleFeatureControl(getParent(), |
| this, labelProviderProvider.get(), owner, |
| feature, proposalCreator, isReadonly()); |
| IObservableValue target = new MultipleFeatureControlObservable(mfc); |
| return new ControlObservablePair(mfc, target); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| protected Control createAndBindValue(EStructuralFeature feature, boolean withPolymorphicDispatch) { |
| IObservableValue featureObservable = createFeatureObserveable(feature, withPolymorphicDispatch); |
| |
| if (withPolymorphicDispatch) { |
| Control control = polymorphicCreateControl(feature, featureObservable); |
| if (control != null) { |
| return control; |
| } |
| } |
| |
| ControlObservablePair retValAndTargetPair = createControlAndObservableValue(feature, withPolymorphicDispatch); |
| Control retVal = retValAndTargetPair.getControl(); |
| IObservableValue controlObservable = retValAndTargetPair |
| .getObservableValue(); |
| |
| if (controlObservable != null) { |
| bindValue(feature, controlObservable, featureObservable); |
| } |
| |
| return retVal; |
| } |
| |
| /** |
| * @since 1.1 |
| */ |
| @SuppressWarnings("rawtypes") |
| protected Binding bindValue(EStructuralFeature feature, IObservableValue target, |
| IObservableValue source) { |
| return dataBindingHelper.bindValue(feature, target, source, owner, edbc); |
| } |
| |
| protected ControlObservablePair createControlAndObservableValue( |
| EStructuralFeature feature, boolean withPolymorphicDispatch) { |
| if (withPolymorphicDispatch) { |
| ControlObservablePair result = polymorphicGetObservableControl(feature); |
| if (result != null) { |
| return result; |
| } |
| } |
| |
| if (featureHelper.isBooleanFeature(feature)) { |
| return createControlAndObservableValueForBoolean(); |
| } else { |
| return createControlAndObservableValueForNonBooleanFeature(feature); |
| } |
| } |
| |
| protected ControlObservablePair createControlAndObservableValueForBoolean() { |
| ControlObservablePair retValAndTargetPair = new ControlObservablePair(); |
| Button b = createButton("", SWT.CHECK); |
| b.setEnabled(!isReadonly()); |
| retValAndTargetPair.setControl(b); |
| retValAndTargetPair.setObservableValue(DatabindingUtil.observeSelection(b)); |
| return retValAndTargetPair; |
| } |
| |
| protected ControlObservablePair createControlAndObservableValueForNonBooleanFeature( |
| EStructuralFeature feature) { |
| List<Object> proposals = null; |
| if (!isReadonly()) { |
| proposals = createProposals(feature); |
| } |
| if (featureHelper.hasPredefinedProposals(feature) && !isReadonly()) { |
| if (!featureHelper.isEnum(feature)) { |
| // empty item for setting reference to null |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=490463 |
| proposals.add(0, SetCommand.UNSET_VALUE); |
| } |
| return createControlAndObservableWithPredefinedProposals(proposals); |
| } else { |
| if (isReadonly() && feature instanceof EReference) { |
| return createControlAndObservableForEObjectReadOnly(); |
| } |
| return createControlAndObservableWithoutPredefinedProposals(proposals); |
| } |
| } |
| |
| protected List<Object> createProposals(EStructuralFeature feature) { |
| proposalCreator.setResource(getResource()); |
| return proposalCreator.proposals(owner, feature); |
| } |
| |
| @SuppressWarnings("deprecation") |
| protected ControlObservablePair createControlAndObservableWithPredefinedProposals( |
| List<?> proposals) { |
| ComboViewer comboViewer = createComboViewer(SWT.READ_ONLY); |
| comboViewer.setContentProvider(new ArrayContentProvider()); |
| comboViewer.setLabelProvider(createComboViewerLabelProvider()); |
| comboViewer.setInput(proposals); |
| ControlObservablePair retValAndTargetPair = new ControlObservablePair(); |
| retValAndTargetPair.setControl(comboViewer.getCombo()); |
| retValAndTargetPair.setObservableValue(ViewersObservables |
| .observeSingleSelection(comboViewer)); |
| return retValAndTargetPair; |
| } |
| |
| protected ControlObservablePair createControlAndObservableWithoutPredefinedProposals( |
| List<?> proposals) { |
| ControlObservablePair retValAndTargetPair = new ControlObservablePair(); |
| Text t = createText(""); |
| t.setEditable(!isReadonly()); |
| addContentProposalAdapter(t, proposals); |
| retValAndTargetPair.setControl(t); |
| retValAndTargetPair.setObservableValue(DatabindingUtil.observeText(t, SWT.Modify)); |
| return retValAndTargetPair; |
| } |
| |
| protected ControlObservablePair createControlAndObservableForEObjectReadOnly() { |
| ControlObservablePair retValAndTargetPair = new ControlObservablePair(); |
| Text t = createText(""); |
| t.setEditable(false); |
| retValAndTargetPair.setControl(t); |
| retValAndTargetPair.setObservableValue(new EObjectTextObservable( |
| createLabelProvider(), t)); |
| |
| return retValAndTargetPair; |
| } |
| |
| protected ContentProposalAdapter addContentProposalAdapter(Text t, List<?> proposals) { |
| if (proposals != null && !proposals.isEmpty()) { |
| Iterable<String> filteredNotNullToString = Iterables.transform( |
| Iterables.filter(proposals, Predicates.notNull()), |
| new Function<Object, String>() { |
| |
| @Override |
| public String apply(Object input) { |
| return input.toString(); |
| } |
| |
| }); |
| ControlDecoration field = new ControlDecoration(t, SWT.BORDER); |
| FieldDecoration requiredFieldIndicator = FieldDecorationRegistry |
| .getDefault().getFieldDecoration( |
| FieldDecorationRegistry.DEC_CONTENT_PROPOSAL); |
| field.setImage(requiredFieldIndicator.getImage()); |
| field.setDescriptionText(requiredFieldIndicator.getDescription()); |
| KeyStroke keyStroke = null; |
| try { |
| keyStroke = KeyStroke.getInstance(contentAssistShortcut); |
| } catch (ParseException e) { |
| EmfParsleyActivator |
| .logError("Error while parsing keystroke: " + contentAssistShortcut, e); |
| } |
| return new ContentProposalAdapter(t, new TextContentAdapter(), |
| new SimpleContentProposalProvider( |
| Iterables.toArray(filteredNotNullToString, String.class)), |
| keyStroke, |
| null); |
| } |
| return null; |
| } |
| |
| private void setupControl(EStructuralFeature f, Control c) { |
| // disable unchangeable and unserializable |
| if (c != null) { |
| // don't override readonly behavior |
| if (c.isEnabled()) { |
| c.setEnabled( |
| featureHelper.isEditable(f)); |
| } |
| c.setData(AbstractControlFactory.ESTRUCTURALFEATURE_KEY, f); |
| c.setData(AbstractControlFactory.EOBJECT_KEY, owner); |
| // set default layout data if not already set by a custom |
| // polymorphic implementation or from the DSL |
| if (c.getLayoutData()==null) { |
| GridData deafultLayout = new GridData(GridData.FILL_HORIZONTAL); |
| deafultLayout.horizontalIndent=GRID_DATA_HORIZONTAL_INDENT; |
| c.setLayoutData(deafultLayout); |
| } |
| } |
| } |
| |
| public void dispose() { |
| edbc.dispose(); |
| } |
| |
| private ControlObservablePair polymorphicGetObservableControl( |
| EStructuralFeature feature) { |
| return PolymorphicDispatcherExtensions |
| .<ControlObservablePair> polymorphicInvokeBasedOnFeature(this, |
| owner.eClass(), feature, CONTROL_PREFIX, feature); |
| } |
| |
| /** |
| * Polymorphically invokes a create_EClass_feature(DataBindingContext, |
| * IObservableValue), trying to get the new version with validation support. |
| * If not found, the old version of the method is searched, for backward compatibility. |
| * |
| * @param feature |
| * @param featureObservable |
| * @return |
| */ |
| @SuppressWarnings("rawtypes") |
| private Control polymorphicCreateControl(EStructuralFeature feature, |
| IObservableValue featureObservable) { |
| Control polymorphicInvokeNewVersion = PolymorphicDispatcherExtensions |
| .<Control> polymorphicInvokeBasedOnFeature(this, |
| owner.eClass(), feature, CONTROL_PREFIX, |
| featureObservable, feature); |
| if(polymorphicInvokeNewVersion!=null){ |
| return polymorphicInvokeNewVersion; |
| } |
| return PolymorphicDispatcherExtensions |
| .<Control> polymorphicInvokeBasedOnFeature(this, |
| owner.eClass(), feature, CONTROL_PREFIX, edbc, |
| featureObservable); |
| } |
| |
| /** |
| * Polymorphically invokes a create_EClass_feature(EObject) |
| * |
| * @param feature |
| * @return |
| */ |
| private Control polymorphicCreateControl(EStructuralFeature feature) { |
| return PolymorphicDispatcherExtensions |
| .<Control> polymorphicInvokeBasedOnFeature(this, |
| owner.eClass(), feature, CONTROL_PREFIX, owner); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| private IObservableValue polymorphicCreateObserveable(EditingDomain domain, |
| EStructuralFeature feature) { |
| return PolymorphicDispatcherExtensions |
| .<IObservableValue> polymorphicInvokeBasedOnFeature(this, |
| owner.eClass(), feature, OBSERVE_PREFIX, domain, owner); |
| } |
| |
| @Override |
| public Label createLabel(String text) { |
| return widgetFactory.createLabel(text); |
| } |
| |
| @Override |
| public Label createLabel(Composite parent, String text) { |
| return widgetFactory.createLabel(parent, text); |
| } |
| |
| @Override |
| public Button createButton(String text, int... styles) { |
| return widgetFactory.createButton(text, styles); |
| } |
| |
| @Override |
| public Button createButton(Composite parent, String text, int style) { |
| return widgetFactory.createButton(parent, text, style); |
| } |
| |
| @Override |
| public Text createText(String text) { |
| return widgetFactory.createText(text); |
| } |
| |
| @Override |
| public Text createText(String text, int... styles) { |
| return widgetFactory.createText(text, styles); |
| } |
| |
| @Override |
| public Text createText(Composite parent, String text) { |
| return widgetFactory.createText(parent, text); |
| } |
| |
| @Override |
| public Text createText(Composite parent, int... styles) { |
| return widgetFactory.createText(parent, styles); |
| } |
| |
| @Override |
| public Text createText(Composite parent, String text, int style) { |
| return widgetFactory.createText(parent, text, style); |
| } |
| |
| @Override |
| public ComboViewer createComboViewer(int... styles) { |
| return widgetFactory.createComboViewer(styles); |
| } |
| |
| @Override |
| public ComboViewer createComboViewer(Composite parent, int style) { |
| return widgetFactory.createComboViewer(parent, style); |
| } |
| |
| @Override |
| public DateTime createDateTime() { |
| return widgetFactory.createDateTime(); |
| } |
| |
| @Override |
| public DateTime createDateTime(int... styles) { |
| return widgetFactory.createDateTime(styles); |
| } |
| |
| @Override |
| public DateTime createDateTime(Composite parent) { |
| return widgetFactory.createDateTime(parent); |
| } |
| |
| @Override |
| public DateTime createDateTime(Composite parent, int style) { |
| return widgetFactory.createDateTime(parent, style); |
| } |
| |
| @Override |
| public Composite getParent() { |
| return widgetFactory.getParent(); |
| } |
| |
| } |