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

commit 6873859374d5616788197fb7c5a2ddde8bd6c743
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Nov 10 16:34:37 2021 +0100

    Consolidation on the widget showing a "coverage view + controls" pair:
    Avoid creating controls before needed, make locale more explicit, etc.
---
 .../src/main/java/org/apache/sis/gui/Widget.java   |  20 ++-
 .../apache/sis/gui/coverage/CoverageControls.java  |   4 +-
 .../apache/sis/gui/coverage/CoverageExplorer.java  | 185 ++++++++++++++-------
 .../org/apache/sis/gui/coverage/GridControls.java  |   4 +-
 .../org/apache/sis/gui/coverage/GridViewSkin.java  |  28 ++--
 .../sis/gui/coverage/ImagePropertyExplorer.java    |  12 +-
 .../{Controls.java => ViewAndControls.java}        |  14 +-
 .../org/apache/sis/gui/dataset/WindowManager.java  |  11 +-
 .../java/org/apache/sis/gui/map/StatusBar.java     |   9 +-
 .../org/apache/sis/internal/gui/ToolbarButton.java |  24 +--
 10 files changed, 201 insertions(+), 110 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/Widget.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/Widget.java
index 222570c..13bd977 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/Widget.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/Widget.java
@@ -16,7 +16,9 @@
  */
 package org.apache.sis.gui;
 
+import java.util.Locale;
 import javafx.scene.layout.Region;
+import org.apache.sis.util.Localized;
 
 
 /**
@@ -36,11 +38,11 @@ import javafx.scene.layout.Region;
  * {@link org.apache.sis.gui.map.MapCanvas}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
-public abstract class Widget {
+public abstract class Widget implements Localized {
     /**
      * Creates a new widget.
      */
