/*
* 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.Set;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Arrays;
import java.util.ArrayList;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.FieldPosition;
import javax.swing.Box;
import javax.swing.Timer;
import javax.swing.JList;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JOptionPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.BorderFactory;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.jdesktop.swingx.JXBusyLabel;
import org.jdesktop.swingx.JXTitledPanel;
import javax.measure.Unit;
import javax.measure.IncommensurableException;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.geotoolkit.util.DateRange;
import org.apache.sis.measure.MeasurementRange;
import org.apache.sis.math.Statistics;
import org.apache.sis.measure.Units;
import org.apache.sis.measure.Angle;
import org.apache.sis.measure.Latitude;
import org.apache.sis.measure.Longitude;
import org.apache.sis.measure.AngleFormat; // Can't use SIS because of Number formatting.
import org.apache.sis.measure.RangeFormat;
import org.geotoolkit.coverage.io.CoverageStoreException;
import org.geotoolkit.coverage.sql.CoverageDatabase;
import org.geotoolkit.coverage.sql.CoverageEnvelope;
import org.geotoolkit.coverage.sql.CoverageTableModel;
import org.geotoolkit.coverage.sql.FutureQuery;
import org.geotoolkit.coverage.sql.Layer;
import org.apache.sis.geometry.Envelope2D;
import org.geotoolkit.gui.swing.Window;
import org.geotoolkit.gui.swing.WindowCreator;
import org.geotoolkit.gui.swing.referencing.CoordinateChooser;
import org.geotoolkit.display.shape.DoubleDimension2D;
import org.geotoolkit.internal.swing.ExceptionMonitor;
import org.geotoolkit.internal.swing.ArrayListModel;
import org.geotoolkit.resources.Vocabulary;
import org.geotoolkit.resources.Widgets;
import org.geotoolkit.resources.Errors;
import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
/**
* A widget displaying the {@linkplain CoverageDatabase#getLayers() list of layers} available
* in a coverage database. This component provides a panel which describes the selected layer.
* The descriptions include:
* <p>
* <ul>
* <li>A <cite>domain</cite> tab with:<ul>
* <li>The {@linkplain Layer#getGeographicBoundingBox() geographic bounding box}</li>
* <li>The {@linkplain Layer#getTimeRange() time range}</li>
* <li>The {@linkplain Layer#getTypicalResolution() typical resolution}</li>
* </ul></li>
* <li>A <cite>format</cite> tab with:<ul>
* <li>The {@linkplain Layer#getImageFormats() image formats}</li>
* <li>The {@linkplain Layer#getSampleValueRanges() range of sample values}</li>
* </ul></li>
* <li>A <cite>elevation</cite> tab with the available elevations.</li>
* </ul>
* <p>
* This panel provides also buttons for adding or removing layers, and a button for creating
* a new window listing the coverages in the selected layer.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.15
*
* @see <a href="{@docRoot}/../modules/display/geotk-wizards-swing/AddCoverages.html">Adding layers and images to the Coverage-SQL database</a>
*
* @since 3.11
* @module
*/
@SuppressWarnings("serial")
public class LayerList extends WindowCreator {
/**
* The default width and height.
*/
private static final int WIDTH = 600, HEIGHT = 400;
/**
* The time resolution for computing the minimal and maximal time values.
* This is useful for avoiding the "Value out of bounds" message when the
* user validates the time range in {@link CoordinateChooser} and the default
* time values have seconds precision, while only minute precision is used in
* the widget.
*/
private static final int TIME_RESOLUTION = 24*60*60*1000;
/**
* Action commands.
*/
private static final String REFRESH="REFRESH", ADD="ADD", REMOVE="REMOVE", COVERAGES="COVERAGES", BUSY="BUSY";
/**
* The database for which to display the list of available layers.
*/
protected final CoverageDatabase database;
/**
* The list of layers.
*/
private final ArrayListModel<String> layers;
/**
* The widget which display the list of selected layers.
*/
private final JList<String> layerList;
/**
* The button for removing a layer or showing the available coverages.
*/
private final JButton removeButton, coveragesButton;
/**
* The panel which display the currently selected layer.
*/
private final JXTitledPanel layerProperties;
/**
* The coverage domain.
*/
private final JLabel west, east, north, south, timeRange, typicalResolution;
/**
* The panel which contains the west, east, north and south bounds.
*/
private final JPanel geographicPanel;
/**
* The list of elevations.
*/
private final ArrayListModel<String> elevations;
/**
* A coma-separated list of formats.
*/
private final JLabel imageFormat;
/**
* Range of sample values.
*/
private final JLabel sampleValueRanges;
/**
* The buzy labels used when loading data for the first time.
*/
private final JXBusyLabel busyDomain, busyFormat;
/**
* The format to use for the time range.
*/
private final DateFormat dateFormat;
/**
* The format to use for elevations. This format is used in a background thread,
* so access to this instance need to be synchronized.
*/
private final NumberFormat heightFormat;
/**
* The format to use for latitudes and longitudes.
*/
private final AngleFormat angleFormat;
/**
* The format to use for formatting range of values.
*/
private final RangeFormat rangeFormat;
/**
* The timer used for waiting a slight delay before to set the properties panel to the
* busy state. Note that we need to use the Swing timer, not the one defined in the
* {@link java.util} package, because we want don't want to create a new thread.
*/
private final Timer timer;
/**
* Creates a new {@code LayerList} instance for the given database.
*
* @param database The database for which to display the list of available layers.
*/
public LayerList(final CoverageDatabase database) {
setLayout(new BorderLayout());
this.database = database;
final Listeners listeners = new Listeners();
timer = new Timer(200, listeners);
timer.setActionCommand(BUSY);
timer.setRepeats(false);
/*
* Localized resources.
*/
final Locale locale = getLocale();
final Vocabulary resources = Vocabulary.getResources(locale);
dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
heightFormat = NumberFormat.getNumberInstance(locale);
angleFormat = AngleFormat.getInstance(locale);
rangeFormat = new RangeFormat(locale);
/*
* List of layers.
*/
layers = new ArrayListModel<>();
layerList = new JList<>(layers);
layerList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setLayerNames(database.getLayers());
layerList.addListSelectionListener(listeners);
final JXTitledPanel layerPanel = new JXTitledPanel(
resources.getString(Vocabulary.Keys.Layers), new JScrollPane(layerList));
/*
* List of available elevations.
*/
elevations = new ArrayListModel<>();
final JList<String> elevationList = new JList<>(elevations);
elevationList.setLayoutOrientation(JList.VERTICAL_WRAP);
elevationList.setVisibleRowCount(0); // Will be calculated from the list height.
final ListCellRenderer<? super String> renderer = elevationList.getCellRenderer();
if (renderer instanceof JLabel) {
((JLabel) renderer).setHorizontalAlignment(JLabel.RIGHT);
}
/*
* The envelope of the currently selected layer.
*/
west = new JLabel((String) null, JLabel.CENTER);
east = new JLabel((String) null, JLabel.CENTER);
north = new JLabel((String) null, JLabel.CENTER);
south = new JLabel((String) null, JLabel.CENTER);
geographicPanel = new JPanel(new GridLayout(3, 3));
geographicPanel.add(filler()); geographicPanel.add(north);
geographicPanel.add(filler()); geographicPanel.add(west);
geographicPanel.add(filler()); geographicPanel.add(east);
geographicPanel.add(filler()); geographicPanel.add(south);
geographicPanel.add(filler());
geographicPanel.setVisible(false);
geographicPanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder(resources.getString(Vocabulary.Keys.GeographicCoordinates)),
BorderFactory.createEmptyBorder(9, 9, 9, 9)));
/*
* The time range of the currently selected layer.
* The resolution of the currently selected layer.
*/
timeRange = createField(resources, Vocabulary.Keys.TimeRange);
typicalResolution = createField(resources, Vocabulary.Keys.Resolution);
busyDomain = new JXBusyLabel();
final JPanel domainPane = combine(busyDomain, geographicPanel, timeRange, typicalResolution);
/*
* The list of formats.
* The range of sample values.
*/
imageFormat = createField(resources, Vocabulary.Keys.Decoders);
sampleValueRanges = createField(resources, Vocabulary.Keys.ValueRange);
busyFormat = new JXBusyLabel();
final JPanel formatPane = combine(busyFormat, imageFormat, sampleValueRanges);
/*
* Properties of the currently selected layer.
* Include the list of available elevations.
*/
final JTabbedPane propertiesTabs = new JTabbedPane();
propertiesTabs.addTab(resources.getString(Vocabulary.Keys.Domain), domainPane);
propertiesTabs.addTab(resources.getString(Vocabulary.Keys.Format), formatPane);
propertiesTabs.addTab(resources.getString(Vocabulary.Keys.Altitudes), new JScrollPane(elevationList));
layerProperties = new JXTitledPanel(getPropertiesTitle(null), propertiesTabs);
/*
* The central component. Include the list of layers
* and the properties of the selected layer.
*/
final JSplitPane layerAndProps = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, layerPanel, layerProperties);
layerAndProps.setContinuousLayout(true);
layerAndProps.setResizeWeight(1);
layerAndProps.setDividerLocation(WIDTH - 320);
layerAndProps.setBorder(BorderFactory.createEmptyBorder(9, 9, 9, 9));
/*
* The buttons bar.
*/
final JButton refreshButton, addButton;
refreshButton = new JButton(resources.getString(Vocabulary.Keys.Refresh));
addButton = new JButton(resources.getString(Vocabulary.Keys.Add));
removeButton = new JButton(resources.getString(Vocabulary.Keys.Delete));
coveragesButton = new JButton(resources.getString(Vocabulary.Keys.ImageList));
refreshButton.setActionCommand(REFRESH);
refreshButton.addActionListener(listeners);
addButton.setActionCommand(ADD);
addButton.addActionListener(listeners);
removeButton.setActionCommand(REMOVE);
removeButton.addActionListener(listeners);
removeButton.setEnabled(false);
coveragesButton.setActionCommand(COVERAGES);
coveragesButton.addActionListener(listeners);
coveragesButton.setEnabled(false);
final JPanel buttonBar = new JPanel(new GridLayout(1, 4));
buttonBar.add(refreshButton);
buttonBar.add(addButton);
buttonBar.add(removeButton);
buttonBar.add(coveragesButton);
final Box b = Box.createHorizontalBox();
b.add(Box.createHorizontalGlue());
b.add(buttonBar);
b.add(Box.createHorizontalGlue());
b.setBorder(BorderFactory.createEmptyBorder(9, 15, 9, 15));
/*
* Put the components in this panel.
*/
add(layerAndProps, BorderLayout.CENTER);
add(b, BorderLayout.AFTER_LAST_LINE);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
}
/**
* Creates an initially hiden field in which to write a layer properties.
*
* @return The field where to write the property value.
*/
private static JLabel createField(final Vocabulary resources, final short key) {
final JLabel label = new JLabel();
label.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder(resources.getString(key)),
BorderFactory.createEmptyBorder(0, 15, 0, 0)));
label.setVisible(false);
return label;
}
/**
* Puts the given component together in a single panel.
*/
private static JPanel combine(final JXBusyLabel busyLabel, final JComponent... components) {
final JPanel pane = new JPanel(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.insets.left = c.insets.right = 6;
c.gridx=0; c.gridy=0; c.insets.top = 12;
for (final JComponent component : components) {
component.setOpaque(false);
pane.add(component, c);
c.gridy++;
}
/*
* Wrap the JXBusyLabel in a JPanel in order to fill the empty
* space even when the label is invisible.
*/
final JComponent filler = filler();
filler.setOpaque(false);
filler.add(busyLabel);
c.insets.top = 0;
c.weighty = 1;
c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.CENTER;
pane.add(filler, c);
pane.setOpaque(false);
return pane;
}
/**
* Returns a component used only for filling space.
*/
private static JComponent filler() {
final JPanel filler = new JPanel(false);
filler.setOpaque(false);
return filler;
}
/**
* Implement all listeners used by the {@link LayerList} class.
*/
private final class Listeners implements ListSelectionListener, ActionListener {
/**
* Invoked when a new layer has been selected. This method delegates to
* {@link LayerList#setLayerProperties(String)} with the selected layer in
* argument.
*/
@Override
public void valueChanged(final ListSelectionEvent event) {
if (!event.getValueIsAdjusting()) {
final String layer = getSelectedLayer();
setLayerProperties(layer);
final boolean enabled = (layer != null);
removeButton.setEnabled(enabled);
coveragesButton.setEnabled(enabled);
}
}
/**
* Invoked when one of the buttons ("Refresh", "Add", etc.) has been pressed.
* This method delegates to the appropriate method in the enclosing class.
*/
@Override
public void actionPerformed(final ActionEvent event) {
final String action = event.getActionCommand();
switch (action) {
case ADD: addNewLayer(); break;
case REMOVE: removeLayer(); break;
case COVERAGES: showCoverages(); break;
case REFRESH: refresh(true); break;
case BUSY: setBusy(true); break;
}
}
}
/**
* Refreshes the list of layer names. This method shall be invoked when the database content
* changed. The refresh action will be run in a background thread.
* <p>
* If the {@code clearCache} argument is {@code false}, then this method refreshes only the
* list of layers. If the {@code clearCache} argument is {@code true}, then this method also
* {@linkplain CoverageDatabase#flush() clears the database cache}. This will force new queries
* of layer domains and formats.
*
* @param clearCache {@code true} if this method should also clears the database cache.
*/
public void refresh(final boolean clearCache) {
final String selected = getSelectedLayer();
if (clearCache) {
database.flush();
}
setLayerNames(database.getLayers());
setLayerProperties(selected);
}
/**
* Refreshes the list of layer names with the result of the given task.
* The given tasks shall be the value returned by {@link CoverageDatabase#getLayers()}.
*
* @param task The task which is computing the list of layer names.
*/
private void setLayerNames(final FutureQuery<Set<String>> task) {
task.invokeAfterCompletion(new Runnable() {
/** The names of all layers. */
private String[] names;
/** Get the names of all layers, then pass them to the JList in the Swing thread. */
@Override public synchronized void run() {
if (names == null) try {
final Set<String> layerNames = task.result();
names = layerNames.toArray(new String[layerNames.size()]);
Arrays.sort(names, String.CASE_INSENSITIVE_ORDER);
} catch (CoverageStoreException ex) {
exceptionOccured(ex);
return;
}
if (!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(this);
} else {
layers.setElements(names);
}
}
});
}
/**
* Returns the title to write in the properties panel for the given layer name.
*
* @param layer The name of the layer for which to get the title, or {@code null}.
* @return The title (never null).
*/
private String getPropertiesTitle(String layer) {
final Vocabulary resources = Vocabulary.getResources(getLocale());
if (layer == null) {
layer = '(' + resources.getString(Vocabulary.Keys.None) + ')';
}
return resources.getString(Vocabulary.Keys.PropertiesOf_1, layer);
}
/**
* Sets the values to show in the domain tab of the <cite>layer properties</cite> panel.
*
* @param layer The name of the currently selected layer, or {@code null} if none.
* @param bbox The geographic bounding box, or {@code null} if none.
* @param startTime The start time to format, or {@code null} if none.
* @param endTime The end time to format, or {@code null} if none.
* @param resolution The typical resolution, or {@code null} if none.
*/
final void setDomain(final String layer, final GeographicBoundingBox bbox,
final Date startTime, final Date endTime, final double[] resolution)
{
layerProperties.setTitle(getPropertiesTitle(layer));
if (bbox != null) {
west .setText(angleFormat.format(new Longitude(bbox.getWestBoundLongitude())));
east .setText(angleFormat.format(new Longitude(bbox.getEastBoundLongitude())));
north.setText(angleFormat.format(new Latitude (bbox.getNorthBoundLatitude())));
south.setText(angleFormat.format(new Latitude (bbox.getSouthBoundLatitude())));
}
geographicPanel.setVisible(bbox != null);
String text = null;
if (startTime != null && endTime != null) {
text = Widgets.getResources(getLocale()).getString(Widgets.Keys.TimeRange_2,
dateFormat.format(startTime), dateFormat.format(endTime));
}
setText(timeRange, text);
/*
* Resolution.
*/
text = null;
if (resolution != null) {
final CoordinateSystem cs = database.getCoordinateReferenceSystem().getCoordinateSystem();
final StringBuffer buffer = new StringBuffer();
for (int i=0; i<resolution.length; i++) {
final double r = resolution[i];
if (!Double.isNaN(r)) {
final CoordinateSystemAxis axis = cs.getAxis(i);
final Unit<?> unit = axis.getUnit();
final Object value = Units.isAngular(unit) ? new Angle(r) : Double.valueOf(r);
if (buffer.length() != 0) {
buffer.append(" \u00D7 "); // Multiplication symbol.
}
angleFormat.format(value, buffer, null);
if (value instanceof Number) {
// Do not format the unit symbol for angles.
buffer.append(' ').append(unit);
}
}
}
if (buffer.length() != 0) {
text = buffer.toString();
}
}
setText(typicalResolution, text);
}
/**
* Sets the values to show in the format tab of the <cite>layer properties</cite> panel.
*
* @param formats The format names, or {@code null} if none.
* @param ranges The ranges of measure, or {@code null} if none.
* @param rangeText The text to display for the ranges, or {@code null} for computing
* it from the {@code ranges} argument. This is non-null only if an error occurred
* while computing the range, because of incompatible units.
*/
final void setFormat(final Set<String> formats, final List<MeasurementRange<?>> ranges, String rangeText) {
String text = null;
if (!isNullOrEmpty(formats)) {
text = formats.toString();
text = text.substring(1, text.length() - 1); // For removing the brackets.
}
setText(imageFormat, text);
if (rangeText == null && !isNullOrEmpty(ranges)) {
boolean hasMore = false;
final StringBuffer buffer = new StringBuffer("<html>");
final FieldPosition pos = new FieldPosition(0);
for (final MeasurementRange<?> range : ranges) {
if (hasMore) {
buffer.append("<br>");
}
rangeFormat.format(range, buffer, pos);
hasMore = true;
}
rangeText = buffer.append("</html>").toString();
}
setText(sampleValueRanges, rangeText);
}
/**
* Sets the text of the given label to the given value, and set the visibility state
* depending on whatever the given text is null or not.
*/
private static void setText(final JLabel label, final String text) {
label.setText(text);
label.setVisible(text != null);
}
/**
* Shows or hides the busy labels. If the {@code busy} argument is {@code true}, then this
* method shows the busy labels only if {@link JXBusyLabel#isBusy()} returns {@code true}.
* <p>
* This method is invoked in the Swing thread if the {@link #setLayerProperties(String)}
* method appears to be busy for a little time.
*/
final void setBusy(final boolean busy) {
if (busy) {
if (busyDomain.isBusy() && !busyDomain.isVisible()) {
busyDomain.setVisible(true);
geographicPanel.setVisible(false);
timeRange.setVisible(false);
typicalResolution.setVisible(false);
}
if (busyFormat.isBusy() && !busyFormat.isVisible()) {
busyFormat.setVisible(true);
imageFormat.setVisible(false);
sampleValueRanges.setVisible(false);
}
} else {
busyDomain.setBusy(false);
busyDomain.setVisible(false);
busyFormat.setBusy(false);
busyFormat.setVisible(false);
}
}
/**
* Returns the name of the currently selected layer, or {@code null} if none.
*
* @return The currently selected layer, or {@code null}.
*/
public String getSelectedLayer() {
return layerList.getSelectedValue();
}
/**
* Sets the currently selected layer. Invoking this method will also refresh
* the content of the properties pane (layer domain, image formats, available
* elevations).
*
* @param layer The currently selected layer, or {@code null} for clearing the selection.
*/
public void setSelectedLayer(final String layer) {
if (layer != null) {
layerList.setSelectedValue(layer, true);
} else {
layerList.clearSelection();
}
// setLayerProperties will be invoked indirectly by the event listener.
}
/**
* Sets the layer to describe in the bottom panel.
* This method does not change the selection in the list.
*
* @param layer The name of the layer to describe.
*/
private void setLayerProperties(final String layer) {
if (layer == null) {
setDomain(null, null, null, null, null);
setFormat(null, null, null);
return;
}
busyDomain.setBusy(true);
busyFormat.setBusy(true);
timer.start(); // Will shows busy labels after 0.2 seconds.
final FutureQuery<Layer> task = database.getLayer(layer);
task.invokeAfterCompletion(new Runnable() {
private String name;
private GeographicBoundingBox bbox;
private Date startTime, endTime;
private double[] resolution;
private String[] elevations;
private Set<String> formats;
private List<MeasurementRange<?>> ranges;
private String rangeError;
/**
* Computes the fields declared in this class. This method can be invoked
* from a background thread.
*/
private void init() throws CoverageStoreException {
final Layer layer = task.result();
name = layer.getName();
bbox = layer.getGeographicBoundingBox();
resolution = layer.getTypicalResolution();
formats = layer.getImageFormats();
try {
ranges = layer.getSampleValueRanges();
} catch (CoverageStoreException e) {
Throwable cause = e.getCause();
if (cause instanceof IncommensurableException) {
rangeError = cause.getLocalizedMessage();
if (rangeError == null) {
rangeError = e.getLocalizedMessage();
}
} else {
throw e;
}
}
final DateRange timeRange = layer.getTimeRange();
if (timeRange != null) {
startTime = timeRange.getMinValue();
endTime = timeRange.getMaxValue();
}
final Set<Number> z = layer.getAvailableElevations();
if (!isNullOrEmpty(z)) {
final Statistics stats = new Statistics(null);
for (final Number value : z) {
stats.accept(value.doubleValue());
}
final FieldPosition pos = new FieldPosition(0);
final StringBuffer buffer = new StringBuffer();
final List<String> fz = new ArrayList<>(z.size());
synchronized (heightFormat) {
for (final Number value : z) {
heightFormat.format(value, buffer, pos).append(" ");
fz.add(buffer.toString());
buffer.setLength(0);
}
}
elevations = fz.toArray(new String[fz.size()]);
}
}
/**
* Get the info from the layer, then pass them in the Swing thread to the widgets.
*/
@Override
public synchronized void run() {
try {
if (name == null) try {
init();
} catch (CoverageStoreException ex) {
exceptionOccured(ex);
return;
}
if (!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(this);
} else {
setDomain(name, bbox, startTime, endTime, resolution);
setFormat(formats, ranges, rangeError);
LayerList.this.elevations.setElements(elevations);
}
} finally {
setBusy(false);
}
}
});
}
/**
* Popups a window for adding a new layer.
*/
private void addNewLayer() {
final Vocabulary resources = Vocabulary.getResources(getLocale());
final String title = resources.getString(Vocabulary.Keys.NewLayer);
String name = JOptionPane.showInputDialog(this, resources.getLabel(Vocabulary.Keys.Name),
title, JOptionPane.QUESTION_MESSAGE);
if (name != null && !(name = name.trim()).isEmpty()) {
final FutureQuery<Boolean> result = database.addLayer(name);
final String layer = name;
result.invokeAfterCompletion(new Runnable() {
@Override public void run() {
if (!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(this);
return;
}
final boolean exists;
try {
exists = result.result();
} catch (CoverageStoreException ex) {
exceptionOccured(ex);
return;
}
if (exists) {
refresh(false);
} else {
JOptionPane.showMessageDialog(LayerList.this,
"<html>" + Errors.format(Errors.Keys.ValueAlreadyDefined_1,
"<cite>" + layer + "</cite>") + "</html>",
title, JOptionPane.WARNING_MESSAGE);
}
}
});
}
}
/**
* Popups a window for removing the currently selected layer.
*/
private void removeLayer() {
final String layer = getSelectedLayer();
if (layer != null) {
final Locale locale = getLocale();
final Widgets resources = Widgets.getResources(locale);
final Vocabulary vocabulary = Vocabulary.getResources(locale);
final String remove = vocabulary.getString(Vocabulary.Keys.Remove);
final String cancel = vocabulary.getString(Vocabulary.Keys.Cancel);
final JLabel confirm = new JLabel(resources.getString(Widgets.Keys.ConfirmDeleteLayer_1, layer));
confirm.setPreferredSize(new Dimension(450, 80));
if (0 == JOptionPane.showOptionDialog(this, confirm,
resources.getString(Widgets.Keys.ConfirmDelete), JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE, null, new String[] {remove, cancel}, cancel))
{
final FutureQuery<Boolean> result = database.removeLayer(layer);
result.invokeAfterCompletion(new Runnable() {
@Override public void run() {
if (!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(this);
} else {
setSelectedLayer(null);
refresh(false);
}
}
});
}
}
}
/**
* Creates a new window which will show the list of available coverages
* in the currently selected layer.
*/
private void showCoverages() {
final String layerName = getSelectedLayer();
if (layerName != null) {
final GeographicBoundingBox bbox;
final DateRange dateRange;
final double[] resolution;
final Layer layer;
try {
layer = database.getLayer(layerName).result();
bbox = layer.getGeographicBoundingBox();
dateRange = layer.getTimeRange();
resolution = layer.getTypicalResolution();
} catch (CoverageStoreException ex) {
exceptionOccured(ex);
return;
}
int hide = 0;
final CoordinateChooser domain;
if (dateRange != null) {
final Date startTime = dateRange.getMinValue();
final Date endTime = dateRange.getMaxValue();
domain = new CoordinateChooser(
new Date((startTime.getTime() / TIME_RESOLUTION) * TIME_RESOLUTION),
new Date(((endTime.getTime() + (TIME_RESOLUTION-1)) / TIME_RESOLUTION) * TIME_RESOLUTION));
domain.setTimeRange(startTime, endTime);
} else {
domain = new CoordinateChooser();
hide = CoordinateChooser.TIME_RANGE;
}
if (bbox != null) {
domain.setGeographicArea(new Envelope2D(bbox));
}
if (resolution != null && resolution.length >= 2) {
domain.setPreferredResolution(new DoubleDimension2D(resolution[0], resolution[1]));
domain.setPreferredResolution(null); // In order to keep the "best resolution" option selected.
} else {
hide |= CoordinateChooser.RESOLUTION;
}
domain.setSelectorVisible(hide, false);
final Locale locale = getLocale();
final Widgets resources = Widgets.getResources(locale);
if (domain.showDialog(this, resources.getString(Widgets.Keys.DomainOfEntries))) {
/*
* Create the CoverageEnvelope from the user selection.
* TODO: Current implementation assumes that the CoverageEnvelope CRS is WGS84.
*/
final CoverageEnvelope envelope;
try {
envelope = layer.getEnvelope(null, null);
} catch (CoverageStoreException ex) {
exceptionOccured(ex);
return;
}
envelope.setHorizontalRange(domain.getGeographicArea());
envelope.setTimeRange(domain.getStartTime(), domain.getEndTime());
envelope.setPreferredResolution(domain.getPreferredResolution());
/*
* Create the widget which will display the list of available coverages.
*/
final CoverageList coverages = new CoverageList(new CoverageTableModel(locale));
coverages.setData(layer, envelope);
final Window frame = getWindowHandler().createWindow(LayerList.this, coverages,
resources.getString(Widgets.Keys.LayerElements_1, layerName));
frame.setVisible(true);
}
}
}
/**
* Invoked when an exception occurred while querying the {@linkplain #database}.
* The default implementation reports the error in an {@link ExceptionMonitor}.
* Subclasses can override this method in order to report the error in a different way.
*
* @param ex The exception which occurred.
*/
protected void exceptionOccured(final CoverageStoreException ex) {
ExceptionMonitor.show(this, ex);
}
}