/* * * * Copyright (C) 2015 CS SI * * * * This program is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the Free * * Software Foundation; either version 3 of the License, or (at your option) * * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * * more details. * * * * You should have received a copy of the GNU General Public License along * * with this program; if not, see http://www.gnu.org/licenses/ * */ package org.esa.snap.ui.tooladapter.dialogs; import com.bc.ceres.binding.*; import com.bc.ceres.binding.converters.ArrayConverter; import com.bc.ceres.binding.converters.FloatConverter; import com.bc.ceres.binding.converters.IntegerConverter; import com.bc.ceres.binding.converters.NumberConverter; import com.bc.ceres.swing.binding.Binding; import com.bc.ceres.swing.binding.BindingContext; import com.bc.ceres.swing.binding.ComponentAdapter; import com.bc.ceres.swing.binding.internal.*; import org.esa.snap.core.gpf.annotations.ParameterDescriptorFactory; import org.esa.snap.core.gpf.descriptor.ToolAdapterOperatorDescriptor; import org.esa.snap.core.gpf.descriptor.ToolParameterDescriptor; import org.esa.snap.core.gpf.operators.tooladapter.ToolAdapterConstants; import org.esa.snap.core.util.StringUtils; import org.esa.snap.rcp.util.Dialogs; import org.esa.snap.ui.AppContext; import org.esa.snap.ui.ModalDialog; import org.esa.snap.ui.tooladapter.actions.EscapeAction; import org.esa.snap.ui.tooladapter.model.OperatorParametersTable; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.io.File; import java.util.*; import java.util.logging.Logger; /** * Form for displaying and editing details of a tool adapter parameter. * * @author Ramona Manda */ public class ToolParameterEditorDialog extends ModalDialog { private static final Logger logger = Logger.getLogger(ToolAdapterEditorDialog.class.getName()); public static final String helpID = "sta_editor"; private static final Map<String, Class<?>> typesMap = new LinkedHashMap<String, Class<?>>(); static { typesMap.put("String", String.class); typesMap.put("File", File.class); typesMap.put("Folder", File.class); typesMap.put("Integer", Integer.class); typesMap.put("Decimal", Float.class); typesMap.put("List", String[].class); typesMap.put("Boolean", Boolean.class); } private ToolParameterDescriptor parameter; private ToolParameterDescriptor oldParameter; private PropertyContainer container; private BindingContext valuesContext; private BindingContext paramContext; private JComponent defaultValueComponent; private JPanel mainPanel; private JTextField valueSetTextComponent; private final ToolAdapterOperatorDescriptor operator; public ToolParameterEditorDialog(AppContext appContext, ToolAdapterOperatorDescriptor operator, ToolParameterDescriptor inputParameter) { super(appContext.getApplicationWindow(), inputParameter.getName(), ID_OK_CANCEL, helpID); this.operator = operator; this.oldParameter = inputParameter; this.parameter = new ToolParameterDescriptor(inputParameter); this.parameter.setDeprecated(inputParameter.isDeprecated()); // copy the value this.container = PropertyContainer.createObjectBacked(this.parameter); this.valuesContext = new BindingContext(this.container); addComponents(); EscapeAction.register(getJDialog()); } @Override protected void onOK() { if (!OperatorParametersTable.checkUniqueParameterName(this.operator, parameter.getName(), this.oldParameter)) { return; } super.onOK(); oldParameter.setName(parameter.getName()); oldParameter.setAlias(parameter.getAlias()); oldParameter.setDataType(parameter.getDataType()); Object defaultValue = getProperty().getValue(); String defaultValueAsString = processDefaultValue(defaultValue); oldParameter.setDefaultValue(defaultValueAsString); oldParameter.setDescription(parameter.getDescription()); oldParameter.setLabel(parameter.getLabel()); oldParameter.setUnit(parameter.getUnit()); oldParameter.setInterval(parameter.getInterval()); oldParameter.setValueSet(parameter.getValueSet()); oldParameter.setCondition(parameter.getCondition()); oldParameter.setPattern(parameter.getPattern()); oldParameter.setFormat(parameter.getFormat()); oldParameter.setNotNull(parameter.isNotNull()); oldParameter.setNotEmpty(parameter.isNotEmpty()); oldParameter.setRasterDataNodeClass(parameter.getRasterDataNodeClass()); oldParameter.setValidatorClass(parameter.getValidatorClass()); oldParameter.setConverterClass(parameter.getConverterClass()); oldParameter.setDomConverterClass(parameter.getDomConverterClass()); oldParameter.setItemAlias(parameter.getItemAlias()); oldParameter.setDeprecated(parameter.isDeprecated()); oldParameter.setParameterType(parameter.getParameterType()); } private void addComponents() { GridBagLayout layout = new GridBagLayout(); layout.columnWidths = new int[]{100, 390}; this.mainPanel = new JPanel(layout); addTextPropertyEditor(mainPanel, "Name: ", "name", 0, "The 'Name' field is required."); addTextPropertyEditor(mainPanel, "Alias: ", "alias", 1, "The 'Alias' field is required."); String itemNameToSelect = null; if (this.parameter.getDataType() == File.class) { itemNameToSelect = "File"; if (this.parameter.getDefaultValue() != null) { File f = new File(this.parameter.getDefaultValue()); if (f.isDirectory()) { itemNameToSelect = "Folder"; } } } else { Iterator<Map.Entry<String, Class<?>>> it = typesMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Class<?>> entry = it.next(); if (entry.getValue() == this.parameter.getDataType()) { itemNameToSelect = entry.getKey(); break; } } } //dataType JComboBox comboEditor = new JComboBox(typesMap.keySet().toArray()); comboEditor.setSelectedItem(itemNameToSelect); comboEditor.addActionListener(ev -> { JComboBox cb = (JComboBox) ev.getSource(); String selectedTypeName = (String) cb.getSelectedItem(); Class<?> selectedTypeClass = typesMap.get(selectedTypeName); if (!parameter.getDataType().equals(selectedTypeClass)) { Class<?> previousTypeClass = parameter.getDataType(); parameter.setDataType(selectedTypeClass); try { valuesContext.getPropertySet().getProperty("defaultValue").setValue(null); // reset the default value } catch (ValidationException e) { logger.warning(e.getMessage()); } Object defaultValue = getProperty().getValue(); String defaultValueAsString = processDefaultValue(defaultValue); boolean canResetValueSet = true; if (selectedTypeClass == String.class || selectedTypeClass == String[].class) { canResetValueSet = false; if (previousTypeClass == Float.class) { defaultValueAsString = null; // reset the default value } } else if (selectedTypeClass == Integer.class) { if (previousTypeClass == Float.class || previousTypeClass == String.class || previousTypeClass == String[].class) { if (canConvertArrayToNumber(new IntegerConverter(), parameter.getValueSet())) { canResetValueSet = false; if (previousTypeClass == Float.class) { defaultValueAsString = null; // reset the default value } } } } else if (selectedTypeClass == Float.class) { if (previousTypeClass == Integer.class || previousTypeClass == String.class || previousTypeClass == String[].class) { if (canConvertArrayToNumber(new FloatConverter(), parameter.getValueSet())) { canResetValueSet = false; } } } if (canResetValueSet) { try { valuesContext.getPropertySet().getProperty("valueSet").setValue(null); // reset the value set } catch (ValidationException e) { logger.warning(e.getMessage()); } } newDataTypeSelected(selectedTypeName, selectedTypeClass, defaultValueAsString); } }); this.mainPanel.add(new JLabel("Data type"), getConstraints(2, 0, 1)); this.mainPanel.add(comboEditor, getConstraints(2, 1, 1)); addTextPropertyEditor(mainPanel, "Description: ", "description", 4, null); addTextPropertyEditor(mainPanel, "Label: ", "label", 5, null); addTextPropertyEditor(mainPanel, "Unit: ", "unit", 6, null); addTextPropertyEditor(mainPanel, "Interval: ", "interval", 7, null); this.valueSetTextComponent = new JTextField(); ValidateTextComponentAdapter adapter = new ValidateTextComponentAdapter(this.valueSetTextComponent) { @Override protected boolean validateText(String valueSetToValidate) { return validateValueSetText(valueSetToValidate); } }; addTextPropertyEditor(mainPanel, adapter, "Value set: ", "valueSet", 8); addTextPropertyEditor(mainPanel, "Condition: ", "condition", 9, null); addTextPropertyEditor(mainPanel, "Pattern: ", "pattern", 10, null); addTextPropertyEditor(mainPanel, "Format: ", "format", 11, null); addBoolPropertyEditor(mainPanel, "Not null", "notNull", 12); addBoolPropertyEditor(mainPanel, "Not empty", "notEmpty", 13); addTextPropertyEditor(mainPanel, "ItemAlias: ", "itemAlias", 14, null); addBoolPropertyEditor(mainPanel, "Deprecated", "deprecated", 15); //defaultValue JLabel label = new JLabel("Default value"); label.setPreferredSize(new Dimension(150, 35)); this.mainPanel.add(label, getConstraints(3, 0, 1)); newDataTypeSelected(itemNameToSelect, this.parameter.getDataType(), this.parameter.getDefaultValue()); setContent(this.mainPanel); } private boolean validateDefaultValueText(String textToValidate) { if (!StringUtils.isNullOrEmpty(textToValidate)) { if (this.parameter.getDataType() == Integer.class) { try { Integer.parseInt(textToValidate); } catch (NumberFormatException ex) { Dialogs.showError("Failed to convert '" + textToValidate + "' to integer number."); return false; } } else if (this.parameter.getDataType() == Float.class) { try { Float.parseFloat(textToValidate); } catch (NumberFormatException ex) { Dialogs.showError("Failed to convert '" + textToValidate + "' to decimal number."); return false; } } } return true; } private boolean validateValueSetText(String valueSetToValidate) { String[] valueSet = null; if (!StringUtils.isNullOrEmpty(valueSetToValidate)) { valueSet = valueSetToValidate.split(ArrayConverter.SEPARATOR); } if (this.parameter.getDataType() == Integer.class) { String wrongValue = null; Integer[] numbers = null; if (valueSet != null && valueSet.length > 0) { numbers = new Integer[valueSet.length]; IntegerConverter integerConverter = new IntegerConverter(); for (int i = 0; i < valueSet.length && wrongValue == null; i++) { try { numbers[i] = integerConverter.parse(valueSet[i]); if (numbers[i] == null) { wrongValue = ""; // an empty value } } catch (ConversionException ex) { wrongValue = valueSet[i]; } } } if (wrongValue == null) { populateDefaultValueComponent(numbers, null); } else { Dialogs.showError("Failed to convert '" + wrongValue + "' to integer number."); return false; } } else if (this.parameter.getDataType() == Float.class) { String wrongValue = null; Float[] numbers = null; if (valueSet != null && valueSet.length > 0) { numbers = new Float[valueSet.length]; FloatConverter floatConverter = new FloatConverter(); for (int i = 0; i < valueSet.length && wrongValue == null; i++) { try { numbers[i] = floatConverter.parse(valueSet[i]); if (numbers[i] == null) { wrongValue = ""; // an empty value } } catch (ConversionException ex) { wrongValue = valueSet[i]; } } } if (wrongValue == null) { populateDefaultValueComponent(numbers, null); } else { Dialogs.showError("Failed to convert '" + wrongValue + "' to decimal number."); return false; } } else if (this.parameter.getDataType() == String.class) { populateDefaultValueComponent(valueSet, null); } else if (this.parameter.getDataType() == String[].class) { populateListComponent(valueSet); } else { throw new IllegalArgumentException("Unknown parameter data type '" + this.parameter.getDataType().getName() + "'."); } return true; } private void populateDefaultValueComponent(Object[] valueSet, Object valueToSelect) { if (valueSet == null || valueSet.length == 0) { removeDefaultValueComponent(); createDefaultValueTextComponent(); addDefaultValueComponent(); } else { if (!(defaultValueComponent instanceof JComboBox)) { removeDefaultValueComponent(); createDefaultValueComboBoxComponent(); addDefaultValueComponent(); } populateDefaultValueComboBoxComponent(valueSet, valueToSelect); } } private void createDefaultValueTextComponent() { this.defaultValueComponent = new JTextField(); ValidateTextComponentAdapter adapter = new ValidateTextComponentAdapter((JTextField)this.defaultValueComponent) { @Override protected boolean validateText(String textToValidate) { return validateDefaultValueText(textToValidate); } }; PropertyDescriptor descriptor = getProperty().getDescriptor(); this.paramContext.bind(descriptor.getName(), adapter); } private void createDefaultValueComboBoxComponent() { PropertyDescriptor descriptor = getProperty().getDescriptor(); SingleSelectionEditor singleSelectionEditor = new SingleSelectionEditor(); this.defaultValueComponent = singleSelectionEditor.createEditorComponent(descriptor, this.paramContext); } private void createDefaultValueComponent(Object[] valueSet, Object valueToSelect) { if (valueSet == null || valueSet.length == 0) { createDefaultValueTextComponent(); } else { createDefaultValueComboBoxComponent(); } } private void addDefaultValueComponent() { this.mainPanel.add(this.defaultValueComponent, getConstraints(3, 1, 1)); this.mainPanel.revalidate(); } private void removeDefaultValueComponent() { if (this.defaultValueComponent != null) { Property property = getProperty(); Binding binding = this.paramContext.getBinding(property.getName()); binding.getComponentAdapter().unbindComponents(); this.mainPanel.remove(this.defaultValueComponent); } } private void populateDefaultValueComboBoxComponent(Object[] valueSet, Object valueToSelect) { JComboBox comboBox = (JComboBox)defaultValueComponent; DefaultComboBoxModel model = new DefaultComboBoxModel(); if (valueSet != null && valueSet.length > 0) { Object itemToSelect = null; for (int i=0; i<valueSet.length; i++) { model.addElement(valueSet[i]); if (valueToSelect != null && valueToSelect.equals(valueSet[i])) { itemToSelect = valueSet[i]; } } model.setSelectedItem(itemToSelect); } comboBox.setModel(model); } private void populateListComponent(String[] valueSet) { JScrollPane scrollPane = (JScrollPane)this.defaultValueComponent; JList list = (JList)scrollPane.getViewport().getView(); DefaultListModel model = new DefaultListModel(); if (valueSet != null) { for (int i=0; i<valueSet.length; i++) { model.addElement(valueSet[i]); } } list.setModel(model); } private Property getProperty() { Property[] properties = this.paramContext.getPropertySet().getProperties(); return properties[0]; } private void newDataTypeSelected(String typeName, Class<?> typeClass, String defaultValue) { removeDefaultValueComponent(); // recreate context of the default value try { if (this.paramContext != null) { // remove the old properties PropertySet propertySet = this.paramContext.getPropertySet(); Property property = getProperty(); propertySet.removeProperty(property); } PropertyDescriptor propertyDescriptor = ParameterDescriptorFactory.convert(this.parameter, new ParameterDescriptorFactory().getSourceProductMap()); DefaultPropertySetDescriptor propertySetDescriptor = new DefaultPropertySetDescriptor(); propertySetDescriptor.addPropertyDescriptor(propertyDescriptor); PropertyContainer paramContainer = PropertyContainer.createMapBacked(new HashMap<>(), propertySetDescriptor); this.paramContext = new BindingContext(paramContainer); } catch (ConversionException e) { logger.warning(e.getMessage()); } boolean enabled = false; String parameterType = ToolAdapterConstants.REGULAR_PARAM_MASK; if (typeClass == Boolean.class) { changeBooleanDataType(defaultValue); } else if (typeClass == String.class) { enabled = true; changeStringDataType(defaultValue); } else if (typeClass == String[].class) { enabled = true; changeListDataType(defaultValue); } else if (typeName.equals("File")) { changeFileDataType(defaultValue); } else if (typeName.equals("Folder")) { parameterType = ToolAdapterConstants.FOLDER_PARAM_MASK; changeFolderDataType(defaultValue); } else if (typeClass == Integer.class) { enabled = true; changeIntegerDataType(defaultValue); } else if (typeClass == Float.class) { enabled = true; changeFloatDataType(defaultValue); } else { throw new IllegalArgumentException("Unknown type name '"+ typeName+"' and type class '" + typeClass.getName() + "'."); } this.parameter.setParameterType(parameterType); this.valueSetTextComponent.setEnabled(enabled); addDefaultValueComponent(); } private void changeBooleanDataType(String defaultValue) { Property property = getProperty(); ValueSet valueSet = new ValueSet(new Object[]{true, false}); property.getDescriptor().setValueSet(valueSet); boolean isSelected = Boolean.parseBoolean(defaultValue); try { property.setValue(isSelected); } catch (ValidationException e) { logger.warning(e.getMessage()); } CheckBoxEditor checkBoxEditor = new CheckBoxEditor(); defaultValueComponent = checkBoxEditor.createEditorComponent(property.getDescriptor(), this.paramContext); defaultValueComponent.setBorder(new EmptyBorder(1, 0, 1, 0)); } private void changeListDataType(String defaultValue) { String[] valueSet = null; if (!StringUtils.isNullOrEmpty(defaultValue)) { valueSet = defaultValue.split(ArrayConverter.SEPARATOR); } try { getProperty().setValue(valueSet); } catch (ValidationException e) { logger.warning(e.getMessage()); } MultiSelectionEditor multiSelectionEditor = new MultiSelectionEditor(); this.defaultValueComponent = multiSelectionEditor.createEditorComponent(getProperty().getDescriptor(), this.paramContext); JScrollPane scrollPane = (JScrollPane)this.defaultValueComponent; JList list = (JList)scrollPane.getViewport().getView(); list.setVisibleRowCount(2); if (valueSet != null && valueSet.length > 0) { java.util.List<Integer> selectedIndices = new ArrayList<Integer>(); for (int i=0; i<list.getModel().getSize(); i++) { Object item = list.getModel().getElementAt(i); for (int k=0; k<valueSet.length; k++) { if (item.equals(valueSet[k])) { selectedIndices.add(i); break; } } } int[] indices = new int[selectedIndices.size()]; for (int i=0; i<selectedIndices.size(); i++) { indices[i] = selectedIndices.get(i).intValue(); } list.setSelectedIndices(indices); } } private void changeStringDataType(String defaultValue) { try { getProperty().setValue(defaultValue); } catch (ValidationException e) { logger.warning(e.getMessage()); } String[] valueSet = this.valuesContext.getPropertySet().getProperty("valueSet").getValue(); createDefaultValueComponent(valueSet, defaultValue); } private void changeFolderDataType(String defaultValue) { File file = null; if (!StringUtils.isNullOrEmpty(defaultValue)) { file = new File(defaultValue); } Property property = getProperty(); try { property.setValue(file); } catch (ValidationException e) { logger.warning(e.getMessage()); } DirectoryEditor folderEditor = new DirectoryEditor(); this.defaultValueComponent = folderEditor.createEditorComponent(property.getDescriptor(), this.paramContext); } private void changeFileDataType(String defaultValue) { File file = null; if (!StringUtils.isNullOrEmpty(defaultValue)) { file = new File(defaultValue); } Property property = getProperty(); try { property.setValue(file); } catch (ValidationException e) { logger.warning(e.getMessage()); } FileEditor fileEditor = new FileEditor(); this.defaultValueComponent = fileEditor.createEditorComponent(property.getDescriptor(), this.paramContext); } private void changeIntegerDataType(String defaultValue) { changeNumberDataType(new IntegerConverter(), defaultValue); } private void changeFloatDataType(String defaultValue) { changeNumberDataType(new FloatConverter(), defaultValue); } private <NumberType extends Number> void changeNumberDataType(NumberConverter<NumberType> converter, String defaultValue) { String[] valueSet = this.valuesContext.getPropertySet().getProperty("valueSet").getValue(); Number[] numbers = null; if (valueSet != null && valueSet.length > 0) { numbers = new Number[valueSet.length]; for (int i=0; i<valueSet.length; i++) { try { numbers[i] = converter.parse(valueSet[i]); } catch (ConversionException ex) { // ignore exception } } } Number defaultNumber = null; if (!StringUtils.isNullOrEmpty(defaultValue)) { try { defaultNumber = converter.parse(defaultValue); } catch (ConversionException e) { logger.warning(e.getMessage()); } } try { getProperty().setValue(defaultNumber); } catch (ValidationException e) { logger.warning(e.getMessage()); } createDefaultValueComponent(numbers, defaultNumber); } private JComponent addTextPropertyEditor(JPanel parent, String label, String propertyName, int line, String requiredMessage) { PropertyDescriptor propertyDescriptor = this.container.getDescriptor(propertyName); JTextField editorComponent = new JTextField(); ComponentAdapter adapter = null; if (StringUtils.isNullOrEmpty(requiredMessage)) { adapter = new TextComponentAdapter(editorComponent); } else { adapter = new RequiredTextComponentAdapter(editorComponent, requiredMessage); } this.valuesContext.bind(propertyDescriptor.getName(), adapter); parent.add(new JLabel(label), getConstraints(line, 0, 1)); parent.add(editorComponent, getConstraints(line, 1, 1)); return editorComponent; } private JComponent addTextPropertyEditor(JPanel parent, ComponentAdapter adapter, String label, String propertyName, int line) { JComponent editorComponent = adapter.getComponents()[0]; PropertyDescriptor propertyDescriptor = this.container.getDescriptor(propertyName); this.valuesContext.bind(propertyDescriptor.getName(), adapter); parent.add(new JLabel(label), getConstraints(line, 0, 1)); parent.add(editorComponent, getConstraints(line, 1, 1)); return editorComponent; } private JComponent addBoolPropertyEditor(JPanel parent, String label, String propertyName, int line) { PropertyDescriptor propertyDescriptor = this.container.getDescriptor(propertyName); CheckBoxEditor boolEditor = new CheckBoxEditor(); JCheckBox checkBoxComponent = (JCheckBox)boolEditor.createEditorComponent(propertyDescriptor, valuesContext); checkBoxComponent.setBorder(new EmptyBorder(1, 0, 1, 0)); parent.add(new JLabel(label), getConstraints(line, 0, 1)); parent.add(checkBoxComponent, getConstraints(line, 1, 1)); return checkBoxComponent; } private static GridBagConstraints getConstraints(int row, int col, int noCells) { GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.HORIZONTAL; c.gridx = col; c.gridy = row; if(noCells != -1){ c.gridwidth = noCells; } c.insets = new Insets(2, 10, 2, 10); return c; } public static String processDefaultValue(Object defaultValue) { String defaultValueAsString = null; if (defaultValue != null) { if (defaultValue.getClass().isArray()) { Object[] array = (Object[])defaultValue; defaultValueAsString = ""; for (int i=0; i<array.length; i++) { if (i > 0) { defaultValueAsString += ","; } defaultValueAsString += array[i].toString(); } } else { defaultValueAsString = defaultValue.toString(); } } return defaultValueAsString; } private static <NumberType extends Number> boolean canConvertArrayToNumber(NumberConverter<NumberType> converter, String[] valueSet) { if (valueSet != null && valueSet.length > 0) { for (int i=0; i<valueSet.length; i++) { try { NumberType number = converter.parse(valueSet[i]); if (number == null) { return false; } } catch (ConversionException e) { return false; } } return true; } return false; } }