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

commit 11f86940a76f3e82cb560329e5f198a55ee6b39a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Apr 22 01:06:58 2020 +0200

    Keep a relationship between the CRS of coordinates shown in the status bar 
and the CRS of the displayed raster.
---
 .../apache/sis/gui/coverage/CoverageControls.java  |   4 +-
 .../apache/sis/gui/coverage/CoverageExplorer.java  |   8 +-
 .../org/apache/sis/gui/coverage/GridControls.java  |   8 +-
 .../java/org/apache/sis/gui/coverage/GridView.java |  13 +-
 .../java/org/apache/sis/gui/map/StatusBar.java     |  45 +++--
 .../java/org/apache/sis/gui/metadata/Section.java  |   2 +-
 .../org/apache/sis/gui/referencing/CRSChooser.java |   9 +-
 .../org/apache/sis/gui/referencing/MenuSync.java   | 215 +++++++++++++++++++++
 .../gui/referencing/RecentReferenceSystems.java    | 199 +++++++++++++++----
 .../org/apache/sis/internal/gui/GUIUtilities.java  |  80 ++++++--
 .../org/apache/sis/internal/gui/Resources.java     |  32 +--
 .../apache/sis/internal/gui/Resources.properties   |  32 +--
 .../sis/internal/gui/Resources_fr.properties       |  32 +--
 .../apache/sis/gui/referencing/CRSChooserApp.java  |   2 +-
 .../java/org/apache/sis/image/ComputedImage.java   |   4 +-
 15 files changed, 561 insertions(+), 124 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
