sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/04: Move the handling of context menu from CoverageCanvas to MapCanvas parent class.
Date Thu, 02 Jul 2020 16:52:23 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

commit e981308a7b2530a56b15fbd1576a6b6eb8b659b2
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Jul 2 11:45:10 2020 +0200

    Move the handling of context menu from CoverageCanvas to MapCanvas parent class.
---
 .../apache/sis/gui/coverage/CoverageCanvas.java    | 188 -------------------
 .../apache/sis/gui/coverage/CoverageControls.java  |  21 +--
 .../java/org/apache/sis/gui/map/MapCanvas.java     | 201 ++++++++++++++++++++-
 .../main/java/org/apache/sis/gui/map/MapMenu.java  | 138 ++++++++++++++
 4 files changed, 341 insertions(+), 207 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index fd6bfa8..ea13486 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -24,33 +24,19 @@ import java.awt.Rectangle;
 import java.awt.image.RenderedImage;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.NoninvertibleTransformException;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
 import javafx.scene.paint.Color;
-import javafx.scene.layout.Pane;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.Background;
 import javafx.scene.layout.BackgroundFill;
-import javafx.scene.control.Menu;
-import javafx.scene.control.RadioMenuItem;
-import javafx.scene.control.ContextMenu;
-import javafx.scene.control.ToggleGroup;
-import javafx.scene.input.MouseEvent;
-import javafx.event.EventHandler;
 import javafx.beans.DefaultProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.value.ObservableValue;
-import javafx.beans.value.WritableValue;
-import javafx.beans.value.ChangeListener;
 import javafx.application.Platform;
 import javafx.concurrent.Task;
 import javax.measure.Quantity;
 import javax.measure.quantity.Length;
 import org.opengis.geometry.Envelope;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.util.FactoryException;
-import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.coverage.grid.GridCoverage;
@@ -60,19 +46,13 @@ import org.apache.sis.coverage.grid.ImageRenderer;
 import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
-import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.geometry.AbstractEnvelope;
-import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.image.PlanarImage;
 import org.apache.sis.image.Interpolation;
 import org.apache.sis.gui.map.MapCanvas;
 import org.apache.sis.gui.map.MapCanvasAWT;
 import org.apache.sis.gui.map.StatusBar;
-import org.apache.sis.gui.referencing.PositionableProjection;
-import org.apache.sis.gui.referencing.RecentReferenceSystems;
 import org.apache.sis.internal.gui.GUIUtilities;
