This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit c372a46be1d550f27abce3e29dc3e40565922038 Author: Martin Desruisseaux <[email protected]> AuthorDate: Thu Oct 8 13:00:32 2026 +0200 Recognize tabular metadata property values in the JavaFX application as well. --- .../sis/gui/controls/FormatTableCellFactory.java | 117 +++++++++++++++++++++ .../sis/gui/coverage/ImagePropertyExplorer.java | 16 +-- .../org/apache/sis/gui/internal/PropertyView.java | 43 ++++++-- 3 files changed, 161 insertions(+), 15 deletions(-) diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatTableCellFactory.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatTableCellFactory.java new file mode 100644 index 0000000000..bf7b358657 --- /dev/null +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatTableCellFactory.java @@ -0,0 +1,117 @@ +/* + * 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.controls; + +import java.text.Format; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.function.Function; +import java.util.function.IntFunction; +import javafx.util.Callback; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.TableColumn; +import org.apache.sis.gui.internal.AlignedTableCell; +import org.apache.sis.gui.internal.ImmutableObjectProperty; + + +/** + * A table cell factory for values formatted using {@code java.text.Format}. + * Each table row shall be a {@link List} of objects recognized by the format. + * Values are presumed immutable. Instances of this class should be discarded + * when the table content change, because it retains references to old values. + * + * @author Martin Desruisseaux (Geomatys) + * + * @param <R> the type of value in each row of the table. + */ +public final class FormatTableCellFactory<R> implements Function<Object, ObservableValue<String>>, + Callback<TableColumn.CellDataFeatures<R, String>, ObservableValue<String>> +{ + /** + * Creates columns. + * + * @param <R> the type of values in the column. + * @param header header of each column. The length of this array will be the number of columns. + * @param formats a supplier of formats for given column indexes. + * @return columns to put in the table. + */ + public static <R> TableColumn<R, String>[] createColumns(final String[] header, final IntFunction<Format> formats) { + @SuppressWarnings({"unchecked", "rawtypes"}) + final TableColumn<R, String>[] columns = new TableColumn[header.length]; + final HashMap<Format, Format> previous = HashMap.newHashMap(columns.length); + for (int i = 0; i < columns.length; i++) { + Format format = formats.apply(i); + Format shared = previous.putIfAbsent(format, format); + if (shared != null) { + format = shared; // We can often use the same format instance for many columns. + } + final var column = new TableColumn<R, String>(header[i]); + column.setCellValueFactory(new FormatTableCellFactory<>(i, format)); + column.setCellFactory(AlignedTableCell.baselineRight()); + column.setMinWidth(60); + columns[i] = column; + } + return columns; + } + + /** + * The column index. + */ + private final int index; + + /** + * The format to use for formatting all values in this column. + */ + private final Format format; + + /** + * String representations of all formatted values. + */ + private final Map<Object, ObservableValue<String>> values; + + /** + * Creates a new factory which will format values using the given format. + */ + private FormatTableCellFactory(final int index, final Format format) { + this.index = index; + this.format = format; + this.values = new HashMap<>(); + } + + /** + * Invoked when a new string representation of a cell is requested. + * + * @param cell the table cell to format. + * @return string representation of the cell value. + */ + @Override + public ObservableValue<String> call(final TableColumn.CellDataFeatures<R, String> cell) { + return values.computeIfAbsent((cell.getValue() instanceof List<?> row) ? row.get(index) : null, this); + } + + /** + * Invoked when a new string representation of a value is requested. + * + * @param value the value to format. + * @return string representation of the value. + */ + @Override + public ObservableValue<String> apply(final Object value) { + return new ImmutableObjectProperty<>(value == null ? null : format.format(value)); + } +} diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java index 4bd0ed1597..3f0b209ab7 100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java @@ -191,7 +191,7 @@ public class ImagePropertyExplorer extends Widget { * Creates all rows. */ static LayoutRow[] values(final Vocabulary vocabulary, final Resources resources) { - final LayoutRow[] rows = new LayoutRow[7]; + final var rows = new LayoutRow[7]; rows[IMAGE_SIZE] = new LayoutRow(true, vocabulary.getString(Vocabulary.Keys.ImageSize)); rows[DISPLAYED_SIZE] = new LayoutRow(false, resources .getString(Resources .Keys.DisplayedSize)); rows[TILE_SIZE] = new LayoutRow(true, vocabulary.getString(Vocabulary.Keys.TileSize)); @@ -402,9 +402,9 @@ public class ImagePropertyExplorer extends Widget { integerFormat = NumberFormat.getIntegerInstance(); layout.setSelectionModel(null); - final TableColumn<LayoutRow, String> label = new TableColumn<>(resources.getString(Resources.Keys.SizeOrPosition)); - final TableColumn<LayoutRow, Number> xCol = new TableColumn<>(resources.getString(Resources.Keys.Along_1, "X")); - final TableColumn<LayoutRow, Number> yCol = new TableColumn<>(resources.getString(Resources.Keys.Along_1, "Y")); + final var label = new TableColumn<LayoutRow, String>(resources.getString(Resources.Keys.SizeOrPosition)); + final var xCol = new TableColumn<LayoutRow, Number>(resources.getString(Resources.Keys.Along_1, "X")); + final var yCol = new TableColumn<LayoutRow, Number>(resources.getString(Resources.Keys.Along_1, "Y")); final Callback<TableColumn<LayoutRow, Number>, TableCell<LayoutRow, Number>> cellFactory = (column) -> new LayoutCell(integerFormat); @@ -430,8 +430,8 @@ public class ImagePropertyExplorer extends Widget { properties = new TableView<>(); propertyRows = properties.getItems(); selectedProperty = properties.getSelectionModel().selectedItemProperty(); - final TableColumn<PropertyRow, String> label = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Property)); - final TableColumn<PropertyRow, Object> value = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Value)); + final var label = new TableColumn<PropertyRow, String>(vocabulary.getString(Vocabulary.Keys.Property)); + final var value = new TableColumn<PropertyRow, Object>(vocabulary.getString(Vocabulary.Keys.Value)); label.setCellValueFactory((cell) -> cell.getValue()); value.setCellValueFactory((cell) -> cell.getValue().value); value.setCellFactory((column) -> new PropertyCell(locale)); @@ -665,7 +665,7 @@ public class ImagePropertyExplorer extends Widget { /** * Creates the renderer of cells in the table of image layout information. */ - private static final class LayoutCell extends TableCell<LayoutRow,Number> { + private static final class LayoutCell extends TableCell<LayoutRow, Number> { /** * The formatter to use for numerical values in the table. */ @@ -691,7 +691,7 @@ public class ImagePropertyExplorer extends Widget { /** * Creates the renderer of cells in the table of image properties. */ - private static final class PropertyCell extends TableCell<PropertyRow,Object> { + private static final class PropertyCell extends TableCell<PropertyRow, Object> { /** * The formatter to use for producing a short string representation of a property value. */ diff --git a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/PropertyView.java b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/PropertyView.java index e0d9dee5a5..48af6a092e 100644 --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/PropertyView.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/PropertyView.java @@ -31,6 +31,7 @@ import javafx.scene.Node; import javafx.scene.text.Font; import javafx.scene.control.Label; import javafx.scene.control.ListView; +import javafx.scene.control.TableView; import javafx.scene.control.TextArea; import javafx.scene.image.ImageView; import javafx.scene.layout.Background; @@ -41,6 +42,8 @@ import org.opengis.referencing.IdentifiedObject; import org.apache.sis.math.Statistics; import org.apache.sis.util.Localized; import org.apache.sis.util.resources.Vocabulary; +import org.apache.sis.util.internal.shared.TableRowList; +import org.apache.sis.gui.controls.FormatTableCellFactory; /** @@ -81,6 +84,12 @@ public final class PropertyView implements Localized, ChangeListener<Number> { */ private ListView<String> listView; + /** + * Shows the {@linkplain #value} as a table. + * This is built only when first needed. + */ + private TableView<Object> tableView; + /** * Shows the {@linkplain #value} as an image. * This is built only when first needed. @@ -185,10 +194,11 @@ public final class PropertyView implements Localized, ChangeListener<Number> { task.cancel(BackgroundThreads.NO_INTERRUPT_DURING_IO); } content = switch (newValue) { - case null -> null; - case Throwable c -> setText(c); - case IdentifiedObject c -> setCRS(c); - case Collection<?> c -> setList(c.toArray()); + case null -> null; + case Throwable c -> setText(c); + case IdentifiedObject c -> setCRS(c); + case TableRowList<?,?> c -> setTable(c); + case Collection<?> c -> setList(c.toArray()); default -> newValue.getClass().isArray() ? setList(newValue) : setText(formats.formatValue(newValue, true)); }; @@ -233,7 +243,21 @@ public final class PropertyView implements Localized, ChangeListener<Number> { for (int i=0; i<list.length; i++) { list[i] = formats.formatValue(Array.get(array, i), true); } - listView.getItems().setAll(list); + node.getItems().setAll(list); + return node; + } + + /** + * Sets the property value to the given table. + */ + private Node setTable(final TableRowList<?,?> table) { + TableView<Object> node = tableView; + if (node == null) { + node = new TableView<>(); + tableView = node; + } + node.getColumns().setAll(FormatTableCellFactory.createColumns(table.columns(), (i) -> table.createFormat(getLocale(), i))); + node.getItems().setAll(table); return node; } @@ -264,6 +288,7 @@ public final class PropertyView implements Localized, ChangeListener<Number> { * @param boundsChanged whether {@link #visibleImageBounds} changed since last call. */ private Node setImage(final RenderedImage image, final boolean boundsChanged) { + @SuppressWarnings("LocalVariableHidesMemberVariable") final Pane imageCanvas = getImageCanvas(); ImageView node = imageView; if (node == null) { @@ -295,7 +320,7 @@ public final class PropertyView implements Localized, ChangeListener<Number> { imagePane.setHgap(0); imageView = node; } - final ImageConverter converter = new ImageConverter(image, visibleImageBounds, node, imageCanvas); + final var converter = new ImageConverter(image, visibleImageBounds, node, imageCanvas); if (converter.needsRun(boundsChanged)) { converter.setOnSucceeded((e) -> taskCompleted(converter.getValue())); converter.setOnFailed((e) -> { @@ -338,7 +363,7 @@ public final class PropertyView implements Localized, ChangeListener<Number> { String mean = null; if (statistics != null && statistics.length != 0) { final Statistics s = statistics[0]; - final StringBuffer buffer = new StringBuffer(); + final var buffer = new StringBuffer(); formats.formatPair(s.minimum(), " … ", s.maximum(), buffer); range = buffer.toString(); @@ -381,6 +406,10 @@ public final class PropertyView implements Localized, ChangeListener<Number> { if (listView != null) { listView.getItems().clear(); } + if (tableView != null) { + tableView.getItems().clear(); + tableView.getColumns().clear(); + } if (imageView != null) { ImageConverter.clear(imageView); }
