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 6404696  Fix other CRS selection problems (wrong CRS checked in menu 
items, "Other…" not working). This commit fixes the last problems we have seen 
so far.
6404696 is described below

commit 64046968baa6d21b4d5f4b229795b1f75537a556
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Apr 25 12:19:13 2020 +0200

    Fix other CRS selection problems (wrong CRS checked in menu items, "Other…" 
not working).
    This commit fixes the last problems we have seen so far.
---
 .../java/org/apache/sis/gui/map/StatusBar.java     | 147 +++++++++++++++------
 .../org/apache/sis/gui/referencing/MenuSync.java   |  14 +-
 .../gui/referencing/RecentReferenceSystems.java    |  56 +++++++-
 3 files changed, 170 insertions(+), 47 deletions(-)

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 3cf8192..c418b5a 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
@@ -39,6 +39,7 @@ import javafx.beans.value.ObservableValue;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
+import javafx.collections.ListChangeListener;
 import javafx.concurrent.Task;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.MismatchedDimensionException;
@@ -66,11 +67,11 @@ import org.apache.sis.util.Classes;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.logging.Logging;
 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.referencing.ReferencingUtilities;
 import org.apache.sis.internal.gui.BackgroundThreads;
 import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.Resources;
@@ -137,11 +138,16 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     private double lastX, lastY;
 
     /**
-     * The area of interest, or {@code null} if none. Used for computing 
{@link #objectiveToFormatCRS}.
-     * This field is a reference to the {@link 
RecentReferenceSystems#areaOfInterest} property.
-     * We do not make this property public because it does not belong to this 
object.
+     * The manager of reference systems chosen by user, or {@code null} if 
none.
+     * The {@link RecentReferenceSystems#areaOfInterest} property is used for
+     * computing {@link #objectiveToFormatCRS}.
      */
-    private final ObjectProperty<Envelope> areaOfInterest;
+    private final RecentReferenceSystems systemChooser;
+
+    /**
+     * The selected reference system, or {@code null} if there is no such 
property.
+     */
+    private final ObjectProperty<ReferenceSystem> selectedSystem;
 
     /**
      * The reference system used for rendering the data for which this status 
bar is providing cursor coordinates.
@@ -206,6 +212,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      *
      * @see #sourceCoordinates
      * @see #position
+     * @see #setTargetCRS(CoordinateReferenceSystem)
      */
     private GeneralDirectPosition targetCoordinates;
 
@@ -298,13 +305,37 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         position.setTextAlignment(TextAlignment.RIGHT);
         canvasProperty = new SimpleObjectProperty<>(this, "canvas");
         canvasProperty.addListener(this::onCanvasSpecified);
