package org.esa.snap.ui; import com.bc.ceres.binding.PropertyContainer; import com.bc.ceres.swing.binding.BindingContext; import javax.measure.unit.NonSI; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.ParseException; import java.util.Locale; /** * This user interface provides a world map and text fields to define region bounds. * The input values from the text fields and from the world map are reflected in the {@link BindingContext}, which can * be retrieved using {@link #getBindingContext()}. */ public class RegionBoundsInputUI { public final static String PROPERTY_NORTH_BOUND = RegionSelectableWorldMapPane.NORTH_BOUND; public final static String PROPERTY_EAST_BOUND = RegionSelectableWorldMapPane.EAST_BOUND; public final static String PROPERTY_SOUTH_BOUND = RegionSelectableWorldMapPane.SOUTH_BOUND; public final static String PROPERTY_WEST_BOUND = RegionSelectableWorldMapPane.WEST_BOUND; private final BindingContext bindingContext; private final JLabel northLabel; private final JFormattedTextField northLatField; private final JLabel northDegreeLabel; private final JLabel eastLabel; private final JFormattedTextField eastLonField; private final JLabel eastDegreeLabel; private final JLabel southLabel; private final JFormattedTextField southLatField; private final JLabel southDegreeLabel; private final JLabel westLabel; private final JFormattedTextField westLonField; private final JLabel westDegreeLabel; private final JPanel worldMapPaneUI; private JPanel ui; /** * Initializes a RegionBoundsInputUI. * This constructor creates the user interface and a binding context with default values. * The created binding context can be retrieved via {@link #getBindingContext()}. */ public RegionBoundsInputUI() { this(75, 30, 35, -15); } /** * Initializes a RegionBoundsInputUI with the given parameters. * If the parameters are valid geographic coordinates, they are used to initialize the user * interface and to create a binding context. * If the values are invalid, default values will be used. * The created binding context can be retrieved via {@link #getBindingContext()}. * * @param northBound The northern bounding latitude value * @param eastBound The eastern bound longitude value * @param southBound The southern bound latitude value * @param westBound The western bound longitude value */ public RegionBoundsInputUI(final double northBound, final double eastBound, final double southBound, final double westBound) { this(createBindingContext(northBound, eastBound, southBound, westBound)); } /** * Initializes a RegionBoundsInputUI with the given {@link BindingContext bindingContext}. * The bindingContext has to contain four parameters: {@link #PROPERTY_NORTH_BOUND northBound} , * {@link #PROPERTY_SOUTH_BOUND southBound}, {@link #PROPERTY_WEST_BOUND westBound} and * {@link #PROPERTY_EAST_BOUND eastBound}. * If the bindingContext contains geographic coordinates, these coordinates are used to initialize the user * interface. * * @param bindingContext The binding context which is needed for initialisation. */ public RegionBoundsInputUI(final BindingContext bindingContext) { RegionSelectableWorldMapPane.ensureValidBindingContext(bindingContext); this.bindingContext = bindingContext; final WorldMapPaneDataModel worldMapPaneDataModel = new WorldMapPaneDataModel(); final RegionSelectableWorldMapPane worldMapPane = new RegionSelectableWorldMapPane(worldMapPaneDataModel, bindingContext); worldMapPaneUI = worldMapPane.createUI(); northLabel = new JLabel("North:"); northDegreeLabel = createDegreeLabel(); northLatField = createTextField(); eastDegreeLabel = createDegreeLabel(); eastLabel = new JLabel("East:"); eastLonField = createTextField(); southLabel = new JLabel("South:"); southDegreeLabel = createDegreeLabel(); southLatField = createTextField(); westDegreeLabel = createDegreeLabel(); westLabel = new JLabel("West:"); westLonField = createTextField(); bindingContext.bind(PROPERTY_WEST_BOUND, westLonField); bindingContext.bind(PROPERTY_EAST_BOUND, eastLonField); bindingContext.bind(PROPERTY_NORTH_BOUND, northLatField); bindingContext.bind(PROPERTY_SOUTH_BOUND, southLatField); } /** * Enables or disables all child components. * * @param enabled - */ public void setEnabled(final boolean enabled) { northLabel.setEnabled(enabled); northLatField.setEnabled(enabled); northDegreeLabel.setEnabled(enabled); eastLabel.setEnabled(enabled); eastLonField.setEnabled(enabled); eastDegreeLabel.setEnabled(enabled); southLabel.setEnabled(enabled); southLatField.setEnabled(enabled); southDegreeLabel.setEnabled(enabled); westLabel.setEnabled(enabled); westLonField.setEnabled(enabled); westDegreeLabel.setEnabled(enabled); worldMapPaneUI.setEnabled(enabled); } /** * @return a {@link JPanel} which contains the user interface elements */ public JPanel getUI() { if (ui == null) { final JPanel fieldsPanel = GridBagUtils.createPanel(); final GridBagConstraints fieldsGBC = GridBagUtils.createDefaultConstraints(); fieldsGBC.anchor = GridBagConstraints.WEST; fieldsGBC.gridy = 0; GridBagUtils.addToPanel(fieldsPanel, northLabel, fieldsGBC, "gridx=3"); GridBagUtils.addToPanel(fieldsPanel, northLatField, fieldsGBC, "gridx=4"); GridBagUtils.addToPanel(fieldsPanel, northDegreeLabel, fieldsGBC, "gridx=5"); fieldsGBC.gridy = 1; GridBagUtils.addToPanel(fieldsPanel, westLabel, fieldsGBC, "gridx=0"); GridBagUtils.addToPanel(fieldsPanel, westLonField, fieldsGBC, "gridx=1"); GridBagUtils.addToPanel(fieldsPanel, westDegreeLabel, fieldsGBC, "gridx=2"); GridBagUtils.addToPanel(fieldsPanel, eastLabel, fieldsGBC, "gridx=6"); GridBagUtils.addToPanel(fieldsPanel, eastLonField, fieldsGBC, "gridx=7"); GridBagUtils.addToPanel(fieldsPanel, eastDegreeLabel, fieldsGBC, "gridx=8"); fieldsGBC.gridy = 2; GridBagUtils.addToPanel(fieldsPanel, southLabel, fieldsGBC, "gridx=3"); GridBagUtils.addToPanel(fieldsPanel, southLatField, fieldsGBC, "gridx=4"); GridBagUtils.addToPanel(fieldsPanel, southDegreeLabel, fieldsGBC, "gridx=5"); ui = GridBagUtils.createPanel(); final GridBagConstraints mainGBC = GridBagUtils.createDefaultConstraints(); mainGBC.fill = GridBagConstraints.HORIZONTAL; mainGBC.gridy = 0; GridBagUtils.addToPanel(ui, fieldsPanel, mainGBC); mainGBC.gridy = 1; GridBagUtils.addToPanel(ui, worldMapPaneUI, mainGBC, "insets.top=10"); } return ui; } /** * Returns the binding context which contains property values {@link #PROPERTY_NORTH_BOUND northBound} , * {@link #PROPERTY_SOUTH_BOUND southBound}, {@link #PROPERTY_WEST_BOUND westBound}, and * {@link #PROPERTY_EAST_BOUND eastBound}. This method should be used to get the bounds set by the UI components. * It is needed when no binding context has been passed to the RegionBoundsInputUI initially. * * @return the binding context. */ public BindingContext getBindingContext() { return bindingContext; } private static BindingContext createBindingContext(double northBound, double eastBound, double southBound, double westBound) { final Bounds bounds = new Bounds(northBound, eastBound, southBound, westBound); final PropertyContainer container = PropertyContainer.createObjectBacked(bounds); return new BindingContext(container); } private JFormattedTextField createTextField() { final int fieldWidth = 60; final DoubleFormatter textFormatter = new DoubleFormatter("###0.0##"); final JFormattedTextField textField = new JFormattedTextField(textFormatter); textField.setHorizontalAlignment(JTextField.RIGHT); final int defaultHeight = textField.getPreferredSize().height; final Dimension size = new Dimension(fieldWidth, defaultHeight); textField.setMinimumSize(size); textField.setPreferredSize(size); textField.setMaximumSize(size); return textField; } private JLabel createDegreeLabel() { return new JLabel(NonSI.DEGREE_ANGLE.toString()); } private static class DoubleFormatter extends JFormattedTextField.AbstractFormatter { private final DecimalFormat format; DoubleFormatter(String pattern) { final DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(Locale.ENGLISH); format = new DecimalFormat(pattern, decimalFormatSymbols); format.setParseIntegerOnly(false); format.setParseBigDecimal(false); format.setDecimalSeparatorAlwaysShown(true); } @Override public Object stringToValue(String text) throws ParseException { return format.parse(text).doubleValue(); } @Override public String valueToString(Object value) throws ParseException { if (value == null) { return ""; } return format.format(value); } } private static class Bounds { double northBound; double eastBound; double southBound; double westBound; private Bounds(double northBound, double eastBound, double southBound, double westBound) { this.northBound = northBound; this.eastBound = eastBound; this.southBound = southBound; this.westBound = westBound; } } }