sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Initial version of an `IsolineTable` for specifying the isoline values and colors. This control is not yet connected to the actual isolines computation.
Date Sun, 10 Jan 2021 23:26:25 GMT
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 87d1b72  Initial version of an `IsolineTable` for specifying the isoline values and
colors. This control is not yet connected to the actual isolines computation.
87d1b72 is described below

commit 87d1b72e82f7a5c0cdccc07e748eac645a8fc35e
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Jan 11 00:23:50 2021 +0100

    Initial version of an `IsolineTable` for specifying the isoline values and colors.
    This control is not yet connected to the actual isolines computation.
---
 .../apache/sis/gui/coverage/CoverageControls.java  |  17 +-
 .../apache/sis/gui/coverage/CoverageStyling.java   |  18 +-
 .../org/apache/sis/gui/coverage/IsolineLevel.java  |  75 ++++++++
 .../org/apache/sis/gui/coverage/IsolineTable.java  | 152 +++++++++++++++
 .../java/org/apache/sis/internal/gui/Styles.java   |   5 +
 .../internal/gui/control/ColorColumnHandler.java   |  48 +++--
 .../apache/sis/internal/gui/control/ColorRamp.java |   4 +-
 .../sis/internal/gui/control/FormatTableCell.java  | 208 +++++++++++++++++++++
 .../apache/sis/gui/coverage/IsolineTableApp.java   |  79 ++++++++
 .../org/apache/sis/util/resources/Vocabulary.java  |  10 +
 .../sis/util/resources/Vocabulary.properties       |   2 +
 .../sis/util/resources/Vocabulary_fr.properties    |   2 +
 12 files changed, 582 insertions(+), 38 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
index 29d0fad..3d15129 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java
@@ -138,16 +138,27 @@ final class CoverageControls extends Controls {
                     labelOfGroup(vocabulary, Vocabulary.Keys.Categories, categoryTable, true),
categoryTable, gp);
         }
         /*
+         * "Isolines" section with the following controls:
+         *    - Colors for each isoline levels
+         */
+        final VBox isolinesPane;
+        {   // Block for making variables locale to this scope.
+            final IsolineTable table = new IsolineTable();
+            final TableView<IsolineLevel> isolinesTable = table.createIsolineTable(vocabulary);
+            isolinesPane = new VBox(isolinesTable); // TODO: add band selector
+        }
+        /*
          * Put all sections together and have the first one expanded by default.
          * The "Properties" section will be built by `PropertyPaneCreator` only if requested.
          */
         final TitledPane p1 = new TitledPane(vocabulary.getString(Vocabulary.Keys.SpatialRepresentation),
displayPane);
         final TitledPane p2 = new TitledPane(vocabulary.getString(Vocabulary.Keys.Colors),
colorsPane);
-        final TitledPane p3 = new TitledPane(vocabulary.getString(Vocabulary.Keys.Properties),
null);
-        controls = new Accordion(p1, p2, p3);
+        final TitledPane p3 = new TitledPane(vocabulary.getString(Vocabulary.Keys.Isolines),
isolinesPane);
+        final TitledPane p4 = new TitledPane(vocabulary.getString(Vocabulary.Keys.Properties),
null);
+        controls = new Accordion(p1, p2, p3, p4);
         controls.setExpandedPane(p1);
         view.coverageProperty.bind(coverage);
-        p3.expandedProperty().addListener(new PropertyPaneCreator(view, p3));
+        p4.expandedProperty().addListener(new PropertyPaneCreator(view, p4));
     }
 
     /**
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageStyling.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageStyling.java
index 8055d93..77bce91 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageStyling.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageStyling.java
@@ -151,21 +151,22 @@ final class CoverageStyling extends ColorColumnHandler<Category>
implements Func
     /**
      * Invoked when users confirmed that (s)he wants to use the selected colors.
      *
-     * @param  value    the category for which to assign new color(s).
-     * @param  newItem  the new color for the given category, or {@code null} for resetting
default value.
+     * @param  category  the category for which to assign new color(s).
+     * @param  colors    the new color for the given category, or {@code null} for resetting
default value.
      * @return the type of color (solid or gradient) shown for the given value.
      */
     @Override
