You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/10/17 19:14:34 UTC
[sis] 02/03: Better identification of case where the image shown in
`MapCanvas` is in error (i.e. because an exception has been thrown during
the rendering process).
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 17bf8499d87b8008b4892b7a012bd15cba46d6e8
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun Oct 17 20:00:40 2021 +0200
Better identification of case where the image shown in `MapCanvas` is in error
(i.e. because an exception has been thrown during the rendering process).
---
.../java/org/apache/sis/gui/map/MapCanvas.java | 87 +++++++++++++++++-----
.../java/org/apache/sis/gui/map/MapCanvasAWT.java | 74 +++++++++++++++++-
.../java/org/apache/sis/gui/map/package-info.java | 2 +-
.../apache/sis/internal/gui/BackgroundThreads.java | 4 +-
.../org/apache/sis/internal/gui/package-info.java | 2 +-
5 files changed, 144 insertions(+), 25 deletions(-)
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 cdf0a26..288bfda 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
@@ -122,7 +122,7 @@ import static org.apache.sis.internal.util.StandardDateFormat.NANOS_PER_MILLISEC
* </ol>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
* @since 1.1
* @module
*/
@@ -273,6 +273,13 @@ public abstract class MapCanvas extends PlanarCanvas {
private boolean isMouseChangeScheduled;
/**
+ * {@code true} if navigation should be disabled.
+ *
+ * @see #setNavigationDisabled(boolean)
+ */
+ private boolean isNavigationDisabled;
+
+ /**
* Whether a rendering is in progress. This property is set to {@code true} when {@code MapCanvas}
* is about to start a background thread for performing a rendering, and is reset to {@code false}
* after the {@code MapCanvas} has been updated with new rendering result.
@@ -290,6 +297,21 @@ public abstract class MapCanvas extends PlanarCanvas {
private final ReadOnlyObjectWrapper<Throwable> error;
/**
+ * Whether the {@link #error} value should be considered non-null.
+ * This is set to {@code false} when a new painting start.
+ * If the value is still {@code false} after painting finished, we can clear {@link #error}.
+ *
+ * <h4>Rational</h4>
+ * A simpler approach would have been to clear {@link #error} when painting start, but this action
+ * fires an event which may resize the {@link StatusBar} height, which in turn may change the size
+ * of this {@code MapCanvas} and cause a new painting. The new painting may fail again, which causes
+ * an error to be reported, which may cause the status bar to change its height again, <i>etc.</i>
+ * It can cause a loop with hundred of repaints before the system stabilize.
+ * Using this flag avoids above problem.
+ */
+ private boolean hasError;
+
+ /**
* If a contextual menu is currently visible, that menu. Otherwise {@code null}.
*/
private ContextMenu menuShown;
@@ -356,11 +378,13 @@ public abstract class MapCanvas extends PlanarCanvas {
switch (event.getButton()) {
case PRIMARY: {
hideContextMenu();
- floatingPane.setCursor(Cursor.CLOSED_HAND);
- floatingPane.requestFocus();
- isDragging = true;
- xPanStart = x;
- yPanStart = y;
+ if (!isNavigationDisabled) {
+ floatingPane.setCursor(Cursor.CLOSED_HAND);
+ floatingPane.requestFocus();
+ isDragging = true;
+ xPanStart = x;
+ yPanStart = y;
+ }
event.consume();
break;
}
@@ -418,6 +442,10 @@ public abstract class MapCanvas extends PlanarCanvas {
// Do not interpret scroll events on touch pad as a zoom.
return;
}
+ if (isNavigationDisabled) {
+ event.consume();
+ return;
+ }
final double delta = event.getDeltaY();
double zoom = Math.abs(delta) / SCROLL_EVENT_SIZE * ZOOM_FACTOR;
if (event.isControlDown()) {
@@ -441,7 +469,7 @@ public abstract class MapCanvas extends PlanarCanvas {
* @see #applyTranslation(double, double, boolean)
*/
private void applyZoomOrRotate(final GestureEvent event, final double zoom, final double angle) {
- if (zoom != 1 || angle != 0) {
+ if (!isNavigationDisabled && (zoom != 1 || angle != 0)) {
double x, y;
if (event != null) {
x = event.getX();
@@ -481,6 +509,10 @@ public abstract class MapCanvas extends PlanarCanvas {
* or zoom-in / zoom-out with page-down / page-up keys. If the control key is down, navigation is finer.
*/
private void onKeyTyped(final KeyEvent event) {
+ if (isNavigationDisabled) {
+ event.consume();
+ return;
+ }
double tx = 0, ty = 0, zoom = 1, angle = 0;
if (event.isAltDown()) {
switch (event.getCode()) {
@@ -532,6 +564,16 @@ public abstract class MapCanvas extends PlanarCanvas {
}
/**
+ * Disables or re-enable navigation. Navigation is disabled when an error occurred while
+ * rendering the image, and navigating is likely to cause the error to happen again.
+ */
+ final void setNavigationDisabled(final boolean disabled) {
+ isNavigationDisabled = disabled;
+ if (disabled) isDragging = false;
+ floatingPane.setCursor(disabled ? Cursor.DEFAULT : Cursor.CROSSHAIR);
+ }
+
+ /**
* If a context menu is currently shown, hide that menu. Otherwise does nothing.
*/
private void hideContextMenu() {
@@ -884,13 +926,14 @@ public abstract class MapCanvas extends PlanarCanvas {
return;
}
}
+ hasError = false;
isRendering.set(true); // Avoid that `requestRepaint(…)` trig new paints.
renderingStartTime = System.nanoTime();
- /*
- * If a new canvas size is known, inform the parent `PlanarCanvas` about that.
- * It may cause a recomputation of the "objective to display" transform.
- */
try {
+ /*
+ * If a new canvas size is known, inform the parent `PlanarCanvas` about that.
+ * It may cause a recomputation of the "objective to display" transform.
+ */
if (sizeChanged) {
sizeChanged = false;
final Pane view = floatingPane;
@@ -986,7 +1029,9 @@ public abstract class MapCanvas extends PlanarCanvas {
isMouseChangeScheduled = true;
}
} else {
- clearError();
+ if (!hasError) {
+ clearError();
+ }
isRendering.set(false);
restoreCursorAfterPaint();
}
@@ -1047,6 +1092,8 @@ public abstract class MapCanvas extends PlanarCanvas {
final Throwable ex = task.getException();
if (ex != null) {
errorOccurred(ex);
+ } else if (!hasError) {
+ clearError();
}
}
@@ -1161,6 +1208,7 @@ public abstract class MapCanvas extends PlanarCanvas {
* Clears the error message in status bar.
*/
protected final void clearError() {
+ hasError = false;
error.set(null);
}
@@ -1172,12 +1220,15 @@ public abstract class MapCanvas extends PlanarCanvas {
* @param ex the exception that occurred (can not be null).
*/
protected void errorOccurred(final Throwable ex) {
- final Throwable current = error.get();
- if (current != null) {
- current.addSuppressed(ex);
- } else {
- error.set(Objects.requireNonNull(ex));
+ if (hasError) {
+ final Throwable current = error.get();
+ if (current != null) {
+ current.addSuppressed(ex);
+ return;
+ }
}
+ hasError = true;
+ error.set(Objects.requireNonNull(ex));
}
/**
@@ -1199,6 +1250,8 @@ public abstract class MapCanvas extends PlanarCanvas {
invalidObjectiveToDisplay = true;
objectiveBounds = null;
clearError();
+ isDragging = false;
+ isNavigationDisabled = false;
isRendering.set(false);
requestRepaint();
}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java
index 1553a93..3fd42b7 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java
@@ -19,9 +19,12 @@ package org.apache.sis.gui.map;
import java.util.Locale;
import java.nio.IntBuffer;
import java.awt.AlphaComposite;
+import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GraphicsConfiguration;
+import java.awt.Font;
+import java.awt.font.GlyphVector;
import java.awt.image.DataBufferInt;
import java.awt.image.RenderedImage;
import java.awt.image.BufferedImage;
@@ -48,7 +51,7 @@ import org.apache.sis.internal.coverage.j2d.ColorModelFactory;
* controls by the user.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
* @since 1.1
* @module
*/
@@ -63,6 +66,16 @@ public abstract class MapCanvasAWT extends MapCanvas {
private static final boolean NATIVE_ACCELERATION = false;
/**
+ * Size (in pixels) of the warning symbol to show if rendering fail.
+ */
+ private static final int WARNING_SIZE = 200;
+
+ /**
+ * The symbols to show on top of the image as a warning when rendering failed.
+ */
+ private static final String WARNING_TEXT = "\u26A0";
+
+ /**
* Number of additional pixels to paint on each sides of the image, outside the viewing area.
* Computing a larger image reduces the black borders that user sees during translations or
* during zoom out before the new image is repainted. The default value is {@code null}.
@@ -122,6 +135,12 @@ public abstract class MapCanvasAWT extends MapCanvas {
protected final ImageView image;
/**
+ * Whether {@link #WARNING_TEXT} has been drawn on top of the image.
+ * When warning is shown, navigation should be disabled.
+ */
+ private boolean isWarningShown;
+
+ /**
* Creates a new canvas for JavaFX application.
*
* @param locale the locale to use for labels and some messages, or {@code null} for default.
@@ -409,6 +428,7 @@ public abstract class MapCanvasAWT extends MapCanvas {
bufferWrapper = wrapper;
bufferConfiguration = configuration;
final boolean done = renderer.commit(MapCanvasAWT.this);
+ clearWarning();
renderingCompleted(this);
if (!done || contentsChanged()) {
repaint();
@@ -517,7 +537,7 @@ public abstract class MapCanvasAWT extends MapCanvas {
} finally {
gr.dispose();
}
- return null;
+ return null; // Indicate that the entire buffer was dirty.
}
/**
@@ -536,14 +556,60 @@ public abstract class MapCanvasAWT extends MapCanvas {
}
renderer.translate(image);
final boolean done = renderer.commit(MapCanvasAWT.this);
+ clearWarning();
renderingCompleted(this);
if (!done || contentsLost || contentsChanged()) {
repaint();
}
}
- @Override protected void failed() {renderingCompleted(this);}
- @Override protected void cancelled() {renderingCompleted(this);}
+ /** Clears the image in the same way than failure. */
+ @Override protected void cancelled() {failed();}
+
+ /**
+ * Invoked in JavaFX thread on failure. No result is available. The JavaFX image is set to an empty image.
+ */
+ @Override
+ protected void failed() {
+ if (!isWarningShown) {
+ final int rw = renderer.getWidth();
+ final int rh = renderer.getHeight();
+ final int x = Math.max((rw - WARNING_SIZE) / 2, 0);
+ final int y = Math.max((rh - WARNING_SIZE) / 2, 0);
+ final int width = rw - 2*x;
+ final int height = rh - 2*y;
+ final Font font = new Font(Font.SERIF, Font.PLAIN, height);
+ bufferWrapper.updateBuffer((final PixelBuffer<IntBuffer> wrapper) -> {
+ final Graphics2D gr = buffer.createGraphics();
+ try {
+ renderer.translate(gr);
+ gr.setColor(new Color(0XA0804040, true));
+ gr.fillRoundRect(x, y, width, height, width/5, height/5);
+ gr.setColor(Color.RED);
+ final GlyphVector glyphs = font.createGlyphVector(gr.getFontRenderContext(), WARNING_TEXT);
+ final java.awt.geom.Rectangle2D vb = glyphs.getVisualBounds();
+ gr.drawGlyphVector(glyphs, (float) (x + 0.5*width - vb.getCenterX()),
+ (float) (y + 0.5*height - vb.getCenterY()));
+ } finally {
+ gr.dispose();
+ }
+ return null;
+ });
+ isWarningShown = true;
+ setNavigationDisabled(true);
+ }
+ renderingCompleted(this);
+ }
+ }
+
+ /**
+ * If warning symbol was drawn on top of the image, clears it and restores navigation.
+ */
+ private void clearWarning() {
+ if (isWarningShown) {
+ isWarningShown = false;
+ setNavigationDisabled(false);
+ }
}
/**
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/package-info.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/package-info.java
index 19e49b1..694f2d9 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/package-info.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/package-info.java
@@ -22,7 +22,7 @@
* {@link org.apache.sis.gui.map.MapCanvasAWT} is a specialization for painting the map using Java2D.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
* @since 1.1
* @module
*/
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/BackgroundThreads.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/BackgroundThreads.java
index 312292f..c2e9316 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/BackgroundThreads.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/BackgroundThreads.java
@@ -46,7 +46,7 @@ import org.apache.sis.util.logging.Logging;
* Users should not rely on this implementation details.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
* @since 1.1
* @module
*/
@@ -133,7 +133,7 @@ public final class BackgroundThreads extends AtomicInteger implements ThreadFact
try {
return f.get();
} catch (ExecutionException e) {
- ExceptionReporter.show(DataViewer.getCurrentStage(), null, null, e.getCause());
+ ExceptionReporter.show(DataViewer.getCurrentStage(), null, null, e);
} catch (InterruptedException e) {
interrupted("runAndWait", e);
}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/package-info.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/package-info.java
index 3f36f7f..3534a98 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/package-info.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/package-info.java
@@ -25,7 +25,7 @@
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
* @since 1.1
* @module
*/