/*
* Copyright (C) 2014 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* 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.binning.operator.ui;
import com.bc.ceres.binding.ValidationException;
import com.bc.ceres.swing.Grid;
import com.bc.ceres.swing.ListControlBar;
import com.bc.ceres.swing.TableLayout;
import com.bc.ceres.swing.binding.BindingContext;
import com.jidesoft.swing.JideSplitPane;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.ui.AppContext;
import org.esa.snap.ui.ModalDialog;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.product.ProductExpressionPane;
import org.esa.snap.ui.tool.ToolButtonFactory;
import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
/**
* The panel in the binning operator UI which allows for specifying the configuration of binning variables.
*
* @author Norman
*/
class BinningConfigurationPanel extends JPanel {
private final AppContext appContext;
private final BinningFormModel binningFormModel;
private double currentGridResolution;
private AggregatorTableController aggregatorTableController;
private VariableTableController variableTableController;
BinningConfigurationPanel(AppContext appContext, BinningFormModel binningFormModel) {
this.appContext = appContext;
this.binningFormModel = binningFormModel;
setLayout(new BorderLayout());
add(createAggregatorsAndVariablesPanel(), BorderLayout.CENTER);
add(createParametersPanel(), BorderLayout.SOUTH);
}
VariableTableController getVariableTableController() {
return variableTableController;
}
AggregatorTableController getAggregatorTableController() {
return aggregatorTableController;
}
private JComponent createAggregatorsAndVariablesPanel() {
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
splitPane.add(createAggregatorsPanel());
splitPane.add(createVariablesPanel());
splitPane.setShowGripper(true);
splitPane.setProportionalLayout(true);
splitPane.setProportions(new double[]{0.6});
return splitPane;
}
private JPanel createParametersPanel() {
BindingContext bindingContext = binningFormModel.getBindingContext();
JLabel validPixelExpressionLabel = new JLabel("Valid pixel expression:");
final JButton validPixelExpressionButton = new JButton("...");
final Dimension preferredSize = validPixelExpressionButton.getPreferredSize();
preferredSize.setSize(25, preferredSize.getHeight());
validPixelExpressionButton.setPreferredSize(preferredSize);
validPixelExpressionButton.setEnabled(hasSourceProducts());
binningFormModel.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(BinningFormModel.PROPERTY_KEY_CONTEXT_SOURCE_PRODUCT)) {
validPixelExpressionButton.setEnabled(hasSourceProducts());
}
}
});
JLabel targetHeightLabel = new JLabel("#Rows (90N - 90S):");
final JTextField validPixelExpressionField = new JTextField();
bindingContext.bind(BinningFormModel.PROPERTY_KEY_MASK_EXPR, validPixelExpressionField);
validPixelExpressionButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
final String expression = editExpression(validPixelExpressionField.getText());
if (expression != null) {
try {
binningFormModel.setProperty(BinningFormModel.PROPERTY_KEY_MASK_EXPR, expression);
} catch (ValidationException e) {
appContext.handleError("Invalid expression", e);
}
}
}
});
final JTextField numRowsTextField = new IntegerTextField(BinningFormModel.DEFAULT_NUM_ROWS);
JLabel resolutionLabel = new JLabel("Spatial resolution (km/px):");
final String defaultResolution = getString(computeResolution(BinningFormModel.DEFAULT_NUM_ROWS));
final JTextField resolutionTextField = new DoubleTextField(defaultResolution);
JButton resolutionButton = new JButton("default");
JLabel superSamplingLabel = new JLabel("Super-sampling:");
final JTextField superSamplingTextField = new IntegerTextField(1);
final ResolutionTextFieldListener listener = new ResolutionTextFieldListener(resolutionTextField, numRowsTextField);
bindingContext.bind(BinningFormModel.PROPERTY_KEY_NUM_ROWS, numRowsTextField);
bindingContext.bind(BinningFormModel.PROPERTY_KEY_SUPERSAMPLING, superSamplingTextField);
bindingContext.getBinding(BinningFormModel.PROPERTY_KEY_NUM_ROWS).setPropertyValue(BinningFormModel.DEFAULT_NUM_ROWS);
bindingContext.getBinding(BinningFormModel.PROPERTY_KEY_SUPERSAMPLING).setPropertyValue(1);
bindingContext.getPropertySet().getProperty(BinningFormModel.PROPERTY_KEY_NUM_ROWS).addPropertyChangeListener(
new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
updateResolutionLabel(numRowsTextField, resolutionTextField, listener);
}
}
);
resolutionTextField.addFocusListener(listener);
resolutionTextField.addActionListener(listener);
resolutionButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
resolutionTextField.setText(defaultResolution);
listener.update();
}
});
validPixelExpressionLabel.setToolTipText("Only those pixels matching this expression are considered");
targetHeightLabel.setToolTipText("<html>The number of rows of the <b>maximum</b> target grid</html>");
resolutionLabel.setToolTipText("The spatial resolution, directly depending on #rows");
superSamplingLabel.setToolTipText("Every input pixel is subdivided into n x n sub-pixels in order to reduce or avoid the Moiré effect");
TableLayout layout = new TableLayout(3);
layout.setTableAnchor(TableLayout.Anchor.NORTHWEST);
layout.setTableWeightX(0.0);
layout.setCellColspan(1, 1, 2);
layout.setCellColspan(3, 1, 2);
layout.setTableFill(TableLayout.Fill.HORIZONTAL);
layout.setColumnWeightX(1, 1.0);
layout.setTablePadding(10, 5);
final JPanel parametersPanel = new JPanel(layout);
parametersPanel.add(validPixelExpressionLabel);
parametersPanel.add(validPixelExpressionField);
parametersPanel.add(validPixelExpressionButton);
parametersPanel.add(targetHeightLabel);
parametersPanel.add(numRowsTextField);
parametersPanel.add(resolutionLabel);
parametersPanel.add(resolutionTextField);
parametersPanel.add(resolutionButton);
parametersPanel.add(superSamplingLabel);
parametersPanel.add(superSamplingTextField);
return parametersPanel;
}
private static int computeNumRows(double resolution) {
final double RE = 6378.145;
int numRows = (int) ((RE * Math.PI) / resolution) + 1;
return numRows % 2 == 0 ? numRows : numRows + 1;
}
private static double computeResolution(int numRows) {
final double RE = 6378.145;
return (RE * Math.PI) / (numRows - 1);
}
private static void updateResolutionLabel(JTextField targetHeightTextField, JTextField resolutionField, ResolutionTextFieldListener listener) {
resolutionField.setText(getResolutionString(Integer.parseInt(targetHeightTextField.getText())));
listener.update();
}
static String getResolutionString(int numRows) {
double number = computeResolution(numRows);
return getString(number);
}
private static String getString(double number) {
final DecimalFormatSymbols formatSymbols = new DecimalFormatSymbols();
formatSymbols.setDecimalSeparator('.');
final DecimalFormat decimalFormat = new DecimalFormat("#.##", formatSymbols);
return decimalFormat.format(number);
}
private boolean hasSourceProducts() {
return binningFormModel.getContextProduct() != null;
}
private String editExpression(String expression) {
final Product product = binningFormModel.getContextProduct();
if (product == null) {
return null;
}
final ProductExpressionPane expressionPane;
expressionPane = ProductExpressionPane.createBooleanExpressionPane(new Product[]{product}, product, appContext.getPreferences());
expressionPane.setCode(expression);
final int i = expressionPane.showModalDialog(appContext.getApplicationWindow(), "Expression Editor");
if (i == ModalDialog.ID_OK) {
return expressionPane.getCode();
}
return null;
}
private JPanel createAggregatorsPanel() {
final Grid grid = new Grid(6, false);
TableLayout gridLayout = grid.getLayout();
gridLayout.setTablePadding(4, 3);
gridLayout.setTableAnchor(TableLayout.Anchor.NORTHWEST);
gridLayout.setColumnFill(2, TableLayout.Fill.HORIZONTAL);
gridLayout.setColumnFill(3, TableLayout.Fill.HORIZONTAL);
gridLayout.setColumnFill(4, TableLayout.Fill.HORIZONTAL);
gridLayout.setColumnWeightX(2, 1.0);
gridLayout.setColumnWeightX(3, 1.0);
gridLayout.setColumnWeightX(4, 1.0);
grid.setHeaderRow(
/*0*/ //selection column
/*1*/ new JLabel("<html><b>Aggregator</b>"),
/*2*/ new JLabel("<html><b>Source Bands</b>"),
/*3*/ new JLabel("<html><b>Parameters</b>"),
/*4*/ new JLabel("<html><b>Target Bands</b>"),
/*5*/ null // column for edit button
);
aggregatorTableController = new AggregatorTableController(grid, binningFormModel);
final ListControlBar gridControlBar = ListControlBar.create(ListControlBar.HORIZONTAL, grid, aggregatorTableController);
final AbstractButton sel = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/ShowSelection16.png"), true);
sel.setToolTipText("Show/hide selection column");
sel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
grid.setShowSelectionColumn(sel.isSelected());
gridControlBar.updateState();
}
});
gridControlBar.add(sel, 0);
JPanel panel = new JPanel(new BorderLayout(4, 4));
panel.setBorder(new EmptyBorder(4, 4, 4, 4));
panel.add(gridControlBar, BorderLayout.NORTH);
panel.add(new JScrollPane(grid), BorderLayout.CENTER);
return panel;
}
private JPanel createVariablesPanel() {
final Grid grid = new Grid(4, false);
TableLayout gridLayout = grid.getLayout();
gridLayout.setTablePadding(4, 3);
gridLayout.setTableAnchor(TableLayout.Anchor.NORTHWEST);
gridLayout.setColumnFill(2, TableLayout.Fill.HORIZONTAL);
gridLayout.setColumnWeightX(2, 1.0);
grid.setHeaderRow(
/*0*/ //selection column
/*1*/ new JLabel("<html><b>Name</b>"),
/*2*/ new JLabel("<html><b>Expression</b>"),
/*5*/ null // column for edit button
);
variableTableController = new VariableTableController(grid, binningFormModel);
final ListControlBar gridControlBar = ListControlBar.create(ListControlBar.HORIZONTAL, grid, variableTableController);
final AbstractButton sel = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/ShowSelection16.png"), true);
sel.setToolTipText("Show/hide selection column");
sel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
grid.setShowSelectionColumn(sel.isSelected());
gridControlBar.updateState();
}
});
gridControlBar.add(sel, 0);
JPanel panel = new JPanel(new BorderLayout(4, 4));
panel.setBorder(new TitledBorder("Intermediate Source Bands (optional)"));
panel.add(gridControlBar, BorderLayout.NORTH);
panel.add(new JScrollPane(grid), BorderLayout.CENTER);
return panel;
}
private static class IntegerTextField extends JTextField {
private final static String disallowedChars = "`§~!@#$%^&*()_+=\\|\"':;?/>.<,- ";
public IntegerTextField(int defaultValue) {
super(defaultValue + "");
}
@Override
protected void processKeyEvent(KeyEvent e) {
if (!Character.isLetter(e.getKeyChar()) && disallowedChars.indexOf(e.getKeyChar()) == -1) {
super.processKeyEvent(e);
}
}
}
private class DoubleTextField extends JTextField {
private final static String disallowedChars = "`§~!@#$%^&*()_+=\\|\"':;?/><,- ";
public DoubleTextField(String defaultValue) {
super(defaultValue);
}
@Override
protected void processKeyEvent(KeyEvent e) {
if (!Character.isLetter(e.getKeyChar()) && disallowedChars.indexOf(e.getKeyChar()) == -1) {
super.processKeyEvent(e);
}
}
}
private class ResolutionTextFieldListener extends FocusAdapter implements ActionListener {
private final JTextField resolutionTextField;
private final JTextField numRowsTextField;
public ResolutionTextFieldListener(JTextField resolutionTextField, JTextField numRowsTextField) {
this.resolutionTextField = resolutionTextField;
this.numRowsTextField = numRowsTextField;
}
@Override
public void focusLost(FocusEvent e) {
update();
}
@Override
public void actionPerformed(ActionEvent e) {
update();
}
private void update() {
double resolution = Double.parseDouble(resolutionTextField.getText());
if (Math.abs(currentGridResolution - resolution) > 1E-6) {
binningFormModel.getBindingContext().getPropertySet().setValue(BinningFormModel.PROPERTY_KEY_NUM_ROWS, computeNumRows(resolution));
numRowsTextField.setText(String.valueOf(computeNumRows(resolution)));
currentGridResolution = resolution;
}
}
}
}