/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.ui.common.definition.editors;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.jface.fieldassist.ComboContentAdapter;
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.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.PlatformUI;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
import eu.esdihumboldt.hale.common.codelist.CodeList;
import eu.esdihumboldt.hale.common.codelist.CodeList.CodeEntry;
import eu.esdihumboldt.hale.common.core.HalePlatform;
import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.Binding;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.Enumeration;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.ValidationConstraint;
import eu.esdihumboldt.hale.ui.codelist.internal.CodeListUIPlugin;
import eu.esdihumboldt.hale.ui.codelist.selector.CodeListSelectionDialog;
import eu.esdihumboldt.hale.ui.codelist.service.CodeListService;
import eu.esdihumboldt.hale.ui.service.project.ProjectVariablesContentProposalProvider;
import eu.esdihumboldt.util.validator.Validator;
/**
* A default attribute editor using binding, enumeration and validation
* constraints.
*
* @author Kai Schwierczek
*/
public class DefaultPropertyEditor extends AbstractBindingValidatingEditor<Object> {
// XXX generic version instead?
private final PropertyDefinition property;
private final EntityDefinition entity;
private final Collection<String> enumerationValues;
private ArrayList<Object> values = null;
private final boolean otherValuesAllowed;
private final Validator validator;
private Composite composite;
private ComboViewer viewer;
private ControlDecoration decoration;
private Class<?> binding;
private final ConversionService cs = HalePlatform.getService(ConversionService.class);
private CodeList codeList;
private final String codeListNamespace;
private final String codeListName;
/**
* Creates an attribute editor for the given type.
*
* @param parent the parent composite
* @param property the property
* @param entity the property entity definition representing the property,
* may be <code>null</code> if unknown or unavailable, needed for
* code list assignment support
*/
public DefaultPropertyEditor(Composite parent, PropertyDefinition property,
EntityDefinition entity) {
super(property.getPropertyType().getConstraint(Binding.class).getBinding());
this.property = property;
this.entity = entity;
TypeDefinition type = property.getPropertyType();
binding = type.getConstraint(Binding.class).getBinding();
validator = type.getConstraint(ValidationConstraint.class).getValidator();
Enumeration<?> enumeration = type.getConstraint(Enumeration.class);
otherValuesAllowed = enumeration.isAllowOthers();
String codeListNamespace = null;
try {
codeListNamespace = property.getParentType().getName().getNamespaceURI();
} catch (Exception e) {
// ignore any case where parent type may be null
}
this.codeListNamespace = codeListNamespace;
String propertyName = property.getName().getLocalPart();
codeListName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1)
+ "Value"; //$NON-NLS-1$
// add enumeration info
if (enumeration.getValues() != null) {
enumerationValues = new ArrayList<String>(enumeration.getValues().size());
// check values against validator and binding
for (Object o : enumeration.getValues())
if (validator.validate(o) == null) {
try {
String stringValue = cs.convert(o, String.class);
cs.convert(stringValue, binding);
enumerationValues.add(stringValue);
} catch (ConversionException ce) {
// value is either not convertable to string or the
// string value
// is not convertable to the target binding.
}
}
}
else
enumerationValues = null;
composite = new Composite(parent, SWT.NONE);
int numColumns = (entity == null) ? (1) : (2);
composite.setLayout(
GridLayoutFactory.swtDefaults().margins(0, 0).numColumns(numColumns).create());
viewer = new ComboViewer(composite,
(otherValuesAllowed ? SWT.NONE : SWT.READ_ONLY) | SWT.BORDER);
viewer.getControl().setLayoutData(
GridDataFactory.fillDefaults().indent(7, 0).grab(true, false).create());
viewer.setContentProvider(ArrayContentProvider.getInstance());
viewer.setLabelProvider(new LabelProvider() {
@Override
public String getText(Object element) {
if (element instanceof CodeEntry) {
CodeEntry e = (CodeEntry) element;
if (e.getName().equals(e.getIdentifier()))
return e.getName();
else
return e.getName() + " (" + e.getIdentifier() + ")";
}
else
return super.getText(element);
}
});
viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
if (!selection.isEmpty() && selection instanceof IStructuredSelection) {
Object selected = ((IStructuredSelection) selection).getFirstElement();
if (selected instanceof CodeEntry) {
CodeEntry entry = (CodeEntry) selected;
viewer.getCombo()
.setToolTipText(entry.getName() + ":\n\n" + entry.getDescription()); //$NON-NLS-1$
return;
}
}
viewer.getCombo().setToolTipText(null);
}
});
viewer.setInput(values);
if (otherValuesAllowed)
viewer.getCombo().setText("");
// create decoration
decoration = new ControlDecoration(viewer.getControl(), SWT.LEFT | SWT.TOP, composite);
FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault()
.getFieldDecoration(FieldDecorationRegistry.DEC_ERROR);
decoration.setImage(fieldDecoration.getImage());
decoration.hide();
viewer.getCombo().addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
String newValue = viewer.getCombo().getText();
if (viewer.getSelection() != null && !viewer.getSelection().isEmpty()
&& viewer.getSelection() instanceof IStructuredSelection) {
Object selection = ((IStructuredSelection) viewer.getSelection())
.getFirstElement();
if (selection instanceof CodeEntry)
newValue = ((CodeEntry) selection).getIdentifier();
}
String validationResult = valueChanged(newValue);
// show or hide decoration
if (validationResult != null) {
decoration.setDescriptionText(validationResult);
decoration.show();
}
else
decoration.hide();
}
});
// set initial selection (triggers modify event -> gets validated
if (values != null && values.size() > 0)
viewer.setSelection(new StructuredSelection(values.iterator().next()));
// add code list selection button
final Image assignImage = CodeListUIPlugin.getImageDescriptor("icons/assign_codelist.gif") //$NON-NLS-1$
.createImage();
if (entity != null) {
Button assign = new Button(composite, SWT.PUSH);
assign.setImage(assignImage);
assign.setToolTipText("Assign a code list"); //$NON-NLS-1$
assign.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
final Display display = Display.getCurrent();
CodeListSelectionDialog dialog = new CodeListSelectionDialog(
display.getActiveShell(), codeList,
MessageFormat.format("Please select a code list to assign to {0}",
DefaultPropertyEditor.this.property.getDisplayName()));
if (dialog.open() == CodeListSelectionDialog.OK) {
CodeList newCodeList = dialog.getCodeList();
CodeListService codeListService = PlatformUI.getWorkbench()
.getService(CodeListService.class);
codeListService.assignEntityCodeList(DefaultPropertyEditor.this.entity,
newCodeList);
updateCodeList();
}
}
});
}
// add project variable content assistance
final ControlDecoration infoDeco = new ControlDecoration(viewer.getControl(),
SWT.TOP | SWT.LEFT);
infoDeco.setDescriptionText("Type { or Ctrl+Space for project variable content assistance");
infoDeco.setImage(FieldDecorationRegistry.getDefault()
.getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION).getImage());
infoDeco.setMarginWidth(2);
ContentProposalAdapter adapter = new ContentProposalAdapter(viewer.getControl(),
new ComboContentAdapter(), new ProjectVariablesContentProposalProvider(true),
ProjectVariablesContentProposalProvider.CTRL_SPACE, new char[] { '{' });
adapter.setAutoActivationDelay(0);
composite.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
assignImage.dispose();
}
});
// add code list info
updateCodeList();
// info on what inputs are valid
StringBuilder infoText = new StringBuilder();
// every string is convertible to string -> leave that out
if (!binding.equals(String.class))
infoText.append("Input must be convertable to ").append(binding).append('.');
// every input is valid -> leave that out
if (!validator.isAlwaysTrue()) {
if (infoText.length() > 0)
infoText.append('\n');
infoText.append(validator.getDescription());
}
if (infoText.length() > 0) {
Label inputInfo = new Label(composite, SWT.WRAP);
inputInfo.setLayoutData(GridDataFactory.fillDefaults().grab(true, true)
.hint(400, SWT.DEFAULT).create());
inputInfo.setText(infoText.toString());
}
}
private void updateCodeList() {
values = new ArrayList<Object>();
if (enumerationValues != null)
values.addAll(enumerationValues);
CodeListService clService = PlatformUI.getWorkbench().getService(CodeListService.class);
if (entity != null) {
codeList = clService.findCodeListByEntity(entity);
}
if (codeList == null)
codeList = clService.findCodeListByIdentifier(codeListNamespace, codeListName);
if (codeList != null) {
// XXX check values against validator and binding?
// codeList values are only added if no enumeration is present or
// other values are explicitly allowed by the enumerations
if (values.isEmpty() || otherValuesAllowed)
values.addAll(codeList.getEntries());
}
values.trimToSize();
// save combo text to restore after setting the new input
String oldValue = viewer.getCombo().getText();
viewer.setInput(values);
viewer.getCombo().setText(oldValue);
}
/**
* @see eu.esdihumboldt.hale.ui.common.AttributeEditor#getControl()
*/
@Override
public Control getControl() {
return composite;
}
/**
* @see eu.esdihumboldt.hale.ui.common.AttributeEditor#setAsText(java.lang.String)
*/
@Override
public void setAsText(String text) {
if (values == null || values.isEmpty())
viewer.getCombo().setText(text);
else if (enumerationValues != null) {
// enumeration -> ignore value if it isn't valid
if (otherValuesAllowed || enumerationValues.contains(text))
viewer.getCombo().setText(text);
}
else {
// a code list is assigned -> try to find matching entry
CodeEntry entry = codeList.getEntryByIdentifier(text);
if (entry != null)
viewer.setSelection(new StructuredSelection(entry));
}
}
/**
* @see eu.esdihumboldt.hale.ui.common.definition.editors.AbstractBindingValidatingEditor#additionalValidate(java.lang.String,
* java.lang.Object)
*/
@Override
protected String additionalValidate(String stringValue, Object objectValue) {
return validator.validate(objectValue);
}
}