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 65e63cc Complete the formatting of header row on top of the grid view
area.
65e63cc is described below
commit 65e63cc4d3188c16d1b5e87e66cc07255478299c
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Jan 25 15:32:53 2020 +0100
Complete the formatting of header row on top of the grid view area.
---
.../java/org/apache/sis/gui/coverage/GridRow.java | 16 --
.../org/apache/sis/gui/coverage/GridRowSkin.java | 44 ++---
.../java/org/apache/sis/gui/coverage/GridView.java | 60 ++++--
.../org/apache/sis/gui/coverage/GridViewSkin.java | 218 ++++++++++++++++-----
4 files changed, 230 insertions(+), 108 deletions(-)
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 ad89f85..361fd4c 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
@@ -42,22 +42,7 @@ import javafx.scene.text.FontWeight;
*/
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.
*/
final GridView view;
@@ -80,7 +65,6 @@ 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());
setFont(Font.font(null, FontWeight.BOLD, -1)); // Apply only to
the header 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 b085114..4bb4390 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
@@ -50,7 +50,7 @@ final class GridRowSkin extends CellSkinBase<GridRow> {
*/
final void setRowIndex(final int index) {
final Text header = (Text) getChildren().get(0);
- header.setText(getSkinnable().view.formatHeaderValue(index));
+ header.setText(getSkinnable().view.formatHeaderValue(index, true));
}
/**
@@ -76,9 +76,18 @@ final class GridRowSkin extends CellSkinBase<GridRow> {
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.
+ * The first child is a `javafx.scene.text.Text`, which we use for row
header.
*/
+ final ObservableList<Node> children = getChildren();
final GridRow row = getSkinnable();
+ final GridViewSkin layout = (GridViewSkin) row.view.getSkin();
+ /*
+ * Set the position of the header cell, but not its content. The
content has been set by
+ * `setRowIndex(int)` and does not need to be recomputed even during
horizontal scroll.
+ */
+ double pos = layout.leftPosition; // Horizontal position
in the virtual view.
+ ((Text) children.get(0)).resizeRelocate(pos, y, layout.headerWidth,
height);
+ pos += layout.headerWidth;
/*
* 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
@@ -86,30 +95,19 @@ final class GridRowSkin extends CellSkinBase<GridRow> {
* 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.
*/
- 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(); //
Horizontal position in the virtual view.
- final double limit = pos + row.flow.getWidth(); //
Horizontal position where to stop.
- int column = (int) (pos / cellWidth); //
Column index in the RenderedImage.
- /*
- * Set the position of the header cell, but not its content. The
content has been set by
- * `setRowIndex(int)` and does not need to be recomputed even during
horizontal scroll.
- */
- final ObservableList<Node> children = getChildren();
- final Text header = (Text) children.get(0);
- header.resizeRelocate(pos + cellSpacing, y, headerWidth - cellSpacing,
height);
- pos += headerWidth;
- /*
- * For sample value, we need to recompute both the values and the
position. Note that even if
- * the cells appear at the same positions visually (with different
content), they moved in the
- * virtual flow if some scrolling occurred.
- */
- int childIndex = 0;
+ final double cellWidth = layout.cellWidth; // Includes
the cell spacing.
+ final double available = layout.cellInnerWidth;
+ final double limit = layout.rightPosition; // Horizontal
position where to stop.
+ int column = layout.firstVisibleColumn; // Column
index in the RenderedImage.
+ int childIndex = 0;
List<GridCell> newChildren = null;
final int count = children.size();
while (pos < limit) {
+ /*
+ * For sample value, we need to recompute both the values and the
position. Note that even if
+ * the cells appear at the same positions visually (with different
content), they moved in the
+ * virtual flow if some scrolling occurred.
+ */
final GridCell cell;
if (++childIndex < count) {
cell = (GridCell) children.get(childIndex);
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 e7b9b6f..45b1407 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
@@ -57,7 +57,9 @@ import org.apache.sis.coverage.grid.GridCoverage;
@DefaultProperty("image")
public class GridView extends Control {
/**
- * Minimum cell width and height.
+ * Minimum cell width and height. Must be greater than zero, otherwise
infinite loops may happen.
+ *
+ * @see #getSizeValue(DoubleProperty)
*/
static final int MIN_CELL_SIZE = 1;
@@ -325,18 +327,6 @@ public class GridView extends Control {
}
/**
- * Invoked when the content may have changed. If {@code all} is {@code
true}, then everything
- * may have changed including the number of rows and columns. If {@code
all} is {@code false}
- * then the number of rows and columns is assumed the same.
- */
- final void contentChanged(final boolean all) {
- final Skin<?> skin = getSkin(); // May be null if the view
is not yet shown.
- if (skin instanceof GridViewSkin) { // Could be a user
instance (not recommended).
- ((GridViewSkin) skin).contentChanged(all);
- }
- }
-
- /**
* Invoked when the {@link #cellFormat} configuration changed.
*
* @param notify whether to notify the renderer about the change. Can be
{@code false}
@@ -351,11 +341,30 @@ public class GridView extends Control {
}
/**
+ * Invoked when the content may have changed. If {@code all} is {@code
true}, then everything
+ * may have changed including the number of rows and columns. If {@code
all} is {@code false}
+ * then the number of rows and columns is assumed the same.
+ */
+ private void contentChanged(final boolean all) {
+ final Skin<?> skin = getSkin(); // May be null if the view
is not yet shown.
+ if (skin instanceof GridViewSkin) { // Could be a user
instance (not recommended).
+ ((GridViewSkin) skin).contentChanged(all);
+ }
+ }
+
+ /**
* 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.
+ * This method does not take in account the space occupied by the vertical
scroll bar.
*/
final double getContentWidth() {
- return width * Math.max(MIN_CELL_SIZE, cellWidth.get()) +
Math.max(MIN_CELL_SIZE, headerWidth.get());
+ /*
+ * Add one more column for avoiding offsets caused by the rounding of
scroll bar position
+ * to integer multiple of column size. The 20 minimal value used below
is arbitrary;
+ * we take a value close to the vertical scrollbar width as a safety.
+ */
+ final double w = getSizeValue(cellWidth);
+ return width * w + getSizeValue(headerWidth) + Math.max(w, 20);
}
/**
@@ -446,10 +455,17 @@ public class GridView extends Control {
/**
* Formats a row index or column index.
+ *
+ * @param index the row or column index to format.
+ * @param vertical {@code true} if formatting row index, or {@code
false} if formatting column index.
*/
- final String formatHeaderValue(final int index) {
- buffer.setLength(0);
- return headerFormat.format(index, buffer, formatField).toString();
+ final String formatHeaderValue(final int index, final boolean vertical) {
+ if (index >= 0 && index < (vertical ? height : width)) {
+ buffer.setLength(0);
+ return headerFormat.format(index, buffer, formatField).toString();
+ } else {
+ return OUT_OF_BOUNDS;
+ }
}
/**
@@ -464,4 +480,14 @@ public class GridView extends Control {
protected final Skin<GridView> createDefaultSkin() {
return new GridViewSkin(this);
}
+
+ /**
+ * Returns the value of the given property as a real number not smaller
than {@value #MIN_CELL_SIZE}.
+ * We use this method instead of {@link Math#max(double, double)} because
we want {@link Double#NaN}
+ * values to be replaced by {@value #MIN_CELL_SIZE}.
+ */
+ static double getSizeValue(final DoubleProperty property) {
+ final double value = property.get();
+ return (value >= MIN_CELL_SIZE) ? value : MIN_CELL_SIZE;
+ }
}
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 8b81e0d..a41bb0b 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
@@ -20,10 +20,13 @@ import java.awt.image.RenderedImage;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
+import javafx.geometry.HPos;
+import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.control.skin.VirtualContainerBase;
+import javafx.scene.layout.HBox;
import javafx.scene.shape.Rectangle;
@@ -47,68 +50,109 @@ import javafx.scene.shape.Rectangle;
*/
final class GridViewSkin extends VirtualContainerBase<GridView, GridRow> {
/**
+ * The cells that we put in the header row on the top of the view. This
list is initially empty;
+ * new elements are added or removed when first needed and when the view
size changed.
+ */
+ private final ObservableList<Node> headerRow;
+
+ /**
+ * Background of the header row (top side) and header column (left side)
of the view.
+ */
+ private final Rectangle topBackground, leftBackground;
+
+ /**
+ * Image index of the first column visible in the view, ignoring the
header column.
+ * This is a {@link RenderedImage} <var>x</var> index (with an arbitrary
origin),
+ * not necessarily the same value than the zero-based index used in JavaFX
views.
+ *
+ * <p>This field is written by {@link #layoutChildren(double, double,
double, double)}.
+ * All other accesses (especially from outside of this class) should be
read-only.</p>
+ */
+ int firstVisibleColumn;
+
+ /**
+ * Horizontal position in the virtual flow where to start writing the text
of the header column.
+ * This value changes during horizontal scrolls, even if the cells
continue to start at the same
+ * visual position on the screen. The position of the column showing
{@link #firstVisibleColumn}
+ * sample values is {@code leftPosition} + {@link #headerWidth}, and that
position is incremented
+ * by {@link #cellWidth} for all other columns.
+ *
+ * <p>This field is written by {@link #layoutChildren(double, double,
double, double)}.
+ * All other accesses (especially from outside of this class) should be
read-only.</p>
+ */
+ double leftPosition;
+
+ /**
+ * Horizontal position where to stop rendering the cells.
+ * This is {@link #leftPosition} + the view width.
+ *
+ * <p>This field is written by {@link #layoutChildren(double, double,
double, double)}.
+ * All other accesses (especially from outside of this class) should be
read-only.</p>
+ */
+ double rightPosition;
+
+ /**
+ * Width of the header column ({@code headerWidth}) and of all other
columns ({@code cellWidth}).
+ * Must be greater than zero, otherwise infinite loop may happen.
+ *
+ * <p>This field is written by {@link #layoutChildren(double, double,
double, double)}.
+ * All other accesses (especially from outside of this class) should be
read-only.</p>
+ */
+ double headerWidth, cellWidth;
+
+ /**
+ * Width of the region where to write the text in a cell. Should be equals
or slightly smaller
+ * than {@link #cellWidth}. We use a smaller width for leaving a small
margin between cells.
+ *
+ * <p>This field is written by {@link #layoutChildren(double, double,
double, double)}.
+ * All other accesses (especially from outside of this class) should be
read-only.</p>
+ */
+ double cellInnerWidth;
+
+ /**
* Creates a new skin for the specified view.
*/
GridViewSkin(final GridView view) {
super(view);
+ final HBox header = new HBox();
+ headerRow = header.getChildren();
+ /*
+ * Main content where sample values will be shown.
+ */
final VirtualFlow<GridRow> flow = getVirtualFlow();
flow.setCellFactory(GridRow::new);
flow.setFocusTraversable(true);
- flow.setFixedCellSize(Math.max(GridView.MIN_CELL_SIZE,
view.cellHeight.get()));
+ flow.setFixedCellSize(GridView.getSizeValue(view.cellHeight));
view.cellHeight .addListener(this::cellHeightChanged);
view.cellWidth .addListener(this::cellWidthChanged);
view.headerWidth.addListener(this::cellWidthChanged);
/*
* Rectangles for filling the background of the cells in the header
row and header column.
- * Those rectangles will be resized when the GridView size changes or
cells size changes.
+ * Those rectangles will be resized and relocated in `layout(…)`
method.
*/
- final Rectangle topBackground = new Rectangle();
- final Rectangle leftBackground = new Rectangle();
- topBackground.setHeight(view.cellHeight.get());
- leftBackground.setY(topBackground.getHeight());
- leftBackground.widthProperty().bind(view.headerWidth);
- leftBackground.fillProperty() .bind(view.headerBackground);
- topBackground .fillProperty() .bind(view.headerBackground);
+ topBackground = new Rectangle();
+ leftBackground = new Rectangle();
+ leftBackground.fillProperty().bind(view.headerBackground);
+ topBackground .fillProperty().bind(view.headerBackground);
/*
* The list of children is initially empty. We need to
* add the virtual flow, otherwise nothing will appear.
*/
- getChildren().addAll(topBackground, leftBackground, flow);
- flow.widthProperty() .addListener(this::gridSizeChanged);
- flow.heightProperty().addListener(this::gridSizeChanged);
+ getChildren().addAll(topBackground, leftBackground, header, flow);
}
/**
- * Invoked when the width or height of {@link GridView} changed. This
method recomputes the size of
- * the rectangles used for painting backgrounds. We listen to changes in
width and height together
- * because a change of width may show or hide the horizontal scroll bar,
which change the height
- * (and conversely for the vertical scroll bar).
- */
- private void gridSizeChanged(ObservableValue<? extends Number> property,
Number oldValue, Number newValue) {
- final Flow flow = (Flow) getVirtualFlow();
- final ObservableList<Node> children = getChildren();
- Rectangle r;
- r = (Rectangle) children.get(0); r.setWidth (flow.getVisibleWidth() -
r.getX());
- r = (Rectangle) children.get(1); r.setHeight(flow.getVisibleHeight() -
r.getY());
- }
-
- /**
- * Invoked when the value of {@link GridView#cellHeight} property changed.
This method copies the new value
- * into {@link VirtualFlow#fixedCellSizeProperty()} after bounds check,
then adjusts the size and position
- * of rectangles filling the header background.
+ * Invoked when the value of {@link GridView#cellHeight} property changed.
+ * This method copies the new value into {@link
VirtualFlow#fixedCellSizeProperty()} after bounds check.
*/
private void cellHeightChanged(ObservableValue<? extends Number> property,
Number oldValue, Number newValue) {
final Flow flow = (Flow) getVirtualFlow();
- final ObservableList<Node> children = getChildren();
- final double height = Math.max(GridView.MIN_CELL_SIZE,
newValue.doubleValue());
- flow.setFixedCellSize(height);
- Rectangle r;
- r = (Rectangle) children.get(0); r.setHeight(height);
- r = (Rectangle) children.get(1); r.setHeight(flow.getVisibleHeight() -
height); r.setY(height);
+ final double value = newValue.doubleValue();
+ flow.setFixedCellSize(value >= GridView.MIN_CELL_SIZE ? value :
GridView.MIN_CELL_SIZE);
}
/**
- * Invoked when the cell width or cell spacing changed.
+ * Invoked when the cell width or header width changed.
* This method notifies all children about the new width.
*/
private void cellWidthChanged(ObservableValue<? extends Number> property,
Number oldValue, Number newValue) {
@@ -125,6 +169,9 @@ final class GridViewSkin extends
VirtualContainerBase<GridView, GridRow> {
* may have changed including the number of rows and columns. If {@code
all} is {@code false}
* then the number of rows and columns is assumed the same.
*
+ * <p>This method is invoked by {@link GridView} when the image has
changed,
+ * or the band in the image to show has changed.</p>
+ *
* @see GridView#contentChanged(boolean)
*/
final void contentChanged(final boolean all) {
@@ -150,6 +197,15 @@ final class GridViewSkin extends
VirtualContainerBase<GridView, GridRow> {
/**
* The virtual flow used by {@link GridViewSkin}. We define that class
* mostly for getting access to the protected {@link #getHbar()} method.
+ * There is two main properties that we want:
+ *
+ * <ul>
+ * <li>{@link #getHorizontalPosition()} for the position of the
horizontal scroll bar.</li>
+ * <li>{@link #getWidth()} for the width of the visible region.
+ * </ul>
+ *
+ * Those two properties are used for creating the minimal amount
+ * of {@link GridCell}s needed for rendering the {@link GridRow}.
*/
static final class Flow extends VirtualFlow<GridRow> implements
ChangeListener<Number> {
/**
@@ -159,8 +215,8 @@ final class GridViewSkin extends
VirtualContainerBase<GridView, GridRow> {
@SuppressWarnings("ThisEscapedInObjectConstruction")
Flow(final GridView view) {
getHbar().valueProperty().addListener(this);
- view.bandProperty .addListener(this);
- view.cellSpacing .addListener(this);
+ view.bandProperty.addListener(this);
+ view.cellSpacing .addListener(this);
// Other listeners are registered by enclosing class.
}
@@ -173,19 +229,9 @@ final class GridViewSkin extends
VirtualContainerBase<GridView, GridRow> {
}
/**
- * Returns the width of the view port area, not counting the vertical
scroll bar.
- */
- final double getVisibleWidth() {
- double width = getWidth();
- final ScrollBar bar = getVbar();
- if (bar.isVisible()) {
- width -= bar.getWidth();
- }
- return width;
- }
-
- /**
- * Returns the height of the view port area, not counting the
horizontal scroll bar.
+ * Returns the height of the view area, not counting the horizontal
scroll bar.
+ * This height does not include the row header neither, because it is
managed by
+ * a separated node ({@link #headerRow}).
*/
final double getVisibleHeight() {
double height = getHeight();
@@ -252,6 +298,74 @@ final class GridViewSkin extends
VirtualContainerBase<GridView, GridRow> {
* It does not perform any layout by itself in this method.
*/
super.layoutChildren(x, y, width, height);
- getVirtualFlow().resizeRelocate(x, y, width, height);
+ /*
+ * Do layout of the flow first because it may cause scroll bars to
appear or disappear,
+ * which may change the size calculations done after that. The flow is
located below the
+ * header row, so we adjust y and height accordingly.
+ */
+ final Flow flow = (Flow) getVirtualFlow();
+ final double cellHeight = flow.getFixedCellSize();
+ final double dataY = y + cellHeight;
+ final double dataHeight = height - cellHeight;
+ final boolean resized = (flow.getWidth() != width) ||
(flow.getHeight() != dataHeight);
+ flow.resizeRelocate(x, dataY, width, dataHeight);
+ /*
+ * Recompute all values which will be needed by GridRowSkin. They are
mostly information about
+ * the horizontal dimension, because the vertical dimension is already
managed by VirtualFlow.
+ * We compute here for avoiding to recompute the same values in each
GridRowSkin instance.
+ */
+ final double oldPos = leftPosition;
+ final GridView view = getSkinnable();
+ headerWidth = GridView.getSizeValue(view.headerWidth);
+ cellWidth = GridView.getSizeValue(view.cellWidth);
+ double cellSpacing = Math.min(view.cellSpacing.get(), cellWidth);
+ if (!(cellSpacing >= 0)) cellSpacing = 0; // Use !
for catching NaN (can not use Math.max).
+ cellInnerWidth = cellWidth - cellSpacing;
+ leftPosition = flow.getHorizontalPosition(); //
Horizontal position in the virtual view.
+ rightPosition = leftPosition + width; //
Horizontal position where to stop.
+ firstVisibleColumn = (int) (leftPosition / cellWidth); // Column
index in the RenderedImage.
+ /*
+ * Set the rectangle position before to do final adjustment on cell
position,
+ * because the background to fill should include the `cellSpacing`
margin.
+ */
+ topBackground .setX(x); // As a
matter of principle, but should be zero.
+ topBackground .setY(y);
+ topBackground .setWidth(width);
+ topBackground .setHeight(cellHeight);
+ leftBackground.setX(x);
+ leftBackground.setY(dataY);
+ leftBackground.setWidth(headerWidth);
+ leftBackground.setHeight(flow.getVisibleHeight());
+ if (cellSpacing < headerWidth) {
+ headerWidth -= cellSpacing;
+ leftPosition += cellSpacing;
+ }
+ /*
+ * Reformat the row header if its content changed. It may be because a
horizontal scroll has been
+ * detected (in which case values changed), or because the view size
changed (in which case cells
+ * may need to be added or removed).
+ */
+ if (resized || oldPos != leftPosition) {
+ final int count = headerRow.size();
+ final int missing = (int) Math.ceil((width - headerWidth) /
cellWidth) - count;
+ if (missing != 0) {
+ if (missing < 0) {
+ headerRow.remove(missing + count, count); // Too
many children. Remove the extra ones.
+ } else {
+ final GridCell[] more = new GridCell[missing];
+ for (int i=0; i<missing; i++) {
+ more[i] = new GridCell();
+ }
+ headerRow.addAll(more); // Single addAll(…)
operation for sending only one event.
+ }
+ }
+ double pos = x + headerWidth;
+ int column = firstVisibleColumn;
+ for (final Node cell : headerRow) {
+ ((GridCell) cell).setText(view.formatHeaderValue(column++,
false));
+ layoutInArea(cell, pos, y, cellWidth, cellHeight, 0,
HPos.CENTER, VPos.CENTER);
+ pos += cellWidth;
+ }
+ }
}
}