This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new b07299c  Connect Gridview to the ResourceExplorer: grid data are now 
shown when a GridCoverageResource is selected.
b07299c is described below

commit b07299cbb7780e743242249a75474481096bfcde
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Jan 29 19:54:43 2020 +0100

    Connect Gridview to the ResourceExplorer: grid data are now shown when a 
GridCoverageResource is selected.
---
 .../java/org/apache/sis/gui/coverage/GridRow.java  |   1 +
 .../java/org/apache/sis/gui/coverage/GridView.java |  65 +++++--
 .../org/apache/sis/gui/coverage/GridViewSkin.java  |   9 +-
 .../org/apache/sis/gui/coverage/ImageLoader.java   | 129 +++++++++++++
 .../org/apache/sis/gui/coverage/ImageRequest.java  | 199 +++++++++++++++++++++
 .../apache/sis/gui/dataset/ResourceExplorer.java   |  49 ++++-
 .../org/apache/sis/gui/metadata/MetadataTree.java  |   6 +-
 7 files changed, 434 insertions(+), 24 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRow.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRow.java
index 721d2aa..732ce9a 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRow.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRow.java
@@ -65,6 +65,7 @@ final class GridRow extends IndexedCell<Void> {
         view = (GridView) owner.getParent();
         setPrefWidth(view.getContentWidth());
         setFont(Font.font(null, FontWeight.BOLD, -1));      // Apply only to 
the header column.
+        setManaged(false);
     }
 
     /**
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
index 1a44ed4..bc40188 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
@@ -31,12 +31,14 @@ import javafx.beans.property.SimpleDoubleProperty;
 import javafx.beans.property.SimpleIntegerProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ObservableValue;
+import javafx.concurrent.WorkerStateEvent;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.internal.gui.BackgroundThreads;
 
 
 /**
@@ -46,8 +48,8 @@ import org.apache.sis.coverage.grid.GridCoverage;
  * dimension).
  *
  * <p>This class is designed for large images, with tiles loaded in a 
background thread only when first needed.
- * For matrices of relatively small size (e.g. less than 100 columns), 
consider using the standard JavaFX
- * {@link javafx.scene.control.TableView} instead.</p>
+ * This is not a general purpose grid viewer; for matrices of relatively small 
size (e.g. less than 100 columns),
+ * consider using the standard JavaFX {@link javafx.scene.control.TableView} 
instead.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
@@ -69,6 +71,13 @@ public class GridView extends Control {
     static final String OUT_OF_BOUNDS = "";
 
     /**
+     * If a loading is in progress, the loading process. Otherwise {@code 
null}.
+     *
+     * @see #coverageDefined(WorkerStateEvent)
+     */
+    private ImageLoader loader;
+
+    /**
      * The data shown in this table. Note that setting this property to a 
non-null value may not
      * modify the grid content immediately. Instead, a background process will 
request the tiles.
      *
@@ -225,12 +234,13 @@ public class GridView extends Control {
         tileHeight       = 1;       // For avoiding division by zero.
 
         setMinSize(120, 40);        // 2 cells on each dimension.
-        imageProperty.addListener(this::startImageLoading);
+        imageProperty.addListener(this::imageDefined);
         // Other listeners registered by GridViewSkin.Flow.
     }
 
     /**
      * Returns the source of sample values for this table.
+     * This method, like all other methods in this class, shall be invoked 
from the JavaFX thread.
      *
      * @return the image shown in this table, or {@code null} if none.
      *
@@ -241,12 +251,9 @@ public class GridView extends Control {
     }
 
     /**
-     * Sets the image to show in this table. This method loads an arbitrary 
amount of tiles
-     * in a background thread. It does not load all tiles if the image is 
large, unless the
-     * user scroll over all tiles.
-     *
-     * <p><b>Note:</b> the table content may appear unmodified after this 
method returns.
-     * The modifications will appear at an undetermined amount of time 
later.</p>
+     * Sets the image to show in this table.
+     * This method shall be invoked from JavaFX thread and returns quickly; it 
does not attempt to fetch any tile.
+     * Calls to {@link RenderedImage#getTile(int, int)} will be done in a 
background thread when first needed.
      *
      * @param  image  the image to show in this table, or {@code null} if none.
      *
@@ -257,6 +264,27 @@ public class GridView extends Control {
     }
 
     /**
+     * Loads image in a background thread from the given source.
+     * This method shall be invoked from JavaFX thread and returns immediately.
+     * The grid content may appear unmodified after this method returns;
+     * the modifications will appear after an undetermined amount of time.
+     *
+     * @param  source  the coverage or resource to load, or {@code null} if 
none.
+     */
+    public void setImage(final ImageRequest source) {
+        if (source == null) {
+            setImage((RenderedImage) null);
+        } else {
+            if (loader != null) {
+                loader.cancel();
+            }
+            loader = new ImageLoader(source);
+            loader.setOnSucceeded(this::coverageDefined);
+            BackgroundThreads.execute(loader);
+        }
+    }
+
+    /**
      * Returns the index of the band shown in this grid view.
      *
      * @return index of the currently visible band number.
@@ -286,6 +314,15 @@ public class GridView extends Control {
     }
 
     /**
+     * Invoked in JavaFX thread after {@link #loader} completed its task 
successfully.
+     */
+    private void coverageDefined(final WorkerStateEvent event) {
+        final ImageLoader result = loader;
+        loader = null;
+        setImage(result.getValue());
+    }
+
+    /**
      * Invoked (indirectly) when the user sets a new {@link RenderedImage}.
      * See {@link #setImage(RenderedImage)} for method description.
      *
@@ -294,9 +331,13 @@ public class GridView extends Control {
      * @param  image     the new image to show. May be {@code null}.
      * @throws ArithmeticException if the "tile grid x/y offset" property is 
too large.
      */