index 177b944..d62a0e7 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
@@ -79,7 +79,7 @@ final class CoverageControls extends Controls {
         final Color background = Color.BLACK;
         view = new CoverageCanvas();
         view.setBackground(background);
-        final StatusBar statusBar = new StatusBar();
+        final StatusBar statusBar = new StatusBar(referenceSystems);
         statusBar.setCanvas(view);
         imageAndStatus = new BorderPane(view.getView());
         imageAndStatus.setBottom(statusBar.getView());
@@ -93,7 +93,7 @@ final class CoverageControls extends Controls {
             final ChoiceBox<ReferenceSystem> systems = 
referenceSystems.createChoiceBox(this::onReferenceSystemSelected);
             systems.setMaxWidth(Double.POSITIVE_INFINITY);
             referenceSystem = systems.valueProperty();
-            final Label systemLabel = new 
Label(localized.getString(Resources.Keys.ReferenceSystem));
+            final Label systemLabel = new 
Label(localized.getLabel(Resources.Keys.ReferenceSystem));
             systemLabel.setPadding(CAPTION_MARGIN);
             systemLabel.setLabelFor(systems);
             final GridPane gp = createControlGrid(
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
index 5e6f2b3..23686e8 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.gui.coverage;
 
+import java.util.Locale;
 import javafx.scene.control.Control;
 import javafx.scene.control.SplitPane;
 import javafx.scene.control.Separator;
@@ -165,13 +166,14 @@ public class CoverageExplorer extends Widget {
          * The coverage property may be shown in various ways (tabular data, 
image).
          * Each visualization way is an entry in the `views` array.
          */
-        final Resources  localized  = Resources.forLocale(null);
-        final Vocabulary vocabulary = 
Vocabulary.getResources(localized.getLocale());
+        final Locale     locale     = null;
+        final Resources  localized  = Resources.forLocale(locale);
+        final Vocabulary vocabulary = Vocabulary.getResources(locale);
         views = new Controls[viewTypes.length];
         for (final View type : viewTypes) {
             final Controls c;
             switch (type) {
-                case TABLE: c = new GridControls(vocabulary); break;
+                case TABLE: c = new GridControls(referenceSystems, 
vocabulary); break;
                 case IMAGE: c = new CoverageControls(localized, vocabulary, 
coverageProperty, referenceSystems); break;
                 default: throw new AssertionError(type);
             }
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
index a1e8098..d729726 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
@@ -30,6 +30,7 @@ import javafx.scene.layout.Region;
 import javafx.scene.layout.VBox;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.gui.referencing.RecentReferenceSystems;
 import org.apache.sis.util.resources.Vocabulary;
 
 
@@ -60,10 +61,11 @@ final class GridControls extends Controls {
     /**
      * Creates a new set of grid controls.
      *
-     * @param  vocabulary  localized set of words, provided in argument 
because often known by the caller.
+     * @param  referenceSystems  the manager of reference systems chosen by 
the user, or {@code null} if none.
+     * @param  vocabulary        localized set of words, provided in argument 
because often known by the caller.
      */
-    GridControls(final Vocabulary vocabulary) {
-        view = new GridView();
+    GridControls(final RecentReferenceSystems referenceSystems, final 
Vocabulary vocabulary) {
+        view = new GridView(referenceSystems);
         sampleDimensions = new 
CategoryCellFactory(view.cellFormat).createSampleDimensionTable(vocabulary);
         
sampleDimensions.getSelectionModel().selectedIndexProperty().addListener(new 
BandSelectionListener(view.bandProperty));
         view.bandProperty.addListener(this::onBandSpecified);
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 b518a54..891f583 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
@@ -42,6 +42,7 @@ import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.internal.gui.BackgroundThreads;
 import org.apache.sis.internal.gui.Styles;
 import org.apache.sis.gui.map.StatusBar;
+import org.apache.sis.gui.referencing.RecentReferenceSystems;
 
 
 /**
@@ -208,6 +209,16 @@ public class GridView extends Control {
      * construction by a call to {@link #setImage(RenderedImage)}.
      */
     public GridView() {
+        this(null);
+    }
+
+    /**
+     * Creates an initially empty grid view. The content can be set after
+     * construction by a call to {@link #setImage(RenderedImage)}.
+     *
+     * @param  referenceSystems  the manager of reference systems chosen by 
the user, or {@code null} if none.
+     */
+    GridView(final RecentReferenceSystems referenceSystems) {
         imageProperty    = new SimpleObjectProperty<>(this, "image");
         bandProperty     = new SimpleIntegerProperty (this, "band");
         headerWidth      = new SimpleDoubleProperty  (this, "headerWidth", 60);
@@ -217,7 +228,7 @@ public class GridView extends Control {
         headerBackground = new SimpleObjectProperty<>(this, 
"headerBackground", Color.GAINSBORO);
         headerFormat     = NumberFormat.getIntegerInstance();
         cellFormat       = new CellFormat(this);
-        statusBar        = new StatusBar();
+        statusBar        = new StatusBar(referenceSystems);
         tileWidth        = 1;
         tileHeight       = 1;       // For avoiding division by zero.
 
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index 166ee20..ae8f08b 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -27,8 +27,8 @@ import javafx.scene.layout.Region;
 import javafx.scene.layout.Priority;
 import javafx.scene.control.Label;
 import javafx.scene.control.Button;
-import javafx.scene.control.Tooltip;
 import javafx.scene.control.ProgressBar;
+import javafx.scene.control.ContextMenu;
 import javafx.scene.input.MouseEvent;
 import javafx.event.EventHandler;
 import javafx.event.EventType;
@@ -37,6 +37,7 @@ import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
 import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.operation.Matrix;
@@ -44,7 +45,6 @@ import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
-import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.geometry.GeneralDirectPosition;
 import org.apache.sis.geometry.CoordinateFormat;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -57,6 +57,7 @@ import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.gui.Widget;
+import org.apache.sis.gui.referencing.RecentReferenceSystems;
 import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.gui.Styles;
@@ -190,8 +191,10 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
 
     /**
      * Creates a new status bar.
+     *
+     * @param  referenceSystems  the manager of reference systems chosen by 
the user, or {@code null} if none.
      */
-    public StatusBar() {
+    public StatusBar(final RecentReferenceSystems referenceSystems) {
         localToCRS        = MathTransforms.identity(BIDIMENSIONAL);
         targetCoordinates = new GeneralDirectPosition(BIDIMENSIONAL);
         sourceCoordinates = targetCoordinates.coordinates;
@@ -208,6 +211,16 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         view.setPadding(PADDING);
         canvasProperty = new SimpleObjectProperty<>(this, "canvas");
         canvasProperty.addListener(this::onCanvasSpecified);
+        if (referenceSystems != null) {
+            final ContextMenu menu = new 
ContextMenu(referenceSystems.createMenuItems((e,o,n) -> setDisplayCRS(n)));
+            view.setOnMousePressed((MouseEvent event) -> {
+                if (event.isSecondaryButtonDown()) {
+                    menu.show((HBox) event.getSource(), event.getScreenX(), 
event.getScreenY());
+                } else {
+                    menu.hide();
+                }
+            });
+        }
     }
 
     /**
@@ -357,23 +370,25 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
             targetCoordinates = new GeneralDirectPosition(BIDIMENSIONAL);
             sourceCoordinates = targetCoordinates.coordinates;      // Okay to 
share array if same dimension.
         }
-        format.setDefaultCRS(crs);
+        setDisplayCRS(crs);
         format.setPrecision(resolution, unit);
-        final String text = IdentifiedObjects.getDisplayName(crs, 
format.getLocale(Locale.Category.DISPLAY));
-        Tooltip tp = null;
-        if (text != null) {
-            tp = coordinates.getTooltip();
-            if (tp == null) {
-                tp = new Tooltip(text);
-            } else {
-                tp.setText(text);
-            }
-        }
-        coordinates.setTooltip(tp);
         lastX = lastY = Double.NaN;
     }
 
     /**
+     * Sets the coordinate reference systems to use for representing 
coordinates in status bar.
+     *
+     * @param  crs  the coordinate reference system to use for coordinates in 
the status bar.
+     */
+    private void setDisplayCRS(final ReferenceSystem crs) {
+        if (crs instanceof CoordinateReferenceSystem) {
+            format.setDefaultCRS((CoordinateReferenceSystem) crs);
+        } else {
+            format.setDefaultCRS(null);
+        }
+    }
+
+    /**
      * Returns the conversion from local coordinates to geographic or 
projected coordinates.
      * The local coordinates are the coordinates of the view, as given for 
example in {@link MouseEvent}.
      * This is initially an identity transform and can be computed by {@link 
#applyCanvasGeometry(GridGeometry)}.
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/Section.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/Section.java
index 10f701b..4df2f3b 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/Section.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/Section.java
@@ -244,7 +244,7 @@ abstract class Section<T> extends GridPane implements 
EventHandler<ActionEvent>
         if (value == null) {
             return;
         }
-        final String labelText = owner.localized.getString(label);
+        final String labelText = owner.localized.getLabel(label);
         final Label labelCtrl, valueCtrl;
         final ObservableList<Node> children = getChildren();
         if (linesEndIndex < children.size()) {
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
index 603722b..b7f3fc1 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
@@ -138,7 +138,7 @@ public class CRSChooser extends 
Dialog<CoordinateReferenceSystem> {
      * Creates a chooser proposing all coordinate reference systems from the 
default factory.
      */
     public CRSChooser() {
-        this(null, null);
+        this(null, null, null);
     }
 
     /**
@@ -149,10 +149,11 @@ public class CRSChooser extends 
Dialog<CoordinateReferenceSystem> {
      *
      * @param  factory         the factory to use for creating coordinate 
reference systems, or {@code null} for default.
      * @param  areaOfInterest  geographic area for which to choose a CRS, or 
{@code null} if no restriction.
+     * @param  locale          the preferred locale for displaying object 
name, or {@code null} for the default locale.
      */
-    public CRSChooser(final CRSAuthorityFactory factory, final Envelope 
areaOfInterest) {
+    public CRSChooser(final CRSAuthorityFactory factory, final Envelope 
areaOfInterest, Locale locale) {
         this.areaOfInterest = Utils.toGeographic(CRSChooser.class, "<init>", 
areaOfInterest);
-        final Locale         locale     = Locale.getDefault();
+        if (locale == null)  locale     = Locale.getDefault();
         final Resources      i18n       = Resources.forLocale(locale);
         final Vocabulary     vocabulary = Vocabulary.getResources(locale);
         final AuthorityCodes codeList   = new AuthorityCodes(factory, locale);
@@ -276,7 +277,7 @@ public class CRSChooser extends 
Dialog<CoordinateReferenceSystem> {
         final ObservableList<Node> children = tools.getChildren();
         final Label label = (Label) children.get(0);
         final Resources i18n = Resources.forLocale(locale);
-        label.setText(i18n.getString(labelText));
+        label.setText(i18n.getLabel(labelText));
         label.setLabelFor(control);
         children.set(1, control);
         content.setCenter(main);
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
new file mode 100644
index 0000000..a51c018
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
@@ -0,0 +1,215 @@
+/*
+ * 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.referencing;
+
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.scene.control.Menu;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.RadioMenuItem;
+import javafx.scene.control.ToggleGroup;
+import org.opengis.referencing.ReferenceSystem;
+import org.apache.sis.internal.gui.GUIUtilities;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.util.resources.Vocabulary;
+
+
+/**
+ * Keep a list of menu items up to date with an {@code 
ObservableList<ReferenceSystem>}.
+ * The selected {@link MenuItem} is given by {@link 
ToggleGroup#selectedToggleProperty()}
+ * but for the purpose of {@link RecentReferenceSystems} we rather need a 
property giving
+ * the selected reference system directly.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements 
EventHandler<ActionEvent> {
+    /**
+     * Keys where to store the reference system in {@link MenuItem}.
+     */
+    private static final String REFERENCE_SYSTEM_KEY = "ReferenceSystem";
+
+    /**
+     * Sentinel value associated to {@link #REFERENCE_SYSTEM_KEY} for 
requesting the {@link CRSChooser}.
+     */
+    private static final String CHOOSER = "CHOOSER";
+
+    /**
+     * The manager of reference systems to synchronize with.
+     */
+    private final RecentReferenceSystems owner;
+
+    /**
+     * The list of menu items to keep up-to-date with an {@code 
ObservableList<ReferenceSystem>}.
+     */
+    private final ObservableList<MenuItem> menus;
+
+    /**
+     * The group of menus.
+     */
+    private final ToggleGroup group;
+
+    /**
+     * The action to execute when a reference system is selected.
+     */
+    private final ChangeListener<ReferenceSystem> action;
+
+    /**
+     * Creates a new synchronization for the given list of menu items.
+     *
+     * @param  owner    the manager of reference systems to synchronize with.
+     * @param  systems  the reference systems for which to build menu items.
+     * @param  bean     the menu to keep synchronized with the list of 
reference systems.
+     * @param  action   the user-specified action to execute when a reference 
system is selected.
+     */
+    @SuppressWarnings("ThisEscapedInObjectConstruction")      // `this` is 
last and this class is final.
+    MenuSync(final RecentReferenceSystems owner, final List<ReferenceSystem> 
systems,
+             final Menu bean, final ChangeListener<ReferenceSystem> action)
+    {
+        super(bean, "value");
+        this.owner  = owner;
+        this.menus  = bean.getItems();
+        this.group  = new ToggleGroup();
+        this.action = action;
+        final MenuItem[] items = new MenuItem[systems.size()];
+        for (int i=0; i<items.length; i++) {
+            items[i] = createItem(systems.get(i));
+        }
+        menus.setAll(items);
+    }
+
+    /**
+     * Creates a new menu item for the given reference system.
+     */
+    private MenuItem createItem(final ReferenceSystem system) {
+        if (system != RecentReferenceSystems.OTHER) {
+            final RadioMenuItem item = new 
RadioMenuItem(IdentifiedObjects.getDisplayName(system, owner.locale));
+            item.getProperties().put(REFERENCE_SYSTEM_KEY, system);
+            item.setToggleGroup(group);
+            item.setOnAction(this);
+            return item;
+        } else {
+            final MenuItem item = new 
MenuItem(Vocabulary.getResources(owner.locale).getString(Vocabulary.Keys.Others)
 + '…');
+            item.getProperties().put(REFERENCE_SYSTEM_KEY, CHOOSER);
+            item.setOnAction(this);
+            return item;
+        }
+    }
+
+    /**
+     * Must be invoked after removing a menu item for avoiding memory leak.
+     */
+    private static void dispose(final MenuItem item) {
+        item.setOnAction(null);
+        if (item instanceof RadioMenuItem) {
+            ((RadioMenuItem) item).setToggleGroup(null);
+        }
+    }
+
+    /**
+     * Invoked when the list of reference systems changed. While it would be 
possible to trace the permutations,
+     * additions, removals and replacements done on the list, it is easier to 
recreate the menu items list from
+     * scratch (with recycling of existing items) and inspect the differences.
+     */
+    final void notifyChanges(final ObservableList<? extends ReferenceSystem> 
systems) {
+        final Map<Object,MenuItem> mapping = new IdentityHashMap<>();
+        for (final Iterator<MenuItem> it = menus.iterator(); it.hasNext();) {
+            final MenuItem item = it.next();
+            if 
(mapping.putIfAbsent(item.getProperties().get(REFERENCE_SYSTEM_KEY), item) != 
null) {
+                it.remove();    // Remove duplicated item. Should never 
happen, but we are paranoiac.
+                dispose(item);
+            }
+        }
+        final MenuItem[] items = new MenuItem[systems.size()];
+        for (int i=0; i<items.length; i++) {
+            items[i] = mapping.remove(systems.get(i));
+        }
+        /*
+         * Previous loop copied all items that could be reused as-is. Now 
search for all items that are new.
+         * If there is some menu items available, recycle them.
+         */
+        final Iterator<MenuItem> recycle = mapping.values().iterator();
+        for (int i=0; i<items.length; i++) {
+            if (items[i] == null) {
+                final ReferenceSystem system = systems.get(i);
+                if (system != RecentReferenceSystems.OTHER && 
recycle.hasNext()) {
+                    final MenuItem item = recycle.next();
+                    recycle.remove();
+                    if (item instanceof RadioMenuItem) {
+                        item.setText(IdentifiedObjects.getDisplayName(system, 
owner.locale));
+                        item.getProperties().put(REFERENCE_SYSTEM_KEY, system);
+                        items[i] = item;
+                        continue;
+                    }
+                }
+                items[i] = createItem(system);
+            }
+        }
+        /*
+         * If there is any item left, we must remove them from the ToggleGroup 
for avoiding memory leak.
+         */
+        while (recycle.hasNext()) {
+            dispose(recycle.next());
+        }
+        GUIUtilities.copyAsDiff(Arrays.asList(items), menus);
+    }
+
+    /**
+     * Invoked when user selects a menu item. This method gets the old and new 
values and sends them
+     * to {@link 
org.apache.sis.gui.referencing.RecentReferenceSystems.Listener} as a change 
event.
+     * That {@code Listener} will update the list of reference systems, which 
may result in a callback
+     * to {@link #notifyChanges(ObservableList)}. If the selected menu item is 
the "Other…" choice,
+     * then {@code Listener} will popup {@link CRSChooser} and callback {@link 
#set(ReferenceSystem)}
+     * for storing the result. Otherwise we need to invoke  {@link 
#set(ReferenceSystem)} ourselves.
+     */
+    @Override
+    public void handle(final ActionEvent event) {
+        // ClassCastException should not happen because this listener is 
registered only on MenuItem.
+        final Object value = ((MenuItem) 
event.getSource()).getProperties().get(REFERENCE_SYSTEM_KEY);
+        ReferenceSystem crs = (value == CHOOSER) ? 
RecentReferenceSystems.OTHER : (ReferenceSystem) value;
+        action.changed(this, get(), crs);
+        if (crs != RecentReferenceSystems.OTHER) {
+            set(crs);
+        }
+    }
+
+    /**
+     * Selects the specified reference system. This method is invoked by 
{@link RecentReferenceSystems}
+     * when the selected CRS changed, either programmatically or by user 
action.
+     */
+    @Override
+    public void set(final ReferenceSystem system) {
+        super.set(system);
+        for (final MenuItem item : menus) {
+            if (item instanceof RadioMenuItem && 
item.getProperties().get(REFERENCE_SYSTEM_KEY) == system) {
+                ((RadioMenuItem) item).setSelected(true);
+                return;
+            }
+        }
+        group.selectToggle(null);
+    }
+}
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
index 35d9869..fd4b0da 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
@@ -18,6 +18,7 @@ package org.apache.sis.gui.referencing;
 
 import java.util.List;
 import java.util.ArrayList;
+import java.util.Locale;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
@@ -27,6 +28,7 @@ import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.scene.control.ChoiceBox;
 import javafx.scene.control.MenuItem;
+import javafx.scene.control.Menu;
 import javafx.concurrent.Task;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.Envelope;
@@ -47,6 +49,7 @@ import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.GUIUtilities;
 import org.apache.sis.internal.gui.NonNullObjectProperty;
 import org.apache.sis.internal.gui.RecentChoices;
+import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Strings;
 
@@ -122,7 +125,12 @@ public class RecentReferenceSystems {
      * if we removed the selected item from the list. We use {@code 
controlValues} for saving currently selected
      * values before to modify the item list, and restore selections after we 
finished to modify the list.
      */
-    private final List<ObjectProperty<ReferenceSystem>> controlValues;
+    private final List<WritableValue<ReferenceSystem>> controlValues;
+
+    /**
+     * The preferred locale for displaying object name, or {@code null} for 
the default locale.
+     */
+    final Locale locale;
 
     /**
      * Wrapper for a {@link ReferenceSystem} which has not yet been compared 
with authoritative definitions.
@@ -149,8 +157,8 @@ public class RecentReferenceSystems {
      * The {@link #OTHER} reference system is <em>not</em> included in this 
list.
      *
      * <p>The list content is specified by calls to {@code setPreferred(…)} 
and {@code addAlternatives(…)} methods,
-     * then is filtered by {@link #filterSystems(ImmutableEnvelope, 
ComparisonMode)} for resolving authority codes
-     * and removing duplicated elements.</p>
+     * then is filtered by {@link #filterReferenceSystems(ImmutableEnvelope, 
ComparisonMode)} for resolving authority
+     * codes and removing duplicated elements.</p>
      *
      * <p>All accesses to this field and to {@link #isModified} field shall be 
done in a block synchronized
      * on {@code systemsOrCodes}.</p>
@@ -183,6 +191,20 @@ public class RecentReferenceSystems {
      * The factory will be capable to handle at least some EPSG codes.
      */
     public RecentReferenceSystems() {
+        this(null, null);
+    }
+
+    /**
+     * Creates a builder which will use the specified authority factory.
+     *
+     * @param  factory  the factory to use for building CRS from authority 
codes, or {@code null} for the default.
+     * @param  locale   the preferred locale for displaying object name, or 
{@code null} for the default locale.
+     *
+     * @see org.apache.sis.referencing.CRS#getAuthorityFactory(String)
+     */
+    public RecentReferenceSystems(final CRSAuthorityFactory factory, final 
Locale locale) {
+        this.factory         = factory;
+        this.locale          = locale;
         systemsOrCodes       = new ArrayList<>();
         areaOfInterest       = new SimpleObjectProperty<>(this, 
"areaOfInterest");
         duplicationCriterion = new NonNullObjectProperty<>(this, 
"duplicationCriterion", ComparisonMode.ALLOW_VARIANT);
@@ -195,19 +217,6 @@ public class RecentReferenceSystems {
     }
 
     /**
-     * Creates a builder which will use the specified authority factory.
-     *
-     * @param  factory  the factory to use for building CRS from authority 
codes.
-     *
-     * @see org.apache.sis.referencing.CRS#getAuthorityFactory(String)
-     */
-    public RecentReferenceSystems(final CRSAuthorityFactory factory) {
-        this();
-        ArgumentChecks.ensureNonNull("factory", factory);
-        this.factory = factory;
-    }
-
-    /**
      * Sets the native or preferred reference system. This is the system to 
always show as the first
      * choice and should typically be the native {@link 
CoordinateReferenceSystem} of visualized data.
      * If a previous preferred system existed, the previous system will be 
moved to alternative choices.
@@ -273,7 +282,7 @@ public class RecentReferenceSystems {
             }
             modified();
         }
-        // Check for duplication will be done in `filterSystems()` method.
+        // Check for duplication will be done in `filterReferenceSystems()` 
method.
     }
 
     /**
@@ -302,7 +311,7 @@ public class RecentReferenceSystems {
             }
             modified();
         }
-        // Parsing will be done in `filterSystems()` method.
+        // Parsing will be done in `filterReferenceSystems()` method.
     }
 
     /**
@@ -326,7 +335,7 @@ public class RecentReferenceSystems {
      * @param  mode    the {@link #duplicationCriterion} value read from 
JavaFX thread.
      * @return the filtered reference systems, or {@code null} if already 
filtered.
      */
-    private List<ReferenceSystem> filterSystems(final ImmutableEnvelope 
domain, final ComparisonMode mode) {
+    private List<ReferenceSystem> filterReferenceSystems(final 
ImmutableEnvelope domain, final ComparisonMode mode) {
         final List<ReferenceSystem> systems;
         synchronized (systemsOrCodes) {
             CRSAuthorityFactory factory = this.factory;         // Hide 
volatile field by local field.
@@ -454,15 +463,33 @@ public class RecentReferenceSystems {
             referenceSystems = FXCollections.observableArrayList();
         }
         synchronized (systemsOrCodes) {
-            systemsOrCodes.addAll(Math.min(systemsOrCodes.size(), 
NUM_CORE_ITEMS), referenceSystems);
-            // Duplicated values will be filtered by the background task below.
+            /*
+             * Prepare a temporary list as the concatenation of all items that 
are currently visible in JavaFX
+             * controls with all items that were specified by 
`setPreferred(…)` or `addAlternatives(…)` methods.
+             * This concatenation creates a lot of duplicated values, but 
those duplications will be filtered by
+             * `filterReferenceSystems(…)` method. The intent is to preserve 
following order:
+             *
+             *   - NUM_CORE_ITEMS preferred reference systems first.
+             *   - All reference systems that are currently selected by JavaFX 
controls.
+             *   - All reference systems offered as choice in JavaFX controls.
+             *   - All reference systems specified by `addAlternatives(…)`.
+             *   - NUM_OTHER_ITEMS systems (will be handled in a special way 
by `filterReferenceSystems(…)`).
+             *
+             * The list will be truncated to NUM_SHOWN_ITEMS after 
duplications are removed and before OTHER
+             * is added. The first occurrence of duplicated values is kept, 
which will result in above-cited
+             * order as the priority order where to insert the CRS.
+             */
             isModified = true;
+            final int insertAt = Math.min(systemsOrCodes.size(), 
NUM_CORE_ITEMS);
+            final List<ReferenceSystem> selected = getSelectedItems();
+            systemsOrCodes.addAll(insertAt, selected);
+            systemsOrCodes.addAll(insertAt + selected.size(), 
referenceSystems);
             final ImmutableEnvelope domain = geographicAOI;
             final ComparisonMode mode = duplicationCriterion.get();
             BackgroundThreads.execute(new Task<List<ReferenceSystem>>() {
                 /** Filters the {@link ReferenceSystem}s in a background 
thread. */
                 @Override protected List<ReferenceSystem> call() {
-                    return filterSystems(domain, mode);
+                    return filterReferenceSystems(domain, mode);
                 }
 
                 /** Should never happen. */
@@ -495,10 +522,12 @@ public class RecentReferenceSystems {
              * in the `referenceSystems` list is temporarily removed (before 
to be inserted elsewhere).
              * Save the values before to modify the list.
              */
-            final ReferenceSystem[] values = 
controlValues.stream().map(ObjectProperty::get).toArray(ReferenceSystem[]::new);
+            final ReferenceSystem[] values = 
controlValues.stream().map(WritableValue::getValue).toArray(ReferenceSystem[]::new);
             try {
                 isAdjusting = true;
-                GUIUtilities.copyAsDiff(systems, referenceSystems);
+                if (GUIUtilities.copyAsDiff(systems, referenceSystems)) {
+                    notifyChanges();
+                }
             } finally {
                 isAdjusting = false;
             }
@@ -519,7 +548,7 @@ public class RecentReferenceSystems {
                         break;
                     }
                 }
-                controlValues.get(j).set(system);
+                controlValues.get(j).setValue(system);
             }
         }
     }
@@ -547,11 +576,18 @@ public class RecentReferenceSystems {
                 return;
             }
             if (newValue == OTHER) {
-                final CRSChooser chooser = new CRSChooser(factory, 
geographicAOI);
+                final CRSChooser chooser = new CRSChooser(factory, 
geographicAOI, locale);
                 newValue = 
chooser.showDialog(GUIUtilities.getWindow(property)).orElse(null);
                 if (newValue == null) {
                     newValue = oldValue;
                 } else {
+                    /*
+                     * If user selected a CRS in the CRSChooser list, verify 
if her/his selection is a CRS
+                     * already presents in the `referenceSystems` list. We 
ignore axis order (by default)
+                     * because the previous CRS in the list may be a CRS given 
by `setPreferred(…)` method,
+                     * which typically come from a DataStore. That previous 
CRS will be replaced by the CRS
+                     * given by CRSChooser, which is more conform to 
authoritative definition.
+                     */
                     final ObservableList<ReferenceSystem> items = 
referenceSystems;
                     final ComparisonMode mode = duplicationCriterion.get();
                     final int count = items.size() - NUM_OTHER_ITEMS;
@@ -565,16 +601,23 @@ public class RecentReferenceSystems {
                             break;
                         }
                     }
+                    /*
+                     * If the selected CRS was not present in the list, we may 
need to remove the last item
+                     * for making room for the new one. New item must be added 
before `property.setValue(…)`
+                     * is invoked, otherwise ChoiceBox may add a new item by 
itself.
+                     */
                     if (!found) {
                         if (count >= NUM_SHOWN_ITEMS) {
                             items.remove(count - 1);        // Remove the last 
item before `OTHER`.
                         }
-                        items.add(Math.min(count, NUM_CORE_ITEMS), newValue);
+                        items.add(Math.min(items.size(), NUM_CORE_ITEMS), 
newValue);
+                        notifyChanges();
                     }
                 }
                 /*
                  * Following cast is safe because this listener is registered 
only on ObjectProperty
                  * instances, and the ObjectProperty class implements 
WritableValue.
+                 * The effect of this method call is to set the selected value.
                  */
                 ((WritableValue<ReferenceSystem>) property).setValue(newValue);
             }
@@ -586,9 +629,9 @@ public class RecentReferenceSystems {
                 action.changed(property, oldValue, newValue);
                 
RecentChoices.useReferenceSystem(IdentifiedObjects.toString(IdentifiedObjects.getIdentifier(newValue,
 null)));
                 /*
-                 * Move the selected reference system as the first choice 
after the core systems.
-                 * We need to remove the old value before to add the new one, 
otherwise it seems
-                 * to confuse the list.
+                 * If the selected CRS is already at the beginning of the 
list, do nothing. The beginning is
+                 * either one of the core items (specified by 
`setPreferred(…)`) or the first item after the
+                 * core items.
                  */
                 final ObservableList<ReferenceSystem> items = referenceSystems;
                 final int count = items.size() - NUM_OTHER_ITEMS;
@@ -597,18 +640,95 @@ public class RecentReferenceSystems {
                         return;
                     }
                 }
+                /*
+                 * Move the selected reference system as the first choice 
after the core systems.
+                 * We need to remove the old value before to add the new one, 
otherwise it seems
+                 * to confuse the list.
+                 */
                 for (int i=count; --i >= NUM_CORE_ITEMS;) {
                     if (items.get(i) == newValue) {
                         items.remove(i);
                         break;
                     }
                 }
-                items.add(Math.max(0, Math.min(count, NUM_CORE_ITEMS)), 
newValue);
+                items.add(Math.min(items.size(), NUM_CORE_ITEMS), newValue);
+                notifyChanges();
+            }
+        }
+    }
+
+    /**
+     * Notifies all {@link MenuSync} that the list of reference systems 
changed. We send a notification manually
+     * instead than relying on {@code ListChangeListener} in order to process 
only one event after we have done
+     * a bunch of changes instead than an event after each individual add or 
remove operation.
+     */
+    private void notifyChanges() {
+        for (final WritableValue<ReferenceSystem> value : controlValues) {
+            if (value instanceof MenuSync) {
+                ((MenuSync) value).notifyChanges(referenceSystems);
             }
         }
     }
 
     /**
+     * Returns all currently selected reference systems in the order they 
appear in JavaFX controls.
+     * This method collects selected values of all controls created by a 
{@code createXXX(…)} method.
+     * The returned list does not contain duplicated values.
+     *
+     * @return currently selected values of all controls, without duplicated 
values and in the order
+     *         they appear in choice lists.
+     */
+    public List<ReferenceSystem> getSelectedItems() {
+        /*
+         * Build an array of selected reference systems. This array may 
contain duplicated elements if two
+         * or more JavaFX controls have the same selected value. Those 
duplications will be resolved later.
+         * Conceptually we will use this array as a java.util.Set, except that 
its length is so small
+         * (usually no more than 3 elements) that it is not worth to use 
HashSet.
+         */
+        int count = 0;
+        final ReferenceSystem[] selected = new 
ReferenceSystem[controlValues.size()];
+        for (final WritableValue<ReferenceSystem> value : controlValues) {
+            final ReferenceSystem system = value.getValue();
+            if (system != null) selected[count++] = system;
+        }
+        /*
+         * Now filter the `referenceSystems` list, retaining only elements 
that are present in `selected`.
+         * We do that way for having selected elements in the same order as 
they appear in JavaFX controls.
+         */
+        final List<ReferenceSystem> ordered = new ArrayList<>(count);
+        if (count != 0) {
+            // (count > 0) implies (referenceSystems != null).
+            for (final ReferenceSystem system : referenceSystems) {
+                if (system != OTHER) {
+                    for (int i=0; i<count; i++) {
+                        if (selected[i] == system) {
+                            ordered.add(system);
+                            if (--count == 0) return ordered;
+                            System.arraycopy(selected, i+1, selected, i, count 
- i);
+                            break;
+                        }
+                    }
+                }
+            }
+            /*
+             * If some selected elements were not found in the 
`referenceSystems` list, add them last.
+             * It should not happen, unless those remaining elements are 
duplicated values (i.e. two
+             * or more controls having the same selection).
+             */
+next:       for (int i=0; i<count; i++) {
+                final ReferenceSystem system = selected[i];
+                for (int j=ordered.size(); --j >= 0;) {
+                    if (ordered.get(j) == system) {
+                        continue next;                  // Skip duplicated 
value.
+                    }
+                }
+                ordered.add(system);
+            }
+        }
+        return ordered;
+    }
+
+    /**
      * Creates a box offering choices among the reference systems specified to 
this {@code ShortChoiceList}.
      * The returned control may be initially empty, in which case its content 
will be automatically set at
      * a later time (after a background thread finished to process the {@link 
CoordinateReferenceSystem}s).
@@ -620,14 +740,25 @@ public class RecentReferenceSystems {
     public ChoiceBox<ReferenceSystem> createChoiceBox(final 
ChangeListener<ReferenceSystem> action) {
         ArgumentChecks.ensureNonNull("action", action);
         final ChoiceBox<ReferenceSystem> choices = new 
ChoiceBox<>(updateItems());
-        choices.setConverter(new ObjectStringConverter<>(choices.getItems(), 
null));
+        choices.setConverter(new ObjectStringConverter<>(choices.getItems(), 
locale));
         choices.valueProperty().addListener(new Listener(action));
         controlValues.add(choices.valueProperty());
         return choices;
     }
 
-    public MenuItem[] createMenuItems() {
-        return null;
+    /**
+     * Creates menu items offering choices among the reference systems 
specified to this {@code ShortChoiceList}.
+     * The items will be inserted in the {@linkplain Menu#getItems() menu 
list}. The content of that list will
+     * change at any time after this method returned: items will be added or 
removed as a result of user actions.
+     *
+     * @param  action  the action to execute when a reference system is 
selected.
+     * @return the menu containing items for reference systems.
+     */
+    public Menu createMenuItems(final ChangeListener<ReferenceSystem> action) {
+        ArgumentChecks.ensureNonNull("action", action);
+        final Menu menu = new 
Menu(Resources.forLocale(locale).getString(Resources.Keys.ReferenceSystem));
+        controlValues.add(new MenuSync(this, updateItems(), menu, new 
Listener(action)));
+        return menu;
     }
 
     /**
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
index 952c784..7c13412 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java
@@ -24,6 +24,8 @@ import javafx.beans.value.ObservableValue;
 import javafx.collections.ObservableList;
 import javafx.scene.Node;
 import javafx.scene.Scene;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.MenuItem;
 import javafx.stage.Window;
 import org.apache.sis.util.Static;
 
@@ -46,7 +48,7 @@ public final class GUIUtilities extends Static {
     /**
      * Returns the window of the bean associated to the given property.
      *
-     * @param  property  the property for which to get the window where it 
appear.
+     * @param  property  the property for which to get the window of the 
control, or {@code null}.
      * @return the window, or {@code null} if unknown.
      */
     public static Window getWindow(final ObservableValue<?> property) {
@@ -57,6 +59,15 @@ public final class GUIUtilities extends Static {
                 if (scene != null) {
                     return scene.getWindow();
                 }
+            } else if (bean instanceof MenuItem) {
+                ContextMenu parent = ((MenuItem) bean).getParentPopup();
+                if (parent != null) {
+                    for (;;) {
+                        final Window owner = parent.getOwnerWindow();
+                        if (!(owner instanceof ContextMenu)) return owner;
+                        parent = (ContextMenu) owner;
+                    }
+                }
             }
         }
         return null;
@@ -71,23 +82,25 @@ public final class GUIUtilities extends Static {
      * @param  <E>     type of elements to copy.
      * @param  source  the list of elements to copy in the target.
      * @param  target  the list to modify with as few operations as possible.
+     * @return {@code true} if the target changed as a result of this method 
call.
      */
     @SuppressWarnings("empty-statement")
-    public static <E> void copyAsDiff(final List<? extends E> source, final 
ObservableList<E> target) {
+    public static <E> boolean copyAsDiff(final List<? extends E> source, final 
ObservableList<E> target) {
         if (source.isEmpty()) {
+            final boolean empty = target.isEmpty();
             target.clear();
-            return;
+            return !empty;
         }
         if (target.isEmpty()) {
-            target.setAll(source);
-            return;
+            return target.setAll(source);           // Return value is correct 
only if source is not empty.
         }
-        final List<E> lcs = longestCommonSubsequence(source, target);
+        final List<? extends E> lcs = longestCommonSubsequence(source, target);
         /*
          * Remove elements before to add new ones, because some listeners
          * seem to be confused when a list contains duplicated elements
          * (the removed elements may be inserted elsewhere).
          */
+        boolean modified = false;
         int upper = target.size();
         for (int i = lcs.size(); --i >= 0;) {
             final E keep = lcs.get(i);
@@ -95,11 +108,13 @@ public final class GUIUtilities extends Static {
             while (target.get(--lower) != keep);    // A negative index here 
would be a bug in LCS computation.
             if (lower + 1 < upper) {
                 target.remove(lower + 1, upper);
+                modified = true;
             }
             upper = lower;
         }
         if (upper != 0) {
             target.remove(0, upper);
+            modified = true;
         }
         assert lcs.equals(target);                  // Because we removed all 
elements that were not present in LCS.
         /*
@@ -115,14 +130,17 @@ public final class GUIUtilities extends Static {
             if (lower < upper) {
                 target.addAll(i, source.subList(lower, upper));
                 i += upper - lower;
+                modified = true;
             }
             lower = upper + 1;
         }
         upper = source.size();
         if (lower < upper) {
             target.addAll(source.subList(lower, upper));
+            modified = true;
         }
         assert source.equals(target);
+        return modified;
     }
 
     /**
@@ -147,17 +165,55 @@ public final class GUIUtilities extends Static {
      * @param  x    the first sequence for which to compute LCS.
      * @param  y    the second sequence for which to compute LCS.
      * @return longest common subsequence (LCS) between the two given 
sequences.
+     *         May be one of the given lists.
      *
      * <a 
href="https://en.wikipedia.org/wiki/Longest_common_subsequence_problem";>Longest 
common subsequence problem</a>
      */
-    static <E> List<E> longestCommonSubsequence(final List<? extends E> x, 
final List<? extends E> y) {
+    static <E> List<? extends E> longestCommonSubsequence(List<? extends E> x, 
List<? extends E> y) {
         /*
-         * This method could be optimized by excluding the common prefix and 
common suffix before to build the
-         * matrix below. For now we don't do that because the given lists are 
small. But we should revisit in
-         * the future if this method become used with longer sequences.
+         * This method can be optimized by excluding the common prefix and 
common suffix before
+         * to build the matrix. It can reduce a lot the matrix size and the 
number of iterations.
          */
         int nx = x.size();
         int ny = y.size();
+        final List<? extends E> prefix;
+        for (int i=0; ; i++) {
+            if (i >= nx) return x;
+            if (i >= ny) return y;
+            if (x.get(i) != y.get(i)) {
+                if (i == 0) {
+                    prefix = Collections.emptyList();
+                } else {
+                    prefix = x.subList(0, i);
+                    assert   y.subList(0, i).equals(prefix);
+                    x = x.subList(i, nx);
+                    y = y.subList(i, ny);
+                    nx -= i;
+                    ny -= i;
+                }
+                break;
+            }
+        }
+        final List<? extends E> suffix;
+        for (int i=0; ; i++) {
+            final int sx = nx - i;
+            final int sy = ny - i;
+            if (sx == 0) return x;
+            if (sy == 0) return y;
+            if (x.get(sx - 1) != y.get(sy - 1)) {
+                if (i == 0) {
+                    suffix = Collections.emptyList();
+                } else {
+                    suffix = x.subList(sx, nx);
+                    assert   y.subList(sy, ny).equals(suffix);
+                    x = x.subList(0, sx);
+                    y = y.subList(0, sy);
+                    nx -= i;
+                    ny -= i;
+                }
+                break;
+            }
+        }
         /*
          * We need a matrix of size (nx x ny) for storing LCS lengths for all 
(x[i], y[j]) pairs of elements.
          * The matrix is augmented by one row and one column where all values 
in the first row and first column
@@ -180,7 +236,7 @@ public final class GUIUtilities extends Static {
          * Following loop is the "traceback" procedure: starting from last 
cell, follows
          * the direction where the length decrease.
          */
-        final List<E> lcs = new ArrayList<>(lengths[nx][ny]);
+        final List<E> lcs = new ArrayList<>(lengths[nx][ny] + prefix.size() + 
suffix.size());
         while (nx > 0 && ny > 0) {
             final int lg = lengths[nx][ny];
             if (lengths[nx-1][ny] >= lg) {
@@ -192,6 +248,8 @@ public final class GUIUtilities extends Static {
             }
         }
         Collections.reverse(lcs);
+        lcs.addAll(0, prefix);
+        lcs.addAll(   suffix);
         return lcs;
     }
 }
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 27817d3..bb0c425 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,7 +56,7 @@ public final class Resources extends IndexedResourceBundle {
         }
 
         /**
-         * Abstract:
+         * Abstract
          */
         public static final short Abstract = 14;
 
@@ -101,7 +101,7 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CanNotReadResource = 55;
 
         /**
-         * Cell geometry:
+         * Cell geometry
          */
         public static final short CellGeometry = 15;
 
@@ -121,12 +121,12 @@ public final class Resources extends 
IndexedResourceBundle {
         public static final short CopyAs = 46;
 
         /**
-         * Creation date:
+         * Creation date
          */
         public static final short CreationDate = 16;
 
         /**
-         * Credit:
+         * Credit
          */
         public static final short Credit = 17;
 
@@ -136,12 +136,12 @@ public final class Resources extends 
IndexedResourceBundle {
         public static final short Data = 32;
 
         /**
-         * Date:
+         * Date
          */
         public static final short Date = 18;
 
         /**
-         * Dimensions:
+         * Dimensions
          */
         public static final short Dimensions = 19;
 
@@ -191,7 +191,7 @@ public final class Resources extends IndexedResourceBundle {
         public static final short Exit = 9;
 
         /**
-         * Extent:
+         * Extent
          */
         public static final short Extent = 20;
 
@@ -201,12 +201,12 @@ public final class Resources extends 
IndexedResourceBundle {
         public static final short File = 10;
 
         /**
-         * Filter:
+         * Filter
          */
         public static final short Filter = 34;
 
         /**
-         * Format:
+         * Format
          */
         public static final short Format = 38;
 
@@ -226,7 +226,7 @@ public final class Resources extends IndexedResourceBundle {
         public static final short GeospatialFiles = 4;
 
         /**
-         * Identifiers:
+         * Identifiers
          */
         public static final short Identifiers = 54;
 
@@ -256,7 +256,7 @@ public final class Resources extends IndexedResourceBundle {
         public static final short NoFeatureTypeInfo = 33;
 
         /**
-         * Number of dimensions:
+         * Number of dimensions
          */
         public static final short NumberOfDimensions = 27;
 
@@ -271,17 +271,17 @@ public final class Resources extends 
IndexedResourceBundle {
         public static final short OpenDataFile = 2;
 
         /**
-         * Publication date:
+         * Publication date
          */
         public static final short PublicationDate = 21;
 
         /**
-         * Purpose:
+         * Purpose
          */
         public static final short Purpose = 28;
 
         /**
-         * Reference system:
+         * Reference system
          */
         public static final short ReferenceSystem = 22;
 
@@ -321,12 +321,12 @@ public final class Resources extends 
IndexedResourceBundle {
         public static final short TabularData = 51;
 
         /**
-         * Topic category:
+         * Topic category
          */
         public static final short TopicCategory = 25;
 
         /**
-         * Type of resource:
+         * 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 bcac615e..4ead37e 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,7 +20,7 @@
 # For resources shared by all modules in the Apache SIS project, see 
"org.apache.sis.util.resources" package.
 #
 
-Abstract               = Abstract:
+Abstract               = Abstract
 AllFiles               = All files
 CanNotFetchTile_2      = Can not fetch tile ({0}, {1}).
 CanNotReadFile_1       = Can not open \u201c{0}\u201d.
@@ -28,16 +28,16 @@ CanNotClose_1          = Can not close \u201c{0}\u201d. 
Data may be lost.
 CanNotCreateCRS_1      = Can not create reference system \u201c{0}\u201d.
 CanNotCreateXML        = Can not create XML document.
 CanNotReadResource     = A resource contained in the file can not be read. The 
cause is given below.
-CellGeometry           = Cell geometry:
+CellGeometry           = Cell geometry
 Close                  = Close
 Copy                   = Copy
 CopyAs                 = Copy as
-CreationDate           = Creation date:
-Credit                 = Credit:
+CreationDate           = Creation date
+Credit                 = Credit
 CRSs                   = Coordinate Reference Systems
 Data                   = Data
-Date                   = Date:
-Dimensions             = Dimensions:
+Date                   = Date
+Dimensions             = Dimensions
 Display                = Display
 DoesNotCoverAOI        = Does not cover the area of interest.
 ErrorDetails           = Details about error
@@ -47,25 +47,25 @@ ErrorClosingFile       = Error closing file
 ErrorCreatingCRS       = Error creating reference system
 ErrorDataAccess        = Error during data access
 Exit                   = Exit
-Extent                 = Extent:
+Extent                 = Extent
 File                   = File
-Filter                 = Filter:
-Format                 = Format:
+Filter                 = Filter
+Format                 = Format
 FromMetadata           = From metadata
 FullScreen             = Full screen
 GeospatialFiles        = Geospatial data files
-Identifiers            = Identifiers:
+Identifiers            = Identifiers
 Loading                = Loading\u2026
 MainWindow             = Main window
 Metadata               = Metadata
 NewWindow              = New window
 NoFeatureTypeInfo      = No feature type information.
-NumberOfDimensions     = Number of dimensions:
+NumberOfDimensions     = Number of dimensions
 Open                   = Open\u2026
 OpenDataFile           = Open data file
-PublicationDate        = Publication date:
-Purpose                = Purpose:
-ReferenceSystem        = Reference system:
+PublicationDate        = Publication date
+Purpose                = Purpose
+ReferenceSystem        = Reference system
 ResourceIdentification = Resource identification
 SpatialRepresentation  = Spatial representation
 SelectCRS              = Select a coordinate reference system
@@ -73,7 +73,7 @@ SendTo                 = Send to
 StandardErrorStream    = Standard error stream
 Summary                = Summary
 TabularData            = Tabular data
-TopicCategory          = Topic category:
-TypeOfResource         = Type of resource:
+TopicCategory          = Topic category
+TypeOfResource         = Type of resource
 Visualize              = Visualize
 Windows                = Windows
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 37b1099..9659427 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,7 +25,7 @@
 #   U+00A0 NO-BREAK SPACE         before  :
 #
 
-Abstract               = R\u00e9sum\u00e9\u00a0:
+Abstract               = R\u00e9sum\u00e9
 AllFiles               = Tous les fichiers
 CanNotFetchTile_2      = Ne peut pas obtenir la tuile ({0}, {1}).
 CanNotReadFile_1       = Ne peut pas ouvrir \u00ab\u202f{0}\u202f\u00bb.
@@ -33,16 +33,16 @@ CanNotClose_1          = Ne peut pas fermer 
\u00ab\u202f{0}\u202f\u00bb. Il pour
 CanNotCreateCRS_1      = Ne peut pas cr\u00e9er le syst\u00e8me de 
r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb.
 CanNotCreateXML        = Ne peut pas cr\u00e9er le document XML.
 CanNotReadResource     = Une ressource contenue dans le fichier ne peut pas 
\u00eatre lue. La cause est donn\u00e9e ci-dessous.
-CellGeometry           = G\u00e9om\u00e9trie des cellules\u00a0:
+CellGeometry           = G\u00e9om\u00e9trie des cellules
 Close                  = Fermer
 Copy                   = Copier
 CopyAs                 = Copier comme
-CreationDate           = Date de cr\u00e9ation\u00a0:
-Credit                 = Cr\u00e9dit\u00a0:
+CreationDate           = Date de cr\u00e9ation
+Credit                 = Cr\u00e9dit
 CRSs                   = Syst\u00e8mes de r\u00e9f\u00e9rence des 
coordonn\u00e9es
 Data                   = Donn\u00e9es
-Date                   = Date\u00a0:
-Dimensions             = Dimensions\u00a0:
+Date                   = Date
+Dimensions             = Dimensions
 Display                = Affichage
 DoesNotCoverAOI        = Ne couvre pas la r\u00e9gion d\u2019int\u00e9r\u00eat.
 ErrorDetails           = D\u00e9tails \u00e0 propos de l\u2019erreur
@@ -52,25 +52,25 @@ ErrorClosingFile       = Erreur \u00e0 la fermeture du 
fichier
 ErrorCreatingCRS       = Erreur \u00e0 la cr\u00e9ation du syst\u00e8me de 
r\u00e9f\u00e9rence
 ErrorDataAccess        = Erreur lors de l\u2019acc\u00e8s \u00e0 la donn\u00e9e
 Exit                   = Quitter
-Extent                 = \u00c9tendue\u00a0:
+Extent                 = \u00c9tendue
 File                   = Fichier
-Filter                 = Filtre\u00a0:
-Format                 = Format\u00a0:
+Filter                 = Filtre
+Format                 = Format
 FromMetadata           = Des m\u00e9ta-donn\u00e9es
 FullScreen             = Plein \u00e9cran
 GeospatialFiles        = Fichiers de donn\u00e9es g\u00e9ospatiales
-Identifiers            = Identifiants\u00a0:
+Identifiers            = Identifiants
 Loading                = Chargement\u2026
 MainWindow             = Fen\u00eatre principale
 Metadata               = Metadonn\u00e9es
 NewWindow              = Nouvelle fen\u00eatre
 NoFeatureTypeInfo      = Pas d\u2019information sur le type d\u2019entit\u00e9.
-NumberOfDimensions     = Nombre de dimensions\u00a0:
+NumberOfDimensions     = Nombre de dimensions
 Open                   = Ouvrir\u2026
 OpenDataFile           = Ouvrir un fichier de donn\u00e9es
-PublicationDate        = Date de publication\u00a0:
-Purpose                = Objectif\u00a0:
-ReferenceSystem        = Syst\u00e8me de r\u00e9f\u00e9rence\u00a0:
+PublicationDate        = Date de publication
+Purpose                = Objectif
+ReferenceSystem        = Syst\u00e8me de r\u00e9f\u00e9rence
 ResourceIdentification = Identification de la ressource
 SpatialRepresentation  = Repr\u00e9sentation spatiale
 SelectCRS              = Choisir un syst\u00e8me de r\u00e9f\u00e9rence des 
coordonn\u00e9es
@@ -78,7 +78,7 @@ SendTo                 = Envoyer vers
 StandardErrorStream    = Flux d\u2019erreur standard
 Summary                = R\u00e9sum\u00e9
 TabularData            = Tableau de valeurs
-TopicCategory          = Cat\u00e9gorie th\u00e9matique\u00a0:
-TypeOfResource         = Type de ressource\u00a0:
+TopicCategory          = Cat\u00e9gorie th\u00e9matique
+TypeOfResource         = Type de ressource
 Visualize              = Visualiser
 Windows                = Fen\u00eatres
diff --git 
a/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java
 
b/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java
index 648ace5..4445ec2 100644
--- 
a/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java
+++ 
b/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java
@@ -73,7 +73,7 @@ public final strictfp class CRSChooserApp extends Application 
{
         final GeneralEnvelope bbox = new 
GeneralEnvelope(CommonCRS.defaultGeographic());
         bbox.setRange(0, -140.99778, -52.6480987209);
         bbox.setRange(1, 41.6751050889, 83.23324);              // Canada
-        final CRSChooser chooser = new CRSChooser(null, bbox);
+        final CRSChooser chooser = new CRSChooser(null, bbox, null);
         final Optional<CoordinateReferenceSystem> crs = 
chooser.showDialog(window);
 
         System.out.println("The selected CRS is: " + crs);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java 
b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
index 13a60f8..d64d8dc 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java
@@ -34,6 +34,7 @@ import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.util.collection.Cache;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Disposable;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.coverage.grid.GridExtent;     // For javadoc
 import org.apache.sis.internal.feature.Resources;
@@ -116,7 +117,7 @@ import org.apache.sis.internal.feature.Resources;
  * @since   1.1
  * @module
  */
-public abstract class ComputedImage extends PlanarImage {
+public abstract class ComputedImage extends PlanarImage implements Disposable {
     /**
      * The property for declaring the amount of additional source pixels 
needed on each side of a destination pixel.
      * This property can be used for calculations that require only a fixed 
rectangular source region around a source
@@ -617,6 +618,7 @@ public abstract class ComputedImage extends PlanarImage {
      * <p><b>Note:</b> keep in mind that this image may be referenced as a 
source of other images.
      * In case of doubt, it may be safer to rely on the garbage collector 
instead than invoking this method.</p>
      */
+    @Override
     public void dispose() {
         reference.dispose();
     }

Reply via email to