/*
* 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.swing.TableLayout;
import com.bc.ceres.swing.binding.BindingContext;
import com.bc.ceres.swing.binding.Enablement;
import com.bc.ceres.swing.binding.internal.AbstractButtonAdapter;
import com.jidesoft.swing.AutoResizingTextArea;
import com.jidesoft.swing.TitledSeparator;
import org.esa.snap.binning.operator.BinningOp;
import org.esa.snap.ui.RegionBoundsInputUI;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import static org.esa.snap.binning.operator.BinningOp.TimeFilterMethod.*;
/**
* The panel in the binning operator UI which allows for setting the regional and temporal filters.
*
* @author Olaf Danne
* @author Thomas Storm
*/
class BinningFilterPanel extends JPanel {
private static final String TIME_FILTER_METHOD_NONE = "ignore pixel observation time, use all source pixels";
private static final String TIME_FILTER_METHOD_TIME_RANGE = "use all pixels that have been acquired in the given binning period";
private static final String TIME_FILTER_METHOD_SPATIOTEMPORAL_DATA_DAY = "<html>use a sensor-dependent, spatial \"data-day\" definition with the goal to minimise the<br>" +
"time between the first and last observation contributing to the same bin in the given binning period.</html>";
private final Map<BinningOp.TimeFilterMethod, String> timeFilterMethodDescriptions;
private final BindingContext bindingContext;
private BinningFormModel binningFormModel;
BinningFilterPanel(final BinningFormModel binningFormModel) {
timeFilterMethodDescriptions = new HashMap<>();
timeFilterMethodDescriptions.put(BinningOp.TimeFilterMethod.NONE, TIME_FILTER_METHOD_NONE);
timeFilterMethodDescriptions.put(BinningOp.TimeFilterMethod.TIME_RANGE, TIME_FILTER_METHOD_TIME_RANGE);
timeFilterMethodDescriptions.put(BinningOp.TimeFilterMethod.SPATIOTEMPORAL_DATA_DAY, TIME_FILTER_METHOD_SPATIOTEMPORAL_DATA_DAY);
this.binningFormModel = binningFormModel;
bindingContext = binningFormModel.getBindingContext();
init();
}
private void init() {
ButtonGroup buttonGroup = new ButtonGroup();
final JRadioButton computeOption = new JRadioButton("Compute the geographical region according to extents of input products");
final JRadioButton globalOption = new JRadioButton("Use the whole globe as region");
final JRadioButton regionOption = new JRadioButton("Specify region:");
final JRadioButton wktOption = new JRadioButton("Enter WKT:");
bindingContext.bind(BinningFormModel.PROPERTY_KEY_COMPUTE_REGION, new RadioButtonAdapter(computeOption));
bindingContext.bind(BinningFormModel.PROPERTY_KEY_GLOBAL, new RadioButtonAdapter(globalOption));
bindingContext.bind(BinningFormModel.PROPERTY_KEY_MANUAL_WKT, new RadioButtonAdapter(wktOption));
bindingContext.bind(BinningFormModel.PROPERTY_KEY_BOUNDS, new RadioButtonAdapter(regionOption));
buttonGroup.add(computeOption);
buttonGroup.add(globalOption);
buttonGroup.add(wktOption);
buttonGroup.add(regionOption);
computeOption.setSelected(true);
final TableLayout layout = new TableLayout(1);
layout.setTableAnchor(TableLayout.Anchor.NORTHEAST);
layout.setTableFill(TableLayout.Fill.HORIZONTAL);
layout.setTableWeightX(1.0);
layout.setTablePadding(4, 3);
setLayout(layout);
add(new TitledSeparator("Spatial Filter / Region", SwingConstants.CENTER));
add(computeOption);
add(globalOption);
add(wktOption);
add(createWktInputPanel());
add(regionOption);
add(createAndInitBoundsUI());
add(new TitledSeparator("Temporal Filter", SwingConstants.CENTER));
add(createTemporalFilterPanel());
add(layout.createVerticalSpacer());
}
private JComponent createWktInputPanel() {
final JTextArea textArea = new AutoResizingTextArea(3, 30);
//Overrides behavior when set enabled
textArea.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (textArea.isEnabled()) {
textArea.setBackground(Color.WHITE);
} else {
textArea.setBackground(new Color(240, 240, 240));
}
}
});
bindingContext.bind(BinningFormModel.PROPERTY_KEY_WKT, textArea);
bindingContext.bindEnabledState(BinningFormModel.PROPERTY_KEY_WKT, false, BinningFormModel.PROPERTY_KEY_MANUAL_WKT, false);
textArea.setEnabled(false);
return new JScrollPane(textArea);
}
private JPanel createAndInitBoundsUI() {
final RegionBoundsInputUI regionBoundsInputUI;
regionBoundsInputUI = new RegionBoundsInputUI(bindingContext);
bindingContext.addPropertyChangeListener(BinningFormModel.PROPERTY_KEY_BOUNDS, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
final boolean enabled = (Boolean) evt.getNewValue();
regionBoundsInputUI.setEnabled(enabled);
}
});
regionBoundsInputUI.setEnabled(false);
return regionBoundsInputUI.getUI();
}
private Component createTemporalFilterPanel() {
TableLayout layout = new TableLayout(3);
layout.setTableFill(TableLayout.Fill.BOTH);
layout.setTableAnchor(TableLayout.Anchor.NORTHWEST);
layout.setTableWeightX(0.0);
layout.setTableWeightY(0.0);
layout.setTablePadding(10, 5);
layout.setCellColspan(0, 1, 2);
layout.setCellColspan(3, 1, 2);
layout.setCellWeightX(2, 1, 1.0);
layout.setCellWeightX(2, 2, 0.0);
layout.setColumnWeightX(1, 1.0);
JPanel panel = new JPanel(layout);
JLabel temporalFilterLabel = new JLabel("Time filter method:");
JLabel startDateLabel = new JLabel("Start date:");
JLabel startDateFormatLabel = new JLabel("yyyy-MM-dd( HH:mm:ss)");
JLabel periodDurationLabel = new JLabel("Period duration:");
JLabel minDataHourLabel = new JLabel("Min data hour:");
JLabel periodDurationUnitLabel = new JLabel("days");
final JComboBox<BinningOp.TimeFilterMethod> temporalFilterComboBox = new JComboBox<>(new BinningOp.TimeFilterMethod[]{
NONE,
TIME_RANGE,
SPATIOTEMPORAL_DATA_DAY
});
temporalFilterComboBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
BinningOp.TimeFilterMethod method = (BinningOp.TimeFilterMethod) temporalFilterComboBox.getSelectedItem();
if (method != null) {
temporalFilterComboBox.setToolTipText(timeFilterMethodDescriptions.get(method));
}
}
});
JTextField startDateTextField = new JTextField();
JTextField periodDurationTextField = new JTextField();
JTextField minDataHourTextField = new JTextField();
startDateLabel.setEnabled(false);
periodDurationLabel.setEnabled(false);
temporalFilterLabel.setToolTipText("The method that is used to decide which source pixels are used with respect to their observation time.");
startDateLabel.setToolTipText("The UTC start date of the binning period. If only the date part is given, the time 00:00:00 is assumed.");
periodDurationLabel.setToolTipText("Duration of the binning period in days.");
minDataHourLabel.setToolTipText(
"A sensor-dependent constant given in hours of a day (0 to 24) at which a sensor has a minimum number of observations at the date line (the 180 degree meridian).");
BindingContext bindingContext = binningFormModel.getBindingContext();
bindingContext.bind(BinningFormModel.PROPERTY_KEY_TIME_FILTER_METHOD, temporalFilterComboBox);
bindingContext.bind(BinningFormModel.PROPERTY_KEY_START_DATE_TIME, startDateTextField);
bindingContext.bind(BinningFormModel.PROPERTY_KEY_PERIOD_DURATION, periodDurationTextField);
bindingContext.bind(BinningFormModel.PROPERTY_KEY_MIN_DATA_HOUR, minDataHourTextField);
bindingContext.getBinding(BinningFormModel.PROPERTY_KEY_START_DATE_TIME).addComponent(startDateLabel);
bindingContext.getBinding(BinningFormModel.PROPERTY_KEY_START_DATE_TIME).addComponent(startDateFormatLabel);
bindingContext.getBinding(BinningFormModel.PROPERTY_KEY_PERIOD_DURATION).addComponent(periodDurationLabel);
bindingContext.getBinding(BinningFormModel.PROPERTY_KEY_PERIOD_DURATION).addComponent(periodDurationUnitLabel);
bindingContext.getBinding(BinningFormModel.PROPERTY_KEY_MIN_DATA_HOUR).addComponent(minDataHourLabel);
temporalFilterComboBox.setSelectedIndex(0); // selected value must not be empty when setting enablement
bindingContext.bindEnabledState(BinningFormModel.PROPERTY_KEY_START_DATE_TIME, true, hasTimeInformation(TIME_RANGE, SPATIOTEMPORAL_DATA_DAY));
bindingContext.bindEnabledState(BinningFormModel.PROPERTY_KEY_PERIOD_DURATION, true, hasTimeInformation(TIME_RANGE, SPATIOTEMPORAL_DATA_DAY));
bindingContext.bindEnabledState(BinningFormModel.PROPERTY_KEY_MIN_DATA_HOUR, true, hasTimeInformation(SPATIOTEMPORAL_DATA_DAY));
temporalFilterComboBox.setSelectedIndex(0); // ensure that enablement is applied
panel.add(temporalFilterLabel);
panel.add(temporalFilterComboBox);
panel.add(startDateLabel);
panel.add(startDateTextField);
panel.add(startDateFormatLabel);
panel.add(periodDurationLabel);
panel.add(periodDurationTextField);
panel.add(periodDurationUnitLabel);
panel.add(minDataHourLabel);
panel.add(minDataHourTextField);
return panel;
}
private static Enablement.Condition hasTimeInformation(final BinningOp.TimeFilterMethod... conditions) {
return new Enablement.Condition() {
@Override
public boolean evaluate(BindingContext bindingContext) {
BinningOp.TimeFilterMethod chosenMethod = bindingContext.getPropertySet().getProperty(
BinningFormModel.PROPERTY_KEY_TIME_FILTER_METHOD).getValue();
for (BinningOp.TimeFilterMethod condition : conditions) {
if (condition == chosenMethod) {
return true;
}
}
return false;
}
@Override
public void install(BindingContext bindingContext, Enablement enablement) {
bindingContext.addPropertyChangeListener(BinningFormModel.PROPERTY_KEY_TIME_FILTER_METHOD, enablement);
}
@Override
public void uninstall(BindingContext bindingContext, Enablement enablement) {
bindingContext.removePropertyChangeListener(BinningFormModel.PROPERTY_KEY_TIME_FILTER_METHOD, enablement);
}
};
}
private static class RadioButtonAdapter extends AbstractButtonAdapter implements ItemListener {
RadioButtonAdapter(AbstractButton button) {
super(button);
}
@Override
public void bindComponents() {
getButton().addItemListener(this);
}
@Override
public void unbindComponents() {
getButton().removeItemListener(this);
}
@Override
public void itemStateChanged(ItemEvent e) {
getBinding().setPropertyValue(getButton().isSelected());
}
}
}