/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2010-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. */ package org.geotoolkit.gui.swing.coverage; import java.util.List; import java.util.Locale; import java.util.Arrays; import java.awt.Dimension; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JFormattedTextField; import javax.swing.DefaultComboBoxModel; import java.text.ParseException; import org.opengis.util.InternationalString; import javax.measure.Unit; import org.geotoolkit.coverage.Category; import org.geotoolkit.coverage.GridSampleDimension; import org.geotoolkit.internal.swing.table.JTables; import org.geotoolkit.internal.swing.UnitFormatter; import org.geotoolkit.image.palette.PaletteFactory; import org.geotoolkit.resources.Vocabulary; import static org.apache.sis.util.collection.Containers.isNullOrEmpty; /** * An editable table listing the categories in a {@link GridSampleDimension}. The table is * backed by a {@link CategoryTable} model. * * @author Martin Desruisseaux (Geomatys) * @version 3.14 * * @since 3.13 * @module */ @SuppressWarnings("serial") public class SampleDimensionPanel extends JComponent { /** * The type of elements to be stored in the {@link JComboBox} for enumerating the * sample dimension names. We don't use {@link String} because we want to work with * duplicated names. */ private static final class BandName { /** The band number. This is used for determining if two elements are equal. */ private final int band; /** The band name. This is the value shown in the combo box. */ String name; /** Creates a new {@link JComboBox} item. */ BandName(final int band, final String name) { this.band = band; this.name = name; } /** Returns the value to be shown in the {@link JComboBox}. */ @Override public String toString() { return name; } /** Returns a hash code value for this item. */ @Override public int hashCode() { return band; } /** Compare the given object with this item for equality. */ @Override public boolean equals(final Object object) { return (object instanceof BandName) && ((BandName) object).band == band; } } /** * Names of the sample dimensions. */ private final JComboBox<BandName> nameField; /** * Units of measurement. */ private final JFormattedTextField unitField; /** * The model for categories table. */ private final CategoryTable categories; /** * The sample dimensions, or {@code null} if none. */ private GridSampleDimension[] sampleDimensions; /** * The records for each sample dimensions. This is remembered in order to avoid * the lost of edited values if the user switch between different bands. */ private CategoryRecord[][] records; /** * The unit for each sample dimensions. */ private Unit<?>[] units; /** * The index of the currently selected sample dimension, or {@code -1} if none. */ private int selectedBandIndex = -1; /** * Creates a new panel which will use the * {@linkplain PaletteFactory#getDefault() default palette factory}. */ public SampleDimensionPanel() { this(null); } /** * Creates a new panel which will use the given factory for creating color palettes. * * @param paletteFactory The factory to use for loading colors from a palette name, * or {@code null} for the {@linkplain PaletteFactory#getDefault() default}. * * @since 3.14 */ public SampleDimensionPanel(final PaletteFactory paletteFactory) { setLayout(new GridBagLayout()); final Locale locale = getLocale(); final Vocabulary resources = Vocabulary.getResources(locale); categories = new CategoryTable(locale, paletteFactory); final JTable table = new JTable(categories); JTables.setHeaderCenterAlignment(table); categories.configure(table); table.setRowSelectionAllowed(false); table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); nameField = new JComboBox<>(); unitField = new JFormattedTextField(new UnitFormatter(locale)); final JLabel nameLabel = new JLabel(resources.getLabel(Vocabulary.Keys.Band)); final JLabel unitLabel = new JLabel(resources.getLabel(Vocabulary.Keys.Units)); nameLabel.setLabelFor(nameField); unitLabel.setLabelFor(unitField); final GridBagConstraints c = new GridBagConstraints(); c.gridy=0; c.fill=GridBagConstraints.HORIZONTAL; c.gridx=0; c.weightx=0; add(nameLabel, c); c.gridx++; c.weightx=1; add(nameField, c); c.gridx++; c.weightx=0; c.insets.left=12; add(unitLabel, c); c.gridx++; c.ipadx=60; c.insets.left= 0; add(unitField, c); c.gridx=0; c.ipadx=0; c.weightx=0; c.weighty=1; c.gridy++; c.gridwidth=4; c.insets.top=3; c.fill = GridBagConstraints.BOTH; add(new JScrollPane(table), c); final Dimension size = table.getPreferredSize(); size.width += 8; // Some approximative size for scrollbar. size.height += 80; // Some approximative size for component above the table. setPreferredSize(size); final Listeners listeners = new Listeners(); nameField.addActionListener(listeners); nameField.setEditable(true); } /** * Implements various listeners used by the enclosing class. */ private final class Listeners implements ActionListener { @Override public void actionPerformed(final ActionEvent event) { bandSelected(); } } /** * Returns the sample dimensions. This method returns a list with the same elements than * the elements specified to the last call to {@link #setSampleDimensions(List)} if the * user didn't edited the values, or a list containing new {@code GridSampleDimension} * instances otherwise. * <p> * <b>Tip:</b> consider invoking {@link #commitEdit()} before to invoke this method. * * @return The sample dimensions, or {@code null} if none. * @throws ParseException If at least two categories have overlapping range of sample values. */ public List<GridSampleDimension> getSampleDimensions() throws ParseException { GridSampleDimension[] bands = sampleDimensions; if (bands == null) { return null; } final PaletteFactory paletteFactory = categories.paletteFactory; bands = bands.clone(); for (int i=0; i<bands.length; i++) { final CategoryRecord[] records = this.records[i]; if (records != null) { // If null, the GridSampleDimension is unmodified. final Category[] categories = new Category[records.length]; for (int j=0; j<categories.length; j++) { categories[j] = records[j].getCategory(paletteFactory); } final String name = nameField.getModel().getElementAt(i).name; final GridSampleDimension band; try { band = new GridSampleDimension(name, categories, units[i]); } catch (IllegalArgumentException e) { ParseException ex = new ParseException(e.getLocalizedMessage(), 0); ex.initCause(e); throw ex; } if (!band.equals(bands[i])) { bands[i] = band; } } } return Arrays.asList(bands); } /** * Sets the sample dimensions to make available in this panel. This method * will initially show the first sample dimension found in the list, if any. * * @param bands The sample dimensions to show, or {@code null} if none. */ public void setSampleDimensions(final List<GridSampleDimension> bands) { final Locale locale = getLocale(); final Vocabulary resources = Vocabulary.getResources(locale); final DefaultComboBoxModel<BandName> nameList = (DefaultComboBoxModel<BandName>) nameField.getModel(); nameList.removeAllElements(); units = null; records = null; sampleDimensions = null; if (!isNullOrEmpty(bands)) { final int n = bands.size(); sampleDimensions = bands.toArray(new GridSampleDimension[n]); records = new CategoryRecord[n][]; units = new Unit<?>[n]; for (int i=0; i<n; i++) { final GridSampleDimension band = sampleDimensions[i]; units[i] = band.getUnits(); final InternationalString desc = band.getDescription(); String name = null; if (desc != null) { name = desc.toString(locale); } if (name == null) { name = resources.getString(Vocabulary.Keys.Band_1, i+1); } nameList.addElement(new BandName(i, name)); } } } /** * Invoked when a particular band has been selected in the list of sample dimensions. */ private void bandSelected() { final int bandIndex = nameField.getSelectedIndex(); if (bandIndex >= 0) { final GridSampleDimension band = sampleDimensions[bandIndex]; /* * Before to assign a new list of categories to the CategoryTable, * save the current values (in case the user edited them). */ final CategoryRecord[][] records = this.records; if (selectedBandIndex >= 0) { if (records[selectedBandIndex] == null) { records[selectedBandIndex] = categories.getElements(); } units[selectedBandIndex] = (Unit<?>) unitField.getValue(); } /* * If the CategoryRecords have been previously created for the sample * dimension to display, reuse them (so we get any user edited values). */ if (records[bandIndex] != null) { categories.setElements(records[bandIndex]); } else { /* * Otherwise create a new set of CategoryRecords. */ categories.setCategories(band.getCategories()); } unitField.setValue(units[bandIndex]); selectedBandIndex = bandIndex; } else if (selectedBandIndex >= 0) { /* * If the user edited the name of an existing item, just update the name. * The categories and units are left untouched. */ final String text = (String) nameField.getSelectedItem(); if (text != null) { nameField.getModel().getElementAt(selectedBandIndex).name = text; } else { /* * Replacing the current selection by a null selection. */ selectedBandIndex = -1; categories.setCategories(null); unitField.setValue(null); } } } /** * Returns {@code true} if the sample dimension is editable. * * @return {@code true} if the sample dimension is editable. */ public boolean isEditable() { return categories.isEditable(); } /** * Sets whatever edition should be allowed for this component. * Editions are enabled by default, like most <cite>Swing</cite> components. * * @param editable {@code false} for disabling edition, or {@code true} for re-enabling it. */ public void setEditable(final boolean editable) { nameField .setEditable(editable); unitField .setEditable(editable); categories.setEditable(editable); } /** * Forces the current value to be taken from the editable fields and set them as the * current values. If this operation fails for at least one field, this method will * set the focus on the offending field before to throw the exception. * * @throws ParseException If at least one values couldn't be commited. */ public void commitEdit() throws ParseException { try { unitField.commitEdit(); } catch (ParseException e) { unitField.requestFocus(); throw e; } if (selectedBandIndex >= 0) { units[selectedBandIndex] = (Unit<?>) unitField.getValue(); records[selectedBandIndex] = categories.getElements(); } } }