+        this.systemChooser = systemChooser;
         if (systemChooser == null) {
-            areaOfInterest = null;
+            selectedSystem = null;
         } else {
-            areaOfInterest = systemChooser.areaOfInterest;
+            /*
+             * Ensure (as much as possible) that the CRS of coordinates 
formatted in this status bar is one
+             * of the CRSs in the list of choices offered to the user.  It 
happens often that the CRS given
+             * to `applyCanvasGeometry(GridGeometry)` has (λ,φ) axis order but 
the CRS offered to user have
+             * (φ,λ) axis order (because we try to comply with definitions 
following geographers practice).
+             * In such case we will replace (λ,φ) by (φ,λ). Since we use the 
list of choices as the source
+             * of desired CRS, we have to listen to new elements added to that 
list. This is necessary since
+             * the list of often empty at construction time and filled later 
after a background thread task.
+             */
+            systemChooser.getItems().addListener((ListChangeListener.Change<? 
extends ReferenceSystem> change) -> {
+                while (change.next()) {
+                    if (change.wasAdded() || change.wasReplaced()) {
+                        setReplaceableTargetCRS(format.getDefaultCRS());
+                        break;
+                    }
+                }
+            });
+            /*
+             * Create a contextual menu offering to user a choice of CRS in 
which to display the coordinates.
+             * The CRS choices are controlled by `RecentReferenceSystems`. 
Selection of a new CRS causes the
+             * `setTargetCRS(…)` method to be invoked. Contextual menu can be 
invoked anywhere on the HBox;
+             * we do not register this menu to `position` only because it is a 
relatively small area.
+             */
             final Menu choices = systemChooser.createMenuItems((property, 
oldValue, newValue) -> {
-                findFormatOperation(newValue instanceof 
CoordinateReferenceSystem ? (CoordinateReferenceSystem) newValue : null);
+                setTargetCRS(newValue instanceof CoordinateReferenceSystem ? 
(CoordinateReferenceSystem) newValue : null);
             });
+            selectedSystem = 
RecentReferenceSystems.getSelectedProperty(choices);
             final ContextMenu menu = new ContextMenu(choices);
             view.setOnMousePressed((event) -> {
                 if (event.isSecondaryButtonDown()) {
@@ -411,7 +442,10 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         {
             if (!value) try {
                 apply(getCanvas().getGridGeometry());
-                reformat();
+                /*
+                 * Do not hide `position` since we assume that "real world" 
coordinates are still valid.
+                 * Do not try to rewrite position neither since `lastX` and 
`lastY` are not valid anymore.
+                 */
             } catch (RenderException e) {
                 setErrorMessage(null, e);
             }
@@ -526,21 +560,15 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         inflatePrecisions   = inflate;
         precisions          = null;
         lastX = lastY       = Double.NaN;                           // Not 
valid anymove — see above block comment.
-        CoordinateReferenceSystem restore = null;
         if (sameCRS) {
-            if (objectiveToFormatCRS != null) {
-                localToFormatCRS = MathTransforms.concatenate(localToCRS, 
objectiveToFormatCRS.getMathTransform());
-            }
+            updateLocalToFormatCRS();
             // Keep the format CRS unchanged since we made `localToFormatCRS` 
consistent with its value.
         } else {
             objectiveToFormatCRS = null;
-            restore = format.getDefaultCRS();           // CRS to restore in a 
background thread.
-            setFormatCRS(crs);                          // Should be invoked 
before to set precision.
+            setFormatCRS(crs);                                      // Should 
be invoked before to set precision.
+            crs = setReplaceableTargetCRS(crs);                     // May 
invoke later setFormatCRS(…) again.
         }
         format.setGroundPrecision(Quantities.create(resolution, unit));
-        if (ReferencingUtilities.getDimension(restore) == 
localToFormatCRS.getTargetDimensions()) {
-            findFormatOperation(restore);               // Not executed if 
`restore` is null.
-        }
         /*
          * If this is the first time that this method is invoked after 
`setCanvas(MapCanvas)`,
          * the listeners are not yet registered and should be added now. 
Listeners registration
@@ -550,20 +578,67 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         if (geometry != null && !isMouseListenerRegistered) {
             registerMouseListeners(canvasProperty.getValue());
         }
+        /*
+         * If the CRS changed, we may need to update the selected menu item. 
It happens when this method
+         * is invoked because new data have been loaded, as opposed to this 
method being invoked after a
+         * gesture event (zoom, pan, rotation).
+         */
+        if (!sameCRS && selectedSystem != null) {
+            selectedSystem.set(crs);
+        }
+    }
+
+    /**
+     * Computes {@link #localToFormatCRS} after a change of {@link 
#localToObjectiveCRS}.
+     * Other properties, in particular {@link #objectiveToFormatCRS}, must be 
valid.
+     */
+    private void updateLocalToFormatCRS() {
+        if (objectiveToFormatCRS != null) {
+            localToFormatCRS = MathTransforms.concatenate(localToObjectiveCRS, 
objectiveToFormatCRS.getMathTransform());
+        }
+    }
+
+    /**
+     * Sets the CRS of the position shown in this status bar after replacement 
by one of the available CRS
+     * if a match is found. This method compares the given CRS with the list 
of choices before to delegate
+     * to {@link #setTargetCRS(CoordinateReferenceSystem)} possibly with 
different axis order. The typical
+     * scenario is {@link #apply(GridGeometry)} invoked with 
(<var>longitude</var>, <var>latitude</var>)
+     * axis order, and this method swapping axes to standard 
(<var>latitude</var>, <var>longitude</var>)
+     * axis order for coordinates display purpose.
+     *
+     * @param  crs  the new CRS (ignoring axis order), or {@code null} for 
{@link #objectiveCRS}.
+     * @return the reference system actually used for formatting coordinates. 
It may have different axis order
+     *         and units than the specified CRS. This is the CRS that {@link 
CoordinateFormat#getDefaultCRS()}
+     *         will return a little bit later (pending completion of a 
background task).
+     */
+    private CoordinateReferenceSystem 
setReplaceableTargetCRS(CoordinateReferenceSystem crs) {
+        if (crs != null) {
+            final ComparisonMode mode = 
systemChooser.duplicationCriterion.get();
+            for (final ReferenceSystem system : systemChooser.getItems()) {
+                if (Utilities.deepEquals(crs, system, mode)) {
+                    crs = (CoordinateReferenceSystem) system;       // Same 
CRS but possibly different axis order.
+                    break;
+                }
+            }
+        }
+        if (crs != format.getDefaultCRS()) {
+            setTargetCRS(crs);
+        }
+        return crs;
     }
 
     /**
-     * Sets the coordinate reference system of the coordinates shown in this 
status bar.
+     * Sets the coordinate reference system of the position shown in this 
status bar.
      * The change may not appear immediately after method return; this method 
may use a
      * background thread for computing the coordinate operation.  That task 
may be long
      * the first time that it is executed, but should be fast on subsequent 
invocations.
      *
      * @param  crs  the new CRS, or {@code null} for {@link #objectiveCRS}.
      */
-    private void findFormatOperation(final CoordinateReferenceSystem crs) {
+    private void setTargetCRS(final CoordinateReferenceSystem crs) {
         if (crs != null && objectiveCRS != null && objectiveCRS != crs) {
             position.setTextFill(Styles.OUTDATED_TEXT);
-            final Envelope aoi = (areaOfInterest != null) ? 
areaOfInterest.get() : null;
+            final Envelope aoi = (systemChooser != null) ? 
systemChooser.areaOfInterest.get() : null;
             BackgroundThreads.execute(new Task<MathTransform>() {
                 /**
                  * The new {@link StatusBar#objectiveToFormatCRS} value if 
successful.
@@ -583,7 +658,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
                     } catch (TransformException e) {
                         bbox = null;
                         
Logging.recoverableException(Logging.getLogger(Modules.APPLICATION),
-                                StatusBar.class, "findFormatOperation", e);
+                                StatusBar.class, "setTargetCRS", e);
                     }
                     operation = CRS.findOperation(objectiveCRS, crs, bbox);
                     return MathTransforms.concatenate(localToObjectiveCRS, 
operation.getMathTransform());
@@ -649,7 +724,14 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         position.setTextFill(Styles.NORMAL_TEXT);
         position.setMinWidth(0);
         setErrorMessage(null, null);
-        reformat();
+        if (isPositionVisible()) {
+            final double x = lastX;
+            final double y = lastY;
+            lastX = lastY = Double.NaN;
+            if (!Double.isNaN(x) && !Double.isNaN(y)) {
+                setLocalCoordinates(x, y);
+            }
+        }
     }
 
     /**
@@ -707,23 +789,6 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     }
 
     /**
-     * Reformats the coordinates shown in {@link #position} using current 
{@link #lastX} and {@link #lastY} values.
-     * This method should be invoked only when the caller knows that those 
values are still valid. Note that those
-     * values may be invalid if {@link javafx.scene.Node#getTransforms()} 
changed even if {@link #objectiveCRS} is
-     * the same.
-     */
-    private void reformat() {
-        if (isPositionVisible()) {
-            final double x = lastX;
-            final double y = lastY;
-            lastX = lastY = Double.NaN;
-            if (!Double.isNaN(x) && !Double.isNaN(y)) {
-                setLocalCoordinates(x, y);
-            }
-        }
-    }
-
-    /**
      * Resets {@link #localToFormatCRS} to its default value. This is invoked 
either when the
      * target CRS is {@link #objectiveCRS}, or when an attempt to use another 
CRS failed.
      */
@@ -795,7 +860,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
             actual   = conversion.getTargetDimensions();
             if (expected == actual) {
                 localToObjectiveCRS = conversion;
-                findFormatOperation(format.getDefaultCRS());                   
 // Recompute `localToFormatCRS`.
+                updateLocalToFormatCRS();
                 return;
             }
         }
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
index f25f675..e6130da 100644
--- 
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
@@ -214,15 +214,19 @@ final class MenuSync extends 
SimpleObjectProperty<ReferenceSystem> implements Ev
     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;
-        if (crs != RecentReferenceSystems.OTHER) {
-            set(crs);
+        if (value == CHOOSER) {
+            action.changed(this, get(), RecentReferenceSystems.OTHER);
+        } else {
+            set((ReferenceSystem) value);
         }
     }
 
     /**
-     * Selects the specified reference system. This method is invoked by 
{@link RecentReferenceSystems}
-     * when the selected CRS changed, either programmatically or by user 
action.
+     * Selects the specified reference system. This method is invoked by 
{@link RecentReferenceSystems} when the
+     * selected CRS changed, either programmatically or by user action. 
User-specified {@link #action} is invoked,
+     * which will typically start a background thread for transforming data. 
This method does nothing if the given
+     * reference system is same as current one; this is important both for 
avoiding infinite loop and for avoiding
+     * to invoke the potentially costly {@link #action}.
      */
     @Override
     public void set(final ReferenceSystem system) {
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 f6ae41d..91f90b6 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
@@ -19,6 +19,7 @@ package org.apache.sis.gui.referencing;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Locale;
+import java.util.Objects;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
@@ -26,6 +27,7 @@ import javafx.beans.value.ObservableValue;
 import javafx.beans.value.WritableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
 import javafx.scene.control.ChoiceBox;
 import javafx.scene.control.MenuItem;
 import javafx.scene.control.Menu;
@@ -91,8 +93,18 @@ public class RecentReferenceSystems {
     private static final int NUM_OTHER_ITEMS = 1;
 
     /**
+     * Key for use with the {@linkplain Menu#getProperties() property map} for 
storing the selected item.
+     * Used for providing the functionality of {@link 
javafx.scene.control.CheckBox#selectedProperty()}
+     * on controls that do not have an explicit selected property.
+     */
+    private static final String SELECTED_ITEM_KEY = "SelectedItem";
+
+    /**
      * A pseudo-reference system for the "Other…" choice. We use a null value 
because {@link ChoiceBox}
      * seems to insist for inserting a null value in the items list when we 
remove the selected item.
+     *
+     * <div class="note"><b>Maintenance note:</b> if this field is changed to 
a non-null value,
+     * search also for usages of {@code Object::nonNull} predicate.</div>
      */
     static final ReferenceSystem OTHER = null;
 
@@ -177,10 +189,19 @@ public class RecentReferenceSystems {
      * The {@link #systemsOrCodes} elements with all codes or wrappers 
replaced by {@link ReferenceSystem}
      * instances and duplicated values removed. This is the list given to 
JavaFX controls that we build.
      * This list includes {@link #OTHER} as its last item.
+     *
+     * @see #updateItems()
      */
     private ObservableList<ReferenceSystem> referenceSystems;
 
     /**
+     * A filtered view of {@link #referenceSystems} without the {@link #OTHER} 
item.
+     *
+     * @see #getItems()
+     */
+    private ObservableList<ReferenceSystem> filteredSystems;
+
+    /**
      * {@code true} if the {@link #referenceSystems} list needs to be rebuilt 
from {@link #systemsOrCodes} content.
      * This field shall be read and modified in a block synchronized on {@link 
#systemsOrCodes}.
      *
@@ -689,6 +710,21 @@ public class RecentReferenceSystems {
     }
 
     /**
+     * Returns all reference systems in the order they appear in JavaFX 
controls. The first element
+     * is the {@link #setPreferred(boolean, ReferenceSystem) preferred} (or 
native) reference system.
+     * All other elements are {@linkplain #addAlternatives(boolean, 
ReferenceSystem...) alternatives}.
+     *
+     * @return all reference systems in the order they appear in JavaFX 
controls.
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    public ObservableList<ReferenceSystem> getItems() {
+        if (filteredSystems == null) {
+            filteredSystems = new FilteredList<>(updateItems(), 
Objects::nonNull);
+        }
+        return filteredSystems;
+    }
+
+    /**
      * 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.
@@ -775,11 +811,29 @@ next:       for (int i=0; i<count; i++) {
     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)));
+        final MenuSync property = new MenuSync(this, updateItems(), menu, new 
Listener(action));
+        menu.getProperties().put(SELECTED_ITEM_KEY, property);
+        controlValues.add(property);
         return menu;
     }
 
     /**
+     * Returns the property for the selected value in a menu created by {@link 
#createMenuItems(ChangeListener)}.
+     *
+     * @param  menu  the menu, or {@code null} if none.
+     * @return the property for the selected value, or {@code null} if none.
+     */
+    public static ObjectProperty<ReferenceSystem> getSelectedProperty(final 
Menu menu) {
+        if (menu != null) {
+            final Object property = 
menu.getProperties().get(SELECTED_ITEM_KEY);
+            if (property instanceof MenuSync) {
+                return (MenuSync) property;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Invoked when an error occurred while filtering a {@link 
ReferenceSystem} instance.
      * The error may be a failure to convert an EPSG code to a {@link 
CoordinateReferenceSystem} instance,
      * or an error during a CRS verification. Some errors may be normal, for 
example because EPSG dataset

Reply via email to