@@ -54,4 +56,18 @@ public abstract class Widget {
      * @return the JavaFX component to insert in a scene graph.
      */
     public abstract Region getView();
+
+    /**
+     * Returns the locale for controls and messages. This is usually the
+     * {@linkplain Locale#getDefault() default locale} but some widgets allow 
alternative locale.
+     * This is indicative; there is no guarantee that this locale will be 
honored by all controls.
+     *
+     * @return the locale for controls in this widget.
+     *
+     * @since 1.2
+     */
+    @Override
+    public Locale getLocale() {
+        return Locale.getDefault(Locale.Category.DISPLAY);
+    }
 }
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 fe7060b..161bcc5 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
@@ -54,11 +54,11 @@ import org.apache.sis.internal.gui.control.ValueColorMapper;
  * A {@link CoverageCanvas} with associated controls to show in a {@link 
CoverageExplorer}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
-final class CoverageControls extends Controls {
+final class CoverageControls extends ViewAndControls {
     /**
      * The component for showing sample values.
      */
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
index 30eab5f..a06d273 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageExplorer.java
@@ -16,9 +16,10 @@
  */
 package org.apache.sis.gui.coverage;
 
-import java.util.Locale;
+import java.util.EnumMap;
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
+import javafx.application.Platform;
 import javafx.beans.DefaultProperty;
 import javafx.scene.control.Control;
 import javafx.scene.control.SplitPane;
@@ -35,6 +36,7 @@ import org.apache.sis.internal.gui.Resources;
 import org.apache.sis.internal.gui.ToolbarButton;
 import org.apache.sis.internal.gui.NonNullObjectProperty;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.gui.referencing.RecentReferenceSystems;
 import org.apache.sis.gui.map.StatusBar;
@@ -44,10 +46,27 @@ import org.apache.sis.storage.Resource;
 
 /**
  * An image or tabular view of {@link GridCoverage} together with controls for 
band selection and other operations.
- * This class manages a {@link CoverageCanvas} and a {@link GridView} for 
showing the visual and the numerical values.
+ * The class contains two properties:
+ *
+ * <ul>
+ *   <li>A {@link GridCoverage} supplied by user, or an {@link ImageRequest} 
for loading a coverage.</li>
+ *   <li>A {@link View} type which specify how to show the coverage:
+ *     <ul>
+ *       <li>using {@link GridView} for showing numerical values in a table, 
or</li>
+ *       <li>using {@link CoverageCanvas} for showing the coverage as an 
image.</li>
+ *     </ul>
+ *   </li>
+ * </ul>
+ *
+ * Controls are provided for allowing user to customize map projection, number 
formats, <i>etc.</i>.
+ * The set of control depends on the view type.
+ *
+ * <h2>Limitations</h2>
+ * Current implementation is restricted to {@link GridCoverage} instances, but 
a future
+ * implementation may generalize to {@link org.opengis.coverage.Coverage} 
instances.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @see CoverageCanvas
  * @see GridView
@@ -60,18 +79,26 @@ public class CoverageExplorer extends Widget {
     /**
      * Type of view shown in the explorer.
      * It may be either an image or a table of numerical values.
+     *
+     * @see #viewTypeProperty
      */
     public enum View {
+        // Enumeration order is the order in which buttons will appear on the 
button bar.
+
+        /**
+         * Shows the coverage visual as an image. This view uses {@link 
CoverageCanvas}.
+         */
+        IMAGE("\uD83D\uDDFA\uFE0F", Resources.Keys.TabularData),    // 🗺 — 
World map.
+
         /**
          * Shows the coverage numerical value in a table. This view uses 
{@link GridView}.
-         * This is the default value of newly constructed {@link 
CoverageExplorer}.
          */
-        TABLE("\uD83D\uDD22\uFE0F", Resources.Keys.Visualize),      // 🔢 — 
Input symbol for numbers.
+        TABLE("\uD83D\uDD22\uFE0F", Resources.Keys.Visualize);      // 🔢 — 
Input symbol for numbers.
 
         /**
-         * Shows the coverage visual as an image. This view uses {@link 
CoverageCanvas}.
+         * Number of enumeration values.
          */
-        IMAGE("\uD83D\uDDFA\uFE0F", Resources.Keys.TabularData);    // 🗺 — 
World map.
+        static final int COUNT = 2;
 
         /**
          * The Unicode characters to use as icon.
@@ -107,12 +134,6 @@ public class CoverageExplorer extends Widget {
 
     /**
      * The type of view (image or tabular data) shown in this explorer.
-     * The default value is {@link View#TABLE}.
-     *
-     * <div class="note"><b>API note:</b>
-     * the reason for setting default value to tabular data is because it 
requires loading much less data with
-     * {@link java.awt.image.RenderedImage}s supporting deferred tile loading. 
By contrast {@link View#IMAGE}
-     * may require loading the full image.</div>
      *
      * @see #getViewType()
      * @see #setViewType(View)
@@ -120,8 +141,8 @@ public class CoverageExplorer extends Widget {
     public final ObjectProperty<View> viewTypeProperty;
 
     /**
-     * Whether the {@link #coverageProperty} is in process of being set, in 
which case some
-     * listeners should not react.
+     * Whether the {@link #coverageProperty} is in process of being set,
+     * in which case some listeners should not react.
      */
     private boolean isCoverageAdjusting;
 
@@ -134,10 +155,13 @@ public class CoverageExplorer extends Widget {
     private SplitPane content;
 
     /**
-     * The different views we can provide on {@link #coverageProperty},
-     * together with associated controls.
+     * The different views we can provide on {@link #coverageProperty}, 
together with associated controls.
+     * Values in this map are initially null and created when first needed.
+     * Concrete classes are {@link GridControls} and {@link CoverageControls}.
+     *
+     * @see #getControl(View)
      */
-    private final Controls[] views;
+    private final EnumMap<View,ViewAndControls> views;
 
     /**
      * Handles the {@link javafx.scene.control.ChoiceBox} and menu items for 
selecting a CRS.
@@ -145,24 +169,52 @@ public class CoverageExplorer extends Widget {
     private final RecentReferenceSystems referenceSystems;
 
     /**
-     * Creates an initially empty explorer.
+     * Creates an initially empty explorer with default view type.
+     * By default {@code CoverageExplorer} will show a coverage as a table of 
values,
+     * i.e. the default view type is {@link View#TABLE}.
+     *
+     * <div class="note"><b>API note:</b>
+     * the reason for setting default value to tabular data is because it 
requires loading much less data with
+     * {@link java.awt.image.RenderedImage}s supporting deferred tile loading. 
By contrast {@link View#IMAGE}
+     * may require loading the full image.</div>
      */
     public CoverageExplorer() {
+        this(View.TABLE);
+    }
+
+    /**
+     * Creates an initially empty explorer with the specified view type.
+     *
+     * @param  type  the way to show coverages in this explorer.
+     *
+     * @see #setViewType(View)
+     *
+     * @since 1.2
+     */
+    public CoverageExplorer(final View type) {
+        ArgumentChecks.ensureNonNull("type", type);
         coverageProperty = new SimpleObjectProperty<>(this, "coverage");
-        viewTypeProperty = new NonNullObjectProperty<>(this, "viewType", 
View.TABLE);
+        viewTypeProperty = new NonNullObjectProperty<>(this, "viewType", type);
         coverageProperty.addListener((p,o,n) -> onCoverageSpecified(n));
         referenceSystems = new RecentReferenceSystems();
         referenceSystems.addUserPreferences();
         referenceSystems.addAlternatives("EPSG:4326", "EPSG:3395");         // 
WGS 84 / World Mercator
         /*
          * The coverage property may be shown in various ways (tabular data, 
image).
-         * Each visualization way is an entry in the `views` array.
+         * Each visualization way is a value in the `views` map.
+         * Elements will be created when first needed.
          */
-        final View[]     viewTypes  = View.values();
-        final Vocabulary vocabulary = Vocabulary.getResources((Locale) null);
-        views = new Controls[viewTypes.length];
-        for (final View type : viewTypes) {
-            final Controls c;
+        views = new EnumMap<>(View.class);
+    }
+
+    /**
+     * Returns the view-control pair for the given view type.
+     * The view-control pair is created when first needed.
+     */
+    private ViewAndControls getViewAndControls(final View type) {
+        ViewAndControls c = views.get(type);
+        if (c == null) {
+            final Vocabulary vocabulary = Vocabulary.getResources(getLocale());
             switch (type) {
                 case TABLE: c = new GridControls(referenceSystems, 
vocabulary); break;
                 case IMAGE: c = new CoverageControls(vocabulary, 
coverageProperty, referenceSystems); break;
@@ -170,8 +222,9 @@ public class CoverageExplorer extends Widget {
             }
             SplitPane.setResizableWithParent(c.controls(), Boolean.FALSE);
             SplitPane.setResizableWithParent(c.view(),     Boolean.TRUE);
-            views[type.ordinal()] = c;
+            views.put(type, c);
         }
+        return c;
     }
 
     /**
@@ -180,27 +233,33 @@ public class CoverageExplorer extends Widget {
      * and may change in any future version.
      *
      * @return the region to show.
+     *
+     * @see #getDataView(View)
+     * @see #getControls(View)
      */
     @Override
     public final Region getView() {
+        assert Platform.isFxApplicationThread();
+        /*
+         * We build when first requested because `ResourceExplorer` for 
example will never request this view.
+         * Instead it will invoke `getDataView(View)` or `getControls(View)` 
and layout those regions itself.
+         */
         if (content == null) {
             /*
              * Prepare buttons to add on the toolbar. Those buttons are not 
managed by this class;
              * they are managed by org.apache.sis.gui.dataset.DataWindow. We 
only declare here the
              * text and action for each button.
              */
-            final Locale      locale  = null;
             final ToggleGroup group   = new ToggleGroup();
-            final Control[]   buttons = new Control[views.length + 1];
-            final Resources localized = Resources.forLocale(locale);
+            final Control[]   buttons = new Control[View.COUNT + 1];
+            final Resources localized = Resources.forLocale(getLocale());
             buttons[0] = new Separator();
             for (final View type : View.values()) {
-                final Controls c = views[type.ordinal()];
-                c.selector = new Selector(type).createButton(group, type.icon, 
localized, type.tooltip);
-                buttons[buttons.length - type.ordinal() - 1] = c.selector;  // 
Buttons in reverse order.
+                buttons[1 + type.ordinal()] = new 
Selector(type).createButton(group, type.icon, localized, type.tooltip);
             }
-            final Controls c = views[0];                            // First 
View enumeration is default value.
-            group.selectToggle(group.getToggles().get(0));
+            final View type = getViewType();
+            final ViewAndControls c = getViewAndControls(type);
+            group.selectToggle(group.getToggles().get(type.ordinal()));
             content = new SplitPane(c.controls(), c.view());
             ToolbarButton.insert(content, buttons);
             viewTypeProperty.addListener((p,o,n) -> onViewTypeSpecified(n));
@@ -209,7 +268,7 @@ public class CoverageExplorer extends Widget {
              * to give all the space to controls and no space to data, which 
is not what we want. However
              * experience with JavaFX 14 shows that this setting gives just a 
reasonable space to controls
              * and most space to data. I have not identified the cause of this 
surprising behavior.
-             * A smaller value result in too few space for the controls.
+             * A smaller value results in too few space for the controls.
              */
             content.setDividerPosition(0, 1);
         }
@@ -222,12 +281,13 @@ public class CoverageExplorer extends Widget {
      * The {@link Region} subclass returned by this method is implementation 
dependent and may change
      * in any future version.
      *
-     * @param  view  whether to obtain a {@link GridView} or {@link 
CoverageCanvas}.
+     * @param  type  whether to obtain a {@link GridView} or {@link 
CoverageCanvas}.
      * @return the requested view for the {@link #coverageProperty}.
      */
-    public final Region getDataView(final View view) {
-        ArgumentChecks.ensureNonNull("view", view);
-        return views[view.ordinal()].view();
+    public final Region getDataView(final View type) {
+        assert Platform.isFxApplicationThread();
+        ArgumentChecks.ensureNonNull("type", type);
+        return getViewAndControls(type).view();
     }
 
     /**
@@ -235,12 +295,13 @@ public class CoverageExplorer extends Widget {
      * The {@link Region} subclass returned by this method is implementation 
dependent and may
      * change in any future version.
      *
-     * @param  view  whether to obtain controls for {@link GridView} or {@link 
CoverageCanvas}.
+     * @param  type  whether to obtain controls for {@link GridView} or {@link 
CoverageCanvas}.
      * @return the controls on specified data view.
      */
-    public final Region getControls(final View view) {
-        ArgumentChecks.ensureNonNull("view", view);
-        return views[view.ordinal()].controls();
+    public final Region getControls(final View type) {
+        assert Platform.isFxApplicationThread();
+        ArgumentChecks.ensureNonNull("type", type);
+        return getViewAndControls(type).controls();
     }
 
     /**
@@ -248,18 +309,19 @@ public class CoverageExplorer extends Widget {
      */
     private final class Selector extends ToolbarButton {
         /** The view to select when the button is pressed. */
-        private final View view;
+        private final View type;
 
         /** Creates a new action which will show the view at the given index. 
*/
-        Selector(final View view) {
-            this.view = view;
+        Selector(final View type) {
+            this.type = type;
         }
 
         /** Invoked when the user selects another view to show (tabular data 
or the image). */
         @Override public void handle(final ActionEvent event) {
             final Toggle button = (Toggle) event.getSource();
             if (button.isSelected()) {
-                setViewType(view);
+                setViewType(type);
+                views.get(type).selector = button;    // Should never null 
null.
             } else {
                 button.setSelected(true);       // Prevent situation where all 
buttons are unselected.
             }
@@ -301,8 +363,12 @@ public class CoverageExplorer extends Widget {
      * @param  source  the coverage or resource to load, or {@code null} if 
none.
      */
     public final void setCoverage(final ImageRequest source) {
+        assert Platform.isFxApplicationThread();
         if (source == null) {
             setCoverage((GridCoverage) null);
+        } else if (source.listener != null) {
+            throw new IllegalArgumentException(Errors.getResources(getLocale())
+                    .getString(Errors.Keys.AlreadyInitialized_1, "listener"));
         } else {
             source.listener = this;
             startLoading(source);
@@ -349,7 +415,7 @@ public class CoverageExplorer extends Widget {
      * @param  source  the coverage or resource to load, or {@code null} if 
none.
      */
     private void startLoading(final ImageRequest source) {
-        final GridView main = (GridView) views[View.TABLE.ordinal()].view();
+        final GridView main = (GridView) getViewAndControls(View.TABLE).view();
         main.setImage(source);
     }
 
@@ -369,7 +435,7 @@ public class CoverageExplorer extends Widget {
                 referenceSystems.setPreferred(true, 
gg.getCoordinateReferenceSystem());
             }
         }
-        for (final Controls c : views) {
+        for (final ViewAndControls c : views.values()) {
             c.coverageChanged(data, originator);
         }
     }
@@ -378,7 +444,7 @@ public class CoverageExplorer extends Widget {
      * Returns the type of view (image or tabular data) shown in this explorer.
      * The default value is {@link View#TABLE}.
      *
-     * @return the type of view shown in this explorer.
+     * @return the way to show coverages in this explorer.
      *
      * @see #viewTypeProperty
      */
@@ -389,22 +455,25 @@ public class CoverageExplorer extends Widget {
     /**
      * Sets the type of view to show in this explorer.
      *
-     * @param  coverage  the type of view to show in this explorer.
+     * @param  type  the new way to show coverages in this explorer.
      *
      * @see #viewTypeProperty
      */
-    public final void setViewType(final View coverage) {
-        viewTypeProperty.set(coverage);
+    public final void setViewType(final View type) {
+        viewTypeProperty.set(type);
     }
 
     /**
      * Invoked when a new view type has been specified.
      *
-     * @param  view  the new view type.
+     * @param  type  the new way to show coverages in this explorer.
      */
-    private void onViewTypeSpecified(final View view) {
-        final Controls c = views[view.ordinal()];
+    private void onViewTypeSpecified(final View type) {
+        final ViewAndControls c = getViewAndControls(type);
         content.getItems().setAll(c.controls(), c.view());
-        ((Toggle) c.selector).setSelected(true);
+        final Toggle selector = c.selector;
+        if (selector != null) {
+            selector.setSelected(true);
+        }
     }
 }
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
index da11e50..876eb97 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridControls.java
@@ -40,11 +40,11 @@ import org.apache.sis.internal.gui.Styles;
  * A {@link GridView} with associated controls to show in a {@link 
CoverageExplorer}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
-final class GridControls extends Controls {
+final class GridControls extends ViewAndControls {
     /**
      * The component for showing sample values.
      */
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
index 9d76891..fe9dfc1 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridViewSkin.java
@@ -532,7 +532,7 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> impleme
              * (count = 1 & missing = 18) — this is where we want to force a 
third layout — then the third layout
              * is stable (count = 19 & missing = 0).
              */
-            layoutAll = count <= missing;
+            layoutAll = (count <= missing);
         }
         /*
          * Update position of the highlights at mouse cursor position. Usually 
the correction computed below is
@@ -543,18 +543,20 @@ final class GridViewSkin extends 
VirtualContainerBase<GridView, GridRow> impleme
          */
         if (selection.isVisible()) {
             GridRow row = flow.getFirstVisibleCell();
-            double sy = selection.getY() - (row.getLayoutY() + headerHeight);
-            final int i = ((int) Math.rint(sy / cellHeight)) + row.getIndex();
-            row = flow.getCell(i);                      // Empty cell if 
beyond the range.
-            sy  = row.getLayoutY() + headerHeight;      // Usually same as 
`selection.y` (see above comment).
-            final double offset = row.getLayoutX() + leftBackground.getWidth();
-            final double column = Math.rint((selection.getX() - offset) / 
cellWidth);
-            final double sx     = column * cellWidth + offset;
-            selection.setX(sx);
-            selection.setY(sy);
-            selectedRow.setY(sy);
-            selectedColumn.setX(sx);
-            getSkinnable().formatCoordinates(firstVisibleColumn + (int) 
column, i);
+            if (row != null) {
+                double sy = selection.getY() - (row.getLayoutY() + 
headerHeight);
+                final int i = ((int) Math.rint(sy / cellHeight)) + 
row.getIndex();
+                row = flow.getCell(i);                      // Empty cell if 
beyond the range.
+                sy  = row.getLayoutY() + headerHeight;      // Usually same as 
`selection.y` (see above comment).
+                final double offset = row.getLayoutX() + 
leftBackground.getWidth();
+                final double column = Math.rint((selection.getX() - offset) / 
cellWidth);
+                final double sx     = column * cellWidth + offset;
+                selection.setX(sx);
+                selection.setY(sy);
+                selectedRow.setY(sy);
+                selectedColumn.setX(sx);
+                getSkinnable().formatCoordinates(firstVisibleColumn + (int) 
column, i);
+            }
         }
         if (hasErrors) {
             computeErrorBounds(flow);
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
index 3a7506b..39e72dc 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
@@ -74,7 +74,7 @@ import org.apache.sis.internal.util.PropertyFormat;
  * For displaying a geospatial raster as a GIS application, see {@link 
CoverageCanvas} instead.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -823,4 +823,14 @@ nextProp:       for (final String property : properties) {
     public Region getView() {
         return view;
     }
+
+    /**
+     * Returns the locale for controls and messages.
+     *
+     * @since 1.2
+     */
+    @Override
+    public final Locale getLocale() {
+        return propertyDetails.getLocale();
+    }
 }
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ViewAndControls.java
similarity index 95%
rename from 
application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
rename to 
application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ViewAndControls.java
index d3ff50d..398bb25 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/Controls.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/ViewAndControls.java
@@ -18,9 +18,9 @@ package org.apache.sis.gui.coverage;
 
 import java.lang.ref.Reference;
 import javafx.geometry.Insets;
-import javafx.scene.control.ButtonBase;
 import javafx.scene.control.Control;
 import javafx.scene.control.Label;
+import javafx.scene.control.Toggle;
 import javafx.scene.layout.Region;
 import javafx.scene.text.Font;
 import javafx.scene.text.FontWeight;
@@ -35,11 +35,11 @@ import org.apache.sis.util.resources.IndexedResourceBundle;
  * to show in a {@link CoverageExplorer}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
-abstract class Controls {
+abstract class ViewAndControls {
     /**
      * Margin to keep around captions on top of tables or lists.
      */
@@ -58,14 +58,14 @@ abstract class Controls {
 
     /**
      * The toolbar button for selecting this view.
-     * This is initialized after construction.
+     * This is initialized after construction and only if a button bar exists.
      */
-    ButtonBase selector;
+    Toggle selector;
 
     /**
-     * Creates a new control.
+     * Creates a new view-control pair.
      */
-    Controls() {
+    ViewAndControls() {
     }
 
     /**
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java
index a19e4d8..4cf5681 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java
@@ -18,6 +18,7 @@ package org.apache.sis.gui.dataset;
 
 import java.util.List;
 import java.util.ArrayList;
+import java.util.Locale;
 import javafx.collections.ObservableList;
 import javafx.stage.Stage;
 import javafx.event.ActionEvent;
@@ -33,7 +34,7 @@ import org.apache.sis.gui.Widget;
  * Manages the list of opened {@link DataWindow}s.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -94,6 +95,14 @@ abstract class WindowManager extends Widget {
     abstract Resources localized();
 
     /**
+     * Returns the locale for controls and messages.
+     */
+    @Override
+    public final Locale getLocale() {
+        return localized().getLocale();
+    }
+
+    /**
      * Creates a menu item for creating new windows for the currently selected 
resource.
      * The new menu item is initially disabled. Its will become enabled 
automatically when
      * a resource is selected.
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index 3177e9d..75e74f2 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -110,7 +110,7 @@ import org.apache.sis.referencing.IdentifiedObjects;
  * {@link #setLocalCoordinates(double, double)} explicitly instead.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -1299,11 +1299,4 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         }
         setErrorMessage(text, details);
     }
-
-    /**
-     * Returns the locale for error messages.
-     */
-    private Locale getLocale() {
-        return format.getLocale(Locale.Category.DISPLAY);
-    }
 }
diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ToolbarButton.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ToolbarButton.java
index 3e6bfe4..0c179f3 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ToolbarButton.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ToolbarButton.java
@@ -18,8 +18,6 @@ package org.apache.sis.internal.gui;
 
 import javafx.scene.Node;
 import javafx.scene.control.Control;
-import javafx.scene.control.Button;
-import javafx.scene.control.ButtonBase;
 import javafx.scene.control.ToggleButton;
 import javafx.scene.control.ToggleGroup;
 import javafx.scene.control.Tooltip;
@@ -38,7 +36,7 @@ import org.apache.sis.util.ArraysExt;
  * in different packages without making toolbar API public.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -92,24 +90,18 @@ public abstract class ToolbarButton implements 
EventHandler<ActionEvent> {
      * Convenience method for creating a button.
      * The action handler will be {@code this}.
      *
-     * @param  group      the group of the toggle button, or {@code null} for 
an ordinary button.
+     * @param  group      the group of the toggle button.
      * @param  icon       the text to put in the button, as a Unicode emoji.
      * @param  localized  an instance of {@link Resources} for current locale.
      * @param  tooltip    the {@link Resources.Keys} value for the tooltip.
      * @return the button configured with text or icon, tooltip and action.
      */
-    public final ButtonBase createButton(final ToggleGroup group, final String 
icon, final Resources localized, final short tooltip) {
-        final ButtonBase b;
-        if (group != null) {
-            final ToggleButton tb = new ToggleButton(icon);
-            tb.setToggleGroup(group);
-            b = tb;
-        } else {
-            b = new Button(icon);
-        }
-        b.setTooltip(new Tooltip(localized.getString(tooltip)));
-        b.setOnAction(this);
-        return b;
+    public final ToggleButton createButton(final ToggleGroup group, final 
String icon, final Resources localized, final short tooltip) {
+        final ToggleButton tb = new ToggleButton(icon);
+        tb.setToggleGroup(group);
+        tb.setTooltip(new Tooltip(localized.getString(tooltip)));
+        tb.setOnAction(this);
+        return tb;
     }
 
     /**

Reply via email to