-    private void startImageLoading(final ObservableValue<? extends 
RenderedImage> property,
-                                   final RenderedImage previous, final 
RenderedImage image)
+    private void imageDefined(final ObservableValue<? extends RenderedImage> 
property,
+                              final RenderedImage previous, final 
RenderedImage image)
     {
+        if (loader != null) {
+            loader.cancel();
+            loader = null;
+        }
         tiles.clear();          // Let garbage collector dispose the rasters.
         lastTile = null;
         width    = 0;
@@ -332,8 +373,8 @@ public class GridView extends Control {
                 cellFormat.setMaximumFractionDigits(1);
             }
             formatChanged(false);
-            contentChanged(true);
         }
+        contentChanged(true);
     }
 
     /**
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
index e5a346d..e40f577 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
@@ -28,6 +28,8 @@ import javafx.scene.control.skin.VirtualFlow;
 import javafx.scene.control.skin.VirtualContainerBase;
 import javafx.scene.layout.HBox;
 import javafx.scene.shape.Rectangle;
+import javafx.scene.text.FontWeight;
+import javafx.scene.text.Font;
 
 
 /**
@@ -382,8 +384,11 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> {
                     children.remove(missing + count, count);        // Too 
many children. Remove the extra ones.
                 } else {
                     final GridCell[] more = new GridCell[missing];
+                    final Font font = Font.font(null, FontWeight.BOLD, -1);
                     for (int i=0; i<missing; i++) {
-                        more[i] = new GridCell();
+                        final GridCell cell = new GridCell();
+                        cell.setFont(font);
+                        more[i] = cell;
                     }
                     children.addAll(more);             // Single addAll(…) 
operation for sending only one event.
                 }
@@ -392,7 +397,7 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> {
             int column = firstVisibleColumn;
             for (final Node cell : children) {
                 ((GridCell) cell).setText(view.formatHeaderValue(column++, 
false));
-                layoutInArea(cell, pos, y, cellWidth, headerHeight, 
Node.BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.CENTER, VPos.CENTER);
+                layoutInArea(cell, pos, y, cellWidth, headerHeight, 
Node.BASELINE_OFFSET_SAME_AS_HEIGHT, HPos.RIGHT, VPos.CENTER);
                 pos += cellWidth;
             }
         }
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageLoader.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageLoader.java
new file mode 100644
index 0000000..236b0f0
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageLoader.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.coverage;
+
+import java.awt.image.RenderedImage;
+import javafx.concurrent.Task;
+import javafx.event.EventHandler;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.internal.gui.ExceptionReporter;
+
+
+/**
+ * A task for loading {@link GridCoverage} from a resource in a background 
thread, then fetching an image from it.
+ * Callers needs to define a task to execute on success with {@link 
#setOnSucceeded(EventHandler)}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class ImageLoader extends Task<RenderedImage> {
+    /**
+     * The {@value} value, for identifying code that assume two-dimensional 
objects.
+     */
+    public static final int BIDIMENSIONAL = 2;
+
+    /**
+     * The image source together with optional parameters for reading only a 
subset.
+     */
+    private final ImageRequest request;
+
+    /**
+     * The grid coverage obtained from the resource.
+     */
+    private volatile GridCoverage coverage;
+
+    /**
+     * Creates a new task for loading an image from the specified resource.
+     *
+     * @param  request  source of the image to load.
+     */
+    ImageLoader(final ImageRequest request) {
+        this.request = request;
+        coverage = request.coverage;
+    }
+
+    /**
+     * Returns the grid coverage loaded by this method.
+     * This method can be invoked from any thread.
+     *
+     * @return the coverage, or {@code null} if none.
+     */
+    final GridCoverage getCoverage() {
+        return coverage;
+    }
+
+    /**
+     * Loads the image. Current implementation reads the full image. If the 
coverage has more than 2 dimensions,
+     * only two of them are taken for the image; for all other dimensions, 
only the values at lowest index will
+     * be read.
+     *
+     * @return the image loaded from the source given at construction time.
+     * @throws DataStoreException if an error occurred while loading the grid 
coverage.
+     */
+    @Override
+    protected RenderedImage call() throws DataStoreException {
+        GridCoverage cv = coverage;
+        if (cv == null) {
+            final GridGeometry domain = request.getDomain().orElse(null);
+            final int[]        range  = request.getRange() .orElse(null);
+            coverage = cv = request.resource.read(domain, range);       // May 
be long to execute.
+        }
+        if (isCancelled()) {
+            return null;
+        }
+        GridExtent sliceExtent = request.getSliceExtent().orElse(null);
+        if (sliceExtent == null) {
+            final GridGeometry gg = cv.getGridGeometry();
+            if (gg != null && gg.getDimension() > BIDIMENSIONAL) {      // 
Should never be null but we are paranoiac.
+                final GridExtent extent = gg.getExtent();
+                final int dimension = extent.getDimension();
+                final int[] sliceDimensions = new int[BIDIMENSIONAL];
+                int k = 0;
+                for (int i=0; i<dimension; i++) {
+                    if (extent.getLow(i) != extent.getHigh(i)) {
+                        sliceDimensions[k] = i;
+                        if (++k >= BIDIMENSIONAL) break;
+                    }
+                }
+                sliceExtent = gg.derive().sliceByRatio(0, 
sliceDimensions).getIntersection();
+            }
+        }
+        return cv.render(sliceExtent);
+    }
+
+    /**
+     * Invoked in JavaFX thread on failure.
+     * This method popups a dialog box for reporting the error.
+     */
+    @Override
+    protected void failed() {
+        super.failed();
+        final GridCoverageResource resource = request.resource;
+        if (resource instanceof StoreListeners) {
+            ExceptionReporter.canNotReadFile(((StoreListeners) 
resource).getSourceName(), getException());
+        } else {
+            ExceptionReporter.canNotUseResource(getException());
+        }
+    }
+}
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
new file mode 100644
index 0000000..05b8b87
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImageRequest.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.gui.coverage;
+
+import java.util.Optional;
+import java.util.concurrent.Future;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.util.ArgumentChecks;
+
+
+/**
+ * A request for a two-dimensional view of a grid coverage. Those requests can 
be used for
+ * {@linkplain GridCoverageResource#read(GridGeometry, int...) reading} or
+ * {@linkplain GridCoverage#render(GridExtent) rendering} and image in a 
background thread.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class ImageRequest {
+    /**
+     * The source from where to read the image, specified at construction time.
+     */
+    GridCoverageResource resource;
+
+    /**
+     * The source for rendering the image, specified at construction time.
+     * After class initialization, only one of {@link #resource} and {@link 
#coverage} is non-null.
+     * But after task execution, this field will be set to the coverage which 
has been read.
+     */
+    volatile GridCoverage coverage;
+
+    /**
+     * Desired grid extent and resolution, or {@code null} for reading the 
whole domain.
+     * This is used only if the data source is a {@link GridCoverageResource}.
+     */
+    private GridGeometry domain;
+
+    /**
+     * 0-based indices of sample dimensions to read, or {@code null} for 
reading them all.
+     * This is used only if the data source is a {@link GridCoverageResource}.
+     */
+    private int[] range;
+
+    /**
+     * A subspace of the grid coverage extent to render, or {@code null} for 
the whole extent.
+     * If the extent has more than two dimensions, then the image will be 
rendered along the
+     * two first dimensions having a size greater than 1 cell.
+     */
+    private GridExtent sliceExtent;
+
+    /**
+     * Creates a new request for loading an image from the specified resource.
+     * If {@code domain} and {@code range} arguments are null, then the full 
coverage will be loaded.
+     * For loading a smaller amount of data, sub-domain or sub-range can be 
specified as documented
+     * in the {@linkplain GridCoverageResource#read(GridGeometry, int...) read 
method javadoc}.
+     *
+     * @param  source  source of the image to load.
+     * @param  domain  desired grid extent and resolution, or {@code null} for 
reading the whole domain.
+     * @param  range   0-based indices of sample dimensions to read, or {@code 
null} or an empty sequence for reading them all.
+     *
+     * @see GridCoverageResource#read(GridGeometry, int...)
+     */
+    public ImageRequest(final GridCoverageResource source, final GridGeometry 
domain, final int[] range) {
+        ArgumentChecks.ensureNonNull("source", source);
+        this.resource = source;
+        this.domain   = domain;
+        this.range    = (range != null && range.length != 0) ? range.clone() : 
null;
+    }
+
+    /**
+     * Creates a new request for loading an image from the specified coverage.
+     * If the {@code sliceExtent} argument is null, then the full coverage 
will be rendered
+     * in the first two dimensions having a size greater than 1 cell. For 
rendering a smaller amount of data,
+     * or for rendering data along other dimensions, a slice extent can be 
specified as documented in the
+     * {@linkplain GridCoverage#render(GridExtent) render method javadoc}.
+     *
+     * @param  source       source of the image to load.
+     * @param  sliceExtent  a subspace of the grid coverage extent to render, 
or {@code null} for the whole extent.
+     *
+     * @see GridCoverage#render(GridExtent)
+     */
+    public ImageRequest(final GridCoverage source, final GridExtent 
sliceExtent) {
+        ArgumentChecks.ensureNonNull("source", source);
+        this.coverage    = source;
+        this.sliceExtent = sliceExtent;
+    }
+
+    /**
+     * Returns the desired grid extent and resolution, if any.
+     * This is the {@code domain} argument specified to the following 
constructor:
+     *
+     * <blockquote>{@link #ImageRequest(GridCoverageResource, GridGeometry, 
int[])}</blockquote>
+     *
+     * and this argument will be transferred verbatim to the following method
+     * (see its javadoc for more explanation):
+     *
+     * <blockquote>{@link GridCoverageResource#read(GridGeometry, 
int...)}</blockquote>
+     *
+     * This property is always empty if this image request has been created 
with the
+     * {@link #ImageRequest(GridCoverage, GridExtent)} constructor, since no 
read
+     * operation will happen in such case.
+     *
+     * @return the desired grid extent and resolution of the coverage.
+     */
+    public Optional<GridGeometry> getDomain() {
+        return Optional.ofNullable(domain);
+    }
+
+    /**
+     * Returns the 0-based indices of sample dimensions to read, or an empty 
value for reading them all.
+     * This is the {@code range} argument specified to the following 
constructor:
+     *
+     * <blockquote>{@link #ImageRequest(GridCoverageResource, GridGeometry, 
int[])}</blockquote>
+     *
+     * and this argument will be transferred verbatim to the following method
+     * (see its javadoc for more explanation):
+     *
+     * <blockquote>{@link GridCoverageResource#read(GridGeometry, 
int...)}</blockquote>
+     *
+     * This property is always empty if this image request has been created 
with the
+     * {@link #ImageRequest(GridCoverage, GridExtent)} constructor, since no 
read
+     * operation will happen in such case.
+     *
+     * @return the 0-based indices of sample dimensions to read.
+     */
+    public Optional<int[]> getRange() {
+        return (range != null) ? Optional.of(range.clone()) : Optional.empty();
+    }
+
+    /**
+     * Returns the subspace of the grid coverage extent to render.
+     * This is the {@code sliceExtent} argument specified to the following 
constructor:
+     *
+     * <blockquote>{@link #ImageRequest(GridCoverage, GridExtent)}</blockquote>
+     *
+     * and this argument will be transferred verbatim to the following method
+     * (see its javadoc for more explanation):
+     *
+     * <blockquote>{@link GridCoverage#render(GridExtent)}</blockquote>
+     *
+     * If non-empty, then all dimensions except two should have a size of 1 
cell.
+     *
+     * @return subspace of the grid coverage extent to render.
+     */
+    public Optional<GridExtent> getSliceExtent() {
+        return Optional.ofNullable(sliceExtent);
+    }
+
+    /**
+     * Sets a new subspace of the grid coverage extent to render. This {@code 
sliceExtent} argument is not specified
+     * to the {@link #ImageRequest(GridCoverageResource, GridGeometry, int[])} 
constructor because when reading data
+     * from a {@link GridCoverageResource}, a slicing can already be done by 
the {@link GridGeometry} {@code domain}
+     * argument. However in the following scenario, it may be useful to 
specify both the {@code domain} and the
+     * {@code sliceExtent}:
+     *
+     * <ol>
+     *   <li>A {@link GridCoverageResource} is read with more data than what 
we want to show at first.</li>
+     *   <li>A subset of loaded data is shown (this is the purpose of {@code 
sliceExtent}).</li>
+     *   <li>The full coverage is obtained by a call to {@link #getCoverage()} 
for performing other operations on it.</li>
+     * </ol>
+     *
+     * That setter method make such scenario possible.
+     *
+     * @param  sliceExtent  subspace of the grid coverage extent to render.
+     */
+    public void setSliceExtent(final GridExtent sliceExtent) {
+        this.sliceExtent = sliceExtent;
+    }
+
+    /**
+     * Provides the grid coverage when it become available. If the grid 
coverage was specified at construction time,
+     * then a call to {@link Future#get()} will return it immediately. 
Otherwise that getter method will block until
+     * the read operation completed.
+     *
+     * @return the grid coverage.
+     */
+    public Future<GridCoverage> getCoverage() {
+        throw new UnsupportedOperationException();  // TODO
+    }
+}
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
index d3f77c8..5bde500 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
@@ -19,6 +19,7 @@ package org.apache.sis.gui.dataset;
 import java.util.Collection;
 import javafx.collections.ListChangeListener;
 import javafx.scene.layout.Region;
