/*******************************************************************************
* Copyright (c) 2013, 2015 GoPivotal, Inc.
* 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:
* GoPivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springsource.ide.eclipse.commons.livexp.ui;
import static org.springsource.ide.eclipse.commons.livexp.ui.UIConstants.FIELD_TEXT_AREA_WIDTH;
import java.util.Collection;
import java.util.Objects;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
import org.springsource.ide.eclipse.commons.livexp.core.SelectionModel;
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
import org.springsource.ide.eclipse.commons.livexp.util.Parser;
/**
* Wizard section to choose one element from list of elements. Uses a pulldown Combo box to allow selecting
* an element.
*/
public class ChooseOneSectionCombo<T> extends AbstractChooseOneSection<T> {
private final SelectionModel<T> selection;
private final String label; //Descriptive Label for this section
private LiveExpression<T[]> options; //The elements to choose from
private boolean useFieldLabelWidthHint = true;
/**
* For a combo that allows text edits, a textInputParser must be provided to convert
* the input text into a selection value.
*/
private Parser<T> inputParser = null;
public ChooseOneSectionCombo(IPageWithSections owner, String label, SelectionModel<T> selection, T[] options) {
this(owner, label, selection, LiveExpression.constant(options));
Assert.isNotNull(options);
}
public ChooseOneSectionCombo<T> useFieldLabelWidthHint(boolean use) {
this.useFieldLabelWidthHint = use;
return this;
}
public ChooseOneSectionCombo(IPageWithSections owner, String label, SelectionModel<T> selection, LiveExpression<T[]> options) {
super(owner);
this.label = label;
this.selection = selection;
this.options = options;
}
@SuppressWarnings("unchecked")
public ChooseOneSectionCombo(IPageWithSections owner, String label, LiveVariable<T> selection, Collection<T> options) {
this(owner,
label,
new SelectionModel<>(selection),
(T[])options.toArray()
);
}
/**
* Enable's support for 'editable' text widget in the Combo. This means user can perform textual edits
* in addition to using the combo.
* <p>
* To support these 'free form' edits. A inputParser must be provided.
*/
public void allowTextEdits(Parser<T> inputParser) {
this.inputParser = inputParser;
}
@Override
public LiveExpression<ValidationResult> getValidator() {
return selection.validator;
}
public LiveVariable<T> getSelection() {
return selection.selection;
}
@Override
public void createContents(Composite page) {
Composite field = new Composite(page, SWT.NONE);
GridLayout layout = GridLayoutFactory.fillDefaults().numColumns(2).create();
field.setLayout(layout);
GridDataFactory.fillDefaults().grab(true, false).applyTo(field);
Label fieldNameLabel = new Label(field, SWT.NONE);
fieldNameLabel.setText(label);
GridDataFactory labelGridData = GridDataFactory
.fillDefaults()
.align(SWT.BEGINNING, SWT.CENTER);
if (useFieldLabelWidthHint) {
labelGridData.hint(UIConstants.fieldLabelWidthHint(fieldNameLabel), SWT.DEFAULT);
}
labelGridData.applyTo(fieldNameLabel);
final Combo combo = new Combo(field, inputParser==null?SWT.READ_ONLY:SWT.NONE);
options.addListener(new ValueListener<T[]>() {
public void gotValue(org.springsource.ide.eclipse.commons.livexp.core.LiveExpression<T[]> exp, T[] value) {
if (combo!=null) {
String oldText = combo.getText();
combo.setItems(getLabels()); //This will clear the selection sometimes
combo_setText(combo, oldText);
}
};
});
if (inputParser==null) {
GridDataFactory.fillDefaults().applyTo(combo);
} else {
GridDataFactory.fillDefaults().hint(FIELD_TEXT_AREA_WIDTH, SWT.DEFAULT).applyTo(combo);
}
combo.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
handleModifyText(combo);
}
});
selection.selection.addListener(new ValueListener<T>() {
public void gotValue(LiveExpression<T> exp, T newSelection) {
if (newSelection!=null) {
//Technically, not entirely correct. This might
// select the wrong element if more than one option
// has the same label text.
String newText = labelProvider.getText(newSelection);
combo_setText(combo, newText);
if (!combo.getText().equals(newText)) {
//widget rejected the selection. To avoid widget state
// and model state getting out-of-sync, refelct current
// widget state back to the model:
handleModifyText(combo);
}
}
}
});
}
private void combo_setText(final Combo combo, String newText) {
if (combo!=null && !combo.isDisposed()) {
String oldText = combo.getText();
if (!Objects.equals(oldText, newText)) {
//Avoid setting the text if its already set to a equal value. This can cause strange effects by
// moving the cursor on some os-es. See https://issuetracker.springsource.com/browse/STS-4377
combo.setText(newText);
}
}
}
private void handleModifyText(final Combo combo) {
int selected = combo.getSelectionIndex();
T[] options = getOptionsArray();
if (options!=null && selected>=0 && selected<options.length) {
selection.selection.setValue(getOptionsArray()[selected]);
} else {
selection.selection.setValue(parse(combo.getText()));
}
}
private T parse(String text) {
try {
if (inputParser!=null) {
return inputParser.parse(text);
}
} catch (Exception e) {
//ignore unparsable input
}
return null;
}
private String[] getLabels() {
String[] labels = new String[getOptionsArray().length];
for (int i = 0; i < labels.length; i++) {
labels[i] = labelProvider.getText(getOptionsArray()[i]);
}
return labels;
}
private T[] getOptionsArray() {
return options.getValue();
}
public LiveExpression<T[]> getOptions() {
return options;
}
/**
* Convenience method that returns the options cast to LiveVariable. This method
* will throw an {@link ClassCastException} if the options were not provided
* via a LiveVariable.
*/
public LiveVariable<T[]> getOptionsVar() {
return (LiveVariable<T[]>) options;
}
}