/******************************************************************************
* Copyright (c) 2011-2013, Linagora
*
* 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:
* Linagora - initial API and implementation
*******************************************************************************/
package com.ebmwebsourcing.petals.common.internal.provisional.emf;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.databinding.EMFObservables;
import org.eclipse.emf.databinding.edit.EditingDomainEObjectObservableValue;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.command.AbstractOverrideableCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.databinding.viewers.ViewersObservables;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.FormToolkit;
import com.ebmwebsourcing.petals.common.internal.Messages;
import com.ebmwebsourcing.petals.common.internal.provisional.utils.StringUtils;
import com.ebmwebsourcing.petals.common.internal.provisional.utils.SwtFactory;
/**
* @author Mickael Istria - EBM WebSourcing
*/
public final class EObjectUIHelper {
/**
* Private constructor for utility class.
*/
private EObjectUIHelper() {
// nothing
}
/**
* A validator for mandatory fields.
*/
private static final class MandatoryFieldValidator implements IValidator {
private final EStructuralFeature feature;
/**
* Constructor.
* @param feature
*/
public MandatoryFieldValidator(EStructuralFeature feature) {
this.feature = feature;
}
/*
* (non-Javadoc)
* @see org.eclipse.core.databinding.validation.IValidator
* #validate(java.lang.Object)
*/
@Override
public IStatus validate( Object value ) {
IStatus result = ValidationStatus.ok();
if( value instanceof String && StringUtils.isEmpty((String) value)) {
String label = StringUtils.camelCaseToHuman( this.feature.getName());
label = StringUtils.capitalize( label );
result = ValidationStatus.error( NLS.bind( Messages.fieldNotSet, label ));
}
return result;
}
}
/**
* An entry description.
*/
public static class EntryDescription {
public Object widget;
public EAttribute attribute;
/**
* Constructor.
* @param widget
* @param att
*/
public EntryDescription(Object widget, EAttribute att) {
this.widget = widget;
this.attribute = att;
}
}
/**
* Generates a 2 column list with left column containing description of widgets and right column containing widget.
* @param eObject the eObject to edit
* @param toolkit a {@link FormToolkit} to create widgets
* @param parent
* @param domain the {@link EditingDomain} in case of transactional edition. Can be null, then no transaction is used.
* @param dbc
* @param toProcessFeatures list of features to edit.
* @return
*/
public static List<EntryDescription> generateWidgets(
EObject eObject,
FormToolkit toolkit,
Composite parent,
EditingDomain domain,
DataBindingContext dbc,
boolean showDecorator,
EStructuralFeature... toProcessFeatures ) {
return generateWidgets(
eObject, toolkit, null,
parent, domain, dbc, showDecorator, toProcessFeatures );
}
/**
* Generates a 2 column list with left column containing description of widgets and right column containing widget.
* @param eObject the eObject to edit
* @param toolkit a {@link FormToolkit} to create widgets
* @param parent
* @param domain the {@link EditingDomain} in case of transactional edition. Can be null, then no transaction is used.
* @param dbc
* @param toProcessFeatures list of features to edit.
* @return
*/
public static List<EntryDescription> generateEditorWidgets(
EObject eObject,
FormToolkit toolkit,
Composite parent,
EditingDomain domain,
DataBindingContext dbc,
boolean showDecorator,
EStructuralFeature... toProcessFeatures ) {
return generateWidgets(
eObject, toolkit, parent.getDisplay().getSystemColor( SWT.COLOR_DARK_BLUE ),
parent, domain, dbc, showDecorator, toProcessFeatures );
}
/**
* Generates a 2 column list with left column containing description of widgets and right column containing widget.
* @param eObject the eObject to edit
* @param toolkit a {@link FormToolkit} to create widgets
* @param labelColor the foreground color for the label
* @param parent
* @param domain the {@link EditingDomain} in case of transactional edition. Can be null, then no transaction is used.
* @param dbc
* @param toProcessFeatures list of features to edit.
* @return
*/
public static List<EntryDescription> generateWidgets(
EObject eObject,
FormToolkit toolkit,
Color labelColor,
Composite parent,
EditingDomain domain,
DataBindingContext dbc,
boolean showDecorator,
EStructuralFeature... toProcessFeatures ) {
Map<EStructuralFeature,String> map = new LinkedHashMap<EStructuralFeature,String> ();
for( EStructuralFeature feature : toProcessFeatures )
map.put( feature, null );
List<EntryDescription> entries = produceWidgets( toolkit, labelColor, parent, map );
setUpDatabinding( eObject, domain, dbc, showDecorator, entries );
return entries;
}
/**
* Generates a 2 column list with left column containing description of widgets and right column containing widget.
* @param eObject the eObject to edit
* @param toolkit a {@link FormToolkit} to create widgets
* @param labelColor the foreground color for the label
* @param parent
* @param domain the {@link EditingDomain} in case of transactional edition. Can be null, then no transaction is used.
* @param dbc
* @param toProcessFeatures list of features to edit.
* @return
*/
public static List<EntryDescription> generateWidget(
EObject eObject,
FormToolkit toolkit,
Composite parent,
EditingDomain domain,
DataBindingContext dbc,
boolean showDecorator,
EStructuralFeature feature,
String labelText ) {
Map<EStructuralFeature,String> map = new LinkedHashMap<EStructuralFeature,String> ();
map.put( feature, labelText );
Color labelColor = parent.getDisplay().getSystemColor( SWT.COLOR_DARK_BLUE );
List<EntryDescription> entries = produceWidgets( toolkit, labelColor, parent, map );
setUpDatabinding( eObject, domain, dbc, showDecorator, entries );
return entries;
}
/**
* Sets up the data binding.
* @param eObject
* @param domain
* @param dbc
* @param entries
*/
private static void setUpDatabinding(
EObject eObject,
EditingDomain domain,
DataBindingContext dbc,
boolean showDecorator,
List<EntryDescription> entries ) {
for( EntryDescription entry : entries ) {
IObservableValue widgetObservable = null;
if( entry.widget instanceof Text )
widgetObservable = SWTObservables.observeDelayedValue( 300, SWTObservables.observeText((Text) entry.widget, SWT.Modify ));
else if( entry.widget instanceof StyledText )
widgetObservable = SWTObservables.observeDelayedValue( 300, SWTObservables.observeText((StyledText) entry.widget, SWT.Modify ));
else if( entry.widget instanceof Spinner )
widgetObservable = SWTObservables.observeSelection((Spinner) entry.widget);
else if( entry.widget instanceof ISelectionProvider )
widgetObservable = ViewersObservables.observeSingleSelection((ISelectionProvider) entry.widget);
else if( entry.widget instanceof Button )
widgetObservable = SWTObservables.observeSelection((Button) entry.widget);
if( widgetObservable != null ) {
UpdateValueStrategy targetToModel = new UpdateValueStrategy();
if( entry.attribute.getLowerBound() > 0 )
targetToModel.setBeforeSetValidator( new MandatoryFieldValidator( entry.attribute ));
IObservableValue iov = domain == null ?
EMFObservables.observeValue( eObject, entry.attribute )
// : EMFEditObservables.observeValue( domain, eObject, entry.attribute );
: createCustomEmfEditObservable( domain, eObject, entry.attribute );
Binding binding;
if( domain == null )
binding = dbc.bindValue( widgetObservable, iov, targetToModel, null );
else
binding = dbc.bindValue( widgetObservable, iov );
if( showDecorator && entry.attribute.getLowerBound() > 0 )
ControlDecorationSupport.create( binding, SWT.TOP | SWT.LEFT );
}
}
}
/**
* Creates an observable using an editing domain.
* <p>
* This method is a workaround for SetCommands (EMF version <2.7.1).
* Keep it, even if it is not used.
* </p>
*
* @param domain
* @param eo
* @param ea
* @return an IObservable value
* TODO: replace this method by EMFEditObservables as soon as EMF 2.8.0 or 2.7.2 is out
* @See https://bugs.eclipse.org/bugs/show_bug.cgi?id=356291
* @See https://bugs.eclipse.org/bugs/show_bug.cgi?id=359043
*/
public static IObservableValue createCustomEmfEditObservable( EditingDomain domain, final EObject eo, final EAttribute ea ) {
return new EditingDomainEObjectObservableValue( domain, eo, ea ) {
@Override
protected void doSetValue( final Object value ) {
Command command = createCustomSetCommand( this.domain, eo, ea, value );
this.domain.getCommandStack().execute( command );
}
};
}
/**
* Creates a custom set command to use with model extensions.
* @param domain
* @param eo
* @param ea
* @param value
* @return a custom set command (with less checks)
* TODO: replace this method by the real SetCommand as soon as EMF 2.8.0 or 2.7.2 is out
* @See https://bugs.eclipse.org/bugs/show_bug.cgi?id=356291
* @See https://bugs.eclipse.org/bugs/show_bug.cgi?id=359043
*/
public static Command createCustomSetCommand( final EditingDomain domain, final EObject eo, final EAttribute ea, final Object value ) {
return new AbstractOverrideableCommand( domain, "MySetCommand" ) {
private Object oldValue;
@Override
public void doExecute() {
this.oldValue = eo.eGet( ea );
eo.eSet( ea, value );
}
@Override
public void doUndo() {
eo.eSet( ea, this.oldValue );
}
@Override
public void doRedo() {
execute();
}
@Override
public boolean doCanExecute() {
return true;
}
};
}
/**
* Produces the widgets.
* @param toolkit
* @param parent
* @param featuresToLabels
* @return
*/
private static List<EntryDescription> produceWidgets(
FormToolkit toolkit,
Color labelColor,
Composite parent,
Map<EStructuralFeature,String> featuresToLabels ) {
List<EntryDescription> entries = new ArrayList<EntryDescription> ();
for( Map.Entry<EStructuralFeature,String> entry : featuresToLabels.entrySet()) {
Object widget = null;
EAttribute attr = (EAttribute) entry.getKey();
String label = entry.getValue();
// The label
// TODO leverage ExtendedMetaData.INSTANCE for tool tip and label
if( label == null ) {
label = StringUtils.camelCaseToHuman( attr.getName());
label = StringUtils.capitalize( label );
if( attr.getLowerBound() > 0)
label += " *";
label += ":";
}
Label labelWidget = toolkit.createLabel( parent, label );
labelWidget.setBackground(parent.getBackground());
if( labelColor != null )
labelWidget.setForeground( labelColor );
// The widget
Class<?> instanceClass = attr.getEType().getInstanceClass();
if (instanceClass.equals( String.class )) {
String lowered = label.toLowerCase();
if( lowered.contains( "password" ) || lowered.contains( "passphrase" ))
widget = SwtFactory.createPasswordField( parent, false ).getText();
else if( lowered.contains( "folder" ) || lowered.contains( "directory" ))
widget = SwtFactory.createDirectoryBrowser( parent ).getText();
else
widget = toolkit.createText(parent, "", SWT.BORDER);
((Text)widget).setLayoutData(new GridData( GridData.FILL_HORIZONTAL ));
} else if (instanceClass.equals(Integer.class) || instanceClass.equals(int.class)) {
widget = new Spinner(parent, SWT.BORDER);
GridDataFactory.swtDefaults().hint( 100, SWT.DEFAULT ).minSize( 100, SWT.DEFAULT ).applyTo((Spinner) widget);
((Spinner) widget).setMaximum( Integer.MAX_VALUE );
} else if (instanceClass.isEnum()) {
widget = new ComboViewer(parent, SWT.READ_ONLY | SWT.FLAT);
ComboViewer viewer = (ComboViewer) widget;
viewer.setContentProvider(new EEnumLiteralsProvider());
viewer.setLabelProvider(new EEnumNameLabelProvider());
viewer.setInput(attr.getEType());
viewer.getCombo().setLayoutData( new GridData( GridData.FILL_HORIZONTAL ));
} else if (instanceClass.equals(Boolean.class) || instanceClass.equals(boolean.class)) {
widget = new ComboViewer(parent, SWT.READ_ONLY | SWT.FLAT);
ComboViewer viewer = (ComboViewer) widget;
viewer.setContentProvider( new ArrayContentProvider());
viewer.setLabelProvider(new LabelProvider());
viewer.setInput( new Boolean[] { Boolean.TRUE, Boolean.FALSE });
Combo combo = ((ComboViewer) widget).getCombo();
GridDataFactory.swtDefaults().hint( 100, SWT.DEFAULT ).minSize( 100, SWT.DEFAULT ).applyTo( combo );
}
if( widget != null )
entries.add( new EntryDescription(widget, attr));
}
return entries;
}
}