sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Consolidation, improve documentation and complete some internationalization.
Date Sun, 03 Nov 2019 17:03:55 GMT
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 607b211  Consolidation, improve documentation and complete some internationalization.
607b211 is described below

commit 607b2119605251107bc448e568e08f5ac8bbc975
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sun Nov 3 15:21:23 2019 +0100

    Consolidation, improve documentation and complete some internationalization.
---
 .../main/java/org/apache/sis/gui/DataViewer.java   |   2 +-
 .../main/java/org/apache/sis/gui/dataset/Form.java |  97 +++++----
 .../apache/sis/gui/dataset/MetadataOverview.java   | 235 ++++++++++++---------
 .../org/apache/sis/gui/dataset/ResourceTree.java   |   5 +
 .../org/apache/sis/internal/gui/Resources.java     |  67 +++++-
 .../apache/sis/internal/gui/Resources.properties   |  39 ++--
 .../sis/internal/gui/Resources_fr.properties       |  39 ++--
 .../sis/util/resources/IndexedResourceBundle.java  |  12 --
 8 files changed, 323 insertions(+), 173 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java
index f81021d..f73b722 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java
@@ -119,7 +119,7 @@ public class DataViewer extends Application {
         final MenuBar menus = new MenuBar();
         final Menu file = new Menu(localized.getString(Resources.Keys.File));
         {
-            final MenuItem open = new MenuItem(localized.getMenuLabel(Resources.Keys.Open));
+            final MenuItem open = new MenuItem(localized.getString(Resources.Keys.Open));
             open.setAccelerator(KeyCombination.keyCombination("Shortcut+O"));
             open.setOnAction(e -> open());
 
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/Form.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/Form.java
index df9dc5f..eccf934 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/Form.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/Form.java
@@ -35,11 +35,16 @@ import javafx.scene.layout.GridPane;
 import javafx.scene.layout.Priority;
 import javafx.scene.layout.TilePane;
 import org.opengis.metadata.Metadata;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.util.ArraysExt;
 
 
 /**
- * Base class of pane with data organized as a form.
+ * Base class of pane with data organized as a form. For all comments in this class,
+ * "line" means a (label, value) pair. This is closely related to a grid pane row but
+ * not identical lines may not start at grid row zero; the grid may have some header
+ * controls before the lines start.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
@@ -56,10 +61,16 @@ abstract class Form<T> extends GridPane implements EventHandler<ActionEvent>
{
     private static final Insets PADDING = new Insets(12);
 
     /**
-     * Number of children per row. This is not necessarily the number of columns in the grid
pane
-     * since we reserve the last column for {@linkplain #pagination}, which span all rows.
+     * The pane which contains this form. Used for fetching information like number format
+     * or localization resources.
      */
-    static final int NUM_CHILD_PER_ROW = 2;
+    final MetadataOverview owner;
+
+    /**
+     * Number of children per line. This is not necessarily the number of columns in the
grid pane
+     * since we reserve the last column for {@linkplain #pagination}, which span all grid
rows.
+     */
+    static final int NUM_CHILD_PER_LINE = 2;
 
     /**
      * For selecting which form to show when there is many. Children are {@link ToggleButton}.
@@ -79,22 +90,29 @@ abstract class Form<T> extends GridPane implements EventHandler<ActionEvent>
{
     private T[] information;
 
     /**
-     * Index of the first child containing the rows added by calls to {@link #addRow(String,
String)} method.
+     * Index of the grid pane row containing the first (label, value) pair of this form.
+     * This is usually 0, unless there is some heading rows managed by the subclass.
+     */
+    private int rowOfFirstLine;
+
+    /**
+     * Index of the first child containing the lines added by calls to {@link #addLine(short,
String)} method.
      * All children from this index until the end of the children list shall be instances
of {@link Label}.
      */
-    private int rowsStart;
+    private int linesStartIndex;
 
     /**
      * Index after the last valid child. Should be equal to {@code getChildren().size()}
but may temporarily
      * differ while we are updating the pane content with new information. The difference
may happen because
      * we try to recycle existing {@link Label} instances before to discard them and create
new ones.
      */
-    private int rowsEnd;
+    private int linesEndIndex;
 
     /**
      * Creates a new form.
      */
-    Form() {
+    Form(final MetadataOverview owner) {
+        this.owner = owner;
         pageGroup  = new ToggleGroup();
         pagination = new TilePane(Orientation.VERTICAL);
         pagination.setAlignment(Pos.TOP_RIGHT);
@@ -103,20 +121,21 @@ abstract class Form<T> extends GridPane implements EventHandler<ActionEvent>
{
         setPadding(PADDING);
         setVgap(9);
         setHgap(9);
-        add(pagination, NUM_CHILD_PER_ROW, 0);
+        add(pagination, NUM_CHILD_PER_LINE, 0);
         setVgrow(pagination, Priority.ALWAYS);
         setHgrow(pagination, Priority.NEVER);
         getColumnConstraints().setAll(
-            new ColumnConstraints(),     // Let GridPane coputes the width of this column.
+            new ColumnConstraints(),     // Let GridPane computes the width of this column.
             new ColumnConstraints(100, 300, Double.MAX_VALUE, Priority.ALWAYS, null, true)
         );
     }
 
     /**
-     * Must be invoked by sub-class constructors after the finished construction.
+     * Must be invoked by sub-class constructors after they finished construction.
      */
     final void finished() {
-        rowsStart = getChildren().size();
+        rowOfFirstLine  = getRowCount();
+        linesStartIndex = getChildren().size();
     }
 
     /**
@@ -124,8 +143,9 @@ abstract class Form<T> extends GridPane implements EventHandler<ActionEvent>
{
      * and delegate to the {@link #setInformation(Collection, IntFunction)} method.
      *
      * @param  metadata  the metadata to show, or {@code null}.
+     * @param  grid      the grid geometry if available, or {@code null}.
      */
-    abstract void setInformation(Metadata metadata);
+    abstract void setInformation(Metadata metadata, GridGeometry grid);
 
     /**
      * Specifies a new set of information objects to show. This method takes a snapshot of
@@ -154,7 +174,7 @@ abstract class Form<T> extends GridPane implements EventHandler<ActionEvent>
{
         final ObservableList<Node> pages = pagination.getChildren();
         int i = pages.size();
         if (i < n) {
-            final NumberFormat format = getNumberFormat();
+            final NumberFormat format = owner.getNumberFormat();
             do {
                 final ToggleButton b = new ToggleButton(format.format(++i));
                 b.setToggleGroup(pageGroup);
@@ -194,46 +214,48 @@ abstract class Form<T> extends GridPane implements EventHandler<ActionEvent>
{
     }
 
     /**
-     * Invoked when the pane needs to update its content. This method reset this pane except
for the
-     * pagination control, invoke {@link #buildContent(Object)} then discard any extraneous
labels.
+     * Invoked when the pane needs to update its content. This method resets this pane except
for the
+     * pagination control, invokes {@link #buildContent(Object)} then discards any extraneous
labels.
      * The pagination control will span as many rows as the grid pane has.
      */
     private void update(final int index) {
-        rowsEnd = rowsStart;
+        linesEndIndex = linesStartIndex;
         buildContent(information[index]);
-        setRowSpan(pagination, nextRowIndex());              // For avoiding to interfer
with getRowCount().
+        setRowSpan(pagination, nextRowIndex());               // For allowing getRowCount()
to compute a correct value.
         final ObservableList<Node> children = getChildren();
-        children.subList(rowsEnd, children.size()).clear();
+        children.subList(linesEndIndex, children.size()).clear();
         setRowSpan(pagination, getRowCount());
     }
 
     /**
      * Invoked when a new information object should be shown in this pane.
-     * Implementer should invoke {@link #addRow(String, String)} method only.
+     * Implementer should invoke {@link #addLine(short, String)} method.
      *
      * @param  info   the information object to show (never {@code null}).
      */
     abstract void buildContent(T info);
 
     /**
-     * Adds a row to this form. This method does nothing if the given {@code value} is null.
+     * Adds a (label, value) pair to this form.
+     * This method does nothing if the given {@code value} is null.
      *
-     * @param  label  the label of the row to add.
+     * @param  label  a {@link Resources.Keys} for the label of the line to add.
      * @param  value  the value associated to the label, or {@code null} if none.
      */
-    final void addRow(final String label, final String value) {
+    final void addLine(final short label, final String value) {
         if (value == null) {
             return;
         }
+        final String labelText = owner.localized.getString(label);
         final Label labelCtrl, valueCtrl;
         final ObservableList<Node> children = getChildren();
-        if (rowsEnd < children.size()) {
-            labelCtrl = (Label) children.get(rowsEnd);
-            valueCtrl = (Label) children.get(rowsEnd + 1);
-            labelCtrl.setText(label);
+        if (linesEndIndex < children.size()) {
+            labelCtrl = (Label) children.get(linesEndIndex);
+            valueCtrl = (Label) children.get(linesEndIndex + 1);
+            labelCtrl.setText(labelText);
         } else {
             final int row = nextRowIndex();
-            labelCtrl = new Label(label);
+            labelCtrl = new Label(labelText);
             valueCtrl = new Label();
             labelCtrl.setLabelFor(valueCtrl);
             valueCtrl.setWrapText(true);
@@ -243,26 +265,27 @@ abstract class Form<T> extends GridPane implements EventHandler<ActionEvent>
{
             setValignment(valueCtrl, VPos.TOP);
         }
         valueCtrl.setText(value);
-        rowsEnd += NUM_CHILD_PER_ROW;
+        linesEndIndex += NUM_CHILD_PER_LINE;
     }
 
     /**
-     * Returns the index of the next row in the grid pane.
+     * Returns the row index in the grid pane of the next line.
      */
     final int nextRowIndex() {
-        return (rowsEnd - rowsStart) / NUM_CHILD_PER_ROW;
+        return (linesEndIndex - linesStartIndex) / NUM_CHILD_PER_LINE + rowOfFirstLine;
     }
 
     /**
-     * Returns {@code true} if this form contains no data.
+     * Returns the number of children before the first line added by {@link #addLine(short,
String)}.
      */
-    boolean isEmpty() {
-        return rowsStart == rowsEnd;
+    final int linesStartIndex() {
+        return linesStartIndex;
     }
 
     /**
-     * Returns the locale-dependent object to use for writing numbers.
-     * Subclasses should return some cached instance.
+     * Returns {@code true} if this form contains no data.
      */
-    abstract NumberFormat getNumberFormat();
+    boolean isEmpty() {
+        return linesStartIndex == linesEndIndex;
+    }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/MetadataOverview.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/MetadataOverview.java
index 00e1e09..dbcdcf2 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/MetadataOverview.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/MetadataOverview.java
@@ -28,16 +28,20 @@ import java.util.StringJoiner;
 import javafx.application.Platform;
 import javafx.collections.ObservableList;
 import javafx.concurrent.Task;
+import javafx.concurrent.Worker;
 import javafx.geometry.HPos;
 import javafx.scene.Node;
 import javafx.scene.canvas.Canvas;
 import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.control.Label;
 import javafx.scene.control.ScrollPane;
 import javafx.scene.control.TitledPane;
 import javafx.scene.image.Image;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.VBox;
 import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontWeight;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
@@ -52,8 +56,6 @@ import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.extent.GeographicDescription;
 import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.metadata.identification.Identification;
-import org.opengis.metadata.identification.DataIdentification;
-import org.opengis.metadata.identification.ServiceIdentification;
 import org.opengis.metadata.spatial.Dimension;
 import org.opengis.metadata.spatial.CellGeometry;
 import org.opengis.metadata.spatial.SpatialRepresentation;
@@ -63,14 +65,19 @@ import org.opengis.util.ControlledVocabulary;
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Longitude;
+import org.apache.sis.util.Workaround;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.resources.Vocabulary;
 
 import static org.apache.sis.internal.util.CollectionsExt.nonNull;
 
@@ -100,9 +107,9 @@ final class MetadataOverview {
     private final ScrollPane content;
 
     /**
-     * The locale to use for international strings.
+     * The resources for localized strings. Stored because needed often.
      */
-    private final Locale textLocale;
+    final Resources localized;
 
     /**
      * The locale to use for date/number formatters.
@@ -137,22 +144,26 @@ final class MetadataOverview {
     private boolean worldMapLoaded;
 
     /**
-     * If the metadata can not be obtained, the reason.
-     *
-     * @todo show in this control.
+     * If this {@link MetadataOverview} is loading metadata, the worker doing this task.
+     * Otherwise {@code null}. This is used for cancelling the currently running loading
+     * process if {@link #setMetadata(Resource)} is invoked again before completion.
      */
-    private Throwable failure;
+    private Worker<Metadata> loader;
 
     /**
-     * Incremented every time that a new metadata needs to be shown.
-     * This is used in case two calls to {@link #setMetadata(Resource)}
-     * are run concurrently and do not finish in the order they were started.
+     * If the metadata or the grid geometry can not be obtained, the reason.
+     * A non-null value does not necessarily implies that there is no metadata to show.
+     * The error may have occurred while fetching additional information after the main
+     * metadata, in which case the error is considered as a warning.
+     *
+     * @todo show in this control.
      */
-    private int selectionCounter;
+    private Throwable error;
 
     /**
      * The pane where to show information about resource identification, spatial representation,
etc.
      * Those panes will be added in the {@link #content} when we determined that they are
not empty.
+     * The content of those panes is updated by {@link #setMetadata(Metadata, GridGeometry)}.
      */
     private final TitledPane[] information;
 
@@ -160,11 +171,11 @@ final class MetadataOverview {
      * Creates an initially empty metadata overview.
      */
     MetadataOverview(final Locale locale) {
-        textLocale   = locale;
+        localized    = Resources.forLocale(locale);
         formatLocale = Locale.getDefault(Locale.Category.FORMAT);
         information  = new TitledPane[] {
-            new TitledPane("Resource identification", new IdentificationInfo()),
-            new TitledPane("Spatial representation",  new RepresentationInfo())
+            new TitledPane(localized.getString(Resources.Keys.ResourceIdentification), new
IdentificationInfo(this)),
+            new TitledPane(localized.getString(Resources.Keys.SpatialRepresentation),  new
RepresentationInfo(this))
         };
         content = new ScrollPane(new VBox());
         content.setFitToWidth(true);
@@ -183,7 +194,7 @@ final class MetadataOverview {
     /**
      * Returns the format to use for writing numbers.
      */
-    private NumberFormat getNumberFormat() {
+    final NumberFormat getNumberFormat() {
         if (numberFormat == null) {
             numberFormat = NumberFormat.getInstance(formatLocale);
         }
@@ -201,33 +212,63 @@ final class MetadataOverview {
     }
 
     /**
-     * Fetches the metadata in a background thread and delegates to {@link #setMetadata(Metadata)}
when ready.
+     * Fetches the metadata in a background thread and delegates to
+     * {@link #setMetadata(Metadata, GridGeometry)} when ready.
      *
      * @param  resource  the resource for which to show metadata, or {@code null}.
      */
     public void setMetadata(final Resource resource) {
         assert Platform.isFxApplicationThread();
+        if (loader != null) {
+            loader.cancel();
+            loader = null;
+        }
         if (resource == null) {
-            setMetadata((Metadata) null);
+            setMetadata(null, null);
         } else {
-            final int sequence = ++selectionCounter;
             final class Getter extends Task<Metadata> {
-                /** Invoked in a background thread for fetching metadata. */
+                /**
+                 * The grid resource, fetched as a complement to metadata.
+                 */
+                private GridGeometry grid;
+
+                /**
+                 * If we failed to get the grid, consider as a warning (not a failure).
+                 */
+                private DataStoreException warning;
+
+                /**
+                 * Invoked in a background thread for fetching metadata,
+                 * eventually with other information like grid geometry.
+                 */
                 @Override protected Metadata call() throws DataStoreException {
-                    return resource.getMetadata();
+                    final Metadata metadata = resource.getMetadata();
+                    if (resource instanceof GridCoverageResource && !isCancelled())
try {
+                        grid = ((GridCoverageResource) resource).getGridGeometry();
+                    } catch (DataStoreException e) {
+                        warning = e;
+                    }
+                    return metadata;
                 }
 
-                /** Shows the result, unless another {@link #setMetadata(Resource)} has been
invoked. */
+                /**
+                 * Shows the result, unless another {@link #setMetadata(Resource)} has been
invoked.
+                 */
                 @Override protected void succeeded() {
-                    if (sequence == selectionCounter) {
-                        setMetadata(getValue());
+                    if (!isCancelled()) {
+                        setMetadata(getValue(), grid);
+                        error = warning;
                     }
                 }
 
-                /** Invoked when an error occurred while fetching metadata. */
+                /**
+                 * Invoked when an error occurred while fetching metadata.
+                 */
                 @Override protected void failed() {
-                    setMetadata((Metadata) null);
-                    failure = getException();
+                    if (!isCancelled()) {
+                        setMetadata(null, null);
+                        error = getException();
+                    }
                 }
             }
             BackgroundThreads.execute(new Getter());
@@ -235,18 +276,26 @@ final class MetadataOverview {
     }
 
     /**
-     * Sets the content of this pane to the given metadata.
+     * Sets the content of this pane to the given metadata. The metadata can be completed
+     * by an optional {@link GridGeometry}, which can be used for producing more complete
+     * information in the "Spatial representation" pane.
      *
      * @param  metadata  the metadata to show, or {@code null}.
+     * @param  grid      if the resource is a {@link GridCoverageResource}, the grid geometry.
      */
-    public void setMetadata(final Metadata metadata) {
+    public void setMetadata(final Metadata metadata, final GridGeometry grid) {
         assert Platform.isFxApplicationThread();
-        failure = null;
+        error = null;
         final ObservableList<Node> children = ((VBox) content.getContent()).getChildren();
+        /*
+         * We want to include only the non-empty panes in the children list. But instead
of
+         * removing everything and adding back non-empty panes, we check case-by-case if
a
+         * child should be added or removed. It will often result in no modification at all.
+         */
         int i = 0;
         for (TitledPane pane : information) {
             final Form<?> info = (Form<?>) pane.getContent();
-            info.setInformation(metadata);
+            info.setInformation(metadata, grid);
             final boolean isEmpty   = info.isEmpty();
             final boolean isPresent = (i < children.size()) && children.get(i)
== pane;
             if (isEmpty == isPresent) {     // Should not be present if empty, or should
be present if non-empty.
@@ -268,7 +317,12 @@ final class MetadataOverview {
      * The same pane can be used for an arbitrary amount of identifications.
      * Each instance is identified by its title.
      */
-    private final class IdentificationInfo extends Form<Identification> {
+    private static final class IdentificationInfo extends Form<Identification> {
+        /**
+         * The resource title, or if non the identifier as a fallback.
+         */
+        private final Label title;
+
         /**
          * The canvas where to draw geographic bounding boxes over a world map.
          * Shall never be null, but need to be recreated for each new map.
@@ -282,22 +336,29 @@ final class MetadataOverview {
         /**
          * Creates an initially empty view for identification information.
          */
-        IdentificationInfo() {
-            extentOnMap = new Canvas();                         // Size of (0,0) by default.
-            add(extentOnMap, 0, 0, NUM_CHILD_PER_ROW, 1);
+        IdentificationInfo(final MetadataOverview owner) {
+            super(owner);
+            title = new Label();
+            title.setFont(Font.font(null, FontWeight.BOLD, 14));
+            add(title, 0, 0, NUM_CHILD_PER_LINE, 1);
+
+            extentOnMap = new Canvas();                     // Size of (0,0) by default.
+            add(extentOnMap, 0, 0, NUM_CHILD_PER_LINE, 1);  // Will be moved to a different
location by buildContent(…).
             setHalignment(extentOnMap, HPos.CENTER);
             finished();
         }
 
         /**
-         * If the world map contains a map from some previous metadata,
-         * discard the old canvas and create a new one.
+         * If the world map contains a map from some previous metadata, discards the old
canvas and create a new one.
+         * We do that because as of JavaFX 13, we found no way to clear the content of an
existing {@link Canvas}.
          */
+        @Workaround(library = "JavaFX", version = "13")
         private void clearWorldMap() {
             if (!isWorldMapEmpty()) {
-                assert getChildren().get(1) == extentOnMap;
-                getChildren().set(1, extentOnMap = new Canvas());
-                setColumnSpan(extentOnMap, NUM_CHILD_PER_ROW);
+                final int p = linesStartIndex() - 1;
+                assert getChildren().get(p) == extentOnMap;
+                getChildren().set(p, extentOnMap = new Canvas());
+                setColumnSpan(extentOnMap, NUM_CHILD_PER_LINE);
                 setHalignment(extentOnMap, HPos.CENTER);
             }
         }
@@ -318,18 +379,10 @@ final class MetadataOverview {
         }
 
         /**
-         * Returns the format to use for writing numbers.
-         */
-        @Override
-        NumberFormat getNumberFormat() {
-            return MetadataOverview.this.getNumberFormat();
-        }
-
-        /**
          * Sets the identification information from the given metadata.
          */
         @Override
-        void setInformation(final Metadata metadata) {
+        void setInformation(final Metadata metadata, final GridGeometry grid) {
             setInformation(nonNull(metadata == null ? null : metadata.getIdentificationInfo()),
Identification[]::new);
         }
 
@@ -344,72 +397,67 @@ final class MetadataOverview {
             String text = null;
             final Citation citation = info.getCitation();
             if (citation != null) {
-                text = string(citation.getTitle());
+                text = owner.string(citation.getTitle());
                 if (text == null) {
                     text = Citations.getIdentifier(citation);
                 }
             }
-            addRow("Title:", text);
+            if (text == null) {
+                text = Vocabulary.getResources(owner.localized.getLocale()).getString(Vocabulary.Keys.Untitled);
+            }
+            title.setText(text);
             /*
              * The abstract, or if there is no abstract the credit as a fallback because
it can provide
              * some hints about the product. The topic category (climatology, health, etc.)
follows.
              */
-            String label = "Abstract:";
-            text = string(info.getAbstract());
+            short label = Resources.Keys.Abstract;
+            text = owner.string(info.getAbstract());
             if (text == null) {
                 for (final InternationalString c : nonNull(info.getCredits())) {
-                    text = string(c);
+                    text = owner.string(c);
                     if (text != null) {
-                        label = "Credit:";
+                        label = Resources.Keys.Credit;
                         break;
                     }
                 }
             }
-            addRow(label, text);
-            addRow("Topic Category:", string(nonNull(info.getTopicCategories())));
-            /*
-             * Whether the resource is about data or about a service. If it is about data,
-             * we will not write anything since this will be considered the default.
-             */
-            text = null;
-            if (!(info instanceof DataIdentification)) {
-                if (info instanceof ServiceIdentification) {
-                    text = "Service";
-                } else {
-                    text = "The resource does not specify if it contains data.";
-                }
-            }
-            addRow("Type of resource:", text);
-            addRow("Representation:", string(nonNull(info.getSpatialRepresentationTypes())));
+            addLine(label, text);
+            addLine(Resources.Keys.TopicCategory, owner.string(nonNull(info.getTopicCategories())));
             /*
              * Select a single, arbitrary date. We take the release or publication date if
available.
              * If no publication date is found, fallback on the creation date. If no creation
date is
              * found neither, fallback on the first date regardless its type.
              */
             if (citation != null) {
-                label = null;
-                Date     date = null;
+                Date date = null;
+                label = Resources.Keys.Date;
                 for (final CitationDate c : nonNull(citation.getDates())) {
                     final Date cd = c.getDate();
                     if (cd != null) {
                         final DateType type = c.getDateType();
                         if (DateType.PUBLICATION.equals(type) || DateType.RELEASED.equals(type))
{
-                            label = "Publication date:";
+                            label = Resources.Keys.PublicationDate;
                             date  = cd;
                             break;                      // Take the first publication or
release date.
                         }
                         final boolean isCreation = DateType.CREATION.equals(type);
                         if (date == null || isCreation) {
-                            label = isCreation ? "Creation date:" : "Date:";
+                            label = isCreation ? Resources.Keys.CreationDate : Resources.Keys.Date;
                             date  = cd;     // Fallback date: creation date, or the first
date otherwise.
                         }
                     }
                 }
                 if (date != null) {
-                    addRow(label, getDateFormat().format(date));
+                    addLine(label, owner.getDateFormat().format(date));
                 }
             }
             /*
+             * Type of resource: vector, grid, table, tin, video, etc. It gives a slight
overview
+             * of the next section, "Spatial representation". For that reason we put it close
to
+             * that next section, i.e. last in this section but just before the map.
+             */
+            addLine(Resources.Keys.TypeOfResource, owner.string(nonNull(info.getSpatialRepresentationTypes())));
+            /*
              * Write the first description about the spatio-temporal extent,
              * then draw all geographic extents on a map.
              */
@@ -418,7 +466,7 @@ final class MetadataOverview {
             for (final Extent extent : nonNull(info.getExtents())) {
                 if (extent != null) {
                     if (text == null) {
-                        text = string(extent.getDescription());
+                        text = owner.string(extent.getDescription());
                     }
                     for (final GeographicExtent ge : nonNull(extent.getGeographicElements()))
{
                         if (identifier == null && ge instanceof GeographicDescription)
{
@@ -433,7 +481,7 @@ final class MetadataOverview {
             if (text == null) {
                 text = IdentifiedObjects.toString(identifier);
             }
-            addRow("Extent:", text);
+            addLine(Resources.Keys.Extent, text);
             setRowIndex(extentOnMap, nextRowIndex());
         }
 
@@ -451,8 +499,10 @@ final class MetadataOverview {
             if (!(north >= south) || !Double.isFinite(east) || !Double.isFinite(west))
{
                 return;
             }
+            // Normalize `west` in the [-180 … +180)° range and apply same normalization
on `east`.
+            east -= (west - (west = Longitude.normalize(west)));
             if (isWorldMapEmpty()) {
-                final Image image = getWorldMap();
+                final Image image = owner.getWorldMap();
                 if (image == null) {
                     return;                         // Failed to load the image.
                 }
@@ -509,7 +559,7 @@ final class MetadataOverview {
      * The pane where to show the values of {@link SpatialRepresentation} objects.
      * The same pane can be used for an arbitrary amount of spatial representations.
      */
-    private final class RepresentationInfo extends Form<SpatialRepresentation> {
+    private static final class RepresentationInfo extends Form<SpatialRepresentation>
{
         /**
          * The reference system, or {@code null} if none.
          */
@@ -518,23 +568,16 @@ final class MetadataOverview {
         /**
          * Creates an initially empty view for spatial representation information.
          */
-        RepresentationInfo() {
+        RepresentationInfo(final MetadataOverview owner) {
+            super(owner);
             finished();
         }
 
         /**
-         * Returns the format to use for writing numbers.
-         */
-        @Override
-        NumberFormat getNumberFormat() {
-            return MetadataOverview.this.getNumberFormat();
-        }
-
-        /**
          * Sets the spatial representation information from the given metadata.
          */
         @Override
-        void setInformation(final Metadata metadata) {
+        void setInformation(final Metadata metadata, final GridGeometry grid) {
             referenceSystem = null;
             setInformation(nonNull(metadata == null ? null : metadata.getSpatialRepresentationInfo()),
SpatialRepresentation[]::new);
             if (metadata != null) {
@@ -553,21 +596,21 @@ final class MetadataOverview {
          */
         @Override
         void buildContent(final SpatialRepresentation info) {
-            addRow("Reference system:", IdentifiedObjects.getName(referenceSystem, null));
+            addLine(Resources.Keys.ReferenceSystem, IdentifiedObjects.getName(referenceSystem,
null));
             if (info instanceof GridSpatialRepresentation) {
                 final GridSpatialRepresentation sr = (GridSpatialRepresentation) info;
                 final StringBuffer buffer = new StringBuffer();
                 for (final Dimension dim : nonNull(sr.getAxisDimensionProperties())) {
-                    getNumberFormat().format(dim.getDimensionSize(), buffer, new FieldPosition(0));
-                    buffer.append(' ').append(string(Types.getCodeTitle(dim.getDimensionName()))).append("
× ");
+                    owner.getNumberFormat().format(dim.getDimensionSize(), buffer, new FieldPosition(0));
+                    buffer.append(' ').append(owner.string(Types.getCodeTitle(dim.getDimensionName()))).append("
× ");
                 }
                 if (buffer.length() != 0) {
                     buffer.setLength(buffer.length() - 3);
-                    addRow("Dimensions:", buffer.toString());
+                    addLine(Resources.Keys.Dimensions, buffer.toString());
                 }
                 final CellGeometry cg = sr.getCellGeometry();
                 if (cg != null) {
-                    addRow("Cell geometry:", string(Types.getCodeTitle(cg)));
+                    addLine(Resources.Keys.CellGeometry, owner.string(Types.getCodeTitle(cg)));
                 }
             }
         }
@@ -610,7 +653,7 @@ final class MetadataOverview {
      */
     private String string(final InternationalString i18n) {
         if (i18n != null) {
-            String t = i18n.toString(textLocale);
+            String t = i18n.toString(localized.getLocale());
             if (t != null && !(t = t.trim()).isEmpty()) {
                 return t;
             }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
index 1e65ef1..b75dc1b 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
@@ -69,6 +69,11 @@ import org.apache.sis.internal.gui.Styles;
  * <p>{@code ResourceTree} registers the necessarily handlers for making this view
a target
  * of "drag and drop" events. Users can drop files or URLs for opening data files.</p>
  *
+ * <h2>Limitations</h2>
+ * If the user selects "close" in the contextual menu, the resource if closed (if it is an
instance
+ * of {@link DataStore}). There is not yet a mechanism for keeping it open if the resource
is shared
+ * by another {@link ResourceTree} instance.
+ *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
index cd9e617..fe471d6 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
@@ -56,6 +56,11 @@ public final class Resources extends IndexedResourceBundle {
         }
 
         /**
+         * Abstract:
+         */
+        public static final short Abstract = 14;
+
+        /**
          * All files
          */
         public static final short AllFiles = 3;
@@ -76,11 +81,36 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CanNotReadFile_1 = 5;
 
         /**
+         * Cell geometry:
+         */
+        public static final short CellGeometry = 15;
+
+        /**
          * Close
          */
         public static final short Close = 8;
 
         /**
+         * Creation date:
+         */
+        public static final short CreationDate = 16;
+
+        /**
+         * Credit:
+         */
+        public static final short Credit = 17;
+
+        /**
+         * Date:
+         */
+        public static final short Date = 18;
+
+        /**
+         * Dimensions:
+         */
+        public static final short Dimensions = 19;
+
+        /**
          * Error closing file
          */
         public static final short ErrorClosingFile = 13;
@@ -96,6 +126,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short Exit = 9;
 
         /**
+         * Extent:
+         */
+        public static final short Extent = 20;
+
+        /**
          * File
          */
         public static final short File = 10;
@@ -111,7 +146,7 @@ public final class Resources extends IndexedResourceBundle {
         public static final short Loading = 7;
 
         /**
-         * Open
+         * Open…
          */
         public static final short Open = 11;
 
@@ -119,6 +154,36 @@ public final class Resources extends IndexedResourceBundle {
          * Open data file
          */
         public static final short OpenDataFile = 2;
+
+        /**
+         * Publication date:
+         */
+        public static final short PublicationDate = 21;
+
+        /**
+         * Reference system:
+         */
+        public static final short ReferenceSystem = 22;
+
+        /**
+         * Resource identification
+         */
+        public static final short ResourceIdentification = 23;
+
+        /**
+         * Spatial representation
+         */
+        public static final short SpatialRepresentation = 24;
+
+        /**
+         * Topic category:
+         */
+        public static final short TopicCategory = 25;
+
+        /**
+         * Type of resource:
+         */
+        public static final short TypeOfResource = 26;
     }
 
     /**
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
index 8181ae1..b03df50 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
@@ -20,16 +20,29 @@
 # For resources shared by all modules in the Apache SIS project, see "org.apache.sis.util.resources"
package.
 #
 
-AllFiles          = All files
-CanNotReadFile_1  = Can not open \u201c{0}\u201d.
-CanNotClose_1     = Can not close \u201c{0}\u201d. Data may be lost.
-Close             = Close
-CRSs              = Coordinate Reference Systems
-ErrorClosingFile  = Error closing file
-ErrorOpeningFile  = Error opening file
-Exit              = Exit
-File              = File
-GeospatialFiles   = Geospatial data files
-Loading           = Loading\u2026
-Open              = Open
-OpenDataFile      = Open data file
+Abstract               = Abstract:
+AllFiles               = All files
+CanNotReadFile_1       = Can not open \u201c{0}\u201d.
+CanNotClose_1          = Can not close \u201c{0}\u201d. Data may be lost.
+CellGeometry           = Cell geometry:
+Close                  = Close
+CreationDate           = Creation date:
+Credit                 = Credit:
+CRSs                   = Coordinate Reference Systems
+Date                   = Date:
+Dimensions             = Dimensions:
+ErrorClosingFile       = Error closing file
+ErrorOpeningFile       = Error opening file
+Exit                   = Exit
+Extent                 = Extent:
+File                   = File
+GeospatialFiles        = Geospatial data files
+Loading                = Loading\u2026
+Open                   = Open\u2026
+OpenDataFile           = Open data file
+PublicationDate        = Publication date:
+ReferenceSystem        = Reference system:
+ResourceIdentification = Resource identification
+SpatialRepresentation  = Spatial representation
+TopicCategory          = Topic category:
+TypeOfResource         = Type of resource:
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
index 097bd4b..33f7f8c 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
@@ -25,16 +25,29 @@
 #   U+00A0 NO-BREAK SPACE         before  :
 #
 
-AllFiles          = Tous les fichiers
-CanNotReadFile_1  = Ne peut pas ouvrir \u00ab\u202f{0}\u202f\u00bb.
-CanNotClose_1     = Ne peut pas fermer \u00ab\u202f{0}\u202f\u00bb. Il pourrait y avoir une
perte de donn\u00e9es.
-Close             = Fermer
-CRSs              = Syst\u00e8mes de r\u00e9f\u00e9rence des coordonn\u00e9es
-ErrorClosingFile  = Erreur \u00e0 la fermeture du fichier
-ErrorOpeningFile  = Erreur \u00e0 l\u2019ouverture du fichier
-Exit              = Quitter
-File              = Fichier
-GeospatialFiles   = Fichiers de donn\u00e9es g\u00e9ospatiales
-Loading           = Chargement\u2026
-Open              = Ouvrir
-OpenDataFile      = Ouvrir un fichier de donn\u00e9es
+Abstract               = R\u00e9sum\u00e9\u00a0:
+AllFiles               = Tous les fichiers
+CanNotReadFile_1       = Ne peut pas ouvrir \u00ab\u202f{0}\u202f\u00bb.
+CanNotClose_1          = Ne peut pas fermer \u00ab\u202f{0}\u202f\u00bb. Il pourrait y avoir
une perte de donn\u00e9es.
+CellGeometry           = G\u00e9om\u00e9trie des cellules\u00a0:
+Close                  = Fermer
+CreationDate           = Date de cr\u00e9ation\u00a0:
+Credit                 = Cr\u00e9dit\u00a0:
+CRSs                   = Syst\u00e8mes de r\u00e9f\u00e9rence des coordonn\u00e9es
+Date                   = Date\u00a0:
+Dimensions             = Dimensions\u00a0:
+ErrorClosingFile       = Erreur \u00e0 la fermeture du fichier
+ErrorOpeningFile       = Erreur \u00e0 l\u2019ouverture du fichier
+Exit                   = Quitter
+Extent                 = \u00c9tendue\u00a0:
+File                   = Fichier
+GeospatialFiles        = Fichiers de donn\u00e9es g\u00e9ospatiales
+Loading                = Chargement\u2026
+Open                   = Ouvrir\u2026
+OpenDataFile           = Ouvrir un fichier de donn\u00e9es
+PublicationDate        = Date de publication\u00a0:
+ReferenceSystem        = Syst\u00e8me de r\u00e9f\u00e9rence\u00a0:
+ResourceIdentification = Identification de la ressource
+SpatialRepresentation  = Repr\u00e9sentation spatiale
+TopicCategory          = Topic category\u00a0:
+TypeOfResource         = Type de ressource\u00a0:
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
index 1427a08..fa3a6c7 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
@@ -487,18 +487,6 @@ public class IndexedResourceBundle extends ResourceBundle implements
Localized {
     }
 
     /**
-     * Gets a string for the given key and appends "…" to it.
-     * This method is typically used for creating menu items.
-     *
-     * @param  key  the key for the desired string.
-     * @return the string for the given key.
-     * @throws MissingResourceException if no object for the given key can be found.
-     */
-    public final String getMenuLabel(final short key) throws MissingResourceException {
-        return getString(key) + '…';
-    }
-
-    /**
      * Gets a string for the given key from this resource bundle or one of its parents.
      *
      * @param  key  the key for the desired string.


Mime
View raw message