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 f7798ef00d236d12b800abd22a5c8c1d9f1157e7
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Jan 22 21:15:34 2020 +0100

    First draft of a control showing sample values from a large tiled image.
---
 .../java/org/apache/sis/gui/coverage/GridCell.java |  74 ++++++
 .../org/apache/sis/gui/coverage/GridCellSkin.java  |  38 +++
 .../java/org/apache/sis/gui/coverage/GridRow.java  | 107 +++++++++
 .../org/apache/sis/gui/coverage/GridRowSkin.java   |  86 +++++++
 .../java/org/apache/sis/gui/coverage/GridView.java | 266 +++++++++++++++++++++
 .../org/apache/sis/gui/coverage/GridViewSkin.java  | 104 ++++++++
 .../org/apache/sis/gui/coverage/package-info.java  |  26 ++
 7 files changed, 701 insertions(+)

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
new file mode 100644
index 0000000..2e84044
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridCell.java
@@ -0,0 +1,74 @@
+/*
+ * 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.IndexedCell;
+import javafx.scene.control.Skin;
+
+
+/**
+ * A single cell in a {@link GridRow}. This cell contains one sample value of 
one pixel in an image.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class GridCell extends IndexedCell<Number> {
+    /**
+     * Creates a new cell.
+     */
+    GridCell() {
+        /*
+         * In unmanaged mode, the parent (GridRow) will ignore the cell 
preferred size computations and layout.
+         * Changes in layout bounds will not trigger relayout above it. This 
is what we want since the parents
+         * decide themselves when to layout in our implementation.
+         */
+        setManaged(false);
+    }
+
+    /**
+     * Sets the sample value to show in this grid cell.
+     * Note that the {@code value} may be null even if {@code empty} is false.
+     * 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.
+     */
+    @Override
+    protected void updateItem(final Number value, final boolean empty) {
+        super.updateItem(value, empty);
+        if (empty) {
+            setText("");
+        } else {
+            // TODO: format the value.
+            setText(String.valueOf(value));
+        }
+    }
+
+    /**
+     * Creates a new instance of the skin responsible for rendering this grid 
cell.
+     * From the perspective of {@link IndexedCell}, the {@link Skin} is a 
black box.
+     * It listens and responds to changes in state of this grid cell.
+     *
+     * @return the renderer of this grid cell.
+     */
+    @Override
+    protected Skin<GridCell> createDefaultSkin() {
+        return new GridCellSkin(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
new file mode 100644
index 0000000..9c25f1b
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridCellSkin.java
@@ -0,0 +1,38 @@
+/*
+ * 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
new file mode 100644
index 0000000..65abcd0
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRow.java
@@ -0,0 +1,107 @@
+/*
+ * 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.image.RenderedImage;
+import javafx.scene.control.IndexedCell;
+import javafx.scene.control.Skin;
+import javafx.scene.control.skin.VirtualFlow;
+
+
+/**
+ * A row in a {@link RenderedImage}. This is only a pointer to a row of pixels 
in an image,
+ * not a storage for pixel values. The row to be shown is identified by {@link 
#getIndex()},
+ * which is a zero-based index. Note that <var>y</var> coordinates in a {@link 
RenderedImage}
+ * do not necessarily starts at 0, so a constant offset may exist between 
{@link #getIndex()}
+ * values and image <var>y</var> coordinates.
+ *
+ * <p>{@link GridRow} instances are created by JavaFX {@link VirtualFlow}, 
which is responsible
+ * for reusing cells. A relatively small amount of {@code GridRow} instances 
should be created
+ * even if the image contains millions of rows.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class GridRow extends IndexedCell<Void> {
+    /**
+     * The grid view where this row will be shown.
+     */
+    private 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.
+     */
+    private int y;
+
+    /**
+     * The zero-based <var>y</var> coordinate of the tile. This is not 
necessarily the
+     * {@code tileY} index in the image, since image tile index may not start 
at zero.
+     * This value is computed from {@link #y} value and cached for efficiency.
+     */
+    private int tileRow;
+
+    /**
+     * Invoked by {@link VirtualFlow} when a new cell is needed.
+     * This constructor is referenced by lambda-function in {@link 
GridViewSkin}.
+     */
+    GridRow(final VirtualFlow<GridRow> owner) {
+        view = (GridView) owner.getParent();
+    }
+
+    /**
+     * 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.
+     *
+     * @param  row  index of the new row.
+     */
+    @Override
+    public void updateIndex(final int row) {
+        super.updateIndex(row);
+        y = view.toImageY(row);
+        tileRow = view.toTileRow(row);
+    }
+
+    /**
+     * Returns the sample value in the given column of this row. If the tile 
is not available at the time
+     * this method is invoked, then the tile will loaded in a background 
thread and the grid view will be
+     * refreshed when the tile become available.
+     *
+     * @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) {
+        return view.getSampleValue(y, tileRow, column);
+    }
+
+    /**
+     * Creates a new instance of the skin responsible for rendering this grid 
row.
+     * From the perspective of {@link IndexedCell}, the {@link Skin} is a 
black box.
+     * It listens and responds to changes in state of this grid row.
+     *
+     * @return the renderer of this grid row.
+     */
+    @Override
+    protected Skin<GridRow> createDefaultSkin() {
+        return new GridRowSkin(this);
+    }
+}
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
new file mode 100644
index 0000000..94277f3
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridRowSkin.java
@@ -0,0 +1,86 @@
+/*
+ * 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.Node;
+import javafx.scene.control.skin.CellSkinBase;
+import javafx.collections.ObservableList;
+import javafx.scene.text.Text;
+
+
+/**
+ * The renderer of {@link GridRow} instances. On construction, this object 
contains only one child.
+ * That child is an instance of {@link javafx.scene.text.Text} and is used for 
the row header. All
+ * other children will be instances of {@link GridCell} created and removed as 
needed during the
+ * layout pass.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class GridRowSkin extends CellSkinBase<GridRow> {
+    /**
+     * Invoked by {@link GridRow#createDefaultSkin()}.
+     */
+    GridRowSkin(final GridRow owner) {
+        super(owner);
+    }
+
+    /**
+     * Invoked during the layout pass to position the cells to be rendered by 
this row.
+     * This method also sets the content of the cell.
+     *
+     * <div class="note"><b>Note:</b> I'm not sure it is a good practice to 
add/remove children
+     * 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>
+     *
+     * @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).
+     */
+    @Override
+    protected void layoutChildren(final double x, final double y, final double 
width, final double height) {
+        /*
+         * Do not invoke super.layoutChildren(…) since we are doing a 
different layout.
+         * The first child is a javafx.scene.text.Text instance, which we use 
for row header.
+         */
+        final GridRow row = getSkinnable();
+        final ObservableList<Node> children = getChildren();
+        ((Text) children.get(0)).setText(String.valueOf(row.getIndex()));
+        /*
+         * All children starting at index 1 (i.e. children at indices `column 
+ 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);
+            } else {
+                child = new GridCell();
+                children.add(child);
+            }
+            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;
+        }
+    }
+}
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
new file mode 100644
index 0000000..169721b
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java
@@ -0,0 +1,266 @@
+/*
+ * 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.util.Arrays;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import javafx.beans.DefaultProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+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.coverage.grid.GridCoverage;
+
+
+/**
+ * A view of numerical values in a {@link RenderedImage}. The rendered image 
is typically a two dimensional slice
+ * of a {@link GridCoverage}. The number of rows is the image height and the 
number of columns is the image width.
+ * The view shows one band at a time, but the band to show can be changed 
(thus providing a navigation in a third
+ * dimension).
+ *
+ * <p>This class is designed for large images, with tiles loaded in a 
background thread only when first needed.
+ * For matrices of relatively small size (e.g. less than 100 columns), 
consider using the standard JavaFX
+ * {@link javafx.scene.control.TableView} instead.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+@DefaultProperty("image")
+public class GridView extends Control {
+    /** TODO: temporary setting. */
+    static final double cellWidth = 60;
+    static final double horizontalCellSpacing = 2;
+
+    /**
+     * The data shown in this table. Note that setting this property to a 
non-null value may not
+     * modify the grid content immediately. Instead, a background process will 
request the tiles.
+     *
+     * @see #getImage()
+     * @see #setImage(RenderedImage)
+     */
+    public final ObjectProperty<RenderedImage> imageProperty;
+
+    /**
+     * Information copied from {@link #imageProperty} for performance.
+     */
+    private int width, height, minX, minY, minTileX, minTileY, tileWidth, 
tileHeight, numXTiles;
+
+    /**
+     * Information copied and adjusted from {@link #imageProperty} for 
performance. Values are adjusted for using
+     * zero-based indices as expected by JavaFX tables (by contrast, pixel 
indices in a {@link RenderedImage} may
+     * start at a non-zero value). The results of those adjustments should be 
0, but we nevertheless compute them
+     * in case a {@link RenderedImage} defines unusual relationship between 
image and tile coordinate system.
+     */
+    private int tileGridXOffset, tileGridYOffset;
+
+    /**
+     * The {@link #imageProperty} tiles, fetched when first needed. All {@code 
Raster[]} array element and
+     * {@code Raster} sub-array elements are initially {@code null} and 
created when first needed.
+     * This field is null if and only if the image is null.
+     */
+    private Raster[][] tiles;
+
+    /**
+     * The image band to show in the table.
+     *
+     * @see #getBand()
+     * @see #setBand(int)
+     */
+    public final IntegerProperty bandProperty;
+
+    /**
+     * 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");
+        imageProperty.addListener(this::startImageLoading);
+        bandProperty = new SimpleIntegerProperty(this, "band");
+        // TODO: add listener. Check value range.
+    }
+
+    /**
+     * Returns the source of sample values for this table.
+     *
+     * @return the image shown in this table, or {@code null} if none.
+     *
+     * @see #imageProperty
+     */
+    public final RenderedImage getImage() {
+        return imageProperty.get();
+    }
+
+    /**
+     * Sets the image to show in this table. This method loads an arbitrary 
amount of tiles
+     * in a background thread. It does not load all tiles if the image is 
large, unless the
+     * user scroll over all tiles.
+     *
+     * <p><b>Note:</b> the table content may appear unmodified after this 
method returns.
+     * The modifications will appear at an undetermined amount of time 
later.</p>
+     *
+     * @param  image  the image to show in this table, or {@code null} if none.
+     *
+     * @see #imageProperty
+     */
+    public final void setImage(final RenderedImage image) {
+        imageProperty.set(image);
+    }
+
+    /**
+     * Returns the number of the band shown in this grid view.
+     *
+     * @return the currently visible band number.
+     *
+     * @see #bandProperty
+     */
+    public final int getBand() {
+        return bandProperty.get();
+    }
+
+    /**
+     * 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.
+     */
+    public final void setBand(final int visible) {
+        bandProperty.set(visible);
+    }
+
+    /**
+     * Invoked (indirectly) when the user sets a new {@link RenderedImage}.
+     * See {@link #setImage(RenderedImage)} for method description.
+     *
+     * @param  property  the {@link #imageProperty} (ignored).
+     * @param  previous  the previous image (ignored).
+     * @param  image     the new image to show. May be {@code null}.
+     * @throws ArithmeticException if the "tile grid x/y offset" property is 
too big. Should never happen
+     *         since those properties should be zero after the adjustment 
mentioned in their javadoc.
+     */
+    private void startImageLoading(final ObservableValue<? extends 
RenderedImage> property,
+                                   final RenderedImage previous, final 
RenderedImage image)
+    {
+        tiles  = null;       // Let garbage collector dispose the rasters.
+        width  = 0;
+        height = 0;
+        if (image != null) {
+            width           = image.getWidth();
+            height          = image.getHeight();
+            minX            = image.getMinX();
+            minY            = image.getMinY();
+            minTileX        = image.getMinTileX();
+            minTileY        = image.getMinTileY();
+            tileWidth       = image.getTileWidth();
+            tileHeight      = image.getTileHeight();
+            tileGridXOffset = Math.toIntExact(((long) 
image.getTileGridXOffset()) - minX + ((long) tileWidth)  * minTileX);
+            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);
+            }
+        }
+    }
+
+    /**
+     * 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.
+     */
+    final int getImageHeight() {
+        return height;
+    }
+
+    /**
+     * 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.
+     *
+     * @param  row  zero-based index of a row in this grid view.
+     * @return image <var>y</var> coordinate (may be outside image bounds).
+     * @throws ArithmeticException if image row for the given index is too 
large.
+     */
+    final int toImageY(final int row) {
+        return Math.addExact(row, minY);
+    }
+
+    /**
+     * Converts a grid row index to tile index. Note that the returned value 
may differ from
+     * the {@link RenderedImage} tile <var>y</var> coordinates because the 
index returned by
+     * this method is zero-based, while image tile index is arbitrary based.
+     */
+    final int toTileRow(final int row) {
+        return Math.subtractExact(row, tileGridYOffset) / tileHeight;
+    }
+
+    /**
+     * Returns the sample value in the given column of the given row. If the 
tile is not available at the
+     * time this method is invoked, then the tile will loaded in a background 
thread and the grid view will
+     * 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}.
+     *
+     * @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.
+     *
+     * @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 int tx = Math.subtractExact(column, tileGridXOffset) / 
tileWidth;
+            Raster[] row = tiles[tileRow];
+            if (row == null) {
+                tiles[tileRow] = row = new Raster[Math.min(16, numXTiles)];    
// Arbitrary limit, expanded if needed.
+            } else if (tx >= row.length && tx < numXTiles) {
+                tiles[tileRow] = row = Arrays.copyOf(row, Math.min(tx*2, 
numXTiles));
+            }
+            Raster tile = row[tx];
+            if (tile == null) {
+                // TODO: load in background
+                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);
+            // TODO: also return Float or Integer.
+        }
+        return null;
+    }
+
+    /**
+     * Creates a new instance of the skin responsible for rendering this grid 
view.
+     * From the perspective of this {@link Control}, the {@link Skin} is a 
black box.
+     * It listens and responds to changes in state of this grid view. This 
method is
+     * called if no skin is provided via CSS or {@link #setSkin(Skin)}.
+     *
+     * @return the renderer of this grid view.
+     */
+    @Override
+    protected final Skin<GridView> createDefaultSkin() {
+        return new GridViewSkin(this);
+    }
+}
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
new file mode 100644
index 0000000..a0825e6
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
@@ -0,0 +1,104 @@
+/*
+ * 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.image.RenderedImage;
+import javafx.scene.control.skin.VirtualContainerBase;
+import javafx.scene.control.skin.VirtualFlow;
+
+
+/**
+ * The {@link GridView} renderer as a virtualized and scrollable content.
+ * The primary direction of virtualization is vertical (rows will stack 
vertically on top of each other).
+ *
+ * <p>Relationships:</p>
+ * <ul>
+ *   <li>This is created by {@link GridView#createDefaultSkin()}.</li>
+ *   <li>The {@link GridView} owner is given by {@link #getSkinnable()}.</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class GridViewSkin extends VirtualContainerBase<GridView, GridRow> {
+    /**
+     * Creates a new skin for the specified view.
+     */
+    GridViewSkin(final GridView view) {
+        super(view);
+        final VirtualFlow<GridRow> flow = getVirtualFlow();
+        flow.setCellFactory(GridRow::new);
+        flow.setFocusTraversable(true);
+        flow.setPannable(false);
+        /*
+         * The list of children is initially empty. We need to
+         * add the virtual flow, otherwise nothing will appear.
+         */
+        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.
+     */
+
+    /**
+     * 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();
+    }
+
+    /**
+     * Invoked when it is possible that the item count has changed. JavaFX may 
invoke this method
+     * when scrolling has occurred, the control has resized, <i>etc.</i>, but 
for {@link GridView}
+     * the count will change only if a new {@link RenderedImage} has been 
specified.
+     */
+    @Override
+    protected void updateItemCount() {
+        /*
+         * 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.
+         */
+        final VirtualFlow<GridRow> flow = getVirtualFlow();
+        flow.setCellCount(getItemCount());                  // Fires event 
only if count changed.
+    }
+
+    // TODO: to update content, invoke getSkinnable().requestLayout().
+
+    /**
+     * Called during the layout pass of the scene graph. Current 
implementation sets the virtual
+     * flow size to the given size.
+     */
+    @Override
+    protected void layoutChildren(final double x, final double y, final double 
width, final double height) {
+        /*
+         * Super-class only invokes `updateItemCount()` if needed.
+         * It does not perform any layout by itself in this method.
+         */
+        super.layoutChildren(x, y, width, height);
+        getVirtualFlow().resizeRelocate(x, y, width, height);
+    }
+}
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
new file mode 100644
index 0000000..fa108e3
--- /dev/null
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+/**
+ * Widgets showing {@link org.apache.sis.coverage.grid.GridCoverage} images or 
values.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+package org.apache.sis.gui.coverage;

Reply via email to