/*
* 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;
import java.util.Collections;
import java.util.HashSet;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import eu.esdihumboldt.hale.common.align.extension.function.FunctionDefinition;
import eu.esdihumboldt.hale.common.align.extension.function.ParameterDefinition;
import eu.esdihumboldt.hale.common.align.extension.function.PropertyParameter;
import eu.esdihumboldt.hale.common.align.extension.function.TypeParameter;
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.align.model.MutableCell;
import eu.esdihumboldt.hale.common.schema.SchemaSpaceID;
import eu.esdihumboldt.hale.ui.HaleWizardPage;
import eu.esdihumboldt.hale.ui.function.generic.AbstractGenericFunctionWizard;
import eu.esdihumboldt.hale.ui.function.generic.pages.internal.Field;
import eu.esdihumboldt.hale.ui.selection.SchemaSelection;
import eu.esdihumboldt.hale.ui.util.components.DynamicScrolledComposite;
/**
* Page that allows assigning cell entities
*
* @param <T> the function type
* @param <F> the field type
* @param <D> the field definition
* @author Simon Templer
*/
public abstract class EntitiesPage<T extends FunctionDefinition<D>, D extends ParameterDefinition, F extends Field<D, ?>>
extends HaleWizardPage<AbstractGenericFunctionWizard<D, T>> implements FunctionWizardPage {
private final Cell initialCell;
private final SchemaSelection initialSelection;
private final Set<EntityDefinition> sourceCandidates = new HashSet<EntityDefinition>();
private final Set<EntityDefinition> targetCandidates = new HashSet<EntityDefinition>();
private final Set<F> functionFields = new HashSet<F>();
private final Observer fieldObserver;
/**
* Create the entities page
*
* @param initialSelection the initial schema selection, may be
* <code>null</code>
* @param initialCell the initial cell, may be <code>null</code>
*/
public EntitiesPage(SchemaSelection initialSelection, Cell initialCell) {
super("entities");
setTitle("Entity selection");
setDescription("Assign entities for the function");
fieldObserver = new Observer() {
@Override
public void update(Observable o, Object arg) {
updateState();
}
};
this.initialCell = initialCell;
this.initialSelection = initialSelection;
// fill candidates
if (initialSelection != null) {
for (EntityDefinition candidate : initialSelection.getSourceItems()) {
if (acceptCandidate(candidate)) {
sourceCandidates.add(candidate);
}
}
for (EntityDefinition candidate : initialSelection.getTargetItems()) {
if (acceptCandidate(candidate)) {
targetCandidates.add(candidate);
}
}
}
if (initialCell != null) {
if (initialCell.getSource() != null) {
for (Entity entity : initialCell.getSource().values()) {
sourceCandidates.add(entity.getDefinition());
}
}
for (Entity entity : initialCell.getTarget().values()) {
targetCandidates.add(entity.getDefinition());
}
}
}
/**
* Determines if a candidate from a selection should be accepted. This
* implementation returns <code>true</code> and therefore accepts any
* {@link EntityDefinition}, override to change behavior.
*
* @param candidate the candidate
* @return if the candidate should be accepted
*/
protected boolean acceptCandidate(EntityDefinition candidate) {
return true;
}
/**
* @see HaleWizardPage#createContent(Composite)
*/
@Override
protected void createContent(Composite page) {
page.setLayout(GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).margins(0, 0)
.create());
Control header = createHeader(page);
if (header != null) {
header.setLayoutData(GridDataFactory.swtDefaults().align(SWT.FILL, SWT.BEGINNING)
.grab(true, false).span(2, 1).create());
}
Control source = createEntityGroup(SchemaSpaceID.SOURCE, page);
source.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Control target = createEntityGroup(SchemaSpaceID.TARGET, page);
target.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
updateState();
}
/**
* @see HaleWizardPage#onShowPage(boolean)
*/
@Override
protected void onShowPage(boolean firstShow) {
super.onShowPage(firstShow);
if (firstShow) {
// redraw to prevent ghost images drawn by ControlDecoration
getControl().getParent().redraw();
/*
* Re-layout the wizard dialog as the buttons may be hidden when
* using the NewRelationWizard.
*/
// for (Control control : getWizard().getShell().getChildren()) {
// if (control instanceof Composite) {
// ((Composite) control).layout(true, true);
// }
// }
getWizard().getShell().layout(true, true);
}
}
/**
* @return the initial cell
*/
protected Cell getInitialCell() {
return initialCell;
}
/**
* @return the initial selection
*/
protected SchemaSelection getInitialSelection() {
return initialSelection;
}
/**
* Get the function fields associated with the page
*
* @return the function fields
*/
protected Set<F> getFunctionFields() {
return Collections.unmodifiableSet(functionFields);
}
/**
* Create the header control.
*
* @param parent the parent composite
* @return the header control or <code>null</code>
*/
protected Control createHeader(Composite parent) {
return null;
}
/**
* Get the entity candidates for the given schema space
*
* @param ssid the schema space ID
* @return the entity candidates
*/
protected Set<EntityDefinition> getCandidates(SchemaSpaceID ssid) {
switch (ssid) {
case SOURCE:
return sourceCandidates;
case TARGET:
return targetCandidates;
default:
throw new IllegalArgumentException();
}
}
/**
* Create an entity group
*
* @param ssid the schema space id
* @param parent the parent composite
* @return the main group control
*/
protected Control createEntityGroup(SchemaSpaceID ssid, Composite parent) {
// return another Composite, since the returned Control's layoutData are
// overwritten.
Composite holder = new Composite(parent, SWT.NONE);
holder.setLayout(GridLayoutFactory.fillDefaults().create());
// Important: Field does rely on DynamicScrolledComposite to be the
// parent of its parent,
// because sadly layout(true, true) on the Shell does not seem to
// propagate to this place.
ScrolledComposite sc = new DynamicScrolledComposite(holder, SWT.V_SCROLL);
sc.setExpandHorizontal(true);
sc.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).hint(200, 200).create());
Group main = new Group(sc, SWT.NONE);
sc.setContent(main);
main.setLayout(GridLayoutFactory.swtDefaults().numColumns(1).margins(10, 5).create());
// set group title
switch (ssid) {
case SOURCE:
main.setText("Source");
break;
case TARGET:
main.setText("Target");
break;
}
// determine fields
T function = getWizard().getFunction();
final Set<? extends D> fields;
switch (ssid) {
case SOURCE:
fields = function.getSource();
break;
case TARGET:
fields = function.getTarget();
break;
default:
fields = new HashSet<D>();
}
// create fields
for (D field : fields) {
F functionField = createField(ssid, field, main);
if (functionField != null) {
functionFields.add(functionField);
functionField.addObserver(fieldObserver);
}
}
return holder;
}
/**
* Create entity assignment fields for the given field definition
*
* @param ssid the schema space identifier
* @param field the field definition, may be a {@link PropertyParameter} or
* a {@link TypeParameter}
* @param parent the parent composite
* @return the created field or <code>null</code>
*/
private F createField(SchemaSpaceID ssid, D field, Composite parent) {
if (field.getMaxOccurrence() == 0) {
return null;
}
return createField(field, ssid, parent, getCandidates(ssid), initialCell);
}
/**
* Create entity assignment fields for the given field definition
*
* @param ssid the schema space identifier
* @param field the field definition, may be a {@link PropertyParameter} or
* a {@link TypeParameter}
* @param parent the parent composite
* @param candidates the entity candidates
* @param initialCell the initial cell
* @return the created field or <code>null</code>
*/
protected abstract F createField(D field, SchemaSpaceID ssid, Composite parent,
Set<EntityDefinition> candidates, Cell initialCell);
/**
* @see FunctionWizardPage#configureCell(MutableCell)
*/
@Override
public void configureCell(MutableCell cell) {
ListMultimap<String, Entity> source = ArrayListMultimap.create();
ListMultimap<String, Entity> target = ArrayListMultimap.create();
// collect entities from fields
for (F field : functionFields) {
switch (field.getSchemaSpace()) {
case SOURCE:
field.fillEntities(source);
break;
case TARGET:
field.fillEntities(target);
break;
default:
throw new IllegalStateException("Illegal schema space");
}
}
cell.setSource(source);
cell.setTarget(target);
}
/**
* Update the page complete state
*/
private void updateState() {
boolean complete = true;
for (Field<?, ?> field : functionFields) {
if (!field.isValid()) {
complete = false;
break;
}
}
setPageComplete(complete);
}
}