+import javafx.scene.control.Control;
 import javafx.scene.control.ContextMenu;
 import javafx.scene.control.SplitPane;
 import javafx.scene.control.Tab;
@@ -28,7 +29,10 @@ import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.gui.metadata.MetadataSummary;
 import org.apache.sis.gui.metadata.MetadataTree;
+import org.apache.sis.gui.coverage.GridView;
+import org.apache.sis.gui.coverage.ImageRequest;
 import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.storage.GridCoverageResource;
 
 
 /**
@@ -47,9 +51,20 @@ public class ResourceExplorer extends WindowManager {
     private final ResourceTree resources;
 
     /**
-     * The data as a table.
+     * The tab where to show {@link #features} or {@link #coverage},
+     * depending on the kind of resource.
      */
-    private final FeatureTable features;
+    private final Tab dataTab;
+
+    /**
+     * The data as a table, created when first needed.
+     */
+    private FeatureTable features;
+
+    /**
+     * The data as a grid coverage, created when first needed.
+     */
+    private GridView coverage;
 
     /**
      * The widget showing metadata about a selected resource.
@@ -70,11 +85,10 @@ public class ResourceExplorer extends WindowManager {
     public ResourceExplorer() {
         resources = new ResourceTree();
         metadata  = new MetadataSummary();
-        features  = new FeatureTable();
         content   = new SplitPane();
 
         final Resources localized = localized();
-        final Tab dataTab = new Tab(localized.getString(Resources.Keys.Data), 
features);
+        dataTab = new Tab(localized.getString(Resources.Keys.Data));
         dataTab.setContextMenu(new ContextMenu(createNewWindowMenu()));
         final TabPane tabs = new TabPane(
             new Tab(localized.getString(Resources.Keys.Summary),  
metadata.getView()), dataTab,
@@ -143,10 +157,31 @@ public class ResourceExplorer extends WindowManager {
                 if (resource != null) break;
             }
         }
-        final FeatureSet data = (resource instanceof FeatureSet) ? 
(FeatureSet) resource : null;
+        Control      view  = null;
+        FeatureSet   table = null;
+        ImageRequest grid  = null;
+        if (resource instanceof GridCoverageResource) {
+            grid = new ImageRequest((GridCoverageResource) resource, null, 
null);
+            if (coverage == null) {
+                coverage = new GridView();
+            }
+            view = coverage;
+        } else if (resource instanceof FeatureSet) {
+            table = (FeatureSet) resource;
+            if (features == null) {
+                features = new FeatureTable();
+            }
+            view = features;
+        }
+        /*
+         * At least one of `grid` or `table` will be null. Invoking the 
following
+         * setter methods with a null argument will release memory.
+         */
+        if (coverage != null) coverage.setImage(grid);
+        if (features != null) features.setFeatures(table);
+        dataTab.setContent(view);
         metadata.setMetadata(resource);
