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 a70927e  Fix numerous problems in the initial GridView implementation 
(horizontal scroll bar was not visible, too many GridCell nodes were created, 
etc.)
a70927e is described below

commit a70927efb3c9e19ac7320a5497cae673b0189cc1
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Jan 23 18:53:43 2020 +0100

    Fix numerous problems in the initial GridView implementation (horizontal 
scroll bar was not visible, too many GridCell nodes were created, etc.)
---
 .../java/org/apache/sis/gui/coverage/GridCell.java |  19 ++-
 .../org/apache/sis/gui/coverage/GridCellSkin.java  |  38 ------
 .../java/org/apache/sis/gui/coverage/GridRow.java  |  28 ++++-
 .../org/apache/sis/gui/coverage/GridRowSkin.java   |  69 ++++++++---
 .../java/org/apache/sis/gui/coverage/GridView.java | 128 +++++++++++++++++----
 .../org/apache/sis/gui/coverage/GridViewSkin.java  | 103 ++++++++++++++---
 .../org/apache/sis/gui/coverage/package-info.java  |   2 +-
 7 files changed, 276 insertions(+), 111 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridCell.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridCell.java
index 2e84044..7955e8e 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridCell.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridCell.java
@@ -18,6 +18,7 @@ package org.apache.sis.gui.coverage;
 
 import javafx.scene.control.IndexedCell;
 import javafx.scene.control.Skin;
+import javafx.scene.control.skin.CellSkinBase;
 
 
 /**
@@ -28,7 +29,7 @@ import javafx.scene.control.Skin;
  * @since   1.1
  * @module
  */