-import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.measure.Units;
@@ -181,141 +161,6 @@ public class CoverageCanvas extends MapCanvasAWT {
     }
 
     /**
-     * Creates and register a contextual menu.
-     *
-     * @todo Consider moving to {@link org.apache.sis.gui.map.MapCanvas}.
-     */
-    final ObjectProperty<ReferenceSystem> createContextMenu(final RecentReferenceSystems
referenceSystems) {
-        final Resources resources = Resources.forLocale(getLocale());
-        final MenuHandler handler = new MenuHandler();
-        final Menu  systemChoices = referenceSystems.createMenuItems(handler);
-        final Menu   localSystems = new Menu(resources.getString(Resources.Keys.CenteredProjection));
-        for (final PositionableProjection projection : PositionableProjection.values()) {
-            final RadioMenuItem item = new RadioMenuItem(projection.toString());
-            item.setToggleGroup(handler.positionables);
-            item.setOnAction((e) -> handler.createProjectedCRS(projection));
-            localSystems.getItems().add(item);
-        }
-        handler.menu.getItems().setAll(systemChoices, localSystems);
-        addPropertyChangeListener(OBJECTIVE_CRS_PROPERTY, handler);
-        fixedPane.setOnMousePressed(handler);
-        return handler.selectedProperty = RecentReferenceSystems.getSelectedProperty(systemChoices);
-    }
-
-    /**
-     * Shows or hides the contextual menu when the right mouse button is clicked. This handler
can determine
-     * the geographic location where the click occurred. This information is used for changing
the projection
-     * while preserving approximately the location, scale and rotation of pixels around the
mouse cursor.
-     */
-    @SuppressWarnings("serial")                                         // Not intended to
be serialized.
-    private final class MenuHandler extends DirectPosition2D
-            implements EventHandler<MouseEvent>, ChangeListener<ReferenceSystem>,
PropertyChangeListener
-    {
-        /**
-         * The property to update if a change of CRS occurs in the enclosing canvas. This
property is provided
-         * by {@link RecentReferenceSystems}, which listen to changes. Setting this property
to a new value
-         * causes the "Referencing systems" radio menus to change the item where the check
mark appear.
-         *
-         * <p>This field is initialized by {@link #createContextMenu(RecentReferenceSystems)}
and should be
-         * considered final after initialization.</p>
-         */
-        ObjectProperty<ReferenceSystem> selectedProperty;
-
-        /**
-         * The group of {@link PositionableProjection} items for projections created on-the-fly
at mouse position.
-         * Those items are not managed by {@link RecentReferenceSystems} so they need to
be handled there.
-         */
-        final ToggleGroup positionables;
-
-        /**
-         * The contextual menu to show or hide when mouse button is clicked on the canvas.
-         */
-        final ContextMenu menu;
-
-        /**
-         * {@code true} if we are in the process of setting a CRS generated by {@link PositionableProjection}.
-         */
-        private boolean isPositionableProjection;
-
-        /**
-         * Creates a new handler for contextual menu in enclosing canvas.
-         */
-        MenuHandler() {
-            super(getDisplayCRS());
-            menu = new ContextMenu();
-            positionables = new ToggleGroup();
-        }
-
-        /**
-         * Invoked when the user click on the canvas.
-         * Shows the menu on right mouse click, hide otherwise.
-         */
-        @Override
-        public void handle(final MouseEvent event) {
-            if (event.isSecondaryButtonDown()) {
-                x = event.getX();
-                y = event.getY();
-                menu.show((Pane) event.getSource(), event.getScreenX(), event.getScreenY());
-                event.consume();
-            } else {
-                menu.hide();
-            }
-        }
-
-        /**
-         * Invoked when user selected a new coordinate reference system among the choices
of predefined CRS.
-         * Those CRS are the ones managed by {@link RecentReferenceSystems}, not the ones
created on-the-fly.
-         */
-        @Override
-        public void changed(final ObservableValue<? extends ReferenceSystem> property,
-                            final ReferenceSystem oldValue, final ReferenceSystem newValue)
-        {
-            if (newValue instanceof CoordinateReferenceSystem) {
-                setObjectiveCRS((CoordinateReferenceSystem) newValue, this, property);
-            }
-        }
-
-        /**
-         * Invoked when user selected a projection centered on mouse position. Those CRS
are generated on-the-fly
-         * and are generally not on the list of CRS managed by {@link RecentReferenceSystems}.
-         */
-        final void createProjectedCRS(final PositionableProjection projection) {
-            try {
-                DirectPosition2D center = new DirectPosition2D();
-                center = (DirectPosition2D) objectiveToDisplay.inverseTransform(this, center);
-                center.setCoordinateReferenceSystem(getObjectiveCRS());
-                CoordinateReferenceSystem crs = projection.createProjectedCRS(center);
-                try {
-                    isPositionableProjection = true;
-                    setObjectiveCRS(crs, this, null);
-                } finally {
-                    isPositionableProjection = false;
-                }
-            } catch (Exception e) {
-                errorOccurred(e);
-                final Resources i18n = Resources.forLocale(getLocale());
-                ExceptionReporter.show(null, i18n.getString(Resources.Keys.CanNotUseRefSys_1,
projection), e);
-            }
-        }
-
-        /**
-         * Invoked when a canvas property changed, typically after a new coverage has been
selected.
-         * The property of interest is {@value CoverageCanvas#OBJECTIVE_CRS_PROPERTY}.
-         * This method updates the CRS selected in the contextual menu.
-         */
-        @Override
-        public void propertyChange(final PropertyChangeEvent event) {
-            final Object value = event.getNewValue();
-            if (value instanceof CoordinateReferenceSystem) {
-                selectedProperty.set((CoordinateReferenceSystem) value);
-            }
-            if (!isPositionableProjection) {
-                positionables.selectToggle(null);
-            }
-        }
-    }
-
-    /**
      * Returns the region containing the image view.
      * The subclass is implementation dependent and may change in any future version.
      *
@@ -690,39 +535,6 @@ public class CoverageCanvas extends MapCanvasAWT {
     }
 
     /**
-     * Invoked when the user changed the CRS from a JavaFX control. If the CRS can not be
set to the specified
-     * value, then an error message is shown in the status bar and the property is reset
to its previous value.
-     *
-     * @param  crs       the new Coordinate Reference System in which to transform all data
before displaying.
-     * @param  anchor    the point to keep at fixed display coordinates, or {@code null}
for default value.
-     * @param  property  the property to reset if the operation fails.
-     */
-    private void setObjectiveCRS(final CoordinateReferenceSystem crs, DirectPosition anchor,
-                                 final ObservableValue<? extends ReferenceSystem> property)
-    {
-        final CoordinateReferenceSystem previous = getObjectiveCRS();
-        if (crs != previous) try {
-            if (anchor == null) {
-                final Envelope2D bounds = getDisplayBounds();
-                if (bounds != null) {
-                    anchor = AbstractEnvelope.castOrCopy(bounds).getMedian();
-                }
-            }
-            setObjectiveCRS(crs, anchor);
-            requestRepaint();
-        } catch (Exception e) {
-            if (property instanceof WritableValue<?>) {
-                ((WritableValue<ReferenceSystem>) property).setValue(previous);
-            }
-            errorOccurred(e);
-            final Locale locale = getLocale();
-            final Resources i18n = Resources.forLocale(locale);
-            ExceptionReporter.show(null, i18n.getString(Resources.Keys.CanNotUseRefSys_1,
-                                   IdentifiedObjects.getDisplayName(crs, locale)), e);
-        }
-    }
-
-    /**
      * Invoked when an exception occurred while computing a transform but the painting process
can continue.
      */
     private static void unexpectedException(final Exception e) {
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 e98811e..b4a39eb 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
@@ -29,7 +29,6 @@ import javafx.scene.layout.VBox;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
-import javafx.beans.binding.ObjectBinding;
 import javafx.collections.ObservableList;
 import javafx.scene.Node;
 import javafx.scene.control.ChoiceBox;
@@ -37,10 +36,9 @@ import javafx.scene.control.Label;
 import javafx.scene.paint.Color;
 import javafx.scene.text.Font;
 import javafx.util.StringConverter;
-import org.opengis.referencing.ReferenceSystem;
 import org.apache.sis.coverage.grid.GridCoverage;
-import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.gui.referencing.RecentReferenceSystems;
+import org.apache.sis.gui.map.MapMenu;
 import org.apache.sis.gui.map.StatusBar;
 import org.apache.sis.image.Interpolation;
 import org.apache.sis.internal.gui.Styles;
@@ -88,8 +86,8 @@ final class CoverageControls extends Controls {
         view.statusBar = statusBar;
         imageAndStatus = new BorderPane(view.getView());
         imageAndStatus.setBottom(statusBar.getView());
-        final ObjectProperty<ReferenceSystem> referenceSystem = view.createContextMenu(referenceSystems);
-        final Locale locale = vocabulary.getLocale();
+        final MapMenu menu = new MapMenu(view);
+        menu.addReferenceSystems(referenceSystems);
         /*
          * "Display" section with the following controls:
          *    - Current CRS
@@ -106,16 +104,7 @@ final class CoverageControls extends Controls {
             crsLabel.setFont(font);
             crsLabel.setPadding(Styles.FORM_INSETS);
             crsShown.setPadding(INDENT_OUTSIDE);
-            crsShown.textProperty().bind(new ObjectBinding<String>() {
-                /* Constructor */ {
-                    bind(referenceSystem);
-                }
-
-                /** Invoked when the reference system changed. */
-                @Override protected String computeValue() {
-                    return IdentifiedObjects.getDisplayName(referenceSystem.get(), locale);
-                }
-            });
+            menu.selectedReferenceSystem().ifPresent((text) -> crsShown.textProperty().bind(text));
             /*
              * The pane containing controls will be divided in sections separated by labels:
              * ones for values and one for colors.
@@ -124,7 +113,7 @@ final class CoverageControls extends Controls {
             final int colorsHeader = 2;
             final GridPane gp;
             gp = Styles.createControlGrid(valuesHeader + 1,
-                label(vocabulary, Vocabulary.Keys.Interpolation, createInterpolationButton(locale)),
+                label(vocabulary, Vocabulary.Keys.Interpolation, createInterpolationButton(vocabulary.getLocale())),
                 label(vocabulary, Vocabulary.Keys.Stretching, Stretching.createButton((p,o,n)
-> view.setStyling(n))),
                 label(vocabulary, Vocabulary.Keys.Background, createBackgroundButton(background)));
             /*
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
index 9ce058b..753f5a3 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
@@ -20,6 +20,8 @@ import java.util.Locale;
 import java.util.Arrays;
 import java.util.Objects;
 import java.awt.geom.AffineTransform;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import javafx.geometry.Bounds;
 import javafx.geometry.Point2D;
 import javafx.scene.layout.Pane;
@@ -33,14 +35,25 @@ import javafx.scene.input.GestureEvent;
 import javafx.scene.Cursor;
 import javafx.event.EventType;
 import javafx.beans.Observable;
+import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyBooleanProperty;
 import javafx.beans.property.ReadOnlyBooleanWrapper;
 import javafx.beans.property.ReadOnlyObjectProperty;
 import javafx.beans.property.ReadOnlyObjectWrapper;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.beans.value.WritableValue;
 import javafx.concurrent.Task;
+import javafx.event.EventHandler;
+import javafx.scene.control.Menu;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.RadioMenuItem;
+import javafx.scene.control.ToggleGroup;
 import javafx.scene.transform.Affine;
 import javafx.scene.transform.NonInvertibleTransformException;
 import org.opengis.geometry.Envelope;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -50,20 +63,27 @@ import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.referencing.cs.CoordinateSystems;
+import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.Envelope2D;
+import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.gui.referencing.PositionableProjection;
+import org.apache.sis.gui.referencing.RecentReferenceSystems;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.gui.GUIUtilities;
+import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.referencing.AxisDirections;
 import org.apache.sis.portrayal.PlanarCanvas;
 import org.apache.sis.portrayal.RenderException;
+import org.apache.sis.referencing.IdentifiedObjects;
 
 
 /**
@@ -494,10 +514,178 @@ public abstract class MapCanvas extends PlanarCanvas {
     }
 
     /**
-     * Returns {@code true} if content changed since the last {@link #repaint()} execution.
+     * Creates and register a contextual menu.
+     *
+     * @return the property for the selected value, or {@code null} if none.
      */
