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;
