sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/02: Refactor CRSChooser as a Dialog subclass.
Date Tue, 12 Nov 2019 14:24:29 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

commit b0b38a05fd8e7c39db82f067a0f582facfcbadbc
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Nov 12 11:21:08 2019 +0100

    Refactor CRSChooser as a Dialog subclass.
---
 .../apache/sis/gui/referencing/AuthorityCodes.java |  93 +++++++---
 .../org/apache/sis/gui/referencing/CRSButton.java  |  75 --------
 .../org/apache/sis/gui/referencing/CRSChooser.java | 169 ++++++++++++++----
 .../org/apache/sis/gui/referencing/WKTPane.java    | 188 ++++++++++++++++++---
 .../apache/sis/internal/gui/ExceptionReporter.java |  11 ++
 .../org/apache/sis/internal/gui/Resources.java     |  20 +++
 .../apache/sis/internal/gui/Resources.properties   |   4 +
 .../sis/internal/gui/Resources_fr.properties       |   6 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 11 files changed, 411 insertions(+), 162 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
index 60e6e8d..0f05141 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
@@ -41,6 +41,7 @@ import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.internal.gui.ExceptionReporter;
 import org.apache.sis.internal.util.Constants;
 
 
@@ -87,13 +88,22 @@ final class AuthorityCodes extends ObservableListBase<Code>
     final Locale locale;
 
     /**
-     * The task where to send request for CRS descriptions, or {@code null} if an error occurred.
-     * In later case, no more background tasks will be scheduled.
+     * The task where to send requests for CRS descriptions (never {@code null}).
      */
     private Loader loader;
 
     /**
+     * Non-null if an error occurred while fetching CRS codes.
+     *
+     * @todo Provide a button for showing this error in an {@link ExceptionReporter}.
+     */
+    private Throwable error;
+
+    /**
      * Creates a new deferred list and starts a background process for loading CRS codes.
+     *
+     * @param  factory  the authority factory, or {@code null} for default factory.
+     * @param  locale   the preferred locale of CRS descriptions.
      */
     AuthorityCodes(final CRSAuthorityFactory factory, final Locale locale) {
         this.locale = locale;
@@ -103,6 +113,14 @@ final class AuthorityCodes extends ObservableListBase<Code>
     }
 
     /**
+     * Returns the authority factory. If no explicit factory has been given at construction
time,
+     * the {@linkplain CRS#getAuthorityFactory(String) Apache SIS default factory} is returned.
+     */
+    final CRSAuthorityFactory getFactory() throws FactoryException {
+        return loader.getFactory();
+    }
+
+    /**
      * Returns the number of elements in this list. This method initially returns only the
number of
      * cached elements. This number may increase progressively as the background loading
progresses.
      */