-        features.setFeatures(data);
-        setNewWindowDisabled(data == null);
+        setNewWindowDisabled(view == null);
     }
 
     /**
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
index d809a2f..397303c 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java
@@ -291,7 +291,7 @@ public class MetadataTree extends 
TreeTableView<TreeTable.Node> {
             copy         = new MenuItem(md.copy);
             copyAsXML    = new MenuItem();
             copyAsWKT    = new MenuItem("WKT — Well Known Text");
-            copyAsLegacy = new MenuItem("XML — ISO 19139:2007");
+            copyAsLegacy = new MenuItem("XML — Metadata (2007)");
             copyAs       = new Menu(md.copyAs, null, copyAsWKT, copyAsXML, 
copyAsLegacy);
             menu         = new ContextMenu(copy, copyAs);
             copyAsLegacy.setOnAction(this);
@@ -313,12 +313,12 @@ public class MetadataTree extends 
TreeTableView<TreeTable.Node> {
                     final Object obj = node.getUserObject();
                     if (obj != null) {
                         if 
(MetadataStandard.ISO_19115.isMetadata(obj.getClass())) {
-                            copyAsXML.setText("XML — ISO 19115-3:2016");
+                            copyAsXML.setText("XML — Metadata (2016)");
                             copyAsWKT.setDisable(true);
                             copyAsLegacy.setDisable(false);
                             disabled = false;
                         } else if (obj instanceof IdentifiedObject) {
-                            copyAsXML.setText("GML — Geographic Markup 
Language");
+                            copyAsXML.setText("XML — Geographic Markup 
Language");
                             copyAsWKT.setDisable(false);
                             copyAsLegacy.setDisable(true);
                             disabled = false;

Reply via email to