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 7fa7573  Replaced the `loading` flag by `status` with more advanced 
cabability to report error.
7fa7573 is described below

commit 7fa75738b61e8e8878a0ea89e20b3654d71fac29
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun Jan 26 00:54:15 2020 +0100

    Replaced the `loading` flag by `status` with more advanced cabability to 
report error.
---
 .../org/apache/sis/gui/coverage/GridError.java     | 103 +++++++++++
 .../java/org/apache/sis/gui/coverage/GridTile.java | 202 ++++++++++++++++-----
 .../java/org/apache/sis/gui/coverage/GridView.java | 158 ++++++++++------
 .../org/apache/sis/gui/coverage/GridViewSkin.java  |  54 +++++-
 4 files changed, 406 insertions(+), 111 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridError.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridError.java
new file mode 100644
index 0000000..269a89d
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridError.java
@@ -0,0 +1,103 @@
+/*
+ * 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.coverage;
+
+import java.awt.Rectangle;
+import javafx.scene.control.Label;
+import javafx.scene.paint.Color;
+import org.apache.sis.util.Classes;
+
+
+/**
+ * Controls to put in the middle of a tile if an error occurred while loading 
that tile.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class GridError extends Label {
+    /**
+     * If we failed to fetch a tile, the tile in error. If more than one tile 
has an error,
+     * then the tile having the largest intersection with the view area. This 
visible error
+     * may change during scrolling.
+     */
+    private GridTile.Error visibleError;
+
+    /**
+     * The zero-based row and columns indices of the area currently shown in 
{@link GridView}.
+     * This is updated by {@link GridViewSkin#layoutChildren(double, double, 
double, double)}.
+     */
+    private Rectangle viewArea;
+
+    /**
+     * Incremented every time that a new layout is performed. This is used for 
detecting if a
+     * {@link GridTile.Error} instance should recompute its visible area. It 
is not a problem
+     * if this value overflows; we just check if values differ, not which one 
is greater.
+     *
+     * @see GridTile.Error#updateCount
+     */
+    private int updateCount;
+
+    /**
+     * Creates a new error control.
+     */
+    GridError() {
+        setTextFill(Color.RED);
+    }
+
+    /**
+     * Invoked by {@link GridViewSkin#layoutChildren(double, double, double, 
double)} when a new layout is beginning.
+     *
+     * @param  area  zero-based row and columns indices of the area currently 
shown in {@link GridView}.
+     */
+    final void initialize(final Rectangle area) {
+        setVisible(false);
+        viewArea     = area;
+        visibleError = null;
+        updateCount++;
+    }
+
+    /**
+     * Updates this error control with the given status. If this control is 
already showing an error message,
+     * it will be updated only if the given status cover a larger view area.
+     *
+     * @param  status  the candidate error status.
+     */
+    final boolean update(final GridTile.Error status) {
+        if (status != visibleError && status.updateAndCompare(updateCount, 
viewArea, visibleError)) {
+            visibleError = status;
+            final Throwable exception = status.exception;
+            String message = exception.getLocalizedMessage();
+            if (message == null) {
+                message = Classes.getShortClassName(exception);
+            }
+            setText(message);
+            setVisible(true);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the zero-based row and column indices of the region in error in 
the view area.
+     * This method returns a direct reference to internal instance; do not 
modify.
+     */
+    final Rectangle getVisibleArea() {
+        return visibleError.visibleArea;
+    }
+}
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridTile.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridTile.java
index e9e2a4e..32b3110 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridTile.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridTile.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.gui.coverage;
 
+import java.awt.Rectangle;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import javafx.concurrent.Task;
@@ -37,25 +38,25 @@ final class GridTile {
     final int tileX, tileY;
 
     /**
-     * Hash code value computed from tile indices.
+     * Hash code value computed from tile indices only. Other fields must be 
ignored.
+     *
+     * @see #hashCode()
      */
     private final int hashCode;
 
     /**
      * The tile, or {@code null} if not yet loaded.
+     *
+     * @see #tile()
      */
-    Raster tile;
-
-    /**
-     * Non-null if an error occurred while reading the tile.
-     */
-    Throwable error;
+    private Raster tile;
 
     /**
-     * Whether a tile loading is in progress. Used for avoiding to create many 
threads requesting the same tile.
-     * If loading is in progress, other requests for that tile will return 
{@code null} immediately.
+     * Non-null if a loading is in progress or if an error occurred while 
fetching the tile.
+     * The loading status is used for avoiding to create many threads 
requesting the same tile.
+     * If loading is in progress, requests for that tile will return {@code 
null} immediately.
      */
-    private boolean loading;
+    private Error status;
 
     /**
      * Creates a new tile for the given tile coordinates.
@@ -67,14 +68,55 @@ final class GridTile {
     }
 
     /**
+     * Returns a hash code value for this tile. This hash code value must be 
based only on tile indices;
+     * the {@link #tile} and the {@link #status} must be ignored, because we 
will use {@link GridTile}
+     * instances also as keys for locating tiles in a hash map.
+     */
+    @Override
+    public int hashCode() {
+        return hashCode;
+    }
+
+    /**
+     * Compares the indices of this tile with the given object for equality.
+     * Only indices are compared; the raster is ignored. See {@link 
#hashCode()} for more information.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other instanceof GridTile) {
+            final GridTile that = (GridTile) other;
+            return tileX == that.tileX && tileY == that.tileY;
+            // Intentionally no other comparisons.
+        }
+        return false;
+    }
+
+    /**
+     * Returns a string representation for debugging purpose only.
+     */
+    @Override
+    public String toString() {
+        return getClass().getCanonicalName() + '[' + tileX + ", " + tileY + 
']';
+    }
+
+    /**
+     * Returns the cached tile if available, or {@code null} otherwise.
+     * If null, then the caller should invoke {@link #load(GridView)}.
+     */
+    final Raster tile() {
+        return tile;
+    }
+
+    /**
      * Loads tile from the given image in a background thread and informs the 
specified view
-     * when the tile become available.
+     * when the tile become available. If we already failed to load that tile 
in a previous
+     * attempt, then this method may set the {@link GridViewSkin#error} field.
      *
      * @param  view  the view for which to load a tile.
      */
     final void load(final GridView view) {
-        if (!loading && error == null) {
-            loading = true;
+        if (status == null) {
+            status = Error.LOADING;                                         // 
Pseudo-error.
             final RenderedImage image = view.getImage();
             BackgroundThreads.execute(new Task<Raster>() {
                 /** Invoked in background thread for fetching the tile. */
@@ -82,60 +124,124 @@ final class GridTile {
                     return image.getTile(tileX, tileY);
                 }
 
-                /** Invoked in JavaFX thread on success. */
+                /**
+                 * Invoked in JavaFX thread on success. If {@link GridView} is 
still showing
+                 * the same image, it will be informed that the tile is 
available. Otherwise
+                 * (if the image has changed) we ignore the result.
+                 */
                 @Override protected void succeeded() {
                     super.succeeded();
-                    tile    = getValue();
-                    error   = null;
-                    loading = false;
-                    view.contentChanged(false);
+                    tile   = null;
+                    status = null;
+                    if (view.getImage() == image) {
+                        tile = getValue();
+                        view.contentChanged(false);
+                    }
                 }
 
-                /** Invoked in JavaFX thread on failure. */
+                /**
+                 * Invoked in JavaFX thread on failure. Discards everything 
and sets the error message
+                 * if {@link GridView} is still showing the image for which we 
failed to load a tile.
+                 */
                 @Override protected void failed() {
                     super.failed();
-                    tile    = null;
-                    error   = getException();
-                    loading = false;
+                    tile   = null;
+                    status = null;
+                    if (view.getImage() == image) {
+                        status = new Error(view.getTileBounds(tileX, tileY), 
getException());
+                        view.contentChanged(false);     // For rendering the 
error message.
+                    }
                 }
 
-                /** Invoked in JavaFX thread on cancellation. */
+                /**
+                 * Invoked in JavaFX thread on cancellation. Just discard 
everything.
+                 * Ideally we should interrupt the {@link 
RenderedImage#getTile(int, int)}
+                 * process, but we currently have no API for that.
+                 */
                 @Override protected void cancelled() {
                     super.cancelled();
-                    tile    = null;
-                    error   = null;
-                    loading = false;
+                    tile   = null;
+                    status = null;
                 }
             });
+        } else if (status != Error.LOADING) {
+            /*
+             * A previous attempt failed to load that tile. We may have an 
error message to report.
+             * If more than one tile failed, take the one with largest visible 
area.
+             */
+            ((GridViewSkin) view.getSkin()).errorOccurred(status);
         }
     }
 
     /**
-     * Returns a hash code value for this tile.
+     * The status of a tile request, either {@link #LOADING} or any other 
instance in case of error.
+     * If not {@link #LOADING}, this class contains the reason why a tile 
request failed, together
+     * with some information that depends on the viewing context. In 
particular {@link #visibleArea}
+     * needs to be recomputed every time the viewed area in the {@link 
GridView} changed.
      */
-    @Override
-    public int hashCode() {
-        return hashCode;
-    }
+    static final class Error {
+        /**
+         * A pseudo-error for saying that the tile is being fetched. Its 
effect is similar to an error
+         * in the sense that {@link #load(GridView)} returns {@code null} 
immediately, except that no
+         * error message is recorded.
+         */
+        private static final Error LOADING = new Error(null, null);
 
-    /**
-     * Compares the indices of this tile with the given object for equality.
-     * Only indices are compared; the raster is ignored.
-     */
-    @Override
-    public boolean equals(final Object other) {
-        if (other instanceof GridTile) {
-            final GridTile that = (GridTile) other;
-            return tileX == that.tileX && tileY == that.tileY;
+        /**
+         * If we failed to load the tile, the reason for the failure.
+         */
+        final Throwable exception;
+
+        /**
+         * If we failed to load the tile, the zero-based row and column 
indices of the tile.
+         * This is computed by {@link GridView#getTileBounds(int, int)} and 
should be constant.
+         */
+        private final Rectangle region;
+
+        /**
+         * Intersection of {@link #region} with the area currently shown in 
the view.
+         * May vary with scrolling and is empty if the tile in error is 
outside visible area.
+         *
+         * @see GridError#getVisibleArea()
+         */
+        Rectangle visibleArea;
+
+        /**
+         * The {@link GridError#updateCount} value last time that {@link 
#visibleArea} was computed.
+         * Used for detecting if we should recompute the visible area.
+         */
+        private int updateCount;
+
+        /**
+         * Creates an error status with the given cause.
+         */
+        private Error(final Rectangle region, final Throwable exception) {
+            this.region    = region;
+            this.exception = exception;
         }
-        return false;
-    }
 
-    /**
-     * Returns a string representation for debugging purpose only.
-     */
-    @Override
-    public String toString() {
-        return getClass().getCanonicalName() + '[' + tileX + ", " + tileY + 
']';
+        /**
+         * Returns the area inside {@link #visibleArea}.
+         */
+        private long area() {
+            return visibleArea.width * (long) visibleArea.height;
+        }
+
+        /**
+         * Recomputes the {@link #visibleArea} value if needed, then returns 
{@code true}
+         * if the visible area in this {@code Error} if wider than the one in 
{@code other}.
+         *
+         * @param  stamp     value of {@link GridError#updateCount}.
+         * @param  viewArea  value of {@link GridError#viewArea}.
+         * @param  other     the previous error, or {@code null}.
+         * @return whether this status should replace {@code other}.
+         */
+        final boolean updateAndCompare(final int stamp, final Rectangle 
viewArea, final Error other) {
+            if (updateCount != stamp) {
+                visibleArea = viewArea.intersection(region);
+                updateCount = stamp;
+            }
+            return (other == null) || area() > other.area();
+        }
     }
 }
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
index ee5c8ca..3e330fe 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
@@ -19,6 +19,7 @@ package org.apache.sis.gui.coverage;
 import java.util.Map;
 import java.text.NumberFormat;
 import java.text.FieldPosition;
+import java.awt.Rectangle;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import java.awt.image.SampleModel;
@@ -82,7 +83,13 @@ public class GridView extends Control {
     /**
      * Information copied from {@link #imageProperty} for performance.
      */
-    private int width, height, minX, minY, tileWidth, tileHeight, numXTiles;
+    private int width, height, minX, minY, numXTiles;
+
+    /**
+     * Information copied from {@link #imageProperty} for performance.
+     * Must be always greater than zero for avoiding division by zero.
+     */
+    private int tileWidth, tileHeight;
 
     /**
      * Information copied and adjusted from {@link #imageProperty} for 
performance. Values are adjusted for using
@@ -195,9 +202,11 @@ public class GridView extends Control {
     private String lastValueAsText;
 
     /**
-     * Whether the sample values are integers.
+     * Whether the sample values are integers. We use this flag for deciding 
which {@code Raster.getSampleValue(…)}
+     * method to invoke, which {@code NumberFormat.format(…)} method to 
invoke, and whether to set a format pattern
+     * with fraction digits.
      */
-    private boolean isInteger;
+    private boolean dataTypeisInteger;
 
     /**
      * Creates an initially empty grid view. The content can be set after
@@ -292,20 +301,20 @@ public class GridView extends Control {
                                    final RenderedImage previous, final 
RenderedImage image)
     {
         tiles.clear();          // Let garbage collector dispose the rasters.
-        lastTile  = null;
-        width     = 0;
-        height    = 0;
-        isInteger = false;
+        lastTile = null;
+        width    = 0;
+        height   = 0;
         if (image != null) {
-            width           = image.getWidth();
-            height          = image.getHeight();
-            minX            = image.getMinX();
-            minY            = image.getMinY();
-            tileWidth       = image.getTileWidth();
-            tileHeight      = image.getTileHeight();
-            tileGridXOffset = Math.subtractExact(image.getTileGridXOffset(), 
minX);
-            tileGridYOffset = Math.subtractExact(image.getTileGridYOffset(), 
minY);
-            numXTiles       = image.getNumXTiles();
+            minX              = image.getMinX();
+            minY              = image.getMinY();
+            width             = image.getWidth();
+            height            = image.getHeight();
+            numXTiles         = image.getNumXTiles();
+            tileWidth         = Math.max(1, image.getTileWidth());
+            tileHeight        = Math.max(1, image.getTileHeight());
+            tileGridXOffset   = Math.subtractExact(image.getTileGridXOffset(), 
minX);
+            tileGridYOffset   = Math.subtractExact(image.getTileGridYOffset(), 
minY);
+            dataTypeisInteger = false;                      // To be kept 
consistent with `cellFormat` pattern.
             final SampleModel sm = image.getSampleModel();
             if (sm != null) {                               // Should never be 
null, but we are paranoiac.
                 final int numBands = sm.getNumBands();
@@ -313,19 +322,19 @@ public class GridView extends Control {
                     bandProperty.set(numBands - 1);
                 }
                 final int dataType = sm.getDataType();
-                isInteger = (dataType >= DataBuffer.TYPE_BYTE && dataType <= 
DataBuffer.TYPE_INT);
-                if (isInteger) {
-                    cellFormat.setMaximumFractionDigits(0);
-                } else {
-                    /*
-                     * TODO: compute the number of fraction digits from a 
"sampleResolution" image property
-                     * (of type float[] or double[]) if present. Provide a 
widget allowing user to set pattern.
-                     */
-                    cellFormat.setMinimumFractionDigits(1);
-                    cellFormat.setMaximumFractionDigits(1);
-                }
-                formatChanged(false);
+                dataTypeisInteger = (dataType >= DataBuffer.TYPE_BYTE && 
dataType <= DataBuffer.TYPE_INT);
+            }
+            if (dataTypeisInteger) {
+                cellFormat.setMaximumFractionDigits(0);
+            } else {
+                /*
+                 * TODO: compute the number of fraction digits from a 
"sampleResolution" image property
+                 * (of type float[] or double[]) if present. Provide a widget 
allowing user to set pattern.
+                 */
+                cellFormat.setMinimumFractionDigits(1);
+                cellFormat.setMaximumFractionDigits(1);
             }
+            formatChanged(false);
             contentChanged(true);
         }
     }
@@ -382,6 +391,24 @@ public class GridView extends Control {
     }
 
     /**
+     * Returns the bounds of a single tile in the image. This method is 
invoked only
+     * if an error occurred during {@link RenderedImage#getTile(int, int)} 
invocation.
+     * The returned bounds are zero-based (may not be the bounds in image 
coordinates).
+     *
+     * <div class="note"><b>Note:</b> we use AWT rectangle instead than JavaFX 
rectangle
+     * because generally we use AWT for everything related to {@link 
RenderedImage}.</div>
+     *
+     * @param  tileX  <var>x</var> coordinates of the tile for which to get 
the bounds.
+     * @param  tileY  <var>y</var> coordinates of the tile for which to get 
the bounds.
+     * @return the zero-based bounds of the specified tile in the image.
+     */
+    final Rectangle getTileBounds(final int tileX, final int tileY) {
+        return new Rectangle(tileX * tileWidth  + tileGridXOffset,
+                             tileY * tileHeight + tileGridYOffset,
+                             tileWidth, tileHeight);
+    }
+
+    /**
      * Converts a grid row index to image <var>y</var> coordinate. Those 
values may differ
      * because the image coordinate system does not necessarily starts at zero.
      *
@@ -421,41 +448,52 @@ public class GridView extends Control {
      * @see GridRow#getSampleValue(int)
      */
     final String getSampleValue(final int y, final int tileY, final int 
column) {
-        if (y >= 0 && y < height && column >= 0 && column < width) {
-            final int tileX = Math.floorDiv(Math.subtractExact(column, 
tileGridXOffset), tileWidth);
-            GridTile cache = lastTile;
-            if (cache == null || cache.tileX != tileX || cache.tileY != tileY) 
{
-                final GridTile key = new GridTile(tileX, tileY, numXTiles);
-                cache = tiles.putIfAbsent(key, key);
-                if (cache == null) cache = key;
-                lastTile = cache;
-            }
-            Raster tile = cache.tile;
-            if (tile == null) {
-                cache.load(this);
-                return null;
+        if (y < 0 || y >= height || column < 0 || column >= width) {
+            return OUT_OF_BOUNDS;
+        }
+        /*
+         * Fetch the tile where is located the (x,y) image coordinate of the 
pixel to get.
+         * If that tile has never been requested before, or has been discarded 
by the cache,
+         * start a background thread for fetching the tile and return null 
immediately; this
+         * method will be invoked again with the same coordinates after the 
tile become ready.
+         */
+        final int tileX = Math.floorDiv(Math.subtractExact(column, 
tileGridXOffset), tileWidth);
+        GridTile cache = lastTile;
+        if (cache == null || cache.tileX != tileX || cache.tileY != tileY) {
+            final GridTile key = new GridTile(tileX, tileY, numXTiles);
+            cache = tiles.putIfAbsent(key, key);
+            if (cache == null) cache = key;
+            lastTile = cache;
+        }
+        Raster tile = cache.tile();
+        if (tile == null) {
+            cache.load(this);
+            return null;
+        }
+        /*
+         * At this point we have the tile. Get the desired number and format 
its string representation.
+         * As a slight optimization, we reuse the previous string 
representation if the number is the same.
+         * It may happen in particular with fill values.
+         */
+        final int x = Math.addExact(column, minX);
+        final int b = getBand();
+        buffer.setLength(0);
+        if (dataTypeisInteger) {
+            final int  integer = tile.getSample(x, y, b);
+            final double value = integer;
+            if (Double.doubleToRawLongBits(value) != 
Double.doubleToRawLongBits(lastValue)) {
+                // The `format` method invoked here is not the same than in 
`double` case.
+                lastValueAsText = cellFormat.format(integer, buffer, 
formatField).toString();
+                lastValue = value;
             }
-            final int x = Math.addExact(column, minX);
-            final int b = getBand();
-            buffer.setLength(0);
-            if (isInteger) {
-                final int  integer = tile.getSample(x, y, b);
-                final double value = integer;
-                if (Double.doubleToRawLongBits(value) != 
Double.doubleToRawLongBits(lastValue)) {
-                    // The `format` method invoked here is not the same than 
in `double` case.
-                    lastValueAsText = cellFormat.format(integer, buffer, 
formatField).toString();
-                    lastValue = value;
-                }
-            } else {
-                final double value = tile.getSampleDouble(x, y, b);
-                if (Double.doubleToRawLongBits(value) != 
Double.doubleToRawLongBits(lastValue)) {
-                    lastValueAsText = cellFormat.format(value, buffer, 
formatField).toString();
-                    lastValue = value;
-                }
+        } else {
+            final double value = tile.getSampleDouble(x, y, b);
+            if (Double.doubleToRawLongBits(value) != 
Double.doubleToRawLongBits(lastValue)) {
+                lastValueAsText = cellFormat.format(value, buffer, 
formatField).toString();
+                lastValue = value;
             }
-            return lastValueAsText;
         }
-        return OUT_OF_BOUNDS;
+        return lastValueAsText;
     }
 
     /**
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
index 267150f..b3fb8f9 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
@@ -110,6 +110,12 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> {
     double cellInnerWidth;
 
     /**
+     * The controls to show in case of error, or {@code null} if none. This is 
created and
+     * added to the children list the first time that an error occurs while 
reading a tile.
+     */
+    private GridError error;
+
+    /**
      * Creates a new skin for the specified view.
      */
     GridViewSkin(final GridView view) {
@@ -128,7 +134,7 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> {
         view.headerWidth.addListener(this::cellWidthChanged);
         /*
          * Rectangles for filling the background of the cells in the header 
row and header column.
-         * Those rectangles will be resized and relocated in `layout(…)` 
method.
+         * Those rectangles will be resized and relocated by the `layout(…)` 
method.
          */
         topBackground  = new Rectangle();
         leftBackground = new Rectangle();
@@ -165,18 +171,44 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> {
     }
 
     /**
+     * Invoked when an error occurred when fetching a tile. If this is the 
first time that an error happens
+     * for this image, then a {@link GridError} node will be added as the last 
child (i.e. will be drawn on
+     * top of everything else). That child will be removed if a new image is 
set.
+     */
+    final void errorOccurred(final GridTile.Error status) {
+        if (error == null) {
+            error = new GridError();
+            getChildren().add(error);
+        }
+        if (error.update(status)) {
+            final double cellHeight = getVirtualFlow().getFixedCellSize();
+            final java.awt.Rectangle area = error.getVisibleArea();
+            positionInArea​(error,
+                    area.x      * cellWidth  + headerWidth,
+                    area.y      * cellHeight + topBackground.getHeight(),
+                    area.width  * cellWidth,
+                    area.height * cellHeight,
+                    0, HPos.CENTER, VPos.CENTER);
+        }
+    }
+
+    /**
      * Invoked when the content may have changed. If {@code all} is {@code 
true}, then everything
      * may have changed including the number of rows and columns. If {@code 
all} is {@code false}
      * then the number of rows and columns is assumed the same.
      *
-     * <p>This method is invoked by {@link GridView} when the image has 
changed,
-     * or the band in the image  to show has changed.</p>
+     * <p>This method is invoked by {@link GridView} when the image has 
changed ({@code all=true}),
+     * or the band in the image to show has changed ({@code all=false}).</p>
      *
      * @see GridView#contentChanged(boolean)
      */
     final void contentChanged(final boolean all) {
         if (all) {
             updateItemCount();
+            if (error != null) {
+                error = null;
+                getChildren().removeIf((node) -> (node instanceof GridError));
+            }
         }
         /*
          * Following call may be redundant with `updateItemCount()` except if 
the number of
@@ -367,5 +399,21 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> {
                 pos += cellWidth;
             }
         }
+        /*
+         * If an error exists somewhere, computes as estimation of the visible 
region
+         * as zero-based column and row indices. We use an AWT rectangle 
instead than
+         * JavaFX object because this rectangle will be intersected with AWT 
rectangle.
+         */
+        if (error != null) {
+            final java.awt.Rectangle viewArea = new java.awt.Rectangle();
+            final GridRow firstVisibleRow = flow.getFirstVisibleCell();
+            if (firstVisibleRow != null) {
+                viewArea.x      = firstVisibleColumn;
+                viewArea.y      = firstVisibleRow.getIndex();
+                viewArea.width  = (int) ((flow.getWidth() - headerWidth) / 
cellWidth);
+                viewArea.height = (int) (flow.getVisibleHeight() / 
flow.getFixedCellSize());
+            }
+            error.initialize(viewArea);
+        }
     }
 }

Reply via email to