@@ -159,7 +177,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
     final ReadOnlyStringWrapper getName(final Code code) {
         final ReadOnlyStringWrapper p = code.name();
         final String name = p.getValue();
-        if (name == null && loader != null) {
+        if (name == null) {
             loader.requestName(code);
         }
         return p;
@@ -245,6 +263,18 @@ final class AuthorityCodes extends ObservableListBase<Code>
         }
 
         /**
+         * Returns the authority factory. This method is normally invoked from the background
thread,
+         * but we nevertheless synchronize it in case {@link AuthorityCodes#getFactory()}
is invoked
+         * concurrently.
+         */
+        final synchronized CRSAuthorityFactory getFactory() throws FactoryException {
+            if (factory == null) {
+                factory = CRS.getAuthorityFactory(Constants.EPSG);
+            }
+            return factory;
+        }
+
+        /**
          * Sends to this background thread a request for fetching the name (description)
of given code.
          * The {@link AuthorityCodes} list will receive an update event after the name has
been fetched.
          * This method is invoked from JavaFX thread.
@@ -271,6 +301,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
                 snapshot = toDescribe.toArray(new Code[size]);
                 toDescribe.clear();
             }
+            final CRSAuthorityFactory factory = getFactory();
             final Map<Code,String> updated = new IdentityHashMap<>(snapshot.length);
             for (final Code code : snapshot) {
                 // Do not update code in this thread; it will be updated in JavaFX thread.
@@ -298,10 +329,8 @@ final class AuthorityCodes extends ObservableListBase<Code>
         protected Object call() throws Exception {
             long lastTime = System.nanoTime();
             List<String> codes = Collections.emptyList();
+            final CRSAuthorityFactory factory = getFactory();
             try {
-                if (factory == null) {
-                    factory = CRS.getAuthorityFactory(Constants.EPSG);
-                }
                 if (loadCodes) {
                     codes = new ArrayList<>(100);
                     final Iterator<String> it = factory.getAuthorityCodes(type).iterator();
@@ -353,37 +382,45 @@ final class AuthorityCodes extends ObservableListBase<Code>
                 updated = (Map<Code,String>) result;
             }
             update(newCodes, updated);
-            /*
-             * Prepare the next task for loading description. If new description requests
were posted
-             * between the end of `call()` execution and the start of this `succeeded()`
execution,
-             * starts the new task immediately.
-             */
-            loader = new Loader(this);
-            final boolean isEmpty;
-            synchronized (toDescribe) {
-                isEmpty = toDescribe.isEmpty();
-            }
-            if (!isEmpty) {
-                BackgroundThreads.execute(loader);
-            }
+            scheduleNewLoader();
         }
 
         /**
-         * Invoked if an error occurred while loading the codes. A pseudo-code is added with
error message
-         * and no more background tasks will be scheduled.
+         * Invoked if an error occurred while loading the codes. A pseudo-code is added with
error message.
+         * A background task is still scheduled for allowing {@link AuthorityCodes} to get
descriptions of
+         * codes obtained so far.
          */
         @Override
         protected void failed() {
             super.failed();
-            loader = null;
             final Throwable e = getException();
-            final Code code = new Code(Vocabulary.getResources(locale).getString(Vocabulary.Keys.Errors));
-            String message = Exceptions.getLocalizedMessage(e, locale);
-            if (message == null) {
-                message = e.toString();
+            if (error == null) {
+                final Code code = new Code(Vocabulary.getResources(locale).getString(Vocabulary.Keys.Errors));
+                String message = Exceptions.getLocalizedMessage(e, locale);
+                if (message == null) {
+                    message = e.getClass().getSimpleName();
+                }
+                code.name().set(message);
+                add(code);
+            }
+            error = e;
+            scheduleNewLoader();
+        }
+
+        /**
+         * Prepares the next task for loading descriptions. If new description requests were
posted
+         * between the end of {@link #call()} execution and the start of the {@link #succeeded()}
or
+         * {@link #failed()} execution, starts the new task immediately.
+         */
+        private void scheduleNewLoader() {
+            loader = new Loader(this);
+            final boolean isEmpty;
+            synchronized (toDescribe) {
+                isEmpty = toDescribe.isEmpty();
+            }
+            if (!isEmpty) {
+                BackgroundThreads.execute(loader);
             }
-            code.name().set(message);
-            add(code);
         }
     }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSButton.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSButton.java
