package org.esa.snap.rcp.imgfilter;
import com.bc.ceres.binding.ConversionException;
import com.bc.ceres.binding.Converter;
import com.bc.ceres.binding.PropertyContainer;
import com.bc.ceres.binding.PropertySet;
import com.bc.ceres.swing.TableLayout;
import com.bc.ceres.swing.binding.BindingContext;
import com.bc.ceres.swing.binding.Enablement;
import org.esa.snap.rcp.imgfilter.model.Filter;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* A tabular editor form for a filter's properties.
*
* @author Norman
*/
public class FilterPropertiesForm extends JPanel implements PropertyChangeListener, Filter.Listener {
public static final TrueCondition TRUE_CONDITION = new TrueCondition();
private Filter filter;
private JComboBox<Filter.Operation> operationComboBox;
private JTextField nameField;
private JTextField shorthandField;
private JTextField tagsField;
private JTextField kernelOffsetXField;
private JTextField kernelOffsetYField;
private JTextField kernelWidthField;
private JTextField kernelHeightField;
private JTextField kernelQuotientField;
private BindingContext bindingContext;
public FilterPropertiesForm(Filter filter) {
// super(new GridBagLayout());
super(new TableLayout());
setBorder(new EmptyBorder(4, 4, 4, 4));
createUI();
setFilter(filter);
}
public Filter getFilter() {
return filter;
}
public void setFilter(Filter filter) {
if (this.filter != filter) {
if (this.filter != null) {
this.filter.removeListener(this);
}
if (bindingContext != null) {
bindingContext.removePropertyChangeListener(this);
bindingContext.unbind(bindingContext.getBinding("operation"));
bindingContext.unbind(bindingContext.getBinding("name"));
bindingContext.unbind(bindingContext.getBinding("shorthand"));
bindingContext.unbind(bindingContext.getBinding("tags"));
bindingContext.unbind(bindingContext.getBinding("kernelQuotient"));
bindingContext.unbind(bindingContext.getBinding("kernelOffsetX"));
bindingContext.unbind(bindingContext.getBinding("kernelOffsetY"));
bindingContext.unbind(bindingContext.getBinding("kernelWidth"));
bindingContext.unbind(bindingContext.getBinding("kernelHeight"));
bindingContext = null;
}
Filter oldFilter = this.filter;
this.filter = filter;
if (this.filter != null) {
PropertyContainer propertyContainer = PropertyContainer.createObjectBacked(this.filter);
propertyContainer.getDescriptor("tags").setConverter(new TagsConverter());
propertyContainer.getDescriptor("operation").setDescription("<html>The image filter operation.<br/>CONVOLVE uses a real-valued kernel matrix.<br/>Other operations have Boolean matrices.");
propertyContainer.getDescriptor("tags").setDescription("<html>Tags are used categorise and group filters.<br/>Use a comma to separate multiple tags.");
propertyContainer.getDescriptor("name").setDescription("The filter's display name");
propertyContainer.getDescriptor("shorthand").setDescription("A shorthand for the name, used as default band name suffix");
propertyContainer.getDescriptor("kernelQuotient").setDescription("<html>Inverse scaling factor, will be used<br/>to pre-multiply the kernel matrix before convolution");
propertyContainer.getDescriptor("kernelWidth").setDescription("<html>Width of the kernel matrix<br/>(editing not supported here, use the graphical editor)");
propertyContainer.getDescriptor("kernelHeight").setDescription("<html>Height of the kernel matrix<br/>(editing not supported here, use the graphical editor)");
propertyContainer.getDescriptor("kernelOffsetX").setDescription("<html>Offset in X of the kernel matrix' 'key element'<br/>(editing not yet supported, will always be kernel center)");
propertyContainer.getDescriptor("kernelOffsetY").setDescription("<html>Offset in Y of the kernel matrix' 'key element'<br/>(editing not yet supported, will always be kernel center)");
bindingContext = new BindingContext(propertyContainer);
bindingContext.bind("operation", operationComboBox);
bindingContext.bind("name", nameField);
bindingContext.bind("shorthand", shorthandField);
bindingContext.bind("tags", tagsField);
bindingContext.bind("kernelQuotient", kernelQuotientField);
bindingContext.bind("kernelOffsetX", kernelOffsetXField);
bindingContext.bind("kernelOffsetY", kernelOffsetYField);
bindingContext.bind("kernelWidth", kernelWidthField);
bindingContext.bind("kernelHeight", kernelHeightField);
Enablement.Condition editableCondition = new Enablement.Condition() {
@Override
public boolean evaluate(BindingContext bindingContext) {
return bindingContext.getPropertySet().getValue("editable");
}
};
Enablement.Condition editableConvolutionCondition = new Enablement.Condition() {
@Override
public boolean evaluate(BindingContext bindingContext) {
PropertySet propertySet = bindingContext.getPropertySet();
return Boolean.TRUE.equals(propertySet.getValue("editable")) && Filter.Operation.CONVOLVE.equals(propertySet.getValue("operation"));
}
};
bindingContext.bindEnabledState("operation", true, editableCondition);
bindingContext.bindEnabledState("name", true, editableCondition);
bindingContext.bindEnabledState("shorthand", true, editableCondition);
bindingContext.bindEnabledState("tags", true, editableCondition);
bindingContext.bindEnabledState("kernelQuotient", true, editableConvolutionCondition);
// width and height are disabled here, because user shall use intended spinners
bindingContext.bindEnabledState("kernelWidth", false, TRUE_CONDITION);
bindingContext.bindEnabledState("kernelHeight", false, TRUE_CONDITION);
// offsetX and offsetY are disabled here, because com.bc.ceres.jai.opimage.GeneralFilterOpImage does not support it so far
bindingContext.bindEnabledState("kernelOffsetX", false, TRUE_CONDITION);
bindingContext.bindEnabledState("kernelOffsetY", false, TRUE_CONDITION);
bindingContext.adjustComponents();
bindingContext.addPropertyChangeListener(this);
this.filter.addListener(this);
} else {
clearComponents();
}
firePropertyChange("filter", oldFilter, this.filter);
}
}
@Override
public void filterChanged(Filter filter, String propertyName) {
if (this.filter == filter) {
bindingContext.adjustComponents();
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (this.filter != null) {
this.filter.fireChange(evt.getPropertyName());
}
}
private void clearComponents() {
operationComboBox.setSelectedItem(null);
nameField.setText(null);
shorthandField.setText(null);
tagsField.setText(null);
kernelQuotientField.setText(null);
kernelOffsetXField.setText(null);
kernelOffsetYField.setText(null);
kernelWidthField.setText(null);
kernelHeightField.setText(null);
}
void createUI() {
operationComboBox = new JComboBox<>(Filter.Operation.values());
nameField = new JTextField(12);
shorthandField = new JTextField(6);
tagsField = new JTextField(16);
kernelQuotientField = new JTextField(8);
kernelOffsetXField = new JTextField(8);
kernelOffsetYField = new JTextField(8);
kernelWidthField = new JTextField(8);
kernelHeightField = new JTextField(8);
TableLayout layout = (TableLayout) getLayout();
layout.setColumnCount(2);
layout.setTableFill(TableLayout.Fill.HORIZONTAL);
layout.setTableAnchor(TableLayout.Anchor.WEST);
layout.setTableWeightX(0.5);
layout.setTablePadding(2, 2);
int row = 0;
add(new JLabel("Operation:"), TableLayout.cell(row, 0));
add(operationComboBox, TableLayout.cell(row, 1));
row++;
add(new JLabel("Name:"), TableLayout.cell(row, 0));
add(nameField, TableLayout.cell(row, 1));
row++;
add(new JLabel("Shorthand:"), TableLayout.cell(row, 0));
add(shorthandField, TableLayout.cell(row, 1));
row++;
add(new JLabel("Tags:"), TableLayout.cell(row, 0));
add(tagsField, TableLayout.cell(row, 1));
row++;
add(new JLabel("Kernel quotient:"), TableLayout.cell(row, 0));
add(kernelQuotientField, TableLayout.cell(row, 1));
row++;
add(new JLabel("Kernel offset X:"), TableLayout.cell(row, 0));
add(kernelOffsetXField, TableLayout.cell(row, 1));
row++;
add(new JLabel("Kernel offset Y:"), TableLayout.cell(row, 0));
add(kernelOffsetYField, TableLayout.cell(row, 1));
row++;
add(new JLabel("Kernel width:"), TableLayout.cell(row, 0));
add(kernelWidthField, TableLayout.cell(row, 1));
row++;
add(new JLabel("Kernel height:"), TableLayout.cell(row, 0));
add(kernelHeightField, TableLayout.cell(row, 1));
/*
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = 0;
gbc.weightx = 0.5;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(2, 0, 2, 0);
gbc.gridy = -1;
gbc.gridy++;
gbc.gridx = 0;
add(new JLabel("Operation:"), gbc);
gbc.gridx = 1;
add(operationComboBox, gbc);
gbc.gridy++;
gbc.gridx = 0;
add(new JLabel("Name:"), gbc);
gbc.gridx = 1;
add(nameField, gbc);
gbc.gridy++;
gbc.gridx = 0;
add(new JLabel("Shorthand:"), gbc);
gbc.gridx = 1;
add(shorthandField, gbc);
gbc.gridy++;
gbc.gridx = 0;
add(new JLabel("Tags:"), gbc);
gbc.gridx = 1;
add(tagsField, gbc);
gbc.gridy++;
gbc.gridx = 0;
add(new JLabel("Kernel quotient:"), gbc);
gbc.gridx = 1;
add(kernelQuotientField, gbc);
gbc.gridy++;
gbc.gridx = 0;
add(new JLabel("Kernel offset X:"), gbc);
gbc.gridx = 1;
add(kernelOffsetXField, gbc);
gbc.gridy++;
gbc.gridx = 0;
add(new JLabel("Kernel offset Y:"), gbc);
gbc.gridx = 1;
add(kernelOffsetYField, gbc);
gbc.gridy++;
gbc.gridx = 0;
add(new JLabel("Kernel width:"), gbc);
gbc.gridx = 1;
add(kernelWidthField, gbc);
gbc.gridy++;
gbc.gridx = 0;
add(new JLabel("Kernel height:"), gbc);
gbc.gridx = 1;
add(kernelHeightField, gbc);
*/
}
private static class TagsConverter implements Converter<Object> {
public static final HashSet<String> EMPTY_TAGS = new HashSet<>();
@Override
public Class<?> getValueType() {
return HashSet.class;
}
@Override
public HashSet<String> parse(String text) throws ConversionException {
if (text == null || text.isEmpty()) {
return EMPTY_TAGS;
}
String[] tagArray = text.split(",");
HashSet<String> tags = new LinkedHashSet<>();
for (String rawTag : tagArray) {
String tag = rawTag.trim();
if (!tag.isEmpty()) {
tags.add(tag);
}
}
return tags;
}
@Override
public String format(Object value) {
if (value instanceof Set) {
Set<String> set = (Set<String>) value;
if (!set.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (String s : set) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(s);
}
return sb.toString();
}
}
return null;
}
}
private static class TrueCondition extends Enablement.Condition {
@Override
public boolean evaluate(BindingContext bindingContext) {
return true;
}
}
}