/* * 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.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import eu.esdihumboldt.hale.common.align.extension.function.AbstractParameter; import eu.esdihumboldt.hale.common.align.extension.function.FunctionParameterDefinition; import eu.esdihumboldt.hale.common.align.model.AlignmentUtil; import eu.esdihumboldt.hale.common.align.model.Cell; import eu.esdihumboldt.hale.common.align.model.Entity; import eu.esdihumboldt.hale.common.align.model.ParameterValue; import eu.esdihumboldt.hale.common.align.model.Property; import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition; import eu.esdihumboldt.hale.common.core.io.Value; import eu.esdihumboldt.hale.ui.HaleWizardPage; import eu.esdihumboldt.hale.ui.common.AttributeEditor; import eu.esdihumboldt.hale.ui.function.extension.ParameterEditorExtension; import eu.esdihumboldt.hale.ui.function.generic.AbstractGenericFunctionWizard; import eu.esdihumboldt.hale.ui.internal.HALEUIPlugin; import eu.esdihumboldt.util.Pair; /** * Page for configuring function parameters. * * @author Simon Templer * @author Kai Schwierczek */ public class GenericParameterPage extends HaleWizardPage<AbstractGenericFunctionWizard<?, ?>> implements ParameterPage { private ListMultimap<String, ParameterValue> initialValues; private Set<FunctionParameterDefinition> params; private final ListMultimap<FunctionParameterDefinition, Pair<AttributeEditor<?>, Button>> inputFields; private final HashMap<FunctionParameterDefinition, Button> addButtons; private static final Image removeImage = HALEUIPlugin.getImageDescriptor("icons/remove.gif") .createImage(); /** * Default constructor. */ public GenericParameterPage() { super("parameters"); setTitle("Function parameters"); setDescription("Specify the parameters for the relation"); inputFields = ArrayListMultimap.create(); addButtons = new HashMap<FunctionParameterDefinition, Button>(); setPageComplete(false); } /** * @see HaleWizardPage#onShowPage(boolean) */ @Override protected void onShowPage(boolean firstShow) { Cell cell = getWizard().getUnfinishedCell(); // update variables as they could have changed if (!AlignmentUtil.isTypeCell(cell)) { Set<PropertyEntityDefinition> variables = new HashSet<PropertyEntityDefinition>(); for (Entity e : cell.getSource().values()) { // Cell is no type cell, so entities are Properties. variables.add(((Property) e).getDefinition()); } for (Pair<AttributeEditor<?>, Button> pair : inputFields.values()) pair.getFirst().setVariables(variables); } updateState(); } /** * Update the page state. */ private void updateState() { for (Map.Entry<FunctionParameterDefinition, Pair<AttributeEditor<?>, Button>> entry : inputFields .entries()) if (!entry.getValue().getFirst().isValid()) { setPageComplete(false); return; } setPageComplete(true); } /** * @see eu.esdihumboldt.hale.ui.function.generic.pages.ParameterPage#setParameter(java.util.Set, * com.google.common.collect.ListMultimap) */ @Override public void setParameter(Set<FunctionParameterDefinition> params, ListMultimap<String, ParameterValue> initialValues) { this.params = params; if (initialValues == null) initialValues = ArrayListMultimap.create(); this.initialValues = initialValues; } @Override public ListMultimap<String, ParameterValue> getConfiguration() { ListMultimap<String, ParameterValue> conf = ArrayListMultimap.create(); for (Map.Entry<FunctionParameterDefinition, Pair<AttributeEditor<?>, Button>> entry : inputFields .entries()) conf.put(entry.getKey().getName(), // new ParameterValue( // entry.getValue().getFirst().getValueType(), // Value.of(entry.getValue().getFirst().getAsText()))); return conf; } /** * @see HaleWizardPage#createContent(Composite) */ @Override protected void createContent(Composite page) { page.setLayout(GridLayoutFactory.swtDefaults().create()); // create section for each function parameter for (final FunctionParameterDefinition fp : params) { boolean fixed = fp.getMinOccurrence() == fp.getMaxOccurrence(); boolean unbounded = fp.getMaxOccurrence() == AbstractParameter.UNBOUNDED; Group group = new Group(page, SWT.NONE); group.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); group.setText(fp.getDisplayName()); // only one column if the amount is fixed (-> no remove buttons) group.setLayout(GridLayoutFactory.swtDefaults().numColumns(fixed ? 1 : 2).create()); if (fp.getDescription() != null) { Label description = new Label(group, SWT.WRAP); description.setText(fp.getDescription()); description.setLayoutData(GridDataFactory.swtDefaults().span(fixed ? 1 : 2, 1) .align(SWT.FILL, SWT.CENTER).grab(true, false).hint(250, SWT.DEFAULT) .create()); } // walk over data of initial cell while creating input fields List<ParameterValue> initialData = initialValues.get(fp.getName()); Iterator<ParameterValue> initialDataIter = initialData.iterator(); // create a minimum number of input fields int i; for (i = 0; i < fp.getMinOccurrence(); i++) if (initialDataIter.hasNext()) createField(group, fp, initialDataIter.next(), fixed); else createField(group, fp, null, fixed); // create further fields if initial cell has more for (; initialDataIter.hasNext() && (unbounded || i < fp.getMaxOccurrence()); i++) createField(group, fp, initialDataIter.next(), false); // create control buttons if max occurrence != min occurrence if (!fixed) createAddButton(group, fp, unbounded || i < fp.getMaxOccurrence()); // enable remove buttons if initial cell added more fields than // required if (i > fp.getMinOccurrence()) for (Pair<AttributeEditor<?>, Button> pair : inputFields.get(fp)) pair.getSecond().setEnabled(true); } // update state now that all texts (with validators) are generated updateState(); } /** * Create add and remove buttons for the given function parameter in the * given composite with the given initial visibility. * * @param parent the composite * @param fp the function parameter * @param addEnabled whether the add button is enabled in the beginning */ private void createAddButton(final Composite parent, final FunctionParameterDefinition fp, boolean addEnabled) { // create add button -> left final Button addButton = new Button(parent, SWT.PUSH); addButtons.put(fp, addButton); addButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, true, false, 2, 1)); addButton.setText("Add parameter value"); addButton.setEnabled(addEnabled); // create selection listeners addButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // add text field List<Pair<AttributeEditor<?>, Button>> texts = inputFields.get(fp); boolean removeButtonsDisabled = texts.size() == fp.getMinOccurrence(); Pair<AttributeEditor<?>, Button> added = createField(addButton.getParent(), fp, null, false); added.getFirst().getControl().moveAbove(addButton); added.getSecond().moveAbove(addButton); // update add button if (texts.size() == fp.getMaxOccurrence()) addButton.setEnabled(false); // need to enable all remove buttons or only the new one? if (removeButtonsDisabled) for (Pair<AttributeEditor<?>, Button> pair : texts) pair.getSecond().setEnabled(true); else added.getSecond().setEnabled(true); // update state, could change to invalid because of new field updateState(); // relayout layoutAndPack(); } }); } /** * Creates a text field for the given function parameter and given initial * value. Does not call updateState! * * @param parent the composite in which to place the text field * @param fp the function parameter * @param initialValue initial value or <code>null</code> * @param fixed whether the field may never be removed under any * circumstances (-> no remove button) * @return the created text field */ private Pair<AttributeEditor<?>, Button> createField(Composite parent, final FunctionParameterDefinition fp, ParameterValue initialValue, boolean fixed) { // create editor, button and pair final AttributeEditor<?> editor = ParameterEditorExtension.getInstance().createEditor( parent, getWizard().getFunctionId(), fp, initialValue); // listen to valid changes editor.setPropertyChangeListener(new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (AttributeEditor.IS_VALID.equals(event.getProperty())) updateState(); } }); // listen for resizes of the editor // needed for the editor chooser editor editor.getControl().addControlListener(new ControlListener() { @Override public void controlResized(ControlEvent e) { /* * call layoutAndPack() later as a call now breaks the wizard * dialog sizing (at least on Linux) and makes the button bar * disappear. */ Display.getCurrent().asyncExec(new Runnable() { @Override public void run() { layoutAndPack(); } }); } @Override public void controlMoved(ControlEvent e) { // ignore } }); final Pair<AttributeEditor<?>, Button> pair; final Button removeButton; if (fixed) removeButton = null; else removeButton = new Button(parent, SWT.NONE); pair = new Pair<AttributeEditor<?>, Button>(editor, removeButton); // configure text editor.getControl().setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); // currently the editor validates the input itself // // add validator // if (!fp.isScriptable() && fp.getValidator() != null) { // final ControlDecoration decorator = new ControlDecoration(editor.getControl(), SWT.LEFT // | SWT.TOP); // // // set initial status // decorator.hide(); // // // set image // FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault() // .getFieldDecoration(FieldDecorationRegistry.DEC_ERROR); // decorator.setImage(fieldDecoration.getImage()); // // // add modify listener // editor.setPropertyChangeListener(new IPropertyChangeListener() { // // @Override // public void propertyChange(PropertyChangeEvent event) { // if (event.getProperty().equals(Editor.VALUE)) { // // update decorator and state // String result = fp.getValidator().validate(editor.getAsText()); // if (result == null) // decorator.hide(); // else { // decorator.setDescriptionText(result); // decorator.show(); // } // updateState(); // } // } // }); // } // configure button if (removeButton != null) { removeButton.setImage(removeImage); removeButton.setEnabled(false); removeButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // remove last text field List<Pair<AttributeEditor<?>, Button>> texts = inputFields.get(fp); texts.remove(pair); updateState(); removeButton.dispose(); editor.getControl().dispose(); addButtons.get(fp).setEnabled(true); if (texts.size() == fp.getMinOccurrence()) for (Pair<AttributeEditor<?>, Button> otherPair : texts) otherPair.getSecond().setEnabled(false); layoutAndPack(); } }); } // add field to map inputFields.put(fp, pair); return pair; } /** * Calls layout on the main composite and than packs the wizards shell, only * updating the height, not the width. */ private void layoutAndPack() { ((Composite) getControl()).getParent().layout(true, true); Shell shell = getWizard().getShell(); Point preferredSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); int width = shell.getSize().x; // Math.min(shell.getSize().x, // preferredSize.x); int height = preferredSize.y; shell.setSize(width, height); // center on current monitor // XXX not needed if the layoutAndPack() is not called to early // Monitor monitor = shell.getMonitor(); // Rectangle bounds = monitor.getBounds(); // Rectangle rect = shell.getBounds(); // // int x = bounds.x + (bounds.width - rect.width) / 2; // int y = bounds.y + (bounds.height - rect.height) / 2; // // shell.setLocation(x, y); } }