deleted file mode 100644
index 5c871e5..0000000
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSButton.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.referencing;
-
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.value.ObservableValue;
-import javafx.event.ActionEvent;
-import javafx.event.EventHandler;
-import javafx.scene.control.Button;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-
-/**
- * Widget button used to select a {@link CoordinateReferenceSystem}.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public class CRSButton extends Button{
-
-    private final ObjectProperty<CoordinateReferenceSystem> crsProperty = new SimpleObjectProperty<>();
-
-    /**
-     * Create a new CRSButton with no {@link CoordinateReferenceSystem} defined.
-     */
-    public CRSButton() {
-        setText("-");
-
-        setOnAction(new EventHandler<ActionEvent>() {
-            @Override
-            public void handle(ActionEvent event) {
-                final CoordinateReferenceSystem crs = CRSChooser.showDialog(CRSButton.this,
crsProperty.get());
-                crsProperty.set(crs);
-            }
-        });
-
-        //update button text when needed
-        crsProperty.addListener((ObservableValue<? extends CoordinateReferenceSystem>
observable,
-                CoordinateReferenceSystem oldValue, CoordinateReferenceSystem newValue) ->
{
-            if (newValue!=null) {
-                setText(newValue.getName().toString());
-            } else {
-                setText(" - ");
-            }
-        });
-    }
-
-    /**
-     * Returns the property containing the edited {@link CoordinateReferenceSystem}.
-     * This property can be modified and will send events.
-     * It can be used with JavaFx binding operations.
-     *
-     * @return Property containing the edited {@link CoordinateReferenceSystem}
-     */
-    public ObjectProperty<CoordinateReferenceSystem> crsProperty() {
-        return crsProperty;
-    }
-
-}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
index 73fd239..52ee27d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
@@ -17,23 +17,29 @@
 package org.apache.sis.gui.referencing;
 
 import java.util.Locale;
+import java.util.Optional;
 import java.util.function.Predicate;
 import javafx.collections.ObservableList;
 import javafx.collections.transformation.FilteredList;
 import javafx.event.ActionEvent;
 import javafx.geometry.Insets;
 import javafx.geometry.Pos;
-import javafx.scene.control.Alert;
-import javafx.scene.control.Button;
+import javafx.scene.Node;
 import javafx.scene.control.ButtonType;
+import javafx.scene.control.Control;
+import javafx.scene.control.Dialog;
 import javafx.scene.control.DialogPane;
 import javafx.scene.control.Label;
 import javafx.scene.control.TableColumn;
 import javafx.scene.control.TableView;
 import javafx.scene.control.TextField;
+import javafx.scene.control.ToggleButton;
 import javafx.scene.layout.BorderPane;
 import javafx.scene.layout.HBox;
 import javafx.scene.layout.Priority;
+import javafx.stage.Window;
+import org.apache.sis.internal.gui.ExceptionReporter;
+import org.opengis.util.FactoryException;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.internal.gui.IdentityValueFactory;
@@ -53,11 +59,23 @@ import org.apache.sis.referencing.CRS;
  * @since   1.1
  * @module
  */
-public class CRSChooser {
+public class CRSChooser extends Dialog<CoordinateReferenceSystem> {
     /**
      * The pane where the controls for this CRS chooser will be put.
+     * The top part contains the tools bar. The center part contains
+     * the table or the WKT and change depending on user actions.
      */
-    final BorderPane content;
+    private final BorderPane content;
+
+    /**
+     * The tools bar for this pane. Children are in this order:
+     * <ul>
+     *   <li>A {@link Label} for the second child.</li>
+     *   <li>A text field, combo box or other control.</li>
+     *   <li>An arbitrary number of buttons.</li>
+     * </ul>
+     */
+    private final HBox tools;
 
     /**
      * The text field where user can enter a fragment of the name of the CRS (s)he is looking
for.
@@ -72,17 +90,27 @@ public class CRSChooser {
     private final TableView<Code> table;
 
     /**
+     * The pane showing the CRS in Well Known Text format.
+     * Created when first needed.
+     */
+    private WKTPane wktPane;
+
+    /**
      * Creates a chooser proposing all coordinate reference systems from the given factory.
      *
      * @param  factory  the factory to use for creating coordinate reference systems, or
{@code null}
      *                  for the {@linkplain CRS#getAuthorityFactory(String) Apache SIS default
factory}.
      */
     public CRSChooser(final CRSAuthorityFactory factory) {
-        final Locale locale = Locale.getDefault();
-        final AuthorityCodes codeList = new AuthorityCodes(factory, locale);
+        final Locale         locale     = Locale.getDefault();
+        final Resources      i18n       = Resources.forLocale(locale);
+        final Vocabulary     vocabulary = Vocabulary.getResources(locale);
+        final AuthorityCodes codeList   = new AuthorityCodes(factory, locale);
         table = new TableView<>(codeList);
-
-        final Vocabulary vocabulary = Vocabulary.getResources(locale);
+        /*
+         * Columns to show in CRS table. First column is typically EPSG codes and second
+         * column is the CRS descriptions. The content is loaded in a background thread.
+         */
         final TableColumn<Code,Code>   codes = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Code));
         final TableColumn<Code,String> names = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Name));
         names.setCellValueFactory(codeList);