-    protected ColorRamp.Type applyColors(final Category value, ColorRamp newItem) {
-        setARGB(value, (newItem != null) ? newItem.colors : null);
-        return value.isQuantitative() ? ColorRamp.Type.GRADIENT : ColorRamp.Type.SOLID;
+    protected ColorRamp.Type applyColors(final Category category, ColorRamp colors) {
+        setARGB(category, (colors != null) ? colors.colors : null);
+        return category.isQuantitative() ? ColorRamp.Type.GRADIENT : ColorRamp.Type.SOLID;
     }
 
     /**
      * Creates a table showing the color of a qualitative or quantitative coverage categories.
      * The color can be modified by selecting the table row, then clicking on the color.
      *
-     * @param  vocabulary  resources for the locale in use.
+     * @param  vocabulary  localized resources, given because already known by the caller
+     *                     (this argument would be removed if this method was public API).
      */
     final TableView<Category> createCategoryTable(final Vocabulary vocabulary) {
         final TableColumn<Category,String> name = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Name));
@@ -173,7 +174,10 @@ final class CoverageStyling extends ColorColumnHandler<Category>
implements Func
         name.setCellFactory(CoverageStyling::createNameCell);
         name.setEditable(false);
         name.setId("name");
-
+        /*
+         * Create the table with above "category name" column (read-only),
+         * and add an editable column for color(s).
+         */
         final TableView<Category> table = new TableView<>();
         table.getColumns().add(name);
         addColumnTo(table, vocabulary);
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineLevel.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineLevel.java
new file mode 100644
index 0000000..7c71f0b
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineLevel.java
@@ -0,0 +1,75 @@
+/*
+ * 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.paint.Color;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import org.apache.sis.internal.gui.control.ColorRamp;
+
+
+/**
+ * Colors to apply on isoline for a given level.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class IsolineLevel {
+    /**
+     * Isolines level.
+     */
+    public final DoubleProperty value;
+
+    /**
+     * Color associated to isoline at this level.
+     *
+     * The value type is {@link ColorRamp} for now, but if this property become public in
a future version
+     * then the type should be changed to {@link Color} and bidirectionally binded to another
property
+     * (package-private) of type {@link ColorRamp}.
+     */
+    final ObjectProperty<ColorRamp> color;
+
+    /**
+     * Whether the isoline should be drawn on the map.
+     */
+    public final BooleanProperty visible;
+
+    /**
+     * Creates an empty isoline level.
+     */
+    IsolineLevel() {
+        value   = new SimpleDoubleProperty  (this, "value", Double.NaN);
+        color   = new SimpleObjectProperty<>(this, "color");
+        visible = new SimpleBooleanProperty (this, "visible");
+    }
+
+    /**
+     * Creates an isoline level for the given value.
+     */
+    IsolineLevel(final double value, final Color color) {
+        this();
+        this.value.set(value);
+        this.color.set(new ColorRamp(color));
+        visible.set(true);
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineTable.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineTable.java
new file mode 100644
index 0000000..572e8ae
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/IsolineTable.java
@@ -0,0 +1,152 @@
+/*
+ * 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.text.NumberFormat;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.ObservableList;
+import javafx.scene.paint.Color;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.cell.CheckBoxTableCell;
+import org.apache.sis.internal.gui.control.FormatTableCell;
+import org.apache.sis.internal.gui.control.ColorColumnHandler;
+import org.apache.sis.internal.gui.control.ColorRamp;
+import org.apache.sis.internal.gui.Styles;
+import org.apache.sis.util.resources.Vocabulary;
+
+
+/**
+ * Table of isolines (values and colors).
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class IsolineTable extends ColorColumnHandler<IsolineLevel> {
+    /**
+     * The format to use for formatting isoline levels.
+     * The same instance will be shared by all {@link FormatTableCell}s in this table.
+     */
+    private final NumberFormat format;
+
+    /**
+     * Creates a new handler.
+     */
+    IsolineTable() {
+        format = NumberFormat.getInstance();
+    }
+
+    /**
+     * Returns the colors to apply for the given isoline level, or {@code null} for transparent.
+     * This method is defined for safety but should not be invoked; use {@link #getObservableValue(S)}
instead.
+     */
+    @Override
+    protected int[] getARGB(final IsolineLevel level) {
+        final ColorRamp r = level.color.get();
+        return (r != null) ? r.colors : null;
+    }
+
+    /**
+     * Returns the color associated to given row as an observable value.
+     *
+     * @param  level  the isoline level for which to get color to show in color cell.
+     * @return the color(s) to use for the given isoline, or {@code null} if none (transparent).
+     */
+    @Override
+    protected ObservableValue<ColorRamp> getObservableValue(final IsolineLevel level)
{
+        return level.color;
+    }
+
+    /**
+     * Invoked when users confirmed that (s)he wants to use the selected colors.
+     *
+     * @param  level   the isoline level for which to assign new color(s).
+     * @param  colors  the new color for the given isoline, or {@code null} for resetting
default value.
+     * @return the type of color (always solid for this class).
+     */
+    @Override
+    protected ColorRamp.Type applyColors(final IsolineLevel level, ColorRamp colors) {
+        level.color.set(colors);
+        return ColorRamp.Type.SOLID;
+    }
+
+    /**
+     * Invoked when an isoline value has been edited. This method saves the value, checks
the isoline
+     * as visible and set a default color if no color was already set.
+     */
+    private static void commitEdit(final TableColumn.CellEditEvent<IsolineLevel,Number>
event) {
+        final IsolineLevel level = event.getRowValue();
+        final Number value = event.getNewValue();
+        level.value.set(value != null ? value.doubleValue() : Double.NaN);
+        if (level.color.get() == null) {
+            level.color.set(new ColorRamp(Color.BLACK));
+        }
+        level.visible.set(true);
+        /*
+         * If the edited line was the insertion row, we need to add a new insertion row.
+         */
+        final ObservableList<IsolineLevel> items = event.getTableView().getItems();
+        final int row = event.getTablePosition().getRow();
+        if (row >= items.size() - 1) {
+            items.add(new IsolineLevel());
+        }
+    }
+
+    /**
+     * Creates a table showing the color of isoline levels.
+     * The color can be modified by selecting the table row, then clicking on the color.
+     *
+     * @param  vocabulary  localized resources, given because already known by the caller
+     *                     (this argument would be removed if this method was public API).
+     */
+    final TableView<IsolineLevel> createIsolineTable(final Vocabulary vocabulary) {
+        /*
+         * First column containing a checkbox for choosing whether the isoline should be
drawn or not.
+         */
+        final TableColumn<IsolineLevel,Boolean> visible = new TableColumn<>("\uD83D\uDD89");
+        visible.setCellFactory(CheckBoxTableCell.forTableColumn(visible));
+        visible.setCellValueFactory((cell) -> cell.getValue().visible);
+        visible.setSortable(false);
+        visible.setResizable(false);
+        visible.setMinWidth(Styles.CHECKBOX_WIDTH);
+        visible.setMaxWidth(Styles.CHECKBOX_WIDTH);
+        /*
+         * Second column containing the level value.
+         * The number can be edited using a `NumberFormat` in current locale.
+         */
+        final TableColumn<IsolineLevel,Number> level = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Level));
+        level.setCellValueFactory((cell) -> cell.getValue().value);
+        level.setCellFactory((column) -> new FormatTableCell<>(Number.class, format));
+        level.setOnEditCommit(IsolineTable::commitEdit);
+        level.setSortable(false);                           // We will do our own sorting.
+        level.setId("level");
+        /*
+         * Create the table with above "category name" column (read-only),
+         * and add an editable column for color(s).
+         */
+        final TableView<IsolineLevel> table = new TableView<>();
+        table.getColumns().setAll(visible, level);
+        addColumnTo(table, vocabulary);
+        /*
+         * Add an empty row that user can edit for adding new data.
+         */
+        table.getItems().add(new IsolineLevel());
+        return table;
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
index 60e4ee0..f673c46 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
@@ -54,6 +54,11 @@ public final class Styles extends Static {
     public static final int SCROLLBAR_WIDTH = 20;
 
     /**
+     * Width of a checkbox or radio item in a table cell.
+     */
+    public static final int CHECKBOX_WIDTH = 40;
+
+    /**
      * "Standard" height of table rows. Can be approximate.
      */
     public static final double ROW_HEIGHT = 30;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorColumnHandler.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorColumnHandler.java
index bb5bad8..c60e977 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorColumnHandler.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorColumnHandler.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.gui.control;
 
 import javafx.util.Callback;
-import javafx.scene.paint.Color;
 import javafx.scene.input.KeyCode;
 import javafx.scene.input.KeyEvent;
 import javafx.scene.control.TableView;
@@ -58,14 +57,17 @@ public abstract class ColorColumnHandler<S> implements Callback<TableColumn.Cell
      * item instead, at implementation choice. The type determines which control (color picker,
combo box,
      * <i>etc.</i>) will be shown if user wants to edit the color.
      *
-     * @param  row      the row to update.
-     * @param  newItem  the new color(s) to assign to the given row item. May be {@code null}.
+     * @param  row     the row to update.
+     * @param  colors  the new color(s) to assign to the given row item. May be {@code null}.
      * @return the type of color (solid or gradient) shown for the given value.
      */
-    protected abstract ColorRamp.Type applyColors(S row, ColorRamp newItem);
+    protected abstract ColorRamp.Type applyColors(S row, ColorRamp colors);
 
     /**
      * Gets the ARGB codes of colors to shown in the cell for the given row data.
+     * This method is sufficient when the color(s) can be changed only by calls to
+     * {@link #applyColors(S, ColorRamp)}. If the color(s) may change externally,
+     * then {@link #getObservableValue(S)} should be overridden too.
      *
      * @param  row  the row item for which to get ARGB codes to show in color cell.
      * @return the colors as ARGB codes, or {@code null} if none (transparent).
@@ -73,14 +75,19 @@ public abstract class ColorColumnHandler<S> implements Callback<TableColumn.Cell
     protected abstract int[] getARGB(S row);
 
     /**
-     * If a {@code Color} instance already exists for the given row, that instance. Otherwise
{@code null}.
-     * In that later case, a {@code Color} or {@code Paint} instance will be created from
{@link #getARGB(S)}.
-     * This method is only for avoiding to create new {@code Color} instances when it already
exists.
+     * Returns the color associated to given row as an observable value. The default implementation
creates
+     * an unmodifiable value derived from {@link #getARGB(S)}. It is okay if the color(s)
can not be changed
+     * in other way than by calls to {@link #applyColors(Object, ColorRamp)}. If this assumption
does not hold,
+     * then subclasses should override this method and return the observable which is mutated
when the value change.
      *
-     * @param  row  the row item for which to get ARGB codes to show in color cell.
-     * @return the color to use for the given row, if an instance already exists.
+     * @param  row  the row item for which to get color to show in color cell. Never {@code
null}.
+     * @return the color(s) to use for the given row, or {@code null} if none (transparent).
      */
-    protected Color getColor(S row) {
+    protected ObservableValue<ColorRamp> getObservableValue(S row) {
+        final int[] ARGB = getARGB(row);
+        if (ARGB != null) {
+            return new ImmutableObjectProperty<>(new ColorRamp(ARGB));
+        }
         return null;
     }
 
@@ -89,26 +96,12 @@ public abstract class ColorColumnHandler<S> implements Callback<TableColumn.Cell
      * This method is public as an implementation side-effect; do not rely on that.
      *
      * @param  cell  the row value together with references to column and table where the
show the color cell.
-     * @return the color cell value.
+     * @return the color cell value, or {@code null} if none (transparent).
      */
     @Override
     public final ObservableValue<ColorRamp> call(final TableColumn.CellDataFeatures<S,ColorRamp>
cell) {
         final S value = cell.getValue();
-        if (value == null) {
-            return null;
-        }
-        final ColorRamp ramp;
-        final Color color = getColor(value);
-        if (color != null) {
-            ramp = new ColorRamp(color);
-        } else {
-            final int[] ARGB = getARGB(value);
-            if (ARGB == null) {
-                return null;
-            }
-            ramp = new ColorRamp(ARGB);
-        }
-        return new ImmutableObjectProperty<>(ramp);
+        return (value != null) ? getObservableValue(value) : null;
     }
 
     /**
@@ -116,7 +109,8 @@ public abstract class ColorColumnHandler<S> implements Callback<TableColumn.Cell
      * This method also modifies the table configuration.
      *
      * @param  table       the table where to add a colors column.
-     * @param  vocabulary  localized resources, provided in argument because often already
known by caller.
+     * @param  vocabulary  localized resources, given because already known by the caller
+     *                     (this argument would be removed if this method was public API).
      */
     protected final void addColumnTo(final TableView<S> table, final Vocabulary vocabulary)
{
         final TableColumn<S,ColorRamp> colors = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Colors));
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorRamp.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorRamp.java
index 8b87dfb..5445cca 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorRamp.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/ColorRamp.java
@@ -103,8 +103,10 @@ public final class ColorRamp {
 
     /**
      * Creates a new item for the given color.
+     *
+     * @param  color  the solid color.
      */
-    ColorRamp(final Color color) {
+    public ColorRamp(final Color color) {
         paint = this.color = color;
         colors = new int[] {GUIUtilities.toARGB(color)};
     }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/FormatTableCell.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/FormatTableCell.java
new file mode 100644
index 0000000..8d07988
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/control/FormatTableCell.java
@@ -0,0 +1,208 @@
+/*
+ * 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.internal.gui.control;
+
+import java.text.Format;
+import java.text.ParsePosition;
+import javafx.geometry.Pos;
+import javafx.scene.input.KeyCode;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.Background;
+import org.apache.sis.internal.gui.Styles;
+import org.apache.sis.util.CharSequences;
+
+
+/**
+ * A table cell where values are parsed and formatted using {@link java.text} API.
+ * This is equivalent to using {@link javafx.scene.control.cell.TextFieldTableCell}
+ * together with {@link javafx.util.converter.FormatStringConverter}, but with more
+ * control on parsing errors.
+ *
+ * <p>Subclasses may need to register a listener with {@code TableColumn.setOnEditCommit(…)}
+ * for copying the value in the object stored by {@link javafx.scene.control.TableView}.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @param  <S>  the type of elements contained in {@link javafx.scene.control.TableView}.
+ * @param  <T>  the type of elements contained in {@link javafx.scene.control.TableColumn}.
+ *
+ * @since 1.1
+ * @module
+ */
+public final class FormatTableCell<S,T> extends TableCell<S,T> {
+    /**
+     * The type of objects expected and returned by {@link #format}.
+     */
+    private final Class<T> valueType;
+
+    /**
+     * The format to use for parsing and formatting {@code <T>} values.
+     */
+    private final Format format;
+
+    /**
+     * The control to use during edition.
+     */
+    private TextField editor;
+
+    /**
+     * The {@link #editor} background to restore after successful parsing or cancellation.
+     * This is non-null only if the background has been changed for signaling a parsing error.
+     */
+    private Background backgroundToRestore;
+
+    /**
+     * Creates a new table cell for parsing and formatting values of the given type.
+     *
+     * @param valueType  the type of objects expected and returned by {@code format}.
+     * @param format     the format to use for parsing and formatting {@code <T>} values.
+     */
+    public FormatTableCell(final Class<T> valueType, final Format format) {
+        this.valueType = valueType;
+        this.format    = format;
+        setAlignment(Pos.CENTER_LEFT);
+    }
+
+    /**
+     * Returns {@code true} if the given item is null or {@link Double#NaN}.
+     * Future version may give some control on the values to filter, if there is a need.
+     */
+    private static boolean isNil(final Object item) {
+        return (item == null) || ((item instanceof Double) && ((Double) item).isNaN());
+    }
+
+    /**
+     * Returns the given item as text, or {@code null} if none. The text will be given
+     * to different control depending on whether this cell is in editing state or not.
+     *
+     * <p>Current implementation does not format {@link Double#NaN} values.
+     * Future version may give some control on the values to filter, if there is a need.</p>
+     */
+    private String format(final T item) {
+        return isNil(item) ? null : format.format(item);
+    }
+
+    /**
+     * Parses the current editor content and, if the parsing is successful, commit.
+     * If the parsing failed, the cell background color is changed and the caret is
+     * moved to the error position.
+     */
+    private void parseAndCommit() {
+        String text = editor.getText();
+        if (text != null) {
+            final int end = CharSequences.skipTrailingWhitespaces(text, 0, text.length());
+            final int start = CharSequences.skipLeadingWhitespaces(text, 0, end);
+            if (start < end) {
+                final ParsePosition pos = new ParsePosition(start);
+                final T value = valueType.cast(format.parseObject(text, pos));
+                final int stop = pos.getIndex();
+                if (stop >= end && !isNil(value)) {
+                    commitEdit(value);
+                    return;
+                }
+                editor.positionCaret(value != null ? stop : pos.getErrorIndex());
+            }
+        }
+        /*
+         * If `format` did not used all characters, either we have a parsing error
+         * or the last characters have been ignored (which we consider as an error).
+         * The 2 cases can be distinguished by `value` being null or not.
+         */
+        if (backgroundToRestore == null) {
+            backgroundToRestore = editor.getBackground();
+            if (backgroundToRestore == null) {
+                backgroundToRestore = Background.EMPTY;
+            }
+        }
+        editor.setBackground(Styles.ERROR_BACKGROUND);
+    }
+
+    /**
+     * Invoked when a new value needs to be shown in the cell. The new value will be formatted
in either
+     * the {@linkplain #editor} or in the label, depending if this cell is in editing state
or not.
+     *
+     * @param  item   the new value to show, or {@code null} if none.
+     * @param  empty  whether the cell is used for rendering an empty row.
+     */
+    @Override
+    protected void updateItem(T item, boolean empty) {
+        super.updateItem(item, empty);
+        String text = null;
+        TextField g = null;
+        if (!empty) {
+            if (isEditing()) {
+                g = editor;
+                if (g != null) {
+                    g.setText(format(item));
+                }
+            } else if (item != null) {
+                text = format(item);
+            }
+        }
+        setText(text);
+        setGraphic(g);
+    }
+
+    /**
+     * Transitions to editing state. If the {@linkplain #editor} has not yet been created,
it is created now.
+     * Then the editor is given the current text and is shown in temporary replacement of
the cell text.
+     * The full text is selected for allowing easy replacement.
+     */
+    @Override
+    public void startEdit() {
+        super.startEdit();
+        if (editor != null) {
+            /*
+             * If the editor background color has been changed because of an error,
+             * restores the normal background.
+             */
+            if (backgroundToRestore != null) {
+                editor.setBackground(backgroundToRestore);
+                backgroundToRestore = null;
+            }
+            editor.setText(getText());
+        } else {
+            editor = new TextField(getText());
+            editor.setOnAction((event) -> {
+                event.consume();
+                parseAndCommit();
+            });
+            editor.setOnKeyReleased((event) -> {
+                if (event.getCode() == KeyCode.ESCAPE) {
+                    event.consume();
+                    cancelEdit();
+                }
+            });
+        }
+        setText(null);
+        setGraphic(editor);
+        editor.selectAll();
+        editor.requestFocus();
+    }
+
+    /**
+     * Invoked when edition has been cancelled.
+     */
+    @Override
+    public void cancelEdit() {
+        super.cancelEdit();
+        setGraphic(null);
+        setText(format(getItem()));
+    }
+}
diff --git a/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/IsolineTableApp.java
b/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/IsolineTableApp.java
new file mode 100644
index 0000000..094ba00
--- /dev/null
+++ b/application/sis-javafx/src/test/java/org/apache/sis/gui/coverage/IsolineTableApp.java
@@ -0,0 +1,79 @@
+/*
+ * 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.Locale;
+import javafx.stage.Stage;
+import javafx.scene.Scene;
+import javafx.scene.paint.Color;
+import javafx.scene.control.Button;
+import javafx.scene.control.TableView;
+import javafx.scene.layout.BorderPane;
+import javafx.application.Application;
+import org.apache.sis.util.resources.Vocabulary;
+
+
+/**
+ * Shows isoline table built by {@link IsolineTable} with arbitrary data.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final strictfp class IsolineTableApp extends Application {
+    /**
+     * Starts the test application.
+     *
+     * @param args  ignored.
+     */
+    public static void main(final String[] args) {
+        launch(args);
+    }
+
+    /**
+     * Creates and starts the test application.
+     *
+     * @param  window  where to show the application.
+     */
+    @Override
+    public void start(final Stage window) {
+        final BorderPane pane = new BorderPane();
+        pane.setCenter(createIsolineTable());
+        pane.setBottom(new Button("Focus here"));
+        window.setTitle("IsolineTable Test");
+        window.setScene(new Scene(pane));
+        window.setWidth (400);
+        window.setHeight(300);
+        window.show();
+    }
+
+    /**
+     * Creates a table with arbitrary isolines to show.
+     */
+    private static TableView<IsolineLevel> createIsolineTable() {
+        final IsolineTable handler = new IsolineTable();
+        final TableView<IsolineLevel> table = handler.createIsolineTable(Vocabulary.getResources((Locale)
null));
+        table.getItems().setAll(
+                new IsolineLevel( 10, Color.BLUE),
+                new IsolineLevel( 25, Color.GREEN),
+                new IsolineLevel( 50, Color.ORANGE),
+                new IsolineLevel(100, Color.RED),
+                new IsolineLevel());                    // Empty row for inserting new values.
+        return table;
+    }
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
index 8b43adc..fb8a22a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
@@ -215,6 +215,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short Code_1 = 29;
 
         /**
+         * Color
+         */
+        public static final short Color = 251;
+
+        /**
          * Color index
          */
         public static final short ColorIndex = 30;
@@ -650,6 +655,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short InverseOperation = 108;
 
         /**
+         * Isolines
+         */
+        public static final short Isolines = 252;
+
+        /**
          * Java extensions
          */
         public static final short JavaExtensions = 109;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
index 10cf9a5..2d9f238 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
@@ -45,6 +45,7 @@ Class                   = Class
 Classpath               = Classpath
 Code                    = Code
 Code_1                  = {0} code
+Color                   = Color
 Colors                  = Colors
 ColorIndex              = Color index
 Commands                = Commands
@@ -133,6 +134,7 @@ Information             = Information
 Interpolation           = Interpolation
 Invalid                 = Invalid
 InverseOperation        = Inverse operation
+Isolines                = Isolines
 JavaExtensions          = Java extensions
 JavaHome                = Java home directory
 Julian                  = Julian
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
index 409c3d5..724644a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -52,6 +52,7 @@ Class                   = Classe
 Classpath               = Chemin de classes
 Code                    = Code
 Code_1                  = Code {0}
+Color                   = Couleur
 Colors                  = Couleurs
 ColorIndex              = Indice de couleur
 Commands                = Commandes
@@ -140,6 +141,7 @@ Information             = Information
 Interpolation           = Interpolation
 Invalid                 = Invalide
 InverseOperation        = Op\u00e9ration inverse
+Isolines                = Isolignes
 JavaExtensions          = Extensions du Java
 JavaHome                = R\u00e9pertoire du Java
 Julian                  = Julien


Mime
View raw message