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 14a89a39bc Move the widget for configuring the isoline as a `MapItem` 
to show in `MapContextView`.
14a89a39bc is described below

commit 14a89a39bcf5af7b0f90353676eab2594009ba0a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Mar 30 11:33:09 2026 +0200

    Move the widget for configuring the isoline as a `MapItem` to show in 
`MapContextView`.
---
 .../apache/sis/gui/controls/FormatApplicator.java  |  6 +-
 .../apache/sis/gui/controls/ValueColorMapper.java  | 12 ++--
 .../apache/sis/gui/coverage/CoverageCanvas.java    | 18 +++--
 .../apache/sis/gui/coverage/CoverageControls.java  | 27 ++-----
 ...IsolineRenderer.java => IsolineController.java} | 82 ++++++++++++++++------
 .../apache/sis/gui/coverage/StyleController.java   | 30 ++++----
 .../sis/gui/coverage/StyledRenderingData.java      |  6 +-
 .../org/apache/sis/gui/internal/Resources.java     | 10 +++
 .../apache/sis/gui/internal/Resources.properties   |  2 +
 .../sis/gui/internal/Resources_fr.properties       |  2 +
 .../apache/sis/gui/map/style/ItemController.java   | 39 ++++++++++
 .../apache/sis/gui/map/style/MapContextView.java   | 23 +++++-
 .../sis/gui/controls/ValueColorMapperApp.java      |  7 +-
 13 files changed, 190 insertions(+), 74 deletions(-)

diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatApplicator.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatApplicator.java
index e1307edb72..ea24ba17b4 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatApplicator.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/FormatApplicator.java
@@ -17,6 +17,7 @@
 package org.apache.sis.gui.controls;
 
 import java.math.BigDecimal;
+import java.util.Locale;
 import java.text.Format;
 import java.text.NumberFormat;
 import java.text.DecimalFormat;
@@ -83,10 +84,11 @@ final class FormatApplicator<T> extends StringConverter<T>
      * rounding errors that may surprise the user, for example if we need to 
compute {@code n * scale}
      * where <var>scale</var> has been specified by user as 0.1.
      *
+     * @param  locale  the locale of the desired number format.
      * @return an instance for parsing and formatting numbers.
      */