@@ -93,24 +121,92 @@ public class CRSChooser {
         table.setPrefWidth(500);
         table.getColumns().setAll(codes, names);
         table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
-
+        /*
+         * Text field for filtering the list of CRS codes using keywords.
+         * The filtering is applied when the "Enter" key is pressed in that field.
+         */
         searchField = new TextField();
         searchField.setOnAction((ActionEvent event) -> {
             filter(searchField.getText());
         });
-        final Resources i18n = Resources.forLocale(locale);
+        HBox.setHgrow(searchField, Priority.ALWAYS);
         final Label label = new Label(i18n.getString(Resources.Keys.Filter));
-        final Button info = new Button("\uD83D\uDEC8");         // Unicode U+1F6C8: Circled
Information Source
         label.setLabelFor(searchField);
-        HBox.setHgrow(searchField, Priority.ALWAYS);
-        final HBox bar = new HBox(label, searchField, info);
-        bar.setSpacing(9);
-        bar.setAlignment(Pos.BASELINE_LEFT);
-        BorderPane.setMargin(bar, new Insets(0, 0, 9, 0));
-
+        /*
+         * Button for showing the CRS description in Well Known Text (WKT) format.
+         * The button is enabled only if a row in the table is selected.
+         */
+        final ToggleButton info = new ToggleButton("ℹ");            // Unicode U+2139:
Information Source.
+        table.getSelectionModel().selectedItemProperty().addListener((e,o,n) -> info.setDisable(n
== null));
+        info.setOnAction((ActionEvent event) -> {
+            setTools(info.isSelected());
+        });
+        info.setDisable(true);
+        /*
+         * Creates the tools bar to show above the table of codes.
+         * The tools bar contains the search field and the button for showing the WKT.
+         */
+        tools = new HBox(label, searchField, info);
+        tools.setSpacing(9);
+        tools.setAlignment(Pos.BASELINE_LEFT);
+        BorderPane.setMargin(tools, new Insets(0, 0, 9, 0));
+        /*
+         * Layout table and tools bar inside the dialog content.
+         * Configure the dialog buttons.
+         */
+        final DialogPane pane = getDialogPane();
         content = new BorderPane();
         content.setCenter(table);
-        content.setTop(bar);
+        content.setTop(tools);
+        pane.setContent(content);
+        pane.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
+        setTitle(i18n.getString(Resources.Keys.SelectCRS));
+        setResultConverter(this::getSelectedCRS);
+        setResizable(true);
+    }
+
+    /**
+     * Sets the tools bar and content to controls for the given mode.
+     * If {@code wkt} is {@code true}, then this method set the controls for showing the
WKT.
+     * If {@code wkt} is {@code false} (the default), then this method set the controls to
the table of CRS codes.
+     */
+    private void setTools(final boolean wkt) {
+        final Locale locale = getAuthorityCodes().locale;
+        final short labelText;
+        final Control control;
+        final Control main;
+        if (wkt) {
+            if (wktPane == null) {
+                wktPane = new WKTPane(locale);
+            }
+            wktPane.setContent(getAuthorityCodes(), table.getSelectionModel().getSelectedItem().code);
+            labelText = Resources.Keys.Format;
+            control   = wktPane.convention;
+            main      = wktPane.text;
+        } else {
+            labelText = Resources.Keys.Filter;
+            control   = searchField;
+            main      = table;
+        }
+        final ObservableList<Node> children = tools.getChildren();
+        final Label label = (Label) children.get(0);
+        final Resources i18n = Resources.forLocale(locale);
+        label.setText(i18n.getString(labelText));
+        label.setLabelFor(control);
+        children.set(1, control);
+        content.setCenter(main);
+    }
+
+    /**
+     * Returns the list of all authority codes. The list may not be complete at the
+     * time this method returns because codes are loaded in a background thread.
+     */
+    private AuthorityCodes getAuthorityCodes() {
+        ObservableList<?> items = table.getItems();
+        if (items instanceof FilteredList<?>) {
+            items = ((FilteredList<?>) items).getSource();
+        }
+        return (AuthorityCodes) items;
     }
 
     /**
@@ -161,21 +257,30 @@ public class CRSChooser {
     }
 
     /**
-     * Show a modal dialog to select a {@link CoordinateReferenceSystem}.
+     * Returns the currently selected CRS, or {@code null} if none.
+     *
+     * @return the currently selected CRS, or {@code null}.
+     */
+    private CoordinateReferenceSystem getSelectedCRS(final ButtonType button) {
+        if (ButtonType.OK.equals(button)) {
+            final Code code = table.getSelectionModel().getSelectedItem();
+            if (code != null) try {
+                return getAuthorityCodes().getFactory().createCoordinateReferenceSystem(code.code);
+            } catch (FactoryException e) {
+                ExceptionReporter.canNotCreateCRS(code.code, e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Shows a dialog to select a {@link CoordinateReferenceSystem}.
      *
-     * @param parent parent frame of widget.
-     * @param crs {@link CoordinateReferenceSystem} to edit.
-     * @return modified {@link CoordinateReferenceSystem}.
+     * @param  parent  parent frame of dialog.
+     * @return the selected {@link CoordinateReferenceSystem}, or empty if none.
      */
-    public static CoordinateReferenceSystem showDialog(Object parent, CoordinateReferenceSystem
crs) {
-        final CRSChooser chooser = new CRSChooser(null);
-//        chooser.crsProperty.set(crs);
-        final Alert alert = new Alert(Alert.AlertType.NONE);
-        final DialogPane pane = alert.getDialogPane();
-        pane.setContent(chooser.content);
-        alert.getButtonTypes().setAll(ButtonType.OK,ButtonType.CANCEL);
-        alert.setResizable(true);
-        final ButtonType res = alert.showAndWait().orElse(ButtonType.CANCEL);
-        return null;//res == ButtonType.CANCEL ? null : chooser.crsProperty.get();
+    public Optional<CoordinateReferenceSystem> showDialog(final Window parent) {
+        initOwner(parent);
+        return showAndWait();
     }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
index b984d4c..c39051f 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
@@ -16,53 +16,189 @@
  */
 package org.apache.sis.gui.referencing;
 
+import java.util.EnumMap;
+import java.util.Locale;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
-import javafx.scene.control.Alert;
-import javafx.scene.control.ButtonType;
+import javafx.concurrent.Task;
 import javafx.scene.control.ChoiceBox;
-import javafx.scene.control.DialogPane;
 import javafx.scene.control.TextArea;
-import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.util.StringConverter;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.io.wkt.Convention;
-import org.apache.sis.io.wkt.FormattableObject;
+import org.apache.sis.io.wkt.WKTFormat;
+import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.Exceptions;
 
 
 /**
  * Small panel to display an object as WKT in various conventions.
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
  * @module
  */
-final class WKTPane extends BorderPane {
+final class WKTPane extends StringConverter<Convention> implements ChangeListener<Convention>
{
+    /**
+     * The object to use for formatting and CRS.
+     */
+    private final WKTFormat format;
 
-    private final ChoiceBox<Convention> choice = new ChoiceBox<>(FXCollections.observableArrayList(Convention.values()));
-    private final TextArea text = new TextArea();
+    /**
+     * A choice box for choosing the WKT conventions.
+     */
+    final ChoiceBox<Convention> convention;
 
-    public WKTPane(final FormattableObject obj) {
-        setTop(choice);
-        setCenter(text);
+    /**
+     * Localized string representations of {@link #convention}.
+     */
+    private final EnumMap<Convention,String> conventionTexts;
 
-        choice.valueProperty().addListener(new ChangeListener<Convention>() {
-            @Override
-            public void changed(ObservableValue<? extends Convention> observable, Convention
oldValue, Convention newValue) {
-                text.setText(obj.toString(newValue));
+    /**
+     * The pane where to show the Well Known Text.
+     */
+    final TextArea text = new TextArea();
+
+    /**
+     * The object to format.
+     */
+    private CoordinateReferenceSystem crs;
+
+    /**
+     * Creates a new pane for showing CRS Well Known Text.
+     */
+    @SuppressWarnings("ThisEscapedInObjectConstruction")
+    WKTPane(final Locale locale) {
+        final Convention[] sc = {           // Selected conventions in the order we want
them to appear.
+            Convention.WKT2_SIMPLIFIED,
+            Convention.WKT2,
+            Convention.WKT1,
+            Convention.WKT1_COMMON_UNITS
+        };
+        conventionTexts = new EnumMap<>(Convention.class);
+        final Vocabulary vocabulary = Vocabulary.getResources(locale);
+        for (final Convention c : sc) {
+            conventionTexts.put(c, toString(c, vocabulary));
+        }
+        format = new WKTFormat(locale, null);
+        format.setConvention(Convention.WKT2_SIMPLIFIED);
+        convention = new ChoiceBox<>(FXCollections.observableArrayList(sc));
+        convention.setConverter(this);
+        convention.getSelectionModel().select(format.getConvention());
+        convention.valueProperty().addListener(this);
+        convention.setMaxWidth(Double.MAX_VALUE);
+        HBox.setHgrow(convention, Priority.ALWAYS);
+    }
+
+    /**
+     * Returns the text to write in {@link #convention} choice box for the given convention.
+     */
+    @SuppressWarnings("fallthrough")
+    private String toString(final Convention c, final Vocabulary vocabulary) {
+        final Object version;
+        boolean simplified = false;
+        switch (c) {
+            case WKT2_SIMPLIFIED:   simplified = true;         // Fall through.
+            case WKT2:              version = 2; break;
+            case WKT1:              version = 1; break;
+            case WKT1_COMMON_UNITS: version = "GDAL 1-2"; break;
+            default: return c.name();
+        }
+        String text = vocabulary.getString(Vocabulary.Keys.Version_2, "WKT (Well Known Text)",
version);
+        if (simplified) {
+            text += " — " + vocabulary.getString(Vocabulary.Keys.Simplified);
+        }
+        return text;
+    }
+
+    /**
+     * Returns the text to write in {@link #convention} choice box for the given convention.
+     */
+    @Override
+    public String toString(final Convention c) {
+        return conventionTexts.get(c);
+    }
+
+    /**
+     * Returns the convention from the given string.
+     * This is the reverse of {@link #toString(Convention)}.
+     */
+    @Override
+    public Convention fromString(final String text) {
+        for (final EnumMap.Entry<Convention,String> e : conventionTexts.entrySet())
{
+            if (e.getValue().equals(text)) return e.getKey();
+        }
+        return null;
+    }
+
+    /**
+     * Invoked when the user select a new format. This method is public as an implementation
side-effect;
+     * it should not be invoked explicitly.
+     */
+    @Override
+    public void changed(ObservableValue<? extends Convention> observable, Convention
oldValue, Convention newValue) {
+        format.setConvention(newValue);
+        refresh();
+    }
+
+    /**
+     * Sets the CRS to show in this pane. The CRS is constructed in a background thread.
+     */
+    final void setContent(final AuthorityCodes source, final String code) {
+        text.setDisable(true);
+        BackgroundThreads.execute(new Task<CoordinateReferenceSystem>() {
+            /** Invoked in background thread for fetching the CRS from an authority code.
*/
+            @Override protected CoordinateReferenceSystem call() throws FactoryException
{
+                return source.getFactory().createCoordinateReferenceSystem(code);
+            }
+
+            /** Invoked in JavaFX thread on success. */
+            @Override protected void succeeded() {
+                super.succeeded();
+                setContent(getValue());
+            }
+
+            /** Invoked in JavaFX thread on cancellation. */
+            @Override protected void cancelled() {
+                super.cancelled();
+                text.setText(null);
+            }
+
+            /** Invoked in JavaFX thread on failure. */
+            @Override protected void failed() {
+                super.failed();
+                text.setDisable(false);
+                text.setEditable(false);
+                text.setText(Exceptions.getLocalizedMessage(getException(), source.locale));
             }
         });
-        choice.getSelectionModel().select(Convention.WKT1);
     }
 
-    public static void showDialog(Object parent, FormattableObject candidate){
-        final WKTPane chooser = new WKTPane(candidate);
+    /**
+     * Sets the content to the given coordianate reference system.
+     */
+    private void setContent(final CoordinateReferenceSystem newCRS) {
+        text.setEditable(false);     // TODO: make editable if we allow WKT parsing in a
future version.
+        text.setDisable(false);
+        if (newCRS != crs) {
+            crs = newCRS;
+            refresh();
+        }
+    }
 
-        final Alert alert = new Alert(Alert.AlertType.NONE);
-        final DialogPane pane = alert.getDialogPane();
-        pane.setContent(chooser);
-        alert.getButtonTypes().setAll(ButtonType.OK);
-        alert.setResizable(true);
-        alert.showAndWait();
+    /**
+     * Rewrites the WKT using current conventions.
+     */
+    private void refresh() {
+        if (crs != null) {
+            text.setText(format.format(crs));
+        }
     }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
index d48274f..458b549 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ExceptionReporter.java
@@ -140,6 +140,17 @@ public final class ExceptionReporter implements EventHandler<ActionEvent>
{
     }
 
     /**
+     * Shows the reporter for a failure to create a CRS.
+     * This method does nothing if the exception is null.
+     *
+     * @param  code       code of the CRS that can not be created.
+     * @param  exception  the error that occurred.
+     */
+    public static void canNotCreateCRS(final String code, final Throwable exception) {
+        show(Resources.Keys.ErrorCreatingCRS, Resources.Keys.CanNotCreateCRS_1, new Object[]
{code}, exception);
+    }
+
+    /**
      * Constructs and shows the exception reporter. The title and text are keys from the
{@link Resources}.
      * If the title and/or text are 0, then the {@link Alert} default title and text will
be used.
      *
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
index 6076310..35c08eb 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
@@ -76,6 +76,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CanNotClose_1 = 12;
 
         /**
+         * Can not create reference system “{0}”.
+         */
+        public static final short CanNotCreateCRS_1 = 35;
+
+        /**
          * Can not open “{0}”.
          */
         public static final short CanNotReadFile_1 = 5;
@@ -126,6 +131,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short ErrorClosingFile = 13;
 
         /**
+         * Error creating reference system
+         */
+        public static final short ErrorCreatingCRS = 36;
+
+        /**
          * Error opening file
          */
         public static final short ErrorOpeningFile = 6;
@@ -151,6 +161,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short Filter = 34;
 
         /**
+         * Format:
+         */
+        public static final short Format = 38;
+
+        /**
          * Geospatial data files
          */
         public static final short GeospatialFiles = 4;
@@ -206,6 +221,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short ResourceIdentification = 23;
 
         /**
+         * Select a coordinate reference system
+         */
+        public static final short SelectCRS = 37;
+
+        /**
          * Spatial representation
          */
         public static final short SpatialRepresentation = 24;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
index 9824bb6..4c13c70 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
@@ -24,6 +24,7 @@ Abstract               = Abstract:
 AllFiles               = All files
 CanNotReadFile_1       = Can not open \u201c{0}\u201d.
 CanNotClose_1          = Can not close \u201c{0}\u201d. Data may be lost.
+CanNotCreateCRS_1      = Can not create reference system \u201c{0}\u201d.
 CellGeometry           = Cell geometry:
 Close                  = Close
 Copy                   = Copy
@@ -35,10 +36,12 @@ Date                   = Date:
 Dimensions             = Dimensions:
 ErrorClosingFile       = Error closing file
 ErrorOpeningFile       = Error opening file
+ErrorCreatingCRS       = Error creating reference system
 Exit                   = Exit
 Extent                 = Extent:
 File                   = File
 Filter                 = Filter:
+Format                 = Format:
 GeospatialFiles        = Geospatial data files
 Loading                = Loading\u2026
 Metadata               = Metadata
@@ -51,6 +54,7 @@ Purpose                = Purpose:
 ReferenceSystem        = Reference system:
 ResourceIdentification = Resource identification
 SpatialRepresentation  = Spatial representation
+SelectCRS              = Select a coordinate reference system
 Summary                = Summary
 TopicCategory          = Topic category:
 TypeOfResource         = Type of resource:
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
index bbce0ce..bc42c59 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
@@ -29,6 +29,7 @@ Abstract               = R\u00e9sum\u00e9\u00a0:
 AllFiles               = Tous les fichiers
 CanNotReadFile_1       = Ne peut pas ouvrir \u00ab\u202f{0}\u202f\u00bb.
 CanNotClose_1          = Ne peut pas fermer \u00ab\u202f{0}\u202f\u00bb. Il pourrait y avoir
une perte de donn\u00e9es.
+CanNotCreateCRS_1      = Ne peut pas cr\u00e9er le syst\u00e8me de r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb.
 CellGeometry           = G\u00e9om\u00e9trie des cellules\u00a0:
 Close                  = Fermer
 Copy                   = Copier
@@ -40,10 +41,12 @@ Date                   = Date\u00a0:
 Dimensions             = Dimensions\u00a0:
 ErrorClosingFile       = Erreur \u00e0 la fermeture du fichier
 ErrorOpeningFile       = Erreur \u00e0 l\u2019ouverture du fichier
+ErrorCreatingCRS       = Erreur \u00e0 la cr\u00e9ation du syst\u00e8me de r\u00e9f\u00e9rence.
 Exit                   = Quitter
 Extent                 = \u00c9tendue\u00a0:
 File                   = Fichier
-Filter                 = Filtrer\u00a0:
+Filter                 = Filtre\u00a0:
+Format                 = Format\u00a0:
 GeospatialFiles        = Fichiers de donn\u00e9es g\u00e9ospatiales
 Loading                = Chargement\u2026
 Metadata               = Metadonn\u00e9es
@@ -56,6 +59,7 @@ Purpose                = Objectif\u00a0:
 ReferenceSystem        = Syst\u00e8me de r\u00e9f\u00e9rence\u00a0:
 ResourceIdentification = Identification de la ressource
 SpatialRepresentation  = Repr\u00e9sentation spatiale
+SelectCRS              = Choisir un syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es
 Summary                = R\u00e9sum\u00e9
 TopicCategory          = Cat\u00e9gorie th\u00e9matique\u00a0:
 TypeOfResource         = Type de ressource\u00a0:
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 92c9568..23aeb70 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
@@ -737,6 +737,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short Scale = 136;
 
         /**
+         * Simplified
+         */
+        public static final short Simplified = 174;
+
+        /**
          * {0}/{1}
          */
         public static final short SlashSeparatedList_2 = 137;
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 90f4c3b..56682fe 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
@@ -150,6 +150,7 @@ Root                    = Root
 RootMeanSquare          = Root Mean Square
 SampleDimensions        = Sample dimensions
 Scale                   = Scale
+Simplified              = Simplified
 SlashSeparatedList_2    = {0}/{1}
 Source                  = Source
 SouthBound              = South bound
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 aecb2c5..0e80ee4 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
@@ -157,6 +157,7 @@ Root                    = Racine
 RootMeanSquare          = Moyenne quadratique
 SampleDimensions        = Dimensions d\u2019\u00e9chantillonnage
 Scale                   = \u00c9chelle
+Simplified              = Simplifi\u00e9
 SlashSeparatedList_2    = {0}/{1}
 Source                  = Source
 SouthBound              = Limite sud


Mime
View raw message