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 6e262a7a28 Bug fix in the drawing of shapes of loaded tiles: the
objective CRS, objective to display transform and the list where shapes are
added must be obtained together for consistency. Before this commit, the list
of shapes where obtained some time after the transform, resulting in a sometime
outdated transform.
6e262a7a28 is described below
commit 6e262a7a28d6f996f6987ce3a603fc76afad3d60
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Apr 16 18:04:14 2026 +0200
Bug fix in the drawing of shapes of loaded tiles:
the objective CRS, objective to display transform and the list where shapes
are added must be obtained together for consistency.
Before this commit, the list of shapes where obtained some time after the
transform, resulting in a sometime outdated transform.
---
.../apache/sis/gui/coverage/CoverageCanvas.java | 89 +++-------
.../main/org/apache/sis/gui/internal/FontGIS.java | 2 +-
.../org/apache/sis/gui/internal/GUIUtilities.java | 22 +++
.../org/apache/sis/gui/map/EvanescentPane.java | 51 +++---
.../main/org/apache/sis/gui/map/MapCanvas.java | 189 +++++++++++++++++----
.../apache/sis/gui/internal/GUIUtilitiesTest.java | 15 ++
6 files changed, 240 insertions(+), 128 deletions(-)
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
index 78dc98a0ef..3acf8f965e 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -1313,7 +1313,7 @@ public class CoverageCanvas extends MapCanvasAWT {
* typically a background thread which is reading the data. The tiles are
enqueued for processing
* in another background thread for avoiding to slow down the thread that
read the data.
*/
- private final class TileReadListener implements
StoreListener<TileReadEvent>, Runnable, EventHandler<ActionEvent> {
+ private final class TileReadListener implements
StoreListener<TileReadEvent>, EventHandler<ActionEvent> {
/**
* Colors of the tiles, using different colors for different
resolutions (pyramid levels).
*/
@@ -1337,39 +1337,24 @@ public class CoverageCanvas extends MapCanvasAWT {
*/
private static final Duration DURATION = new Duration(4000);
- /**
- * The tiles to highlight, as a thread-safe queue.
- */
- private final Queue<TileReadEvent> tileEvents;
-
/**
* The JavaFX shapes (usually rectangles) for highlighting the tiles.
- * Elements of this queue are derived from {@link #tileEvents}.
+ * This queue shall be thread-safe as it is read and written from
different threads.
*/
private final Queue<FadeTransition> tileShapes;
- /**
- * The objective <abbr>CRS</abbr> of the canvas.
- * This information is updated in the JavaFX thread after each
rendering.
- *
- * @see CoverageCanvas#getObjectiveCRS()
- */
- private CoordinateReferenceSystem objectiveCRS;
-
/**
* The transform from objective <abbr>CRS</abbr> to the display
coordinate system of the canvas.
* This information is updated in the JavaFX thread after each
rendering, so that creations of
* JavaFX shapes will use the information that reflects the image
shown in the canvas.
- *
- * @see CoverageCanvas#objectiveToDisplay
*/
- private LinearTransform objectiveToDisplay;
+ volatile ObjectiveSnapshot snapshot;
/**
* Creates a new listener of tile read events.
+ * This constructor must be invoked from the JavaFX thread.
*/
TileReadListener() {
- tileEvents = new ConcurrentLinkedQueue<>();
tileShapes = new ConcurrentLinkedQueue<>();
takeSnapshotOfObjectiveCRS();
}
@@ -1379,43 +1364,22 @@ public class CoverageCanvas extends MapCanvasAWT {
* This method should be invoked after each rendering, so that
creations of JavaFX shapes will use
* the information that reflects the image shown in the canvas.
*/
- final synchronized void takeSnapshotOfObjectiveCRS() {
- objectiveCRS = getObjectiveCRS();
- objectiveToDisplay = getObjectiveToDisplay();
- }
-
- /**
- * Invoked when a tile has been read. The specified tile is added to
the list of tiles to highlight.
- * Starts a background thread for deriving the JavaFX shapes if such
thread is not already running.
- */
- @Override
- public void eventOccured(final TileReadEvent event) {
- tileEvents.add(event);
- if (TRACE) {
- trace("TileReadListener.accept(%d)", tileEvents.size());
- }
- BackgroundThreads.EXECUTOR.execute(this);
+ final void takeSnapshotOfObjectiveCRS() {
+ snapshot = forObjectiveSnapshot();
}
/**
- * Invoked in a background thread for converting the tile events into
JavaFX shapes.
- * Usually, the calculation done in this method is faster than the
reading of tiles,
- * therefore each invocation of this method usually has only one tile
to convert.
+ * Invoked when a tile has been read. This method computes the JavaFX
shape in a background thread.
+ * One thread is used for each shape (we do not collect the shapes in
a queue) because that thread
+ * is likely to finish before the next tile has been read anyway.
*/
@Override
@SuppressWarnings({"UseSpecificCatch",
"LocalVariableHidesMemberVariable"})
- public void run() {
- Exception error = null;
- TileReadEvent event;
- while ((event = tileEvents.poll()) != null) {
- try {
- final CoordinateReferenceSystem objectiveCRS;
- final AffineTransform objectiveToDisplay;
- synchronized (this) {
- objectiveCRS = this.objectiveCRS;
- objectiveToDisplay = (AffineTransform)
this.objectiveToDisplay;
- }
- final Shape tile =
ShapeConverter.convert(event.outline(objectiveCRS), objectiveToDisplay);
+ public void eventOccured(final TileReadEvent event) {
+ BackgroundThreads.EXECUTOR.execute(() -> {
+ final ObjectiveSnapshot snapshot =
TileReadListener.this.snapshot;
+ if (snapshot.objectiveToDisplay instanceof AffineTransform
objectiveToDisplay) try {
+ final Shape tile =
ShapeConverter.convert(event.outline(snapshot.objectiveCRS),
objectiveToDisplay);
final int ic = event.getPyramidLevel() %
TILE_COLORS.length;
tile.setStroke(TILE_COLORS[ic]);
tile.setFill(FILL_COLORS[ic]);
@@ -1426,21 +1390,20 @@ public class CoverageCanvas extends MapCanvasAWT {
transition.setOnFinished(this);
tileShapes.add(transition);
} catch (Exception e) {
- if (error == null) error = e;
- else error.addSuppressed(e);
- }
- }
- Platform.runLater(() -> {
- final ObservableList<Node> children =
getEvanescentPane().getChildren();
- FadeTransition transition;
- while ((transition = tileShapes.poll()) != null) {
- children.add(transition.getNode());
- transition.play();
+ Logging.recoverableException(LOGGER,
TileReadListener.class, "eventOccured", e);
}
+ Platform.runLater(() -> {
+ FadeTransition transition = tileShapes.poll();
+ if (transition != null) {
+ final ObservableList<Node> children =
snapshot.getChildren();
+ do {
+ children.add(transition.getNode());
+ transition.play();
+ transition = tileShapes.poll();
+ } while (transition != null);
+ }
+ });
});
- if (error != null) {
- Logging.recoverableException(LOGGER, TileReadListener.class,
"run", error);
- }
}
/**
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/FontGIS.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/FontGIS.java
index ac024dc940..70c6fbb21e 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/FontGIS.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/FontGIS.java
@@ -50,7 +50,7 @@ public final class FontGIS {
Font font = null;
final String filename = "font-gis.ttf";
try {
- InputStream in = Code.class.getResourceAsStream(filename);
+ InputStream in = FontGIS.class.getResourceAsStream(filename);
if (in != null) try (in) {
font = Font.loadFont(in, 0);
} else {
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/GUIUtilities.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/GUIUtilities.java
index e401730f63..30c747439b 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/GUIUtilities.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/GUIUtilities.java
@@ -20,6 +20,7 @@ import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;
+import java.util.function.Predicate;
import javafx.beans.property.ReadOnlyProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
@@ -272,6 +273,27 @@ walk: for (final T search : path) {
return modified;
}
+ /**
+ * Alternative to {@link List#removeIf(Predicate)} where elements are
removed by ranges.
+ * The intent is to have events sent for ranges of indexes instead of one
event for each individual element.
+ *
+ * @param <E> type of elements in the list.
+ * @param list list where to remove elements.
+ * @param filter predicate which returns {@code true} for elements to be
removed.
+ *
+ * @see ObservableList#removeIf(Predicate)
+ */
+ public static <E> void removeIf(final ObservableList<E> list, final
Predicate<? super E> filter) {
+ for (int lower = list.size(); --lower >= 0;) {
+ if (filter.test(list.get(lower))) {
+ final int upper = lower;
+ do if (--lower < 0) break;
+ while (filter.test(list.get(lower)));
+ list.remove(lower+1, upper+1);
+ }
+ }
+ }
+
/**
* Returns the longest subsequence common to both specified sequences.
* This is known as <dfn>longest common subsequence</dfn> (LCS) problem.
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/EvanescentPane.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/EvanescentPane.java
index 8e908701a4..d178f5ef0d 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/EvanescentPane.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/EvanescentPane.java
@@ -16,7 +16,6 @@
*/
package org.apache.sis.gui.map;
-import java.util.List;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.collections.ObservableList;
@@ -35,56 +34,54 @@ import javafx.collections.ListChangeListener;
*/
final class EvanescentPane extends Pane implements ListChangeListener<Node> {
/**
- * Whether the pane had at least one child.
+ * The object that created this pane.
*/
- private boolean hadChildren;
+ final MapCanvas.ObjectiveSnapshot owner;
+
+ /**
+ * Whether this pane has been added in {@link MapCanvas#floatingPane}.
+ */
+ private boolean added;
/**
* Creates an initially empty pane with no shape and no transform.
+ *
+ * @param owner the object that created this pane.
*/
- private EvanescentPane() {
+ private EvanescentPane(final MapCanvas.ObjectiveSnapshot owner) {
+ this.owner = owner;
}
/**
- * Returns a pane with an identity transform from the given list of
children.
- * If there is no pane with an identity transform, a new pane is created
and
- * added to the list of children.
+ * Creates an initially empty pane with no shape and no transform.
*
* <p>Note that the returned pane has an identity transform at the time
when
* this method is invoked, but that transform may become non-identity later
* if the user navigates on the map (e.g. zoom or pan events).</p>
*
- * @param children the children of {@link MapCanvas#floatingPane}.
+ * @param owner the object that created this pane.
* @return a pane with an identity transform at the time when this method
is invoked.
*/
- static EvanescentPane getOrCreate(final List<Node> children) {
- for (int i = children.size(); --i >= 0;) {
- if (children.get(i) instanceof EvanescentPane pane) {
- if (pane.getTransforms().isEmpty()) {
- return pane;
- }
- }
- }
- final var pane = new EvanescentPane();
+ static EvanescentPane create(final MapCanvas.ObjectiveSnapshot owner) {
+ final var pane = new EvanescentPane(owner);
pane.getChildren().addListener(pane);
- children.add(pane);
return pane;
}
/**
- * Invoked when the list of children changed.
+ * Invoked after the list of evanescent children changed.
* If the last child has been removed, removes this pane from the map
canvas.
*/
@Override
public void onChanged(Change<? extends Node> change) {
- final ObservableList<Node> children = getChildren();
- if (!children.isEmpty()) {
- hadChildren = true;
- } else if (hadChildren) {
- children.removeListener(this);
- final Pane parent = (Pane) getParent();
- if (parent != null) {
- parent.getChildren().remove(this);
+ if (getChildren().isEmpty() == added) {
+ final ObservableList<Node> siblings =
owner.floatingPane.getChildren();
+ if (added) {
+ siblings.remove(this);
+ added = false;
+ } else {
+ siblings.add(this);
+ added = true;
}
}
}
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
index 4b5d1b5b72..858c21f857 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
@@ -45,6 +45,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WritableValue;
+import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.scene.control.ContextMenu;
@@ -192,7 +193,7 @@ public abstract class MapCanvas extends PlanarCanvas {
* in a background thread. Once calculation is completed and the content
of this pane has been updated,
* the {@code floatingPane} {@link Affine} transform is reset to
identity.</p>
*
- * @see #getEvanescentPane()
+ * @see #forObjectiveSnapshot()
*/
protected final Pane floatingPane;
@@ -225,7 +226,8 @@ public abstract class MapCanvas extends PlanarCanvas {
private GridGeometry initialState;
/**
- * Incremented when the map needs to be rendered again.
+ * Incremented when the map needs to be rendered again. It is okay if this
value overflows,
+ * because we just need to distinguish between a few consecutive repaint
events.
*
* @see #renderedContentStamp
* @see #contentsChanged()
@@ -441,24 +443,128 @@ public abstract class MapCanvas extends PlanarCanvas {
* Returns a pane for evanescent shapes shown in this canvas. The shapes
added in the returned pane should
* use an animation effect such as {@link javafx.animation.FadeTransition}
and be removed after a few seconds.
* This method allows to show shapes more easily than adding them directly
as {@link #floatingPane} children,
- * but at the expanse of quality. Evanescent panes are easier to use
because the callers do not need to care
- * about changes of the {@linkplain #getObjectiveToDisplay() objective to
display} transform.
- * Instead, callers can add shapes with coordinate values computed using
the current transform
- * at the time when this method is invoked, and ignore the changes that
may happen afterward.
- * If the user continues to navigate on the map, the {@linkplain
Pane#getTransforms() list of transforms}
- * of the returned pane will be updated.
- *
- * <p>While this method allows a much easier strategy than adding shapes
into {@link #floatingPane}
- * and tracking <i>objective to display</i> changes, the result is rougher
(especially after zooms).
- * For this reason, this method should be used for short-lived shapes such
as animation effects,
- * when the high-quality strategy is not worth the effort. Since the
returned pane is aimed to be short-lived,
- * it is automatically removed from this {@code MapPane} when its list of
children become empty.</p>
+ * but at the expanse of quality. See {@link ObjectiveSnapshot} for more
information.
*
* @return a pane where to add shapes without the need to track navigation
events after this method call.
* @since 1.7
*/
- public Pane getEvanescentPane() {
- return EvanescentPane.getOrCreate(floatingPane.getChildren());
+ public ObjectiveSnapshot forObjectiveSnapshot() {
+ return new ObjectiveSnapshot(this).unique();
+ }
+
+ /**
+ * JavaFX nodes rendered using a snapshot of the <i>objective to
display</i> transform.
+ * This object provides a way to show shapes more easily than adding them
as {@link #floatingPane} children,
+ * but at the expanse of quality. This object is easier to use because it
allows to ignore the dynamic nature
+ * of the {@linkplain MapCanvas#getObjectiveToDisplay() transform managed
by the enclosing canvas}.
+ * Instead, users can add shapes with coordinate values computed with a
{@linkplain #getObjectiveToDisplay()
+ * snapshot of the transform at the time when this instance is obtained},
+ * then ignore all changes in the enclosing canvas that may happen
afterward.
+ *
+ * <p>While this object allows a much easier strategy than adding shapes
into {@link #floatingPane}
+ * and tracking <i>objective to display</i> changes, the result is rougher
(especially after zooms).
+ * For this reason, this object should be used for short-lived shapes such
as animation effects,
+ * when the high-quality strategy is not worth the effort. The nodes added
through this object
+ * may disappear at any time, for example after a change of objective
<abbr>CRS</abbr>.</p>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.7
+ * @since 1.7
+ */
+ public static class ObjectiveSnapshot {
+ /**
+ * The parent where to add the nodes computed from a snapshot of this
<i>objective to display</i> transform.
+ *
+ * @see MapCanvas#floatingPane
+ */
+ final Pane floatingPane;
+
+ /**
+ * The <abbr>CRS</abbr> which is the source of the <i>objective to
display</i> transform.
+ * This is the value of {@link #getObjectiveCRS()} at the time when
this
+ * {@code ObjectiveSnapshot} instance has been obtained.
+ * May be {@code null} if the snapshot was created before {@code
Canvas} was initialized with data.
+ *
+ * @see #getObjectiveCRS()
+ */
+ public final CoordinateReferenceSystem objectiveCRS;
+
+ /**
+ * Returns the (usually affine) conversion from objective
<abbr>CRS</abbr> to display coordinate system.
+ * This is the value of {@link #getObjectiveToDisplay()} at the time
when this {@code ObjectiveSnapshot}
+ * instance has been obtained. This is never {@code null}.
+ *
+ * @see #getObjectiveToDisplay()
+ */
+ public final LinearTransform objectiveToDisplay;
+
+ /**
+ * The pane of evanescent nodes with coordinates computed from this
snapshot.
+ * This is created when first needed.
+ */
+ private EvanescentPane pane;
+
+ /**
+ * Creates a snapshot of the <i>objective to display</i> transform
from the given canvas.
+ * This constructor is made available for {@code MapCanvas} subclasses
wanting to override
+ * their {@link #forObjectiveSnapshot()} method. This constructor
should be used in a code
+ * equivalent to the following snippet:
+ *
+ * {@snippet lang="java" :
+ * @Override
+ * public ObjectiveSnapshot forObjectiveSnapshot() {
+ * return new MySnapshotSubclass(this).unique();
+ * }
+ * }
+ *
+ * @param canvas the enclosing canvas.
+ *
+ * @see #forObjectiveSnapshot()
+ */
+ protected ObjectiveSnapshot(final MapCanvas canvas) {
+ floatingPane = canvas.floatingPane;
+ objectiveCRS = canvas.getObjectiveCRS();
+ objectiveToDisplay = canvas.getObjectiveToDisplay();
+ }
+
+ /**
+ * Returns a unique instance of this snapshot. If the canvas specified
at construction time
+ * already contains an {@code ObjectiveSnapshot} of the same class as
{@code this} and with
+ * equal {@link #objectiveCRS} and {@link #objectiveToDisplay}
transform, then that snapshot
+ * is returned and {@code this} should be discarded. Otherwise, this
method returns {@code this}.
+ *
+ * @return a unique instance of this snapshot.
+ */
+ protected ObjectiveSnapshot unique() {
+ final ObservableList<Node> siblings = floatingPane.getChildren();
+ for (int i = siblings.size(); --i >= 0;) {
+ if (siblings.get(i) instanceof EvanescentPane pane) {
+ final ObjectiveSnapshot other = pane.owner;
+ if (other.getClass() == getClass()
+ && Objects.equals(objectiveCRS, other.objectiveCRS)
+ &&
objectiveToDisplay.equals(other.objectiveToDisplay))
+ {
+ return other;
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns children to show with coordinates computed from the
<i>objective to display</i> snapshot.
+ * The shapes added in the children list should use an animation
effect such as
+ * {@link javafx.animation.FadeTransition} and be removed from the
list after a few seconds.
+ * This method shall be invoked from the JavaFX thread.
+ *
+ * @return children to show using the <i>objective to display</i>
snapshot.
+ */
+ public ObservableList<Node> getChildren() {
+ if (pane == null) {
+ pane = EvanescentPane.create(this);
+ }
+ return pane.getChildren();
+ }
}
/**
@@ -957,11 +1063,16 @@ public abstract class MapCanvas extends PlanarCanvas {
* the current image, then a more accurate image is prepared in a
background thread.
*
* <h4>Transform events</h4>
- * This method fires immediately an {@value
#OBJECTIVE_TO_DISPLAY_PROPERTY} event with
- * {@link TransformChangeEvent.Reason#INTERIM}. This event does not yet
reflect the state of the
- * {@linkplain #getObjectiveToDisplay() objective to display} transform.
At some arbitrary time in the future,
- * another {@value #OBJECTIVE_TO_DISPLAY_PROPERTY} event will occur (still
in JavaFX thread)
- * with {@link TransformChangeEvent.Reason#DISPLAY_NAVIGATION} (really
display, not objective).
+ * This method sends immediately a {@link TransformChangeEvent}
+ * with the {@value #OBJECTIVE_TO_DISPLAY_PROPERTY} property name
+ * and {@link TransformChangeEvent.Reason#INTERIM Reason.INTERIM}.
+ * The interim reason means that the change is reflected visually but not
yet
+ * in the {@linkplain #getObjectiveToDisplay() objective to display}
transform.
+ * A short time after this method call, the objective to display transform
is effectively updated
+ * and another {@value #OBJECTIVE_TO_DISPLAY_PROPERTY} event will be sent
in the JavaFX thread,
+ * but with {@link TransformChangeEvent.Reason#DISPLAY_NAVIGATION
Reason.DISPLAY_NAVIGATION}.
+ * Note that the latter event really expresses the change in display units,
+ * not in the objective units of the transform given to this method.
* That event will consolidate all {@code INTERIM} events that happened
since the last non-interim event.
*
* @param before coordinate conversion to apply before the current
<i>objective to display</i> transform.
@@ -992,12 +1103,15 @@ public abstract class MapCanvas extends PlanarCanvas {
* the current image, then a more accurate image is prepared in a
background thread.
*
* <h4>Transform events</h4>
- * This method fires immediately an {@value
#OBJECTIVE_TO_DISPLAY_PROPERTY} event with
- * {@link TransformChangeEvent.Reason#INTERIM}. This event does not yet
reflect the state of the
- * {@linkplain #getObjectiveToDisplay() objective to display} transform.
At some arbitrary time in the future,
- * another {@value #OBJECTIVE_TO_DISPLAY_PROPERTY} event will occur (still
in JavaFX thread)
- * with {@link TransformChangeEvent.Reason#DISPLAY_NAVIGATION}. That event
will consolidate
- * all {@code INTERIM} events that happened since the last non-interim
event.
+ * This method sends immediately a {@link TransformChangeEvent}
+ * with the {@value #OBJECTIVE_TO_DISPLAY_PROPERTY} property name
+ * and {@link TransformChangeEvent.Reason#INTERIM Reason.INTERIM}.
+ * The interim reason means that the change is reflected visually but not
yet
+ * in the {@linkplain #getObjectiveToDisplay() objective to display}
transform.
+ * A short time after this method call, the objective to display transform
is effectively updated
+ * and another {@value #OBJECTIVE_TO_DISPLAY_PROPERTY} event will be sent
in the JavaFX thread,
+ * but with {@link TransformChangeEvent.Reason#DISPLAY_NAVIGATION
Reason.DISPLAY_NAVIGATION}.
+ * That event will consolidate all {@code INTERIM} events that happened
since the last non-interim event.
*
* @param after coordinate conversion to apply after the current
<i>objective to display</i> transform.
*
@@ -1037,7 +1151,7 @@ public abstract class MapCanvas extends PlanarCanvas {
/**
* Fires a {@link TransformChangeEvent} for a change in the {@link
#transform}.
- * This method needs a modifiable {@code before} instance; it will be
modified.
+ * This method needs a modifiable {@code before} instance as it will
modify it.
*
* @param before value of {@link #getInterimTransform(boolean)} before
the change.
* @param change change in pixel coordinates, or {@code null} for lazy
computation.
@@ -1060,7 +1174,7 @@ public abstract class MapCanvas extends PlanarCanvas {
* if (interim != null) {
* fireInterimTransform(interim, change);
* }
- * }
+ * }
*
* @return a copy of {@link #transform} as a modifiable Java2D object, or
{@code null} if not needed.
*/
@@ -1193,7 +1307,7 @@ public abstract class MapCanvas extends PlanarCanvas {
public final void requestRepaint() {
contentChangeCount++;
if (renderingInProgress == null && !isRendering.get()) {
- final Delayed delay = new Delayed();
+ final var delay = new Delayed();
BackgroundThreads.execute(delay);
renderingInProgress = delay; // Set last after we know that the
task has been scheduled.
}
@@ -1210,7 +1324,7 @@ public abstract class MapCanvas extends PlanarCanvas {
assert Platform.isFxApplicationThread();
/*
* If a rendering is already in progress, do not send a new request
now.
- * Wait for current rendering to finish; a new one will be
automatically
+ * Wait for current rendering to finish. A new one will be
automatically
* requested if content changes are detected after the rendering.
*/
if (renderingInProgress != null) {
@@ -1223,7 +1337,7 @@ public abstract class MapCanvas extends PlanarCanvas {
}
}
hasError = false;
- isRendering.set(true); // Avoid that
`requestRepaint(…)` trig new paints.
+ isRendering.set(true); // Avoid that `requestRepaint(…)` trig new
paints.
renderingStartTime = System.nanoTime();
try {
/*
@@ -1233,16 +1347,17 @@ public abstract class MapCanvas extends PlanarCanvas {
if (sizeChanged) {
sizeChanged = false;
final Pane view = floatingPane;
- Envelope2D bounds = new Envelope2D(null, view.getLayoutX(),
view.getLayoutY(), view.getWidth(), view.getHeight());
+ var bounds = new Envelope2D(null, view.getLayoutX(),
view.getLayoutY(), view.getWidth(), view.getHeight());
if (bounds.isEmpty()) return;
setDisplayBounds(bounds);
}
/*
* Compute the `objectiveToDisplay` only before the first
rendering, because the display
* bounds may not be known before (it may be zero at the time
`MapCanvas` is initialized).
- * This code is executed only once for a new map.
+ * This code is executed only once after construction, reset or
change of grid geometry.
*/
if (invalidObjectiveToDisplay) {
+ GUIUtilities.removeIf(floatingPane.getChildren(), (child) ->
child instanceof EvanescentPane);
final Envelope2D target = getDisplayBounds();
if (target == null) {
// Bounds are still unknown. Another repaint event will
happen when they will become known.
@@ -1471,13 +1586,13 @@ public abstract class MapCanvas extends PlanarCanvas {
}
/**
- * A pseudo-rendering task which wait for some delay before to perform the
real repaint.
+ * A pseudo-rendering task which waits for some delay before to perform
the real repaint.
* The intent is to collect some more gesture events (pans, zooms,
<i>etc.</i>) before consuming CPU time.
* This is especially useful when the first gesture event is a tiny change
because the user just started
* panning or zooming.
*
* <h4>Design note</h4>
- * using a thread for waiting seems a waste of resources, but a thread
(likely this one) is going to be used
+ * Using a thread for waiting seems a waste of resources, but a thread
(likely this one) is going to be used
* for real after the waiting time is elapsed. That thread usually exists
anyway in {@link BackgroundThreads}
* as an idle thread, and it is unlikely that other parts of this JavaFX
application need that thread in same
* time (if it happens, other threads will be created).
@@ -1741,7 +1856,7 @@ public abstract class MapCanvas extends PlanarCanvas {
*/
@Override
public String toString() {
- final Formatter buffer = new Formatter();
+ final var buffer = new Formatter();
final double tx = transform.getTx();
final double ty = transform.getTy();
try {
diff --git
a/optional/src/org.apache.sis.gui/test/org/apache/sis/gui/internal/GUIUtilitiesTest.java
b/optional/src/org.apache.sis.gui/test/org/apache/sis/gui/internal/GUIUtilitiesTest.java
index c95d1ab61c..2f54cdc0f3 100644
---
a/optional/src/org.apache.sis.gui/test/org/apache/sis/gui/internal/GUIUtilitiesTest.java
+++
b/optional/src/org.apache.sis.gui/test/org/apache/sis/gui/internal/GUIUtilitiesTest.java
@@ -16,7 +16,11 @@
*/
package org.apache.sis.gui.internal;
+import java.util.Arrays;
import java.util.List;
+import java.util.function.Predicate;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
import javafx.scene.control.TreeItem;
import javafx.scene.paint.Color;
@@ -116,6 +120,17 @@ public final class GUIUtilitiesTest extends TestCase {
assertTrue(root.getChildren().isEmpty());
}
+ /**
+ * Tests {@link GUIUtilities#testRemoveIf(Predicate)}.
+ */
+ @Test
+ public void testRemoveIf() {
+ final ObservableList<Integer> list =
FXCollections.observableArrayList();
+ list.addAll(4, 7, 3, 2, 4, 8, 1);
+ GUIUtilities.removeIf(list, (i) -> (i & 1) == 0); // Remove even
values.
+ assertEquals(list, Arrays.asList(7, 3, 1));
+ }
+
/**
* Tests {@link GUIUtilities#longestCommonSubsequence(List, List)}.
*/