/* * 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.function.generic.pages.internal; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Observable; import java.util.Set; 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.ISelectionChangedListener; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import com.google.common.collect.ListMultimap; import eu.esdihumboldt.hale.common.align.extension.function.ParameterDefinition; import eu.esdihumboldt.hale.common.align.model.Cell; import eu.esdihumboldt.hale.common.align.model.Entity; import eu.esdihumboldt.hale.common.align.model.EntityDefinition; import eu.esdihumboldt.hale.common.schema.SchemaSpaceID; import eu.esdihumboldt.hale.ui.common.CommonSharedImages; import eu.esdihumboldt.hale.ui.function.common.EntitySelector; /** * Represents named entities in a function * * @param <S> the entity selector type * @param <F> the field type * @author Simon Templer */ public abstract class Field<F extends ParameterDefinition, S extends EntitySelector<F>> extends Observable { private final F definition; private final SchemaSpaceID ssid; private final List<S> selectors = new ArrayList<S>(); private final Composite selectorContainer; private final ISelectionChangedListener selectionChangedListener; private boolean valid = false; /** * Create a field * * @param definition the field definition * @param ssid the schema space * @param parent the parent composite * @param candidates the entity candidates * @param initialCell the initial cell */ public Field(F definition, SchemaSpaceID ssid, final Composite parent, Set<EntityDefinition> candidates, Cell initialCell) { super(); this.definition = definition; this.ssid = ssid; ControlDecoration descriptionDecoration = null; // field name if (!definition.getDisplayName().isEmpty()) { Label name = new Label(parent, SWT.NONE); name.setText(definition.getDisplayName()); name.setLayoutData(GridDataFactory.swtDefaults().create()); if (definition.getDescription() != null) { // add decoration descriptionDecoration = new ControlDecoration(name, SWT.RIGHT, parent); } } selectorContainer = new Composite(parent, SWT.NONE); selectorContainer.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create()); // left margin 6 pixels for ControlDecorations to have place within this // component // so they're not drawn outside of the ScrolledComposite in case it's // present. selectorContainer.setLayout(GridLayoutFactory.fillDefaults().extendedMargins(6, 0, 0, 0) .create()); selectionChangedListener = new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { int changedIndex = selectors.indexOf(event.getSelectionProvider()); S changedSelector = selectors.get(changedIndex); // add/remove selector // check whether all selectors are valid (so must the changed // one be) if (countValidEntities() == selectors.size()) { // maybe last invalid entity was set, check whether to add // another one if (Field.this.definition.getMaxOccurrence() != selectors.size()) { S newSelector = createEntitySelector(Field.this.ssid, Field.this.definition, selectorContainer); newSelector.getControl().setLayoutData( GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER) .grab(true, false).create()); addSelector(newSelector); // layout new selector in scrolled pane selectorContainer.getParent().getParent().layout(); } } else { // check whether a field was set to None and remove the // field if it isn't the last one and minOccurrence is still // met if (event.getSelection().isEmpty() && changedIndex != selectors.size() - 1 && Field.this.definition.getMinOccurrence() < selectors.size()) { // check whether first selector will be removed and it // had the fields description boolean createDescriptionDecoration = changedIndex == 0 && Field.this.definition.getDisplayName().isEmpty() && !Field.this.definition.getDescription().isEmpty(); removeSelector(changedSelector); // add new description decoration if necessary if (createDescriptionDecoration) { ControlDecoration descriptionDecoration = new ControlDecoration( selectors.get(0).getControl(), SWT.RIGHT | SWT.TOP, parent); descriptionDecoration.setDescriptionText(Field.this.definition .getDescription()); FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION); descriptionDecoration.setImage(fieldDecoration.getImage()); descriptionDecoration.setMarginWidth(2); } // necessary layout call for control decoration to // appear at the correct place selectorContainer.getParent().getParent().layout(); // add mandatory decoration to next selector if needed if (changedIndex < Field.this.definition.getMinOccurrence()) { S newMandatorySelector = selectors.get(Field.this.definition .getMinOccurrence() - 1); ControlDecoration mandatory = new ControlDecoration( newMandatorySelector.getControl(), SWT.LEFT | SWT.TOP, parent); FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_REQUIRED); mandatory.setImage(CommonSharedImages.getImageRegistry().get( CommonSharedImages.IMG_DECORATION_MANDATORY)); mandatory.setDescriptionText(fieldDecoration.getDescription()); } } } // update state updateState(); } }; // determine number of contained fields and the corresponding values int initialFieldCount = definition.getMinOccurrence(); // TODO determine filters from definition List<EntityDefinition> fieldValues = new ArrayList<EntityDefinition>(); if (initialCell != null) { // entities from cell List<? extends Entity> entities = null; switch (ssid) { case SOURCE: if (initialCell.getSource() != null) { entities = initialCell.getSource().get(definition.getName()); } break; case TARGET: if (initialCell.getTarget() != null) { entities = initialCell.getTarget().get(definition.getName()); } break; default: throw new IllegalStateException("Illegal schema space"); } if (entities != null) { for (Entity entity : entities) { fieldValues.add(entity.getDefinition()); // FIXME what about // the // information // in the // entity?! } } } else if (candidates != null && !candidates.isEmpty()) { // populate from candidates LinkedHashSet<EntityDefinition> rotatingCandidates = new LinkedHashSet<EntityDefinition>( candidates); // try to add candidates for each required entity int limit = Math.max(initialFieldCount, candidates.size()); for (int i = 0; i < limit; i++) { boolean found = false; for (EntityDefinition candidate : rotatingCandidates) { // XXX checked against filters later, because here filters // aren't present yet. // if (true) { fieldValues.add(candidate); rotatingCandidates.remove(candidate); rotatingCandidates.add(candidate); found = true; break; // } } if (!found) { fieldValues.add(null); } } } // adapt initialFieldCount if needed (and possible) if (fieldValues.size() >= initialFieldCount) { initialFieldCount = fieldValues.size() + 1; if (definition.getMaxOccurrence() != F.UNBOUNDED) { initialFieldCount = Math.min(initialFieldCount, definition.getMaxOccurrence()); } } // add fields for (int num = 0; num < initialFieldCount; num++) { // create entity selector S selector = createEntitySelector(ssid, definition, selectorContainer); selector.getControl().setLayoutData( GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false) .create()); // do initial selection (before adding it to not trigger selection // event) EntityDefinition value = (num < fieldValues.size()) ? (fieldValues.get(num)) : (null); if (value == null || !selector.accepts(value)) selector.setSelection(new StructuredSelection()); else selector.setSelection(new StructuredSelection(value)); // add the selector now addSelector(selector); // add decorations if (num < definition.getMinOccurrence()) { ControlDecoration mandatory = new ControlDecoration(selector.getControl(), SWT.LEFT | SWT.TOP, parent); FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_REQUIRED); mandatory.setImage(CommonSharedImages.getImageRegistry().get( CommonSharedImages.IMG_DECORATION_MANDATORY)); mandatory.setDescriptionText(fieldDecoration.getDescription()); } if (descriptionDecoration == null && definition.getDescription() != null) descriptionDecoration = new ControlDecoration(selector.getControl(), SWT.RIGHT | SWT.TOP, parent); } // setup description decoration if (descriptionDecoration != null) { descriptionDecoration.setDescriptionText(definition.getDescription()); FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION); descriptionDecoration.setImage(fieldDecoration.getImage()); descriptionDecoration.setMarginWidth(2); } updateState(); } /** * Create an entity selector * * @param ssid the schema space * @param field the field definition * @param parent the parent composite * @return the entity selector */ protected abstract S createEntitySelector(SchemaSpaceID ssid, F field, Composite parent); /** * Get the selectors associated with the field * * @return the selectors */ protected List<S> getSelectors() { return Collections.unmodifiableList(selectors); } /** * Add a selector * * @param selector the entity selector to add */ protected void addSelector(S selector) { selectors.add(selector); selector.addSelectionChangedListener(selectionChangedListener); } /** * Remove a selector * * @param selector the entity selector to remove */ protected void removeSelector(S selector) { selector.removeSelectionChangedListener(selectionChangedListener); selectors.remove(selector); selector.getControl().dispose(); } /** * Get the schema space * * @return the schema space */ public SchemaSpaceID getSchemaSpace() { return ssid; } /** * Counts valid entities. * * @return number of valid entities */ private int countValidEntities() { int validCount = 0; for (EntitySelector<F> selector : selectors) { if (!selector.getSelection().isEmpty()) // TODO improve condition validCount++; } return validCount; } /** * Updates the valid state */ private void updateState() { boolean newValid = countValidEntities() >= definition.getMinOccurrence(); boolean change = newValid != valid; valid = newValid; if (change) { setChanged(); notifyObservers(); } } /** * Determines if the field is valid in its current configuration * * @return if the field is valid */ public boolean isValid() { return valid; } /** * Fill the given map with the field's entities * * @param target the map to add the entities to */ public void fillEntities(ListMultimap<String, Entity> target) { for (S selector : selectors) { Entity entity = selector.getEntity(); if (entity != null) { target.put(definition.getName(), entity); } } } }