blob: 6ade542da2955b7834125f30aa352d4045940128 [file] [log] [blame]
/**
* 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();
}
}