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 0a02a2a Add a "Copy > Coordinates of here" menu item for making easier to check
position on e.g. Google Map.
0a02a2a is described below
commit 0a02a2a9816ccde86bdec56096e8e3010b3e8ec4
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Sep 1 16:01:37 2020 +0200
Add a "Copy > Coordinates of here" menu item for making easier to check position on
e.g. Google Map.
---
.../apache/sis/gui/coverage/CoverageControls.java | 1 +
.../java/org/apache/sis/gui/map/MapCanvas.java | 71 +++++---------
.../main/java/org/apache/sis/gui/map/MapMenu.java | 106 ++++++++++++++++++---
.../java/org/apache/sis/gui/map/StatusBar.java | 105 ++++++++++++--------
.../apache/sis/internal/gui/ExceptionReporter.java | 6 ++
.../org/apache/sis/internal/gui/Resources.java | 5 +
.../apache/sis/internal/gui/Resources.properties | 1 +
.../sis/internal/gui/Resources_fr.properties | 1 +
8 files changed, 199 insertions(+), 97 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 f07e984..ab2e257 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
@@ -97,6 +97,7 @@ final class CoverageControls extends Controls {
imageAndStatus.setBottom(statusBar.getView());
final MapMenu menu = new MapMenu(view);
menu.addReferenceSystems(referenceSystems);
+ menu.addCopyOptions(statusBar);
/*
* "Display" section with the following controls:
* - Current CRS
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 a6f1e93..dc66142 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
@@ -43,9 +43,7 @@ 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;
@@ -520,59 +518,37 @@ public abstract class MapCanvas extends PlanarCanvas {
}
/**
- * Creates and register a contextual menu.
- *
- * @return the property for the selected value, or {@code null} if none.
- */
- 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);
- fixedPane.setOnMouseReleased(handler); // As recommended by MouseEvent.isPopupTrigger().
- 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
+ final class MenuHandler extends DirectPosition2D
implements EventHandler<MouseEvent>, ChangeListener<ReferenceSystem>,
PropertyChangeListener
{
/**
+ * The contextual menu to show or hide when mouse button is clicked on the canvas.
+ */
+ private final ContextMenu menu;
+
+ /**
* 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)}
+ * <p>This field is initialized by {@link MapMenu#addReferenceSystems(RecentReferenceSystems)}
* and should be considered final after initialization.</p>
*/
- ObjectProperty<ReferenceSystem> selectedProperty;
+ ObjectProperty<ReferenceSystem> selectedCrsProperty;
/**
* 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.
+ *
+ * <p>This field is initialized by {@link MapMenu#addReferenceSystems(RecentReferenceSystems)}
+ * and should be considered final after initialization.</p>
*/
- final ToggleGroup positionables;
-
- /**
- * The contextual menu to show or hide when mouse button is clicked on the canvas.
- */
- private final ContextMenu menu;
+ ToggleGroup positionables;
/**
* {@code true} if we are in the process of setting a CRS generated by {@link PositionableProjection}.
@@ -580,16 +556,19 @@ public abstract class MapCanvas extends PlanarCanvas {
private boolean isPositionableProjection;
/**
- * Creates a new handler for contextual menu in enclosing canvas.
+ * Creates and registers a new handler for showing a contextual menu in the enclosing
canvas.
+ * It is caller responsibility to ensure that this method is invoked only once.
*/
+ @SuppressWarnings("ThisEscapedInObjectConstruction")
MenuHandler(final ContextMenu menu) {
super(getDisplayCRS());
this.menu = menu;
- positionables = new ToggleGroup();
+ fixedPane.setOnMousePressed (this);
+ fixedPane.setOnMouseReleased(this); // As recommended by MouseEvent.isPopupTrigger().
}
/**
- * Invoked when the user click on the canvas.
+ * Invoked when the user clicks on the canvas.
* Shows the menu on right mouse click, hide otherwise.
*/
@Override
@@ -647,12 +626,14 @@ public abstract class MapCanvas extends PlanarCanvas {
*/
@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);
+ if (OBJECTIVE_CRS_PROPERTY.equals(event.getPropertyName())) {
+ final Object value = event.getNewValue();
+ if (value instanceof CoordinateReferenceSystem) {
+ selectedCrsProperty.set((CoordinateReferenceSystem) value);
+ }
+ if (!isPositionableProjection) {
+ positionables.selectToggle(null);
+ }
}
}
}
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
index 39d7811..1af6a55 100644
--- 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
@@ -18,13 +18,22 @@ package org.apache.sis.gui.map;
import java.util.Locale;
import java.util.Optional;
+import javafx.scene.control.Menu;
+import javafx.scene.control.MenuItem;
import javafx.scene.control.ContextMenu;
+import javafx.scene.control.RadioMenuItem;
+import javafx.scene.control.ToggleGroup;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableObjectValue;
+import javafx.scene.input.Clipboard;
+import javafx.scene.input.ClipboardContent;
import org.opengis.referencing.ReferenceSystem;
+import org.opengis.referencing.operation.TransformException;
import org.apache.sis.gui.referencing.RecentReferenceSystems;
import org.apache.sis.gui.referencing.PositionableProjection;
+import org.apache.sis.internal.gui.ExceptionReporter;
+import org.apache.sis.internal.gui.Resources;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.util.ArgumentChecks;
@@ -56,14 +65,22 @@ public class MapMenu extends ContextMenu {
private final MapCanvas canvas;
/**
- * The property for the selected coordinate reference system, created when first needed.
+ * A handler for controlling the contextual menu.
+ * Created when first needed.
*/
- private ObjectProperty<ReferenceSystem> selectedSystem;
+ private MapCanvas.MenuHandler menuHandler;
/**
- * Whether {@link #addReferenceSystems(RecentReferenceSystems)} has been invoked.
+ * Groups of menu items that have been added. Bits in this mask are set when {@link #addCopyOptions(StatusBar)}
+ * {@link #addReferenceSystems(RecentReferenceSystems)} or similar methods are invoked.
Each {@code addFoo(…)}
+ * method can be invoked only once.
*/
- private boolean hasCRS;
+ private int defined;
+
+ /**
+ * Bit in {@link #defined} mask for tracking which {@code addFoo(…)} methods have been
invoked.
+ */
+ private static final int CRS = 1, COPY = 2;
/**
* Creates an initially empty menu for the given canvas.
@@ -76,6 +93,27 @@ public class MapMenu extends ContextMenu {
}
/**
+ * Invoked before an {@code addFoo(…)} method starts creating new menu items.
+ * First, this method ensures that the specified group of menus has not yet been added.
+ * Then the group of menus is marked as added. Next the {@link #menuHandler} instance
+ * is created if needed, then returned.
+ *
+ * @param mask one of {@link #CRS}, {@link #COPY}, <i>etc.</i> constants.
+ * @return the {@link #menuHandler} instance, created when first needed.
+ * @throws IllegalStateException if the specified group has already been added.
+ */
+ private MapCanvas.MenuHandler startNewMenuItems(final int mask) {
+ if ((defined & mask) != 0) {
+ throw new IllegalStateException();
+ }
+ defined |= mask;
+ if (menuHandler == null) {
+ menuHandler = canvas.new MenuHandler(this);
+ }
+ return menuHandler;
+ }
+
+ /**
* Adds menu items for CRS selection. The menu items are in two groups:
*
* <ul>
@@ -88,28 +126,72 @@ public class MapMenu extends ContextMenu {
* @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.
+ *
+ * @see #selectedReferenceSystem()
*/
public void addReferenceSystems(final RecentReferenceSystems preferences) {
ArgumentChecks.ensureNonNull("preferences", preferences);
- if (hasCRS) {
- throw new IllegalStateException();
+ final MapCanvas.MenuHandler handler = startNewMenuItems(CRS);
+ final Menu systemChoices = preferences.createMenuItems(handler);
+ handler.selectedCrsProperty = RecentReferenceSystems.getSelectedProperty(systemChoices);
+ handler.positionables = new ToggleGroup();
+
+ final Resources resources = Resources.forLocale(canvas.getLocale());
+ 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);
}
- hasCRS = true;
- selectedSystem = canvas.createContextMenu(this, preferences);
+ getItems().addAll(systemChoices, localSystems);
+ canvas.addPropertyChangeListener(MapCanvas.OBJECTIVE_CRS_PROPERTY, handler);
+ }
+
+ /**
+ * Adds a menu item for copying coordinates at the mouse position where right click occurred.
+ * The coordinate reference system is determined by the status bar; it is not necessarily
the
+ * coordinate reference system of the map.
+ *
+ * @param format status bar determining the CRS and format to use for coordinate values.
+ */
+ public void addCopyOptions(final StatusBar format) {
+ ArgumentChecks.ensureNonNull("format", format);
+ final MapCanvas.MenuHandler handler = startNewMenuItems(COPY);
+ final Resources resources = Resources.forLocale(canvas.getLocale());
+ final MenuItem coordinates = new MenuItem(resources.getString(Resources.Keys.CoordinatesOfHere));
+ coordinates.setOnAction((event) -> {
+ try {
+ final String text = format.formatCoordinates(handler.x, handler.y);
+ final ClipboardContent content = new ClipboardContent();
+ content.putString(text);
+ Clipboard.getSystemClipboard().setContent(content);
+ } catch (TransformException | RuntimeException e) {
+ ExceptionReporter.show(((MenuItem) event.getSource()).getText(), null, e);
+ }
+ });
+ final Menu group = new Menu(resources.getString(Resources.Keys.Copy));
+ group.getItems().setAll(coordinates);
+ getItems().add(group);
}
+
/**
* 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.
+ *
+ * @see #addReferenceSystems(RecentReferenceSystems)
*/
public Optional<ObservableObjectValue<String>> selectedReferenceSystem()
{
- if (selectedSystem != null) {
- return Optional.of(new SelectedCRS(selectedSystem, canvas.getLocale()));
- } else {
- return Optional.empty();
+ if (menuHandler != null) {
+ final ObjectProperty<ReferenceSystem> selectedCrsProperty = menuHandler.selectedCrsProperty;
+ if (selectedCrsProperty != null) {
+ return Optional.of(new SelectedCRS(selectedCrsProperty, canvas.getLocale()));
+ }
}
+ return Optional.empty();
}
/**
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 8cb0823..466fd19 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
@@ -1043,46 +1043,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
boolean success = false;
String text;
try {
- Matrix derivative;
- try {
- derivative = MathTransforms.derivativeAndTransform(localToPositionCRS,
- sourceCoordinates, 0, targetCoordinates.coordinates, 0);
- } catch (TransformException ignore) {
- /*
- * If above operation failed, it may be because the MathTransform does
not support
- * derivative calculation. Try again without derivative (the precision
will be set
- * to the default resolution computed in `setCanvasGeometry(…)`).
- */
- localToPositionCRS.transform(sourceCoordinates, 0, targetCoordinates.coordinates,
0, 1);
- derivative = null;
- }
- if (derivative == null) {
- precisions = null;
- } else {
- if (precisions == null) {
- precisions = new double[targetCoordinates.getDimension()];
- }
- /*
- * Estimate the precision by looking at the maximal displacement in the
CRS caused by
- * a displacement of one cell (i.e. when moving by one row or column).
We search for
- * maximal displacement instead than minimal because we expect the displacement
to be
- * zero along some axes (e.g. one row down does not change longitude
value in a Plate
- * Carrée projection).
- */
- for (int j=derivative.getNumRow(); --j >= 0;) {
- double p = 0;
- for (int i=derivative.getNumCol(); --i >= 0;) {
- double e = Math.abs(derivative.getElement(j, i));
- if (inflatePrecisions != null) {
- e *= inflatePrecisions[i];
- }
- if (e > p) p = e;
- }
- precisions[j] = p;
- }
- }
- format.setPrecisions(precisions);
- text = format.format(targetCoordinates);
+ text = formatCoordinates();
success = true;
} catch (TransformException | RuntimeException e) {
/*
@@ -1119,6 +1080,70 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent>
{
}
/**
+ * Converts and formats the local coordinates currently stored in {@link #sourceCoordinates}
array.
+ */
+ private String formatCoordinates() throws TransformException {
+ Matrix derivative;
+ try {
+ derivative = MathTransforms.derivativeAndTransform(localToPositionCRS,
+ sourceCoordinates, 0, targetCoordinates.coordinates, 0);
+ } catch (TransformException ignore) {
+ /*
+ * If above operation failed, it may be because the MathTransform does not support
+ * derivative calculation. Try again without derivative (the precision will be
set
+ * to the default resolution computed in `setCanvasGeometry(…)`).
+ */
+ localToPositionCRS.transform(sourceCoordinates, 0, targetCoordinates.coordinates,
0, 1);
+ derivative = null;
+ }
+ if (derivative == null) {
+ precisions = null;
+ } else {
+ if (precisions == null) {
+ precisions = new double[targetCoordinates.getDimension()];
+ }
+ /*
+ * Estimate the precision by looking at the maximal displacement in the CRS caused
by
+ * a displacement of one cell (i.e. when moving by one row or column). We search
for
+ * maximal displacement instead than minimal because we expect the displacement
to be
+ * zero along some axes (e.g. one row down does not change longitude value in
a Plate
+ * Carrée projection).
+ */
+ for (int j=derivative.getNumRow(); --j >= 0;) {
+ double p = 0;
+ for (int i=derivative.getNumCol(); --i >= 0;) {
+ double e = Math.abs(derivative.getElement(j, i));
+ if (inflatePrecisions != null) {
+ e *= inflatePrecisions[i];
+ }
+ if (e > p) p = e;
+ }
+ precisions[j] = p;
+ }
+ }
+ format.setPrecisions(precisions);
+ return format.format(targetCoordinates);
+ }
+
+ /**
+ * Converts and formats the given local coordinates, but without modifying text shown
in this status bar.
+ *
+ * @param x the <var>x</var> coordinate local to the view.
+ * @param y the <var>y</var> coordinate local to the view.
+ */
+ final String formatCoordinates(final double x, final double y) throws TransformException
{
+ sourceCoordinates[0] = x;
+ sourceCoordinates[1] = y;
+ final String separator = format.getSeparator();
+ try {
+ format.setSeparator("\t");
+ return formatCoordinates();
+ } finally {
+ format.setSeparator(separator);
+ }
+ }
+
+ /**
* Updates the coordinates shown in the status bar with the value given by the mouse
event.
* This method handles the following events:
*
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
index 2617a98..f3b621e 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
@@ -18,6 +18,7 @@ package org.apache.sis.internal.gui;
import java.io.PrintWriter;
import java.io.StringWriter;
+import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.concurrent.WorkerStateEvent;
@@ -195,12 +196,17 @@ public final class ExceptionReporter {
/**
* Constructs and shows the exception reporter.
+ * This method can be invoked from any thread.
*
* @param title the window the title, or {@code null} if none.
* @param text the text in the dialog box, or {@code null} if none.
* @param exception the exception to report.
*/
public static void show(final String title, final String text, final Throwable exception)
{
+ if (!Platform.isFxApplicationThread()) {
+ Platform.runLater(() -> show(title, text, exception));
+ return;
+ }
String message = exception.getLocalizedMessage();
if (message == null) {
message = Classes.getShortClassName(exception);
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 f5077d7..16be644 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
@@ -126,6 +126,11 @@ public final class Resources extends IndexedResourceBundle {
public static final short Close = 10;
/**
+ * Coordinates of here
+ */
+ public static final short CoordinatesOfHere = 50;
+
+ /**
* Copy
*/
public static final short Copy = 11;
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 3c48248..f61ccc7 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
@@ -34,6 +34,7 @@ CanNotRender = An error occurred while rendering the data.
CanNotUseRefSys_1 = Can not use the \u201c{0}\u201d reference system.
CenteredProjection = Centered projection
Close = Close
+CoordinatesOfHere = Coordinates of here
Copy = Copy
CopyAs = Copy as
DisplayedSize = Displayed size
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 d775028..fdda04f 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
@@ -39,6 +39,7 @@ CanNotRender = Une erreur est survenue lors de l\u2019affichage
des do
CanNotUseRefSys_1 = Ne peut pas utiliser le syst\u00e8me de r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb.
CenteredProjection = Projection centr\u00e9e
Close = Fermer
+CoordinatesOfHere = Coordonn\u00e9es d\u2019ici
Copy = Copier
CopyAs = Copier comme
DisplayedSize = Taille affich\u00e9e
|