-    public static FormatApplicator<Number> createNumberFormat() {
-        final FormatApplicator<Number> f = new 
FormatApplicator<>(Number.class, NumberFormat.getInstance());
+    public static FormatApplicator<Number> createNumberFormat(final Locale 
locale) {
+        final var f = new FormatApplicator<Number>(Number.class, 
NumberFormat.getInstance(locale));
         if (f.format instanceof DecimalFormat) {
             ((DecimalFormat) f.format).setParseBigDecimal(true);
         }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/ValueColorMapper.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/ValueColorMapper.java
index 7eb60b66d5..e9da2185a0 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/ValueColorMapper.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/ValueColorMapper.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.gui.controls;
 
+import java.util.Locale;
 import java.util.Objects;
 import java.math.BigDecimal;
 import javafx.beans.property.BooleanProperty;
@@ -178,14 +179,13 @@ public final class ValueColorMapper extends TabularWidget 
{
     /**
      * Creates a new "value-color mapper" widget.
      *
-     * @param  resources   localized resources, given because already known by 
the caller.
-     * @param  vocabulary  localized resources, given because already known by 
the caller
-     *                     (those arguments would be removed if this 
constructor was public API).
+     * @param  locale  the locale to use in this widget.
      */
-    public ValueColorMapper(final Resources resources, final Vocabulary 
vocabulary) {
+    public ValueColorMapper(final Locale locale) {
         table = newTable();
-        textConverter = FormatApplicator.createNumberFormat();
-        createIsolineTable(vocabulary);
+        textConverter = FormatApplicator.createNumberFormat(locale);
+        createIsolineTable(Vocabulary.forLocale(locale));
+        final Resources resources = Resources.forLocale(locale);
         final MenuItem rangeMenu = new 
MenuItem(resources.getString(Resources.Keys.RangeOfValues));
         final MenuItem clearAll  = new 
MenuItem(resources.getString(Resources.Keys.ClearAll));
         rangeMenu.setOnAction((e) -> insertRangeOfValues());
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
index 7433c89b84..ca1a9b00ae 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -289,7 +289,7 @@ public class CoverageCanvas extends MapCanvasAWT {
      * references to {@link javafx.scene.control.TableView} list of items, 
which are the list of isoline levels
      * with their colors.
      */
-    IsolineRenderer isolines;
+    IsolineController isolines;
 
     /**
      * Listener notified when tiles are read, for showing them on top of the 
image as translucent tiles.
@@ -775,6 +775,10 @@ public class CoverageCanvas extends MapCanvasAWT {
      * If the {@link StyledRenderingData#clear()} method is not invoked, then 
the map projection,
      * zoom, <i>etc.</i> are preserved.
      *
+     * <p>The caller is responsible for invoking {@link #requestRepaint()} or 
something equivalent,
+     * possibly indirectly through a listener on a modified property. The 
{@link #requestRepaint()}
+     * method is not invoked by this method because the caller will typically 
do more cleaning.</p>
+     *
      * @see #clear()
      */
     private void clearRenderedImage() {
@@ -844,6 +848,10 @@ public class CoverageCanvas extends MapCanvasAWT {
     /**
      * Clears all information that are derived from the raw image projected to 
objective CRS.
      * In current version this is only isolines.
+     *
+     * <p>The caller is responsible for invoking {@link #requestRepaint()} or 
something equivalent,
+     * possibly indirectly through a listener on a modified property. The 
{@link #requestRepaint()}
+     * method is not invoked by this method because the caller will typically 
do more cleaning.</p>
      */
     private void clearIsolines() {
         if (isolines != null) {
@@ -995,7 +1003,7 @@ public class CoverageCanvas extends MapCanvasAWT {
         /**
          * Snapshot of information required for rendering isolines, or {@code 
null} if none.
          */
-        private IsolineRenderer.Snapshot[] isolines;
+        private IsolineController.Snapshot[] isolines;
 
         /**
          * Creates a new renderer. Shall be invoked in JavaFX thread.
@@ -1124,7 +1132,7 @@ public class CoverageCanvas extends MapCanvasAWT {
                     prefetchedImage = data.prefetch(resampledImage, 
resampledToDisplay, displayBounds);
                 }
                 if (newIsolines != null) {
-                    IsolineRenderer.complete(isolines, newIsolines);
+                    IsolineController.complete(isolines, newIsolines);
                 }
             } finally {
                 LogHandler.loadingStop(id);
@@ -1151,7 +1159,7 @@ public class CoverageCanvas extends MapCanvasAWT {
                 // Arbitrarily use a line tickness of 1/8 of source pixel size 
for making apparent when zoom is strong.
                 gr.setStroke(new 
BasicStroke(data.getDataPixelSize(objectivePOI) / 8));
                 gr.transform((AffineTransform) objectiveToDisplay);     // 
This cast is safe in PlanarCanvas subclass.
-                for (final IsolineRenderer.Snapshot s : isolines) {
+                for (final IsolineController.Snapshot s : isolines) {
                     s.paint(gr, objectiveAOI);
                 }
                 gr.setTransform(at);
@@ -1176,7 +1184,7 @@ public class CoverageCanvas extends MapCanvasAWT {
                 tileReadListener.takeSnapshotOfObjectiveCRS();
             }
             if (isolines != null) {
-                for (final IsolineRenderer.Snapshot s : isolines) {
+                for (final IsolineController.Snapshot s : isolines) {
                     s.commit();
                 }
             }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageControls.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageControls.java
index 2daf592fc9..a3ea19acd9 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageControls.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageControls.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.gui.coverage;
 
-import java.util.List;
 import java.util.Locale;
 import javafx.application.Platform;
 import javafx.scene.control.TitledPane;
@@ -25,7 +24,6 @@ import javafx.scene.control.Label;
 import javafx.scene.control.TableView;
 import javafx.scene.control.Tooltip;
 import javafx.scene.layout.GridPane;
-import javafx.scene.layout.Region;
 import javafx.scene.layout.VBox;
 import javafx.scene.layout.Priority;
 import javafx.collections.ObservableList;
@@ -44,7 +42,6 @@ import org.apache.sis.gui.internal.Resources;
 import org.apache.sis.gui.internal.DataStoreOpener;
 import org.apache.sis.gui.internal.BackgroundThreads;
 import static org.apache.sis.gui.internal.LogHandler.LOGGER;
-import org.apache.sis.gui.controls.ValueColorMapper;
 import org.apache.sis.gui.controls.SyncWindowList;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.logging.Logging;
@@ -91,11 +88,6 @@ final class CoverageControls extends ViewAndControls {
      */
     private final ChoiceBox<Interpolation> interpolation;
 
-    /**
-     * The renderer of isolines.
-     */
-    private final IsolineRenderer isolines;
-
     /**
      * Creates a new set of coverage controls.
      *
@@ -118,7 +110,7 @@ final class CoverageControls extends ViewAndControls {
          *    - Tree of layers associated to the coverage (styling, isolines, 
visual indication of loaded tiles).
          */
         final var layers = new MapContextView(resources);
-        style = new StyleController(view, resources);
+        style = new StyleController(view);
         layers.setRootItem(style);
         /*
          * "Display" section with the following controls:
@@ -161,14 +153,10 @@ final class CoverageControls extends ViewAndControls {
          * "Isolines" section with the following controls:
          *    - Colors for each isoline levels
          */
-        final VBox isolinesPane;
-        {   // Block for making variables locale to this scope.
-            final ValueColorMapper mapper = new ValueColorMapper(resources, 
vocabulary);
-            isolines = new IsolineRenderer(view);
-            isolines.setIsolineTables(List.of(mapper.getSteps()));
-            final Region style = mapper.getView();
-            VBox.setVgrow(style, Priority.ALWAYS);
-            isolinesPane = new VBox(style);                         // TODO: 
add band selector
+        if (view.isolines == null) {
+            final var isolines = new IsolineController(view, 
vocabulary.getString(Vocabulary.Keys.Isolines));
+            style.getChildren().add(isolines);
+            view.isolines = isolines;
         }
         /*
          * Synchronized windows. A synchronized windows is a window which can 
reproduce the same gestures
@@ -182,9 +170,8 @@ final class CoverageControls extends ViewAndControls {
          */
         final TitledPane deferred;                  // Control to be built 
only if requested.
         controlPanes = new TitledPane[] {
-            new TitledPane(vocabulary.getString(Vocabulary.Keys.Layers),   
layers.getView()),
-            new TitledPane(vocabulary.getString(Vocabulary.Keys.Display),  
displayPane),
-            new TitledPane(vocabulary.getString(Vocabulary.Keys.Isolines), 
isolinesPane),
+            new TitledPane(vocabulary.getString(Vocabulary.Keys.Layers),  
layers.getView()),
+            new TitledPane(vocabulary.getString(Vocabulary.Keys.Display), 
displayPane),
             new TitledPane(resources.getString(Resources.Keys.Windows), 
windows.getView()),
             deferred = new 
TitledPane(vocabulary.getString(Vocabulary.Keys.Properties), null)
         };
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/IsolineRenderer.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/IsolineController.java
similarity index 86%
rename from 
optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/IsolineRenderer.java
rename to 
optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/IsolineController.java
index 19b59ba0a1..fbecd75e17 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/IsolineRenderer.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/IsolineController.java
@@ -31,6 +31,9 @@ import java.awt.geom.Rectangle2D;
 import java.awt.image.RenderedImage;
 import javafx.application.Platform;
 import javafx.scene.control.TableView;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.collections.ObservableList;
@@ -38,7 +41,10 @@ import javafx.collections.ListChangeListener;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.gui.controls.ColorRamp;
+import org.apache.sis.gui.controls.ValueColorMapper;
 import org.apache.sis.gui.controls.ValueColorMapper.Step;
+import org.apache.sis.gui.map.style.MapItem;
+import org.apache.sis.gui.map.style.ItemController;
 import org.apache.sis.image.processing.isoline.Isolines;
 import org.apache.sis.image.internal.shared.ImageUtilities;
 import org.apache.sis.geometry.wrapper.j2d.EmptyShape;
@@ -47,15 +53,16 @@ import org.apache.sis.util.ArraysExt;
 
 
 /**
- * Caches and draws isoline shapes in a {@link CoverageCanvas}. This class is 
designed for interactive use
- * in JavaFX widget; this is not a class for doing symbology e.g. in a web 
service. Most of the work done
- * by {@code IsolineRenderer} is about listening to changes in {@link 
TableView}, managing data exchanges
- * between JavaFX thread and background thread, and computes only isolines 
that are new compared to previous
- * rendering.
+ * Configures, draws and caches isoline shapes in a {@link CoverageCanvas}.
+ * This class is designed for interactive use in JavaFX widget.
+ * This is not a class for doing symbology e.g. in a web service.
+ * Most of the work done by {@code IsolineController} is about listening to 
changes in {@link TableView},
+ * managing data exchanges between JavaFX thread and background thread,
+ * and computes only isolines that are new compared to previous rendering.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class IsolineRenderer {
+final class IsolineController extends ItemController {
     /**
      * The canvas where isolines are drawn.
      */
@@ -66,17 +73,25 @@ final class IsolineRenderer {
      */
     private Band[] bands;
 
+    /**
+     * The widgets for configuring the isolines. Created when first requested.
+     *
+     * @see #getConfigurationPanel()
+     */
+    private Region configurationPanel;
+
     /**
      * Creates an initially empty set of isolines.
+     * The controller is unselected by default.
      *
      * @param  canvas  the canvas where isolines are drawn.
+     * @param  title   the title to show in the tree view. Usually "Isolines" 
in the current locale.
      */
-    public IsolineRenderer(final CoverageCanvas canvas) {
-        if (canvas.isolines != null) {
-            throw new IllegalArgumentException();
-        }
+    public IsolineController(final CoverageCanvas canvas, final String title) {
+        super(new MapItem(title));
         this.canvas = canvas;
-        canvas.isolines = this;
+        setIndependent(true);
+        selectedProperty().addListener((invalid) -> canvas.requestRepaint());
     }
 
     /**
@@ -99,6 +114,10 @@ final class IsolineRenderer {
     /**
      * Clears the cache. This method shall be invoked when the image used for 
computing isolines has changed,
      * or when the {@code gridToCRS} transform has changed. This method shall 
be invoked in JavaFX thread.
+     *
+     * <p>The caller is responsible for invoking {@link 
CoverageCanvas#requestRepaint()}.
+     * This is not done by this method because the caller will typically need 
to do more
+     * cleaning before to request a repaint.</p>
      */
     final void clear() {
         assert Platform.isFxApplicationThread();
@@ -109,6 +128,24 @@ final class IsolineRenderer {
         }
     }
 
+    /**
+     * Returns the panel of JavaFX controls for configuring the isolines.
+     * This method must be invoked from the JavaFX thread.
+     *
+     * @return the isoline configuration panel.
+     */
+    @Override
+    protected Region getConfigurationPanel() {
+        if (configurationPanel == null) {
+            final var mapper = new ValueColorMapper(canvas.getLocale());
+            setIsolineTables(List.of(mapper.getSteps()));
+            final Region style = mapper.getView();
+            VBox.setVgrow(style, Priority.ALWAYS);
+            configurationPanel = new VBox(style);       // TODO: add band 
selector
+        }
+        return configurationPanel;
+    }
+
     /**
      * Sets the isoline values for all bands from the content of tables edited 
by user.
      * This method registers listener on the given lists for repainting the 
isolines
@@ -152,6 +189,7 @@ final class IsolineRenderer {
          *
          * @param  steps  the list of isoline levels to render.
          */
+        @SuppressWarnings("this-escape")
         Band(final ObservableList<Step> steps) {
             this.steps = steps;
             addListeners(steps);
@@ -162,7 +200,7 @@ final class IsolineRenderer {
          * Clears the cache. This method shall be invoked when the image used 
for computing isolines has changed,
          * or when the {@code gridToCRS} transform has changed. This method 
shall be invoked in JavaFX thread.
          *
-         * @see IsolineRenderer#clear()
+         * @see IsolineController#clear()
          */
         final void clear() {
             // Force new instance instead of `Map.clear()` because previous 
instance may be used by `Snapshot`.
@@ -191,7 +229,11 @@ final class IsolineRenderer {
                     addListeners(change.getAddedSubList());
                 }
             }
-            canvas.requestRepaint();
+            final boolean isEmpty = isEmpty();
+            if (isSelected() == isEmpty) {
+                setSelected(!isEmpty);
+                canvas.requestRepaint();
+            }
         }
 
         /**
@@ -237,7 +279,7 @@ final class IsolineRenderer {
          * @param  keep  an empty set used by this method for listing the 
levels to keep in the cache.
          * @return a snapshot of current {@code Band} state for use by a 
background thread.
          *
-         * @see IsolineRenderer#prepare()
+         * @see IsolineController#prepare()
          */
         final Snapshot prepare(final Set<Double> keep) {
             if (isolines == null) {
@@ -271,11 +313,11 @@ final class IsolineRenderer {
      */
     final Snapshot[] prepare() {
         assert Platform.isFxApplicationThread();
-        if (isEmpty()) {
+        if (!isSelected() || isEmpty()) {
             return null;
         }
-        final Snapshot[] snapshots = new Snapshot[bands.length];
-        final Set<Double> keep = new HashSet<>();
+        final var snapshots = new Snapshot[bands.length];
+        final var keep = new HashSet<Double>();
         for (int i=0; i < snapshots.length; i++) {
             snapshots[i] = bands[i].prepare(keep);
         }
@@ -325,7 +367,7 @@ final class IsolineRenderer {
          */
         if (levels != null) {
             if (CoverageCanvas.TRACE) {
-                System.out.println("IsolineRenderer.complete(…):");
+                System.out.println("IsolineController.complete(…):");
                 for (int i=0; i<levels.length; i++) {
                     System.out.printf("\tFor band %d: %s%n", i, 
Arrays.toString(levels[i]));
                 }
@@ -389,7 +431,7 @@ final class IsolineRenderer {
          * the {@link #isolines} map after the isoline painting is completed. 
Stored in a separated map
          * because we must wait to be back to JavaFX thread before we can 
write in {@link #isolines}.
          */
-        private Map<Double,Shape> newIsolines;
+        private Map<Double, Shape> newIsolines;
 
         /**
          * The isoline shapes to draw. May contain {@code null} elements if 
some shapes are missing.
@@ -454,7 +496,7 @@ final class IsolineRenderer {
          */
         private void complete(final Isolines isolines) {
             newIsolines = isolines.polylines();
-            for (final Map.Entry<Double,Shape> entry : newIsolines.entrySet()) 
{
+            for (final Map.Entry<Double, Shape> entry : 
newIsolines.entrySet()) {
                 final Integer j = missingLevels.get(entry.getKey());
                 if (j != null) shapes[j] = entry.getValue();
             }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyleController.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyleController.java
index 18548647a8..514f2aa75e 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyleController.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyleController.java
@@ -32,26 +32,28 @@ import 
org.apache.sis.storage.tiling.TiledGridCoverageResource;
  * @author  Martin Desruisseaux (Geomatys)
  */
 final class StyleController extends ItemController {
+    /**
+     * The canvas for which to control the rendering.
+     */
+    private final CoverageCanvas canvas;
+
     /**
      * Whether to show a visual indication of which tiles are read.
+     * Creates when first needed.
      */
-    private final ItemController showTileReads;
+    private ItemController showTileReads;
 
     /**
      * Creates a controller for a map layer.
      * The controller is initially selected.
      *
-     * @param  view       where the coverage will be rendered.
-     * @param  resources  resources for localized <abbr>GUI</abbr> elements.
+     * @param  canvas  where the coverage will be rendered.
      */
-    StyleController(final CoverageCanvas view, final Resources resources) {
+    StyleController(final CoverageCanvas canvas) {
+        this.canvas = canvas;
         setSelected(true);
         setIndependent(true);
-        selectedProperty().addListener((p,o,n) -> view.setCoverageHidden(!n));
-        showTileReads = new ItemController(new 
MapItem(resources.getString(Resources.Keys.ShowTileReadEvents)));
-        showTileReads.selectedProperty().addListener((p,o,n) -> 
view.showTileReads(n));
-        showTileReads.setIndependent(true);
-        getChildren().add(showTileReads);
+        selectedProperty().addListener((p,o,n) -> 
canvas.setCoverageHidden(!n));
     }
 
     /**
@@ -68,10 +70,14 @@ final class StyleController extends ItemController {
             if (!isTiled) {
                 children.remove(last);
             }
-        } else {
-            if (isTiled) {
-                children.add(showTileReads);
+        } else if (isTiled) {
+            if (showTileReads == null) {
+                final Resources resources = 
Resources.forLocale(canvas.getLocale());
+                showTileReads = new ItemController(new 
MapItem(resources.getString(Resources.Keys.ShowTileReadEvents)));
+                showTileReads.selectedProperty().addListener((p,o,n) -> 
canvas.showTileReads(n));
+                showTileReads.setIndependent(true);
             }
+            children.add(showTileReads);
         }
     }
 }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyledRenderingData.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyledRenderingData.java
index 95ecbabc4e..24eff709b8 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyledRenderingData.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyledRenderingData.java
@@ -81,14 +81,14 @@ final class StyledRenderingData extends RenderingData {
      * This method shall be invoked in a background thread after image 
rendering has been completed (because this
      * method uses some image computation results).
      *
-     * @param  isolines  value of {@link IsolineRenderer#prepare()}, or {@code 
null} if none.
+     * @param  isolines  value of {@link IsolineController#prepare()}, or 
{@code null} if none.
      * @return result of isolines generation, or {@code null} if there are no 
isolines to compute.
      * @throws TransformException if an interpolated point cannot be 
transformed using the given transform.
      */
-    final Future<Isolines[]> generate(final IsolineRenderer.Snapshot[] 
isolines) throws TransformException {
+    final Future<Isolines[]> generate(final IsolineController.Snapshot[] 
isolines) throws TransformException {
         if (isolines == null) return null;
         final MathTransform centerToObjective = 
getDataToObjective(PixelInCell.CELL_CENTER);
-        return IsolineRenderer.generate(isolines, getSourceImage(), 
centerToObjective);
+        return IsolineController.generate(isolines, getSourceImage(), 
centerToObjective);
     }
 
     /**
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources.java
index f0224a4976..d7a52b71bd 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources.java
@@ -311,6 +311,16 @@ public class Resources extends IndexedResourceBundle {
          */
         public static final short NoFeatureTypeInfo = 27;
 
+        /**
+         * No selected item.
+         */
+        public static final short NoSelectedItem = 78;
+
+        /**
+         * Nothing to configure.
+         */
+        public static final short NothingToConfigure = 79;
+
         /**
          * Open…
          */
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources.properties
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources.properties
index 616e05df42..3238bad540 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources.properties
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources.properties
@@ -71,6 +71,8 @@ Mercator               = Mercator
 MainWindow             = Main window
 NewWindow              = New window
 NoFeatureTypeInfo      = No feature type information.
+NoSelectedItem         = No selected item.
+NothingToConfigure     = Nothing to configure.
 Open                   = Open\u2026
 OpenURL                = Open URL\u2026
 OpenContainingFolder   = Open containing folder
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources_fr.properties
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources_fr.properties
index 5d24f447dd..b4d044e410 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources_fr.properties
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/Resources_fr.properties
@@ -76,6 +76,8 @@ Mercator               = Mercator
 MainWindow             = Fen\u00eatre principale
 NewWindow              = Nouvelle fen\u00eatre
 NoFeatureTypeInfo      = Pas d\u2019information sur le type d\u2019entit\u00e9.
+NoSelectedItem         = Pas d\u2019item de s\u00e9lectionn\u00e9.
+NothingToConfigure     = Rien \u00e0 configurer.
 Open                   = Ouvrir\u2026
 OpenURL                = Ouvrir un URL\u2026
 OpenContainingFolder   = Ouvrir le dossier contenant
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/style/ItemController.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/style/ItemController.java
index 86e14a75ff..734e9d394f 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/style/ItemController.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/style/ItemController.java
@@ -16,7 +16,12 @@
  */
 package org.apache.sis.gui.map.style;
 
+import javafx.geometry.Pos;
+import javafx.scene.layout.VBox;
+import javafx.scene.layout.Region;
+import javafx.scene.control.Label;
 import javafx.scene.control.CheckBoxTreeItem;
+import org.apache.sis.gui.internal.Resources;
 
 
 /**
@@ -33,18 +38,52 @@ import javafx.scene.control.CheckBoxTreeItem;
  * @author  Martin Desruisseaux (Geomatys)
  */
 public class ItemController extends CheckBoxTreeItem<MapItem> {
+    /**
+     * A default configuration panel which said that there is nothing to 
configure.
+     * Created when first needed.
+     */
+    private Region configurationPanel;
+
     /**
      * Creates an initially empty controller.
+     * The controller is unselected by default.
      */
     public ItemController() {
     }
 
     /**
      * Creates a controller for the given map item.
+     * The controller is unselected by default.
      *
      * @param  item  the map item, or {@code null} if none.
      */
     public ItemController(final MapItem item) {
         super(item);
     }
+
+    /**
+     * Returns a panel of JavaFX controls for configuring the map item.
+     * This method should create the panel when first requested, then cache it.
+     * This method will be invoked from the JavaFX thread.
+     *
+     * @return the configuration panel for the map item.
+     */
+    protected Region getConfigurationPanel() {
+        if (configurationPanel == null) {
+            configurationPanel = 
createLabel(Resources.Keys.NothingToConfigure);
+        }
+        return configurationPanel;
+    }
+
+    /**
+     * Creates a panel showing a label such a "No selected item".
+     *
+     * @param  label  one of the {@link Resources} constants.
+     * @return the label to show.
+     */
+    static Region createLabel(final short label) {
+        final var box = new VBox(new Label(Resources.format(label)));
+        box.setAlignment(Pos.CENTER);
+        return box;
+    }
 }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/style/MapContextView.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/style/MapContextView.java
index 0c3d8d7cef..666c8c5c0a 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/style/MapContextView.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/style/MapContextView.java
@@ -87,6 +87,11 @@ public class MapContextView extends Widget {
      */
     private final SplitPane itemsAndConfiguration;
 
+    /**
+     * The node to show when no map item is selected.
+     */
+    private final Region noSelection;
+
     /**
      * Creates an initially empty tree.
      *
@@ -96,8 +101,10 @@ public class MapContextView extends Widget {
     public MapContextView(final Resources resources) {
         items = new TreeView<>();
         items.setCellFactory(CellFactory.INSTANCE);
-        itemsAndConfiguration = new SplitPane(items);
+        noSelection = 
ItemController.createLabel(Resources.Keys.NoSelectedItem);
+        itemsAndConfiguration = new SplitPane(items, noSelection);
         itemsAndConfiguration.setOrientation(Orientation.VERTICAL);
+        items.getSelectionModel().selectedItemProperty().addListener((p,o,n) 
-> onSelected(n));
     }
 
     /**
@@ -119,4 +126,18 @@ public class MapContextView extends Widget {
     public void setRootItem(final ItemController root) {
         items.setRoot(root);
     }
+
+    /**
+     * Invoked when a new map item is selected.
+     * This method shows the configuration panel of the selected item.
+     *
+     * @param  item  the selected map item, or {@code null} if none.
+     */
+    private void onSelected(final TreeItem<MapItem> item) {
+        Region config = noSelection;
+        if (item instanceof ItemController) {
+            config = ((ItemController) item).getConfigurationPanel();
+        }
+        itemsAndConfiguration.getItems().set(1, config);
+    }
 }
diff --git 
a/optional/src/org.apache.sis.gui/test/org/apache/sis/gui/controls/ValueColorMapperApp.java
 
b/optional/src/org.apache.sis.gui/test/org/apache/sis/gui/controls/ValueColorMapperApp.java
index 659a604e82..92df9595f0 100644
--- 
a/optional/src/org.apache.sis.gui/test/org/apache/sis/gui/controls/ValueColorMapperApp.java
+++ 
b/optional/src/org.apache.sis.gui/test/org/apache/sis/gui/controls/ValueColorMapperApp.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.gui.controls;
 
+import java.util.Locale;
 import javafx.stage.Stage;
 import javafx.scene.Scene;
 import javafx.scene.paint.Color;
@@ -23,8 +24,6 @@ import javafx.scene.control.Button;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.BorderPane;
 import javafx.application.Application;
-import org.apache.sis.gui.internal.Resources;
-import org.apache.sis.util.resources.Vocabulary;
 
 
 /**
@@ -71,9 +70,7 @@ public final class ValueColorMapperApp extends Application {
      * Creates a table with arbitrary isolines to show.
      */
     private static Region createIsolineTable() {
-        final ValueColorMapper handler = new ValueColorMapper(
-                Resources.forLocale(null),
-                Vocabulary.forLocale(null));
+        final ValueColorMapper handler = new 
ValueColorMapper(Locale.getDefault());
         handler.getSteps().setAll(
                 new ValueColorMapper.Step( 10, Color.BLUE),
                 new ValueColorMapper.Step( 25, Color.GREEN),

Reply via email to