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);
         }

Reply via email to