-final class GridCell extends IndexedCell<Number> {
+final class GridCell extends IndexedCell<String> {
     /**
      * Creates a new cell.
      */
@@ -47,17 +48,14 @@ final class GridCell extends IndexedCell<Number> {
      * It may happen if the image is still loading in a background thread.
      *
      * @param  value  the sample value, or {@code null} if not available.
-     * @param  empty  whether this cell is used for filling empty space.
+     * @param  empty  whether this cell is used for filling empty space
+     *                (not to be confused with value not yet available).
      */
     @Override
-    protected void updateItem(final Number value, final boolean empty) {
+    protected void updateItem(String value, final boolean empty) {
         super.updateItem(value, empty);
-        if (empty) {
-            setText("");
-        } else {
-            // TODO: format the value.
-            setText(String.valueOf(value));
-        }
+        if (value == null) value = "";
+        setText(value);
     }
 
     /**
@@ -69,6 +67,7 @@ final class GridCell extends IndexedCell<Number> {
      */
     @Override
     protected Skin<GridCell> createDefaultSkin() {
-        return new GridCellSkin(this);
+        // We have nothing to add compared to the base implementation.
+        return new CellSkinBase<>(this);
     }
 }
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridCellSkin.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridCellSkin.java
deleted file mode 100644
index 9c25f1b..0000000
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridCellSkin.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 javafx.scene.control.skin.CellSkinBase;
-
-
-/**
- * The renderer of {@link GridCell} instances.
- * Contains only one child, which is an instance of {@link 
javafx.scene.text.Text}.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-final class GridCellSkin extends CellSkinBase<GridCell> {
-    /**
-     * Creates a new renderer for the specified cell.
-     */
-    GridCellSkin(final GridCell view) {
-        super(view);
-    }
-}
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRow.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRow.java
index 65abcd0..9b17b0b 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRow.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRow.java
@@ -40,14 +40,29 @@ import javafx.scene.control.skin.VirtualFlow;
  */
 final class GridRow extends IndexedCell<Void> {
     /**
+     * The {@link VirtualFlow} which is managing this row. This is the value 
given to the constructor,
+     * casted to the type used by {@link GridView}. There is two main 
properties that we want to access:
+     *
+     * <ul>
+     *   <li>{@link GridViewSkin.Flow#getHorizontalPosition()} for the 
position of the horizontal scroll bar.</li>
+     *   <li>{@link GridViewSkin.Flow#getWidth()} for the width of the visible 
region.
+     * </ul>
+     *
+     * Those two properties are used for creating the minimal amount of {@link 
GridCell} needed
+     * for rendering this row.
+     */
+    final GridViewSkin.Flow flow;
+
+    /**
      * The grid view where this row will be shown.
+     * This is {@code flow.getParent()} but fetched once for efficiency.
      */
-    private final GridView view;
+    final GridView view;
 
     /**
      * The arbitrary-based <var>y</var> coordinate in the image. This is not 
necessarily the {@code row}
      * index in the table since {@link RenderedImage} coordinate system do not 
necessarily starts at zero.
-     * This value may be outside image bounds, in which case this row should 
be rendered as empty.
+     * This value may be outside image bounds, in which case this {@code 
GridRow} should be rendered as empty.
      */
     private int y;
 
@@ -63,14 +78,15 @@ final class GridRow extends IndexedCell<Void> {
      * This constructor is referenced by lambda-function in {@link 
GridViewSkin}.
      */
     GridRow(final VirtualFlow<GridRow> owner) {
+        flow = (GridViewSkin.Flow) owner;
         view = (GridView) owner.getParent();
+        setPrefWidth(view.getContentWidth());
     }
 
     /**
      * Invoked when this {@code GridRow} is used for showing a new image row.
-     * We override this method as an alternative to registering a listener to 
{@link #indexProperty()}
-     * (for reducing the number of object allocations). This method 
{@linkplain #setItem set the item}
-     * to the information required for getting pixel values on that row.
+     * We override this method as an alternative to registering a listener to
+     * {@link #indexProperty()} (for reducing the number of object 
allocations).
      *
      * @param  row  index of the new row.
      */
@@ -89,7 +105,7 @@ final class GridRow extends IndexedCell<Void> {
      * @param  column  zero-based <var>x</var> coordinate of sample to get 
(may differ from image coordinate).
      * @return the sample value in the specified column, or {@code null} if 
not yet available.
      */
-    final Number getSampleValue(final int column) {
+    final String getSampleValue(final int column) {
         return view.getSampleValue(y, tileRow, column);
     }
 
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRowSkin.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRowSkin.java
index 94277f3..bd54c0f 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRowSkin.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRowSkin.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.gui.coverage;
 
+import java.util.List;
+import java.util.ArrayList;
 import javafx.scene.Node;
 import javafx.scene.control.skin.CellSkinBase;
 import javafx.collections.ObservableList;
@@ -49,10 +51,16 @@ final class GridRowSkin extends CellSkinBase<GridRow> {
      * and to modify text values here, but I have not identified another place 
yet. However the
      * JavaFX implementation of table skin seems to do the same, so I presume 
it is okay.</div>
      *
+     * The {@code width} argument can be a large number (for example 24000) 
because it includes
+     * the area outside the view. In order to avoid creating a large amount of 
{@link GridCell}
+     * instances, this method have to find the current view port area and 
render only the cells
+     * in that area. We do not have to do that vertically because the vertical 
virtualization
+     * is done by {@link GridViewSkin} parent class.
+     *
      * @param  x       the <var>x</var> position of this row, usually 0.
      * @param  y       the <var>y</var> position of this row, usually 0 (this 
is a relative position).
-     * @param  width   width of the region where to render this row (for 
example 400).
-     * @param  height  height of the region where to render this row (for 
example 400).
+     * @param  width   width of the row, including the area currently hidden 
because out of view.
+     * @param  height  height of the region where to render this row (for 
example 16).
      */
     @Override
     protected void layoutChildren(final double x, final double y, final double 
width, final double height) {
@@ -62,25 +70,52 @@ final class GridRowSkin extends CellSkinBase<GridRow> {
          */
         final GridRow row = getSkinnable();
         final ObservableList<Node> children = getChildren();
-        ((Text) children.get(0)).setText(String.valueOf(row.getIndex()));
+        final Text header = (Text) children.get(0);
+        header.setText(String.valueOf(row.getIndex()));     // TODO: format
         /*
-         * All children starting at index 1 (i.e. children at indices `column 
+ 1`)
-         * shall be GridCell instances created in this method.
+         * Get the beginning (pos) and end (limit) of the region to render. We 
create only the amount
+         * of GridCell instances needed for rendering this region. We should 
not create cells for the
+         * whole row since it would be too many cells (can be millions). 
Instead we recycle the cells
+         * in a list of children that we try to keep small. All children 
starting at index 1 shall be
+         * GridCell instances created in this method.
          */
-        int column = 0;
-        double xc = GridView.cellWidth;
-        while (xc < width) {
-            final GridCell child;
-            if (++column < children.size()) {
-                child = (GridCell) children.get(column);
+        final double headerWidth = row.view.headerWidth.get();
+        final double cellWidth   = row.view.cellWidth.get();            // 
Includes the cell spacing.
+        final double cellSpacing = row.view.cellSpacing.get();
+        final double available   = cellWidth - cellSpacing;
+        double pos = row.flow.getHorizontalPosition();
+        final double limit = pos + row.flow.getWidth();
+        int column = (int) (pos / cellWidth);
+        int childIndex = 0;
+        List<GridCell> newChildren = null;
+        header.resizeRelocate(pos, y, headerWidth, height);
+        pos += headerWidth;
+        final int count = children.size();
+        while (pos < limit) {
+            final GridCell cell;
+            if (++childIndex < count) {
+                cell = (GridCell) children.get(childIndex);
             } else {
-                child = new GridCell();
-                children.add(child);
+                cell = new GridCell();
+                if (newChildren == null) {
+                    newChildren = new ArrayList<>(1 + (int) ((limit - pos) / 
cellWidth));
+                }
+                newChildren.add(cell);
             }
-            final Number value = row.getSampleValue(column);
-            child.updateItem(value, value == null);     // TODO: difference 
between "empty" and "still loading".
-            child.resizeRelocate(xc + GridView.horizontalCellSpacing, 0, 
GridView.cellWidth, height);
-            xc += GridView.cellWidth + 2*GridView.horizontalCellSpacing;
+            final String value = row.getSampleValue(column++);
+            cell.updateItem(value, value == GridView.OUT_OF_BOUNDS);           
 // Identity comparison is okay here.
+            cell.resizeRelocate(pos + cellSpacing, 0, available, height);
+            pos += cellWidth;
+        }
+        /*
+         * Add or remove fields only at the end of this method in order to 
fire only one change event.
+         * It is important to remove unused fields not only for saving memory, 
but also for preventing
+         * those fields to appear at random positions in the rendered region.
+         */
+        if (newChildren != null) {
+            children.addAll(newChildren);
+        } else if (++childIndex < count) {
+            children.remove(childIndex, count);
         }
     }
 }
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 169721b..598b169 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,14 +19,18 @@ package org.apache.sis.gui.coverage;
 import java.util.Arrays;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
+import java.awt.image.SampleModel;
 import javafx.beans.DefaultProperty;
+import javafx.beans.property.DoubleProperty;
 import javafx.beans.property.IntegerProperty;
 import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleDoubleProperty;
 import javafx.beans.property.SimpleIntegerProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ObservableValue;
 import javafx.scene.control.Control;
 import javafx.scene.control.Skin;
+import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.coverage.grid.GridCoverage;
 
 
@@ -47,9 +51,15 @@ import org.apache.sis.coverage.grid.GridCoverage;
  */
 @DefaultProperty("image")
 public class GridView extends Control {
-    /** TODO: temporary setting. */
-    static final double cellWidth = 60;
-    static final double horizontalCellSpacing = 2;
+    /**
+     * Minimum cell width and height.
+     */
+    static final int MIN_CELL_SIZE = 1;
+
+    /**
+     * The string value for sample values that are out of image bounds.
+     */
+    static final String OUT_OF_BOUNDS = "";
 
     /**
      * The data shown in this table. Note that setting this property to a 
non-null value may not
@@ -82,6 +92,7 @@ public class GridView extends Control {
 
     /**
      * The image band to show in the table.
+     * It should be a number between 0 inclusive and {@link 
SampleModel#getNumBands()} exclusive.
      *
      * @see #getBand()
      * @see #setBand(int)
@@ -89,14 +100,62 @@ public class GridView extends Control {
     public final IntegerProperty bandProperty;
 
     /**
-     * Creates an initially empty grid view. The content can be set after 
construction by a call
-     * to {@link #setImage(RenderedImage)}.
+     * Width of header cells to be shown in the first column.
+     * The first (header) column contains the row indices, or sometime the 
coordinate values.
+     * This size includes the {@linkplain #cellSpacing cell spacing}.
+     * It shall be a number strictly greater than zero.
+     *
+     * <p>We do not define getter/setter for this property; use {@link 
DoubleProperty#set(double)}
+     * directly instead. We omit the "Property" suffix for making this 
operation more natural.</p>
+     */
+    public final DoubleProperty headerWidth;
+
+    /**
+     * Width of data cell to be shown in other columns than the header column.
+     * This size includes the {@linkplain #cellSpacing cell spacing}.
+     * It shall be a number strictly greater than zero.
+     *
+     * <p>We do not define getter/setter for this property; use {@link 
DoubleProperty#set(double)}
+     * directly instead. We omit the "Property" suffix for making this 
operation more natural.</p>
+     */
+    public final DoubleProperty cellWidth;
+
+    /**
+     * Height of all rows in the grid.
+     * It shall be a number strictly greater than zero.
+     *
+     * <p>We do not define getter/setter for this property; use {@link 
DoubleProperty#set(double)}
+     * directly instead. We omit the "Property" suffix for making this 
operation more natural.</p>
+     */
+    public final DoubleProperty cellHeight;
+
+    /**
+     * Horizontal space between cells, as a number equals or greater than zero.
+     * There is no property for vertical cell spacing because increasing the
+     * {@linkplain #cellHeight cell height} should be sufficient.
+     *
+     * <p>We do not define getter/setter for this property; use {@link 
DoubleProperty#set(double)}
+     * directly instead. We omit the "Property" suffix for making this 
operation more natural.</p>
+     */
+    public final DoubleProperty cellSpacing;
+
+    /**
+     * Creates an initially empty grid view. The content can be set after
+     * construction by a call to {@link #setImage(RenderedImage)}.
      */
     public GridView() {
         imageProperty = new SimpleObjectProperty<>(this, "image");
+        bandProperty  = new SimpleIntegerProperty (this, "band");
+        headerWidth   = new SimpleDoubleProperty  (this, "headerWidth", 60);
+        cellWidth     = new SimpleDoubleProperty  (this, "cellWidth",   60);
+        cellHeight    = new SimpleDoubleProperty  (this, "cellHeight",  20);
+        cellSpacing   = new SimpleDoubleProperty  (this, "cellSpacing",  2);
+
         imageProperty.addListener(this::startImageLoading);
-        bandProperty = new SimpleIntegerProperty(this, "band");
-        // TODO: add listener. Check value range.
+        // Other listeners registered by GridViewSkin.Flow.
+
+        tileWidth  = 1;
+        tileHeight = 1;     // For avoiding division by zero.
     }
 
     /**
@@ -127,9 +186,9 @@ public class GridView extends Control {
     }
 
     /**
-     * Returns the number of the band shown in this grid view.
+     * Returns the index of the band shown in this grid view.
      *
-     * @return the currently visible band number.
+     * @return index of the currently visible band number.
      *
      * @see #bandProperty
      */
@@ -141,10 +200,17 @@ public class GridView extends Control {
      * Sets the number of the band to show in this grid view.
      * This value should be from 0 (inclusive) to the number of bands in the 
image (exclusive).
      *
-     * @param  visible  the band to make visible.
+     * @param  index  the band to make visible.
+     * @throws IllegalArgumentException if the given band index is out of 
bounds.
      */
-    public final void setBand(final int visible) {
-        bandProperty.set(visible);
+    public final void setBand(final int index) {
+        final RenderedImage image = getImage();
+        if (image != null) {
+            ArgumentChecks.ensureBetween("band", 0, 
image.getSampleModel().getNumBands() - 1, index);
+        } else {
+            ArgumentChecks.ensurePositive("band", index);
+        }
+        bandProperty.set(index);
     }
 
     /**
@@ -176,14 +242,26 @@ public class GridView extends Control {
             tileGridYOffset = Math.toIntExact(((long) 
image.getTileGridYOffset()) - minY + ((long) tileHeight) * minTileY);
             numXTiles       = image.getNumXTiles();
             tiles           = new Raster[image.getNumYTiles()][];
-            final int numBands = image.getSampleModel().getNumBands();
-            if (bandProperty.get() > numBands) {
-                bandProperty.set(numBands - 1);
+            final int n     = image.getSampleModel().getNumBands();
+            if (bandProperty.get() >= n) {
+                bandProperty.set(n - 1);
+            }
+            final Skin<?> skin = getSkin();
+            if (skin instanceof GridViewSkin) {
+                ((GridViewSkin) skin).updateItemCount();
             }
         }
     }
 
     /**
+     * Returns the width that this view would have if it was fully shown 
(without horizontal scroll bar).
+     * This value depends on the number of columns in the image and the size 
of each cell.
+     */
+    final double getContentWidth() {
+        return width * Math.max(MIN_CELL_SIZE, cellWidth.get()) + 
Math.max(MIN_CELL_SIZE, headerWidth.get());
+    }
+
+    /**
      * Returns the number of rows in the image. This is also the number of 
rows in the
      * {@link GridViewSkin} virtual flow, which is using a vertical primary 
direction.
      */
@@ -218,18 +296,19 @@ public class GridView extends Control {
      * be refreshed when the tile become available.
      *
      * <p>The {@code y} parameter is computed by {@link #toImageY(int)} and 
the {@code tileRow} parameter
-     * is computed by {@link #toTileRow(int)}. Those values are stored in 
{@link GridRow}.
+     * is computed by {@link #toTileRow(int)}. Those values are stored in 
{@link GridRow}.</p>
      *
      * @param  y        arbitrary-based <var>y</var> coordinate in the image 
(may differ from table {@code row}).
      * @param  tileRow  zero-based <var>y</var> coordinate of the tile (may 
differ from image tile Y).
      * @param  column   zero-based <var>x</var> coordinate of sample to get 
(may differ from image coordinate X).
-     * @return the sample value in the specified column, or {@code null} if 
out of bounds or not yet available.
-     * @throws ArithmeticException if an index is too large.
+     * @return the sample value in the specified column, or {@code null} if 
unknown (because the loading process
+     *         is still under progress), or the empty string ({@code ""}) if 
out of bounds.
+     * @throws ArithmeticException if an index is too large for the 32 bits 
integer capacity.
      *
      * @see GridRow#getSampleValue(int)
      */
-    final Number getSampleValue(final int y, final int tileRow, final int 
column) {
-        if (y >= 0 && y < height && column > 0 && column < width) {
+    final String getSampleValue(final int y, final int tileRow, final int 
column) {
+        if (y >= 0 && y < height && column >= 0 && column < width) {
             final int tx = Math.subtractExact(column, tileGridXOffset) / 
tileWidth;
             Raster[] row = tiles[tileRow];
             if (row == null) {
@@ -239,16 +318,19 @@ public class GridView extends Control {
             }
             Raster tile = row[tx];
             if (tile == null) {
-                // TODO: load in background
+                // TODO: load in background and return null for meaning "not 
yet available".
                 tile = getImage().getTile(Math.addExact(tx, minTileX), 
Math.addExact(tileRow, minTileY));
                 row[tx] = tile;
             }
             final int x = Math.addExact(column, minX);
             final int b = getBand();
-            return tile.getSampleDouble(x, y, b);
+            final Number value;
             // TODO: also return Float or Integer.
+            value = tile.getSampleDouble(x, y, b);
+            // TODO: format.
+            return value.toString();
         }
-        return null;
+        return OUT_OF_BOUNDS;
     }
 
     /**
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 a0825e6..7f88e60 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
@@ -17,8 +17,11 @@
 package org.apache.sis.gui.coverage;
 
 import java.awt.image.RenderedImage;
-import javafx.scene.control.skin.VirtualContainerBase;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.Node;
 import javafx.scene.control.skin.VirtualFlow;
+import javafx.scene.control.skin.VirtualContainerBase;
 
 
 /**
@@ -28,7 +31,10 @@ import javafx.scene.control.skin.VirtualFlow;
  * <p>Relationships:</p>
  * <ul>
  *   <li>This is created by {@link GridView#createDefaultSkin()}.</li>
- *   <li>The {@link GridView} owner is given by {@link #getSkinnable()}.</li>
+ *   <li>The {@link GridView} which own this skin is given by {@link 
#getSkinnable()}.</li>
+ *   <li>This {@code GridViewSkin} contains an arbitrary amount of {@link 
GridRow} children.
+ *       It should be limited to the number of children that are visible in 
same time,
+ *       not the total number of rows in the image.</li>
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -45,7 +51,10 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> {
         final VirtualFlow<GridRow> flow = getVirtualFlow();
         flow.setCellFactory(GridRow::new);
         flow.setFocusTraversable(true);
-        flow.setPannable(false);
+        flow.setFixedCellSize(Math.max(GridView.MIN_CELL_SIZE, 
view.cellHeight.get()));
+        view.cellHeight .addListener(this::cellHeightChanged);
+        view.cellWidth  .addListener(this::cellWidthChanged);
+        view.headerWidth.addListener(this::cellWidthChanged);
         /*
          * The list of children is initially empty. We need to
          * add the virtual flow, otherwise nothing will appear.
@@ -53,20 +62,76 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> {
         getChildren().add(flow);
     }
 
-    /*
-     * TODO:
-     * VirtualFlow.setFixedCellSize​(double): For optimisation purposes, some 
use cases can trade
-     * dynamic cell length for speed. If fixedCellSize is greater than zero 
JavaFX uses that rather
-     * than determining it by querying the cell itself.
+    /**
+     * Invoked when the value of {@link GridView#cellHeight} property changed.
      */
+    private void cellHeightChanged(ObservableValue<? extends Number> property, 
Number oldValue, Number newValue) {
+        getVirtualFlow().setFixedCellSize(Math.max(GridView.MIN_CELL_SIZE, 
newValue.intValue()));
+    }
 
     /**
-     * Returns the total number of image rows, including those that are 
currently hidden because
-     * they are out of view. The returned value is (indirectly) {@link 
RenderedImage#getHeight()}.
+     * Invoked when the cell width or cell spacing changed. This method 
notifies all children of the new width.
+     */
+    private void cellWidthChanged(ObservableValue<? extends Number> property, 
Number oldValue, Number newValue) {
+        final double width = getSkinnable().getContentWidth();
+        for (final Node child : getChildren()) {
+            if (child instanceof GridRow) {             // The first instance 
if not a GridRow.
+                ((GridRow) child).setPrefWidth(width);
+            }
+        }
+    }
+
+    /**
+     * Creates the virtual flow used by this {@link GridViewSkin}. The virtual 
flow
+     * created by this method registers a listener for horizontal scroll bar 
events.
      */
     @Override
-    public int getItemCount() {
-        return getSkinnable().getImageHeight();
+    protected VirtualFlow<GridRow> createVirtualFlow() {
+        return new Flow(getSkinnable());
+    }
+
+    /**
+     * The virtual flow used by {@link GridViewSkin}. We define that class
+     * mostly for getting access to the protected {@link #getHbar()} method.
+     */
+    static final class Flow extends VirtualFlow<GridRow> implements 
ChangeListener<Number> {
+        /**
+         * Creates a new flow for the given view. This method registers 
listeners
+         * on the properties that may require a redrawn of the full view port.
+         */
+        @SuppressWarnings("ThisEscapedInObjectConstruction")
+        Flow(final GridView view) {
+            getHbar().valueProperty().addListener(this);
+            view.bandProperty .addListener(this);
+            view.cellSpacing  .addListener(this);
+            // Other listeners are registered by enclosing class.
+        }
+
+        /**
+         * The position of the horizontal scroll bar. This is a value between 
0 and
+         * the width that the {@link GridView} would have if we were showing 
it fully.
+         */
+        final double getHorizontalPosition() {
+            return getHbar().getValue();
+        }
+
+        /**
+         * Invoked when the content to show changed because of a change in a 
property.
+         * The most important event is a change in the position of horizontal 
scroll bar,
+         * which is handled as a change of content because we will need to 
change values
+         * shown by the cells (because we reuse a small number of cells in 
visible region).
+         * But this method is also invoked for real changes of content like 
changes in the
+         * index of the band to show, provided that the number of rows and 
columns is the same.
+         *
+         * @param  property  the property that changed (ignored).
+         * @param  oldValue  the old value (ignored).
+         * @param  newValue  the new value (ignored).
+         */
+        @Override
+        public void changed(ObservableValue<? extends Number> property, Number 
oldValue, Number newValue) {
+            // Inform VirtualFlow that a layout pass should be done, but no 
GridRows have been added or removed.
+            reconfigureCells();
+        }
     }
 
     /**
@@ -80,13 +145,19 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> {
          * VirtualFlow.setCellCount(int) indicates the number of cells that 
should be in the flow.
          * When the cell count changes, VirtualFlow responds by updating the 
visuals. If the items
          * backing the cells change but the count has not changed, then 
reconfigureCells() should
-         * be invoked instead.
+         * be invoked instead. This is done by the `Flow` inner class above.
          */
-        final VirtualFlow<GridRow> flow = getVirtualFlow();
-        flow.setCellCount(getItemCount());                  // Fires event 
only if count changed.
+        getVirtualFlow().setCellCount(getItemCount());      // Fires event 
only if count changed.
     }
 
-    // TODO: to update content, invoke getSkinnable().requestLayout().
+    /**
+     * Returns the total number of image rows, including those that are 
currently hidden because
+     * they are out of view. The returned value is (indirectly) {@link 
RenderedImage#getHeight()}.
+     */
+    @Override
+    public int getItemCount() {
+        return getSkinnable().getImageHeight();
+    }
 
     /**
      * Called during the layout pass of the scene graph. Current 
implementation sets the virtual
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/package-info.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/package-info.java
index fa108e3..7492013 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/package-info.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/package-info.java
@@ -16,7 +16,7 @@
  */
 
 /**
- * Widgets showing {@link org.apache.sis.coverage.grid.GridCoverage} images or 
values.
+ * Widgets showing {@link org.apache.sis.coverage.grid.GridCoverage} images or 
sample values.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1

Reply via email to