-    final boolean contentsChanged() {
-        return contentChangeCount != renderedContentStamp;
+    final ObjectProperty<ReferenceSystem> createContextMenu(final ContextMenu menu,
+            final RecentReferenceSystems referenceSystems)
+    {
+        final Resources resources = Resources.forLocale(getLocale());
+        final MenuHandler handler = new MenuHandler(menu);
+        final Menu  systemChoices = referenceSystems.createMenuItems(handler);
+        final Menu   localSystems = new Menu(resources.getString(Resources.Keys.CenteredProjection));
+        for (final PositionableProjection projection : PositionableProjection.values()) {
+            final RadioMenuItem item = new RadioMenuItem(projection.toString());
+            item.setToggleGroup(handler.positionables);
+            item.setOnAction((e) -> handler.createProjectedCRS(projection));
+            localSystems.getItems().add(item);
+        }
+        menu.getItems().setAll(systemChoices, localSystems);
+        addPropertyChangeListener(OBJECTIVE_CRS_PROPERTY, handler);
+        fixedPane.setOnMousePressed(handler);
+        return handler.selectedProperty = RecentReferenceSystems.getSelectedProperty(systemChoices);
+    }
+
+    /**
+     * Shows or hides the contextual menu when the right mouse button is clicked. This handler
can determine
+     * the geographic location where the click occurred. This information is used for changing
the projection
+     * while preserving approximately the location, scale and rotation of pixels around the
mouse cursor.
+     */
+    @SuppressWarnings("serial")                                         // Not intended to
be serialized.
+    private final class MenuHandler extends DirectPosition2D
+            implements EventHandler<MouseEvent>, ChangeListener<ReferenceSystem>,
PropertyChangeListener
+    {
+        /**
+         * The property to update if a change of CRS occurs in the enclosing canvas. This
property is provided
+         * by {@link RecentReferenceSystems}, which listen to changes. Setting this property
to a new value
+         * causes the "Referencing systems" radio menus to change the item where the check
mark appear.
+         *
+         * <p>This field is initialized by {@link #createContextMenu(ContextMenu, RecentReferenceSystems)}
+         * and should be considered final after initialization.</p>
+         */
+        ObjectProperty<ReferenceSystem> selectedProperty;
+
+        /**
+         * The group of {@link PositionableProjection} items for projections created on-the-fly
at mouse position.
+         * Those items are not managed by {@link RecentReferenceSystems} so they need to
be handled there.
+         */
+        final ToggleGroup positionables;
+
+        /**
+         * The contextual menu to show or hide when mouse button is clicked on the canvas.
+         */
+        private final ContextMenu menu;
+
+        /**
+         * {@code true} if we are in the process of setting a CRS generated by {@link PositionableProjection}.
+         */
+        private boolean isPositionableProjection;
+
+        /**
+         * Creates a new handler for contextual menu in enclosing canvas.
+         */
+        MenuHandler(final ContextMenu menu) {
+            super(getDisplayCRS());
+            this.menu = menu;
+            positionables = new ToggleGroup();
+        }
+
+        /**
+         * Invoked when the user click on the canvas.
+         * Shows the menu on right mouse click, hide otherwise.
+         */
+        @Override
+        public void handle(final MouseEvent event) {
+            if (event.isSecondaryButtonDown()) {
+                x = event.getX();
+                y = event.getY();
+                menu.show((Pane) event.getSource(), event.getScreenX(), event.getScreenY());
+                event.consume();
+            } else {
+                menu.hide();
+            }
+        }
+
+        /**
+         * Invoked when user selected a new coordinate reference system among the choices
of predefined CRS.
+         * Those CRS are the ones managed by {@link RecentReferenceSystems}, not the ones
created on-the-fly.
+         */
+        @Override
+        public void changed(final ObservableValue<? extends ReferenceSystem> property,
+                            final ReferenceSystem oldValue, final ReferenceSystem newValue)
+        {
+            if (newValue instanceof CoordinateReferenceSystem) {
+                setObjectiveCRS((CoordinateReferenceSystem) newValue, this, property);
+            }
+        }
+
+        /**
+         * Invoked when user selected a projection centered on mouse position. Those CRS
are generated on-the-fly
+         * and are generally not on the list of CRS managed by {@link RecentReferenceSystems}.
+         */
+        final void createProjectedCRS(final PositionableProjection projection) {
+            try {
+                DirectPosition2D center = new DirectPosition2D();
+                center = (DirectPosition2D) objectiveToDisplay.inverseTransform(this, center);
+                center.setCoordinateReferenceSystem(getObjectiveCRS());
+                CoordinateReferenceSystem crs = projection.createProjectedCRS(center);
+                try {
+                    isPositionableProjection = true;
+                    setObjectiveCRS(crs, this, null);
+                } finally {
+                    isPositionableProjection = false;
+                }
+            } catch (Exception e) {
+                errorOccurred(e);
+                final Resources i18n = Resources.forLocale(getLocale());
+                ExceptionReporter.show(null, i18n.getString(Resources.Keys.CanNotUseRefSys_1,
projection), e);
+            }
+        }
+
+        /**
+         * Invoked when a canvas property changed, typically after new data are shown.
+         * The property of interest is {@value MapCanvas#OBJECTIVE_CRS_PROPERTY}.
+         * This method updates the CRS selected in the contextual menu.
+         */
+        @Override
+        public void propertyChange(final PropertyChangeEvent event) {
+            final Object value = event.getNewValue();
+            if (value instanceof CoordinateReferenceSystem) {
+                selectedProperty.set((CoordinateReferenceSystem) value);
+            }
+            if (!isPositionableProjection) {
+                positionables.selectToggle(null);
+            }
+        }
+    }
+
+    /**
+     * Invoked when the user changed the CRS from a JavaFX control. If the CRS can not be
set to the specified
+     * value, then an error message is shown in the status bar and the property is reset
to its previous value.
+     *
+     * @param  crs       the new Coordinate Reference System in which to transform all data
before displaying.
+     * @param  anchor    the point to keep at fixed display coordinates, or {@code null}
for default value.
+     * @param  property  the property to reset if the operation fails.
+     */
+    private void setObjectiveCRS(final CoordinateReferenceSystem crs, DirectPosition anchor,
+                                 final ObservableValue<? extends ReferenceSystem> property)
+    {
+        final CoordinateReferenceSystem previous = getObjectiveCRS();
+        if (crs != previous) try {
+            /*
+             * If no anchor is specified, the first default is the center of the region currently
visible
+             * in the canvas. If that center can not be determined neither, null anchor defaults
to the
+             * point of interest (POI) managed by the Canvas parent class.
+             */
+            if (anchor == null) {
+                final Envelope2D bounds = getDisplayBounds();
+                if (bounds != null) {
+                    anchor = AbstractEnvelope.castOrCopy(bounds).getMedian();
+                }
+            }
+            setObjectiveCRS(crs, anchor);
+            requestRepaint();
+        } catch (Exception e) {
+            if (property instanceof WritableValue<?>) {
+                ((WritableValue<ReferenceSystem>) property).setValue(previous);
+            }
+            errorOccurred(e);
+            final Locale locale = getLocale();
+            final Resources i18n = Resources.forLocale(locale);
+            ExceptionReporter.show(null, i18n.getString(Resources.Keys.CanNotUseRefSys_1,
+                                   IdentifiedObjects.getDisplayName(crs, locale)), e);
+        }
     }
 
     /**
@@ -641,6 +829,13 @@ public abstract class MapCanvas extends PlanarCanvas {
     }
 
     /**
+     * Returns {@code true} if content changed since the last {@link #repaint()} execution.
+     */
+    final boolean contentsChanged() {
+        return contentChangeCount != renderedContentStamp;
+    }
+
+    /**
      * Requests the map to be rendered again, possibly with new data. Invoking this
      * method does not necessarily causes the repaint process to start immediately.
      * The request will be queued and executed at an arbitrary time.
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java
new file mode 100644
index 0000000..39d7811
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java
@@ -0,0 +1,138 @@
+/*
+ * 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.map;
+
+import java.util.Locale;
+import java.util.Optional;
+import javafx.scene.control.ContextMenu;
+import javafx.beans.binding.ObjectBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.value.ObservableObjectValue;
+import org.opengis.referencing.ReferenceSystem;
+import org.apache.sis.gui.referencing.RecentReferenceSystems;
+import org.apache.sis.gui.referencing.PositionableProjection;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.util.ArgumentChecks;
+
+
+/**
+ * A {@link ContextMenu} that can be shown in a {@link MapCanvas}.
+ * On construction, this menu is initially empty.
+ * Items can be added by the following method calls:
+ *
+ * <ul>
+ *   <li>{@link #addReferenceSystems(RecentReferenceSystems)}:<ul>
+ *     <li><cite>Reference system</cite> with some items from EPSG database.</li>
+ *     <li><cite>Centered projection</cite> with the list of {@link PositionableProjection}
items.</li>
+ *   </ul></li>
+ * </ul>
+ *
+ * More choices may be added in a future versions.
+ * In current implementation, there is no mechanism for removing menu items.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class MapMenu extends ContextMenu {
+    /**
+     * The canvas where this menu can be shown.
+     */
+    private final MapCanvas canvas;
+
+    /**
+     * The property for the selected coordinate reference system, created when first needed.
+     */
+    private ObjectProperty<ReferenceSystem> selectedSystem;
+
+    /**
+     * Whether {@link #addReferenceSystems(RecentReferenceSystems)} has been invoked.
+     */
+    private boolean hasCRS;
+
+    /**
+     * Creates an initially empty menu for the given canvas.
+     *
+     * @param  canvas  the canvas for which to create menus.
+     */
+    public MapMenu(final MapCanvas canvas) {
+        ArgumentChecks.ensureNonNull("canvas", canvas);
+        this.canvas = canvas;
+    }
+
+    /**
+     * Adds menu items for CRS selection. The menu items are in two groups:
+     *
+     * <ul>
+     *   <li><cite>Reference system</cite> with some items from EPSG database.</li>
+     *   <li><cite>Centered projection</cite> with the list of {@link PositionableProjection}
items.</li>
+     * </ul>
+     *
+     * This method can be invoked at most once.
+     *
+     * @param  preferences  handler of menu items for selecting a CRS from a list of EPSG
codes.
+     *         Often {@linkplain RecentReferenceSystems#addUserPreferences() built from user
preferences}.
+     * @throws IllegalStateException if this method has already been invoked.
+     */
+    public void addReferenceSystems(final RecentReferenceSystems preferences) {
+        ArgumentChecks.ensureNonNull("preferences", preferences);
+        if (hasCRS) {
+            throw new IllegalStateException();
+        }
+        hasCRS = true;
+        selectedSystem = canvas.createContextMenu(this, preferences);
+    }
+
+    /**
+     * Returns an observable value for showing the currently selected CRS as a text.
+     * The value is absent if {@link #addReferenceSystems(RecentReferenceSystems)} has never
been invoked.
+     *
+     * @return the currently selected CRS as a text.
+     */
+    public Optional<ObservableObjectValue<String>> selectedReferenceSystem()
{
+        if (selectedSystem != null) {
+            return Optional.of(new SelectedCRS(selectedSystem, canvas.getLocale()));
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    /**
+     * Implementation of the value returned by {@link #selectedReferenceSystem()}.
+     * Implemented as a static class for reducing the number of direct references to {@link
MapMenu}.
+     */
+    private static final class SelectedCRS extends ObjectBinding<String> {
+        /** The property for the selected coordinate reference system. */
+        private final ObjectProperty<ReferenceSystem> selectedSystem;
+
+        /** The locale to use for fetching CRS name. */
+        private final Locale locale;
+
+        /** Creates a new binding. */
+        SelectedCRS(final ObjectProperty<ReferenceSystem> selectedSystem, final Locale
locale) {
+            this.selectedSystem = selectedSystem;
+            this.locale = locale;
+            bind(selectedSystem);
+        }
+
+        /** Invoked when the reference system changed. */
+        @Override protected String computeValue() {
+            return IdentifiedObjects.getDisplayName(selectedSystem.get(), locale);
+        }
+    }
+}


Mime
View raw message