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 11f86940a76f3e82cb560329e5f198a55ee6b39a Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Apr 22 01:06:58 2020 +0200 Keep a relationship between the CRS of coordinates shown in the status bar and the CRS of the displayed raster. --- .../apache/sis/gui/coverage/CoverageControls.java | 4 +- .../apache/sis/gui/coverage/CoverageExplorer.java | 8 +- .../org/apache/sis/gui/coverage/GridControls.java | 8 +- .../java/org/apache/sis/gui/coverage/GridView.java | 13 +- .../java/org/apache/sis/gui/map/StatusBar.java | 45 +++-- .../java/org/apache/sis/gui/metadata/Section.java | 2 +- .../org/apache/sis/gui/referencing/CRSChooser.java | 9 +- .../org/apache/sis/gui/referencing/MenuSync.java | 215 +++++++++++++++++++++ .../gui/referencing/RecentReferenceSystems.java | 199 +++++++++++++++---- .../org/apache/sis/internal/gui/GUIUtilities.java | 80 ++++++-- .../org/apache/sis/internal/gui/Resources.java | 32 +-- .../apache/sis/internal/gui/Resources.properties | 32 +-- .../sis/internal/gui/Resources_fr.properties | 32 +-- .../apache/sis/gui/referencing/CRSChooserApp.java | 2 +- .../java/org/apache/sis/image/ComputedImage.java | 4 +- 15 files changed, 561 insertions(+), 124 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageControls.java index 177b944..d62a0e7 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 @@ -79,7 +79,7 @@ final class CoverageControls extends Controls { final Color background = Color.BLACK; view = new CoverageCanvas(); view.setBackground(background); - final StatusBar statusBar = new StatusBar(); + final StatusBar statusBar = new StatusBar(referenceSystems); statusBar.setCanvas(view); imageAndStatus = new BorderPane(view.getView()); imageAndStatus.setBottom(statusBar.getView()); @@ -93,7 +93,7 @@ final class CoverageControls extends Controls { final ChoiceBox<ReferenceSystem> systems = referenceSystems.createChoiceBox(this::onReferenceSystemSelected); systems.setMaxWidth(Double.POSITIVE_INFINITY); referenceSystem = systems.valueProperty(); - final Label systemLabel = new Label(localized.getString(Resources.Keys.ReferenceSystem)); + final Label systemLabel = new Label(localized.getLabel(Resources.Keys.ReferenceSystem)); systemLabel.setPadding(CAPTION_MARGIN); systemLabel.setLabelFor(systems); final GridPane gp = createControlGrid( 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 5e6f2b3..23686e8 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,6 +16,7 @@ */ package org.apache.sis.gui.coverage; +import java.util.Locale; import javafx.scene.control.Control; import javafx.scene.control.SplitPane; import javafx.scene.control.Separator; @@ -165,13 +166,14 @@ public class CoverageExplorer extends Widget { * The coverage property may be shown in various ways (tabular data, image). * Each visualization way is an entry in the `views` array. */ - final Resources localized = Resources.forLocale(null); - final Vocabulary vocabulary = Vocabulary.getResources(localized.getLocale()); + final Locale locale = null; + final Resources localized = Resources.forLocale(locale); + final Vocabulary vocabulary = Vocabulary.getResources(locale); views = new Controls[viewTypes.length]; for (final View type : viewTypes) { final Controls c; switch (type) { - case TABLE: c = new GridControls(vocabulary); break; + case TABLE: c = new GridControls(referenceSystems, vocabulary); break; case IMAGE: c = new CoverageControls(localized, vocabulary, coverageProperty, referenceSystems); break; default: throw new AssertionError(type); } 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 a1e8098..d729726 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 @@ -30,6 +30,7 @@ import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.grid.GridCoverage; +import org.apache.sis.gui.referencing.RecentReferenceSystems; import org.apache.sis.util.resources.Vocabulary; @@ -60,10 +61,11 @@ final class GridControls extends Controls { /** * Creates a new set of grid controls. * - * @param vocabulary localized set of words, provided in argument because often known by the caller. + * @param referenceSystems the manager of reference systems chosen by the user, or {@code null} if none. + * @param vocabulary localized set of words, provided in argument because often known by the caller. */ - GridControls(final Vocabulary vocabulary) { - view = new GridView(); + GridControls(final RecentReferenceSystems referenceSystems, final Vocabulary vocabulary) { + view = new GridView(referenceSystems); sampleDimensions = new CategoryCellFactory(view.cellFormat).createSampleDimensionTable(vocabulary); sampleDimensions.getSelectionModel().selectedIndexProperty().addListener(new BandSelectionListener(view.bandProperty)); view.bandProperty.addListener(this::onBandSpecified); diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java index b518a54..891f583 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/GridView.java @@ -42,6 +42,7 @@ import org.apache.sis.coverage.grid.GridCoverage; import org.apache.sis.internal.gui.BackgroundThreads; import org.apache.sis.internal.gui.Styles; import org.apache.sis.gui.map.StatusBar; +import org.apache.sis.gui.referencing.RecentReferenceSystems; /** @@ -208,6 +209,16 @@ public class GridView extends Control { * construction by a call to {@link #setImage(RenderedImage)}. */ public GridView() { + this(null); + } + + /** + * Creates an initially empty grid view. The content can be set after + * construction by a call to {@link #setImage(RenderedImage)}. + * + * @param referenceSystems the manager of reference systems chosen by the user, or {@code null} if none. + */ + GridView(final RecentReferenceSystems referenceSystems) { imageProperty = new SimpleObjectProperty<>(this, "image"); bandProperty = new SimpleIntegerProperty (this, "band"); headerWidth = new SimpleDoubleProperty (this, "headerWidth", 60); @@ -217,7 +228,7 @@ public class GridView extends Control { headerBackground = new SimpleObjectProperty<>(this, "headerBackground", Color.GAINSBORO); headerFormat = NumberFormat.getIntegerInstance(); cellFormat = new CellFormat(this); - statusBar = new StatusBar(); + statusBar = new StatusBar(referenceSystems); tileWidth = 1; tileHeight = 1; // For avoiding division by zero. 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 166ee20..ae8f08b 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 @@ -27,8 +27,8 @@ import javafx.scene.layout.Region; import javafx.scene.layout.Priority; import javafx.scene.control.Label; import javafx.scene.control.Button; -import javafx.scene.control.Tooltip; import javafx.scene.control.ProgressBar; +import javafx.scene.control.ContextMenu; import javafx.scene.input.MouseEvent; import javafx.event.EventHandler; import javafx.event.EventType; @@ -37,6 +37,7 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; import org.opengis.geometry.MismatchedDimensionException; +import org.opengis.referencing.ReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.operation.Matrix; @@ -44,7 +45,6 @@ import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.referencing.operation.transform.MathTransforms; -import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.geometry.GeneralDirectPosition; import org.apache.sis.geometry.CoordinateFormat; import org.apache.sis.coverage.grid.GridGeometry; @@ -57,6 +57,7 @@ import org.apache.sis.util.Exceptions; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; import org.apache.sis.gui.Widget; +import org.apache.sis.gui.referencing.RecentReferenceSystems; import org.apache.sis.internal.gui.ExceptionReporter; import org.apache.sis.internal.gui.Resources; import org.apache.sis.internal.gui.Styles; @@ -190,8 +191,10 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent> { /** * Creates a new status bar. + * + * @param referenceSystems the manager of reference systems chosen by the user, or {@code null} if none. */ - public StatusBar() { + public StatusBar(final RecentReferenceSystems referenceSystems) { localToCRS = MathTransforms.identity(BIDIMENSIONAL); targetCoordinates = new GeneralDirectPosition(BIDIMENSIONAL); sourceCoordinates = targetCoordinates.coordinates; @@ -208,6 +211,16 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent> { view.setPadding(PADDING); canvasProperty = new SimpleObjectProperty<>(this, "canvas"); canvasProperty.addListener(this::onCanvasSpecified); + if (referenceSystems != null) { + final ContextMenu menu = new ContextMenu(referenceSystems.createMenuItems((e,o,n) -> setDisplayCRS(n))); + view.setOnMousePressed((MouseEvent event) -> { + if (event.isSecondaryButtonDown()) { + menu.show((HBox) event.getSource(), event.getScreenX(), event.getScreenY()); + } else { + menu.hide(); + } + }); + } } /** @@ -357,23 +370,25 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent> { targetCoordinates = new GeneralDirectPosition(BIDIMENSIONAL); sourceCoordinates = targetCoordinates.coordinates; // Okay to share array if same dimension. } - format.setDefaultCRS(crs); + setDisplayCRS(crs); format.setPrecision(resolution, unit); - final String text = IdentifiedObjects.getDisplayName(crs, format.getLocale(Locale.Category.DISPLAY)); - Tooltip tp = null; - if (text != null) { - tp = coordinates.getTooltip(); - if (tp == null) { - tp = new Tooltip(text); - } else { - tp.setText(text); - } - } - coordinates.setTooltip(tp); lastX = lastY = Double.NaN; } /** + * Sets the coordinate reference systems to use for representing coordinates in status bar. + * + * @param crs the coordinate reference system to use for coordinates in the status bar. + */ + private void setDisplayCRS(final ReferenceSystem crs) { + if (crs instanceof CoordinateReferenceSystem) { + format.setDefaultCRS((CoordinateReferenceSystem) crs); + } else { + format.setDefaultCRS(null); + } + } + + /** * Returns the conversion from local coordinates to geographic or projected coordinates. * The local coordinates are the coordinates of the view, as given for example in {@link MouseEvent}. * This is initially an identity transform and can be computed by {@link #applyCanvasGeometry(GridGeometry)}. diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/Section.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/Section.java index 10f701b..4df2f3b 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/Section.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/Section.java @@ -244,7 +244,7 @@ abstract class Section<T> extends GridPane implements EventHandler<ActionEvent> if (value == null) { return; } - final String labelText = owner.localized.getString(label); + final String labelText = owner.localized.getLabel(label); final Label labelCtrl, valueCtrl; final ObservableList<Node> children = getChildren(); if (linesEndIndex < children.size()) { 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 603722b..b7f3fc1 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 @@ -138,7 +138,7 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem> { * Creates a chooser proposing all coordinate reference systems from the default factory. */ public CRSChooser() { - this(null, null); + this(null, null, null); } /** @@ -149,10 +149,11 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem> { * * @param factory the factory to use for creating coordinate reference systems, or {@code null} for default. * @param areaOfInterest geographic area for which to choose a CRS, or {@code null} if no restriction. + * @param locale the preferred locale for displaying object name, or {@code null} for the default locale. */ - public CRSChooser(final CRSAuthorityFactory factory, final Envelope areaOfInterest) { + public CRSChooser(final CRSAuthorityFactory factory, final Envelope areaOfInterest, Locale locale) { this.areaOfInterest = Utils.toGeographic(CRSChooser.class, "<init>", areaOfInterest); - final Locale locale = Locale.getDefault(); + if (locale == null) locale = Locale.getDefault(); final Resources i18n = Resources.forLocale(locale); final Vocabulary vocabulary = Vocabulary.getResources(locale); final AuthorityCodes codeList = new AuthorityCodes(factory, locale); @@ -276,7 +277,7 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem> { 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.setText(i18n.getLabel(labelText)); label.setLabelFor(control); children.set(1, control); content.setCenter(main); diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java new file mode 100644 index 0000000..a51c018 --- /dev/null +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java @@ -0,0 +1,215 @@ +/* + * 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 java.util.Arrays; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.RadioMenuItem; +import javafx.scene.control.ToggleGroup; +import org.opengis.referencing.ReferenceSystem; +import org.apache.sis.internal.gui.GUIUtilities; +import org.apache.sis.referencing.IdentifiedObjects; +import org.apache.sis.util.resources.Vocabulary; + + +/** + * Keep a list of menu items up to date with an {@code ObservableList<ReferenceSystem>}. + * The selected {@link MenuItem} is given by {@link ToggleGroup#selectedToggleProperty()} + * but for the purpose of {@link RecentReferenceSystems} we rather need a property giving + * the selected reference system directly. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.1 + * @since 1.1 + * @module + */ +final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements EventHandler<ActionEvent> { + /** + * Keys where to store the reference system in {@link MenuItem}. + */ + private static final String REFERENCE_SYSTEM_KEY = "ReferenceSystem"; + + /** + * Sentinel value associated to {@link #REFERENCE_SYSTEM_KEY} for requesting the {@link CRSChooser}. + */ + private static final String CHOOSER = "CHOOSER"; + + /** + * The manager of reference systems to synchronize with. + */ + private final RecentReferenceSystems owner; + + /** + * The list of menu items to keep up-to-date with an {@code ObservableList<ReferenceSystem>}. + */ + private final ObservableList<MenuItem> menus; + + /** + * The group of menus. + */ + private final ToggleGroup group; + + /** + * The action to execute when a reference system is selected. + */ + private final ChangeListener<ReferenceSystem> action; + + /** + * Creates a new synchronization for the given list of menu items. + * + * @param owner the manager of reference systems to synchronize with. + * @param systems the reference systems for which to build menu items. + * @param bean the menu to keep synchronized with the list of reference systems. + * @param action the user-specified action to execute when a reference system is selected. + */ + @SuppressWarnings("ThisEscapedInObjectConstruction") // `this` is last and this class is final. + MenuSync(final RecentReferenceSystems owner, final List<ReferenceSystem> systems, + final Menu bean, final ChangeListener<ReferenceSystem> action) + { + super(bean, "value"); + this.owner = owner; + this.menus = bean.getItems(); + this.group = new ToggleGroup(); + this.action = action; + final MenuItem[] items = new MenuItem[systems.size()]; + for (int i=0; i<items.length; i++) { + items[i] = createItem(systems.get(i)); + } + menus.setAll(items); + } + + /** + * Creates a new menu item for the given reference system. + */ + private MenuItem createItem(final ReferenceSystem system) { + if (system != RecentReferenceSystems.OTHER) { + final RadioMenuItem item = new RadioMenuItem(IdentifiedObjects.getDisplayName(system, owner.locale)); + item.getProperties().put(REFERENCE_SYSTEM_KEY, system); + item.setToggleGroup(group); + item.setOnAction(this); + return item; + } else { + final MenuItem item = new MenuItem(Vocabulary.getResources(owner.locale).getString(Vocabulary.Keys.Others) + '…'); + item.getProperties().put(REFERENCE_SYSTEM_KEY, CHOOSER); + item.setOnAction(this); + return item; + } + } + + /** + * Must be invoked after removing a menu item for avoiding memory leak. + */ + private static void dispose(final MenuItem item) { + item.setOnAction(null); + if (item instanceof RadioMenuItem) { + ((RadioMenuItem) item).setToggleGroup(null); + } + } + + /** + * Invoked when the list of reference systems changed. While it would be possible to trace the permutations, + * additions, removals and replacements done on the list, it is easier to recreate the menu items list from + * scratch (with recycling of existing items) and inspect the differences. + */ + final void notifyChanges(final ObservableList<? extends ReferenceSystem> systems) { + final Map<Object,MenuItem> mapping = new IdentityHashMap<>(); + for (final Iterator<MenuItem> it = menus.iterator(); it.hasNext();) { + final MenuItem item = it.next(); + if (mapping.putIfAbsent(item.getProperties().get(REFERENCE_SYSTEM_KEY), item) != null) { + it.remove(); // Remove duplicated item. Should never happen, but we are paranoiac. + dispose(item); + } + } + final MenuItem[] items = new MenuItem[systems.size()]; + for (int i=0; i<items.length; i++) { + items[i] = mapping.remove(systems.get(i)); + } + /* + * Previous loop copied all items that could be reused as-is. Now search for all items that are new. + * If there is some menu items available, recycle them. + */ + final Iterator<MenuItem> recycle = mapping.values().iterator(); + for (int i=0; i<items.length; i++) { + if (items[i] == null) { + final ReferenceSystem system = systems.get(i); + if (system != RecentReferenceSystems.OTHER && recycle.hasNext()) { + final MenuItem item = recycle.next(); + recycle.remove(); + if (item instanceof RadioMenuItem) { + item.setText(IdentifiedObjects.getDisplayName(system, owner.locale)); + item.getProperties().put(REFERENCE_SYSTEM_KEY, system); + items[i] = item; + continue; + } + } + items[i] = createItem(system); + } + } + /* + * If there is any item left, we must remove them from the ToggleGroup for avoiding memory leak. + */ + while (recycle.hasNext()) { + dispose(recycle.next()); + } + GUIUtilities.copyAsDiff(Arrays.asList(items), menus); + } + + /** + * Invoked when user selects a menu item. This method gets the old and new values and sends them + * to {@link org.apache.sis.gui.referencing.RecentReferenceSystems.Listener} as a change event. + * That {@code Listener} will update the list of reference systems, which may result in a callback + * to {@link #notifyChanges(ObservableList)}. If the selected menu item is the "Other…" choice, + * then {@code Listener} will popup {@link CRSChooser} and callback {@link #set(ReferenceSystem)} + * for storing the result. Otherwise we need to invoke {@link #set(ReferenceSystem)} ourselves. + */ + @Override + public void handle(final ActionEvent event) { + // ClassCastException should not happen because this listener is registered only on MenuItem. + final Object value = ((MenuItem) event.getSource()).getProperties().get(REFERENCE_SYSTEM_KEY); + ReferenceSystem crs = (value == CHOOSER) ? RecentReferenceSystems.OTHER : (ReferenceSystem) value; + action.changed(this, get(), crs); + if (crs != RecentReferenceSystems.OTHER) { + set(crs); + } + } + + /** + * Selects the specified reference system. This method is invoked by {@link RecentReferenceSystems} + * when the selected CRS changed, either programmatically or by user action. + */ + @Override + public void set(final ReferenceSystem system) { + super.set(system); + for (final MenuItem item : menus) { + if (item instanceof RadioMenuItem && item.getProperties().get(REFERENCE_SYSTEM_KEY) == system) { + ((RadioMenuItem) item).setSelected(true); + return; + } + } + group.selectToggle(null); + } +} diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java index 35d9869..fd4b0da 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java @@ -18,6 +18,7 @@ package org.apache.sis.gui.referencing; import java.util.List; import java.util.ArrayList; +import java.util.Locale; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; @@ -27,6 +28,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.ChoiceBox; import javafx.scene.control.MenuItem; +import javafx.scene.control.Menu; import javafx.concurrent.Task; import org.opengis.util.FactoryException; import org.opengis.geometry.Envelope; @@ -47,6 +49,7 @@ import org.apache.sis.internal.gui.ExceptionReporter; import org.apache.sis.internal.gui.GUIUtilities; import org.apache.sis.internal.gui.NonNullObjectProperty; import org.apache.sis.internal.gui.RecentChoices; +import org.apache.sis.internal.gui.Resources; import org.apache.sis.internal.system.Modules; import org.apache.sis.internal.util.Strings; @@ -122,7 +125,12 @@ public class RecentReferenceSystems { * if we removed the selected item from the list. We use {@code controlValues} for saving currently selected * values before to modify the item list, and restore selections after we finished to modify the list. */ - private final List<ObjectProperty<ReferenceSystem>> controlValues; + private final List<WritableValue<ReferenceSystem>> controlValues; + + /** + * The preferred locale for displaying object name, or {@code null} for the default locale. + */ + final Locale locale; /** * Wrapper for a {@link ReferenceSystem} which has not yet been compared with authoritative definitions. @@ -149,8 +157,8 @@ public class RecentReferenceSystems { * The {@link #OTHER} reference system is <em>not</em> included in this list. * * <p>The list content is specified by calls to {@code setPreferred(…)} and {@code addAlternatives(…)} methods, - * then is filtered by {@link #filterSystems(ImmutableEnvelope, ComparisonMode)} for resolving authority codes - * and removing duplicated elements.</p> + * then is filtered by {@link #filterReferenceSystems(ImmutableEnvelope, ComparisonMode)} for resolving authority + * codes and removing duplicated elements.</p> * * <p>All accesses to this field and to {@link #isModified} field shall be done in a block synchronized * on {@code systemsOrCodes}.</p> @@ -183,6 +191,20 @@ public class RecentReferenceSystems { * The factory will be capable to handle at least some EPSG codes. */ public RecentReferenceSystems() { + this(null, null); + } + + /** + * Creates a builder which will use the specified authority factory. + * + * @param factory the factory to use for building CRS from authority codes, or {@code null} for the default. + * @param locale the preferred locale for displaying object name, or {@code null} for the default locale. + * + * @see org.apache.sis.referencing.CRS#getAuthorityFactory(String) + */ + public RecentReferenceSystems(final CRSAuthorityFactory factory, final Locale locale) { + this.factory = factory; + this.locale = locale; systemsOrCodes = new ArrayList<>(); areaOfInterest = new SimpleObjectProperty<>(this, "areaOfInterest"); duplicationCriterion = new NonNullObjectProperty<>(this, "duplicationCriterion", ComparisonMode.ALLOW_VARIANT); @@ -195,19 +217,6 @@ public class RecentReferenceSystems { } /** - * Creates a builder which will use the specified authority factory. - * - * @param factory the factory to use for building CRS from authority codes. - * - * @see org.apache.sis.referencing.CRS#getAuthorityFactory(String) - */ - public RecentReferenceSystems(final CRSAuthorityFactory factory) { - this(); - ArgumentChecks.ensureNonNull("factory", factory); - this.factory = factory; - } - - /** * Sets the native or preferred reference system. This is the system to always show as the first * choice and should typically be the native {@link CoordinateReferenceSystem} of visualized data. * If a previous preferred system existed, the previous system will be moved to alternative choices. @@ -273,7 +282,7 @@ public class RecentReferenceSystems { } modified(); } - // Check for duplication will be done in `filterSystems()` method. + // Check for duplication will be done in `filterReferenceSystems()` method. } /** @@ -302,7 +311,7 @@ public class RecentReferenceSystems { } modified(); } - // Parsing will be done in `filterSystems()` method. + // Parsing will be done in `filterReferenceSystems()` method. } /** @@ -326,7 +335,7 @@ public class RecentReferenceSystems { * @param mode the {@link #duplicationCriterion} value read from JavaFX thread. * @return the filtered reference systems, or {@code null} if already filtered. */ - private List<ReferenceSystem> filterSystems(final ImmutableEnvelope domain, final ComparisonMode mode) { + private List<ReferenceSystem> filterReferenceSystems(final ImmutableEnvelope domain, final ComparisonMode mode) { final List<ReferenceSystem> systems; synchronized (systemsOrCodes) { CRSAuthorityFactory factory = this.factory; // Hide volatile field by local field. @@ -454,15 +463,33 @@ public class RecentReferenceSystems { referenceSystems = FXCollections.observableArrayList(); } synchronized (systemsOrCodes) { - systemsOrCodes.addAll(Math.min(systemsOrCodes.size(), NUM_CORE_ITEMS), referenceSystems); - // Duplicated values will be filtered by the background task below. + /* + * Prepare a temporary list as the concatenation of all items that are currently visible in JavaFX + * controls with all items that were specified by `setPreferred(…)` or `addAlternatives(…)` methods. + * This concatenation creates a lot of duplicated values, but those duplications will be filtered by + * `filterReferenceSystems(…)` method. The intent is to preserve following order: + * + * - NUM_CORE_ITEMS preferred reference systems first. + * - All reference systems that are currently selected by JavaFX controls. + * - All reference systems offered as choice in JavaFX controls. + * - All reference systems specified by `addAlternatives(…)`. + * - NUM_OTHER_ITEMS systems (will be handled in a special way by `filterReferenceSystems(…)`). + * + * The list will be truncated to NUM_SHOWN_ITEMS after duplications are removed and before OTHER + * is added. The first occurrence of duplicated values is kept, which will result in above-cited + * order as the priority order where to insert the CRS. + */ isModified = true; + final int insertAt = Math.min(systemsOrCodes.size(), NUM_CORE_ITEMS); + final List<ReferenceSystem> selected = getSelectedItems(); + systemsOrCodes.addAll(insertAt, selected); + systemsOrCodes.addAll(insertAt + selected.size(), referenceSystems); final ImmutableEnvelope domain = geographicAOI; final ComparisonMode mode = duplicationCriterion.get(); BackgroundThreads.execute(new Task<List<ReferenceSystem>>() { /** Filters the {@link ReferenceSystem}s in a background thread. */ @Override protected List<ReferenceSystem> call() { - return filterSystems(domain, mode); + return filterReferenceSystems(domain, mode); } /** Should never happen. */ @@ -495,10 +522,12 @@ public class RecentReferenceSystems { * in the `referenceSystems` list is temporarily removed (before to be inserted elsewhere). * Save the values before to modify the list. */ - final ReferenceSystem[] values = controlValues.stream().map(ObjectProperty::get).toArray(ReferenceSystem[]::new); + final ReferenceSystem[] values = controlValues.stream().map(WritableValue::getValue).toArray(ReferenceSystem[]::new); try { isAdjusting = true; - GUIUtilities.copyAsDiff(systems, referenceSystems); + if (GUIUtilities.copyAsDiff(systems, referenceSystems)) { + notifyChanges(); + } } finally { isAdjusting = false; } @@ -519,7 +548,7 @@ public class RecentReferenceSystems { break; } } - controlValues.get(j).set(system); + controlValues.get(j).setValue(system); } } } @@ -547,11 +576,18 @@ public class RecentReferenceSystems { return; } if (newValue == OTHER) { - final CRSChooser chooser = new CRSChooser(factory, geographicAOI); + final CRSChooser chooser = new CRSChooser(factory, geographicAOI, locale); newValue = chooser.showDialog(GUIUtilities.getWindow(property)).orElse(null); if (newValue == null) { newValue = oldValue; } else { + /* + * If user selected a CRS in the CRSChooser list, verify if her/his selection is a CRS + * already presents in the `referenceSystems` list. We ignore axis order (by default) + * because the previous CRS in the list may be a CRS given by `setPreferred(…)` method, + * which typically come from a DataStore. That previous CRS will be replaced by the CRS + * given by CRSChooser, which is more conform to authoritative definition. + */ final ObservableList<ReferenceSystem> items = referenceSystems; final ComparisonMode mode = duplicationCriterion.get(); final int count = items.size() - NUM_OTHER_ITEMS; @@ -565,16 +601,23 @@ public class RecentReferenceSystems { break; } } + /* + * If the selected CRS was not present in the list, we may need to remove the last item + * for making room for the new one. New item must be added before `property.setValue(…)` + * is invoked, otherwise ChoiceBox may add a new item by itself. + */ if (!found) { if (count >= NUM_SHOWN_ITEMS) { items.remove(count - 1); // Remove the last item before `OTHER`. } - items.add(Math.min(count, NUM_CORE_ITEMS), newValue); + items.add(Math.min(items.size(), NUM_CORE_ITEMS), newValue); + notifyChanges(); } } /* * Following cast is safe because this listener is registered only on ObjectProperty * instances, and the ObjectProperty class implements WritableValue. + * The effect of this method call is to set the selected value. */ ((WritableValue<ReferenceSystem>) property).setValue(newValue); } @@ -586,9 +629,9 @@ public class RecentReferenceSystems { action.changed(property, oldValue, newValue); RecentChoices.useReferenceSystem(IdentifiedObjects.toString(IdentifiedObjects.getIdentifier(newValue, null))); /* - * Move the selected reference system as the first choice after the core systems. - * We need to remove the old value before to add the new one, otherwise it seems - * to confuse the list. + * If the selected CRS is already at the beginning of the list, do nothing. The beginning is + * either one of the core items (specified by `setPreferred(…)`) or the first item after the + * core items. */ final ObservableList<ReferenceSystem> items = referenceSystems; final int count = items.size() - NUM_OTHER_ITEMS; @@ -597,18 +640,95 @@ public class RecentReferenceSystems { return; } } + /* + * Move the selected reference system as the first choice after the core systems. + * We need to remove the old value before to add the new one, otherwise it seems + * to confuse the list. + */ for (int i=count; --i >= NUM_CORE_ITEMS;) { if (items.get(i) == newValue) { items.remove(i); break; } } - items.add(Math.max(0, Math.min(count, NUM_CORE_ITEMS)), newValue); + items.add(Math.min(items.size(), NUM_CORE_ITEMS), newValue); + notifyChanges(); + } + } + } + + /** + * Notifies all {@link MenuSync} that the list of reference systems changed. We send a notification manually + * instead than relying on {@code ListChangeListener} in order to process only one event after we have done + * a bunch of changes instead than an event after each individual add or remove operation. + */ + private void notifyChanges() { + for (final WritableValue<ReferenceSystem> value : controlValues) { + if (value instanceof MenuSync) { + ((MenuSync) value).notifyChanges(referenceSystems); } } } /** + * Returns all currently selected reference systems in the order they appear in JavaFX controls. + * This method collects selected values of all controls created by a {@code createXXX(…)} method. + * The returned list does not contain duplicated values. + * + * @return currently selected values of all controls, without duplicated values and in the order + * they appear in choice lists. + */ + public List<ReferenceSystem> getSelectedItems() { + /* + * Build an array of selected reference systems. This array may contain duplicated elements if two + * or more JavaFX controls have the same selected value. Those duplications will be resolved later. + * Conceptually we will use this array as a java.util.Set, except that its length is so small + * (usually no more than 3 elements) that it is not worth to use HashSet. + */ + int count = 0; + final ReferenceSystem[] selected = new ReferenceSystem[controlValues.size()]; + for (final WritableValue<ReferenceSystem> value : controlValues) { + final ReferenceSystem system = value.getValue(); + if (system != null) selected[count++] = system; + } + /* + * Now filter the `referenceSystems` list, retaining only elements that are present in `selected`. + * We do that way for having selected elements in the same order as they appear in JavaFX controls. + */ + final List<ReferenceSystem> ordered = new ArrayList<>(count); + if (count != 0) { + // (count > 0) implies (referenceSystems != null). + for (final ReferenceSystem system : referenceSystems) { + if (system != OTHER) { + for (int i=0; i<count; i++) { + if (selected[i] == system) { + ordered.add(system); + if (--count == 0) return ordered; + System.arraycopy(selected, i+1, selected, i, count - i); + break; + } + } + } + } + /* + * If some selected elements were not found in the `referenceSystems` list, add them last. + * It should not happen, unless those remaining elements are duplicated values (i.e. two + * or more controls having the same selection). + */ +next: for (int i=0; i<count; i++) { + final ReferenceSystem system = selected[i]; + for (int j=ordered.size(); --j >= 0;) { + if (ordered.get(j) == system) { + continue next; // Skip duplicated value. + } + } + ordered.add(system); + } + } + return ordered; + } + + /** * Creates a box offering choices among the reference systems specified to this {@code ShortChoiceList}. * The returned control may be initially empty, in which case its content will be automatically set at * a later time (after a background thread finished to process the {@link CoordinateReferenceSystem}s). @@ -620,14 +740,25 @@ public class RecentReferenceSystems { public ChoiceBox<ReferenceSystem> createChoiceBox(final ChangeListener<ReferenceSystem> action) { ArgumentChecks.ensureNonNull("action", action); final ChoiceBox<ReferenceSystem> choices = new ChoiceBox<>(updateItems()); - choices.setConverter(new ObjectStringConverter<>(choices.getItems(), null)); + choices.setConverter(new ObjectStringConverter<>(choices.getItems(), locale)); choices.valueProperty().addListener(new Listener(action)); controlValues.add(choices.valueProperty()); return choices; } - public MenuItem[] createMenuItems() { - return null; + /** + * Creates menu items offering choices among the reference systems specified to this {@code ShortChoiceList}. + * The items will be inserted in the {@linkplain Menu#getItems() menu list}. The content of that list will + * change at any time after this method returned: items will be added or removed as a result of user actions. + * + * @param action the action to execute when a reference system is selected. + * @return the menu containing items for reference systems. + */ + public Menu createMenuItems(final ChangeListener<ReferenceSystem> action) { + ArgumentChecks.ensureNonNull("action", action); + final Menu menu = new Menu(Resources.forLocale(locale).getString(Resources.Keys.ReferenceSystem)); + controlValues.add(new MenuSync(this, updateItems(), menu, new Listener(action))); + return menu; } /** diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java index 952c784..7c13412 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/GUIUtilities.java @@ -24,6 +24,8 @@ import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; import javafx.stage.Window; import org.apache.sis.util.Static; @@ -46,7 +48,7 @@ public final class GUIUtilities extends Static { /** * Returns the window of the bean associated to the given property. * - * @param property the property for which to get the window where it appear. + * @param property the property for which to get the window of the control, or {@code null}. * @return the window, or {@code null} if unknown. */ public static Window getWindow(final ObservableValue<?> property) { @@ -57,6 +59,15 @@ public final class GUIUtilities extends Static { if (scene != null) { return scene.getWindow(); } + } else if (bean instanceof MenuItem) { + ContextMenu parent = ((MenuItem) bean).getParentPopup(); + if (parent != null) { + for (;;) { + final Window owner = parent.getOwnerWindow(); + if (!(owner instanceof ContextMenu)) return owner; + parent = (ContextMenu) owner; + } + } } } return null; @@ -71,23 +82,25 @@ public final class GUIUtilities extends Static { * @param <E> type of elements to copy. * @param source the list of elements to copy in the target. * @param target the list to modify with as few operations as possible. + * @return {@code true} if the target changed as a result of this method call. */ @SuppressWarnings("empty-statement") - public static <E> void copyAsDiff(final List<? extends E> source, final ObservableList<E> target) { + public static <E> boolean copyAsDiff(final List<? extends E> source, final ObservableList<E> target) { if (source.isEmpty()) { + final boolean empty = target.isEmpty(); target.clear(); - return; + return !empty; } if (target.isEmpty()) { - target.setAll(source); - return; + return target.setAll(source); // Return value is correct only if source is not empty. } - final List<E> lcs = longestCommonSubsequence(source, target); + final List<? extends E> lcs = longestCommonSubsequence(source, target); /* * Remove elements before to add new ones, because some listeners * seem to be confused when a list contains duplicated elements * (the removed elements may be inserted elsewhere). */ + boolean modified = false; int upper = target.size(); for (int i = lcs.size(); --i >= 0;) { final E keep = lcs.get(i); @@ -95,11 +108,13 @@ public final class GUIUtilities extends Static { while (target.get(--lower) != keep); // A negative index here would be a bug in LCS computation. if (lower + 1 < upper) { target.remove(lower + 1, upper); + modified = true; } upper = lower; } if (upper != 0) { target.remove(0, upper); + modified = true; } assert lcs.equals(target); // Because we removed all elements that were not present in LCS. /* @@ -115,14 +130,17 @@ public final class GUIUtilities extends Static { if (lower < upper) { target.addAll(i, source.subList(lower, upper)); i += upper - lower; + modified = true; } lower = upper + 1; } upper = source.size(); if (lower < upper) { target.addAll(source.subList(lower, upper)); + modified = true; } assert source.equals(target); + return modified; } /** @@ -147,17 +165,55 @@ public final class GUIUtilities extends Static { * @param x the first sequence for which to compute LCS. * @param y the second sequence for which to compute LCS. * @return longest common subsequence (LCS) between the two given sequences. + * May be one of the given lists. * * <a href="https://en.wikipedia.org/wiki/Longest_common_subsequence_problem">Longest common subsequence problem</a> */ - static <E> List<E> longestCommonSubsequence(final List<? extends E> x, final List<? extends E> y) { + static <E> List<? extends E> longestCommonSubsequence(List<? extends E> x, List<? extends E> y) { /* - * This method could be optimized by excluding the common prefix and common suffix before to build the - * matrix below. For now we don't do that because the given lists are small. But we should revisit in - * the future if this method become used with longer sequences. + * This method can be optimized by excluding the common prefix and common suffix before + * to build the matrix. It can reduce a lot the matrix size and the number of iterations. */ int nx = x.size(); int ny = y.size(); + final List<? extends E> prefix; + for (int i=0; ; i++) { + if (i >= nx) return x; + if (i >= ny) return y; + if (x.get(i) != y.get(i)) { + if (i == 0) { + prefix = Collections.emptyList(); + } else { + prefix = x.subList(0, i); + assert y.subList(0, i).equals(prefix); + x = x.subList(i, nx); + y = y.subList(i, ny); + nx -= i; + ny -= i; + } + break; + } + } + final List<? extends E> suffix; + for (int i=0; ; i++) { + final int sx = nx - i; + final int sy = ny - i; + if (sx == 0) return x; + if (sy == 0) return y; + if (x.get(sx - 1) != y.get(sy - 1)) { + if (i == 0) { + suffix = Collections.emptyList(); + } else { + suffix = x.subList(sx, nx); + assert y.subList(sy, ny).equals(suffix); + x = x.subList(0, sx); + y = y.subList(0, sy); + nx -= i; + ny -= i; + } + break; + } + } /* * We need a matrix of size (nx x ny) for storing LCS lengths for all (x[i], y[j]) pairs of elements. * The matrix is augmented by one row and one column where all values in the first row and first column @@ -180,7 +236,7 @@ public final class GUIUtilities extends Static { * Following loop is the "traceback" procedure: starting from last cell, follows * the direction where the length decrease. */ - final List<E> lcs = new ArrayList<>(lengths[nx][ny]); + final List<E> lcs = new ArrayList<>(lengths[nx][ny] + prefix.size() + suffix.size()); while (nx > 0 && ny > 0) { final int lg = lengths[nx][ny]; if (lengths[nx-1][ny] >= lg) { @@ -192,6 +248,8 @@ public final class GUIUtilities extends Static { } } Collections.reverse(lcs); + lcs.addAll(0, prefix); + lcs.addAll( suffix); return lcs; } } 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 27817d3..bb0c425 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 @@ -56,7 +56,7 @@ public final class Resources extends IndexedResourceBundle { } /** - * Abstract: + * Abstract */ public static final short Abstract = 14; @@ -101,7 +101,7 @@ public final class Resources extends IndexedResourceBundle { public static final short CanNotReadResource = 55; /** - * Cell geometry: + * Cell geometry */ public static final short CellGeometry = 15; @@ -121,12 +121,12 @@ public final class Resources extends IndexedResourceBundle { public static final short CopyAs = 46; /** - * Creation date: + * Creation date */ public static final short CreationDate = 16; /** - * Credit: + * Credit */ public static final short Credit = 17; @@ -136,12 +136,12 @@ public final class Resources extends IndexedResourceBundle { public static final short Data = 32; /** - * Date: + * Date */ public static final short Date = 18; /** - * Dimensions: + * Dimensions */ public static final short Dimensions = 19; @@ -191,7 +191,7 @@ public final class Resources extends IndexedResourceBundle { public static final short Exit = 9; /** - * Extent: + * Extent */ public static final short Extent = 20; @@ -201,12 +201,12 @@ public final class Resources extends IndexedResourceBundle { public static final short File = 10; /** - * Filter: + * Filter */ public static final short Filter = 34; /** - * Format: + * Format */ public static final short Format = 38; @@ -226,7 +226,7 @@ public final class Resources extends IndexedResourceBundle { public static final short GeospatialFiles = 4; /** - * Identifiers: + * Identifiers */ public static final short Identifiers = 54; @@ -256,7 +256,7 @@ public final class Resources extends IndexedResourceBundle { public static final short NoFeatureTypeInfo = 33; /** - * Number of dimensions: + * Number of dimensions */ public static final short NumberOfDimensions = 27; @@ -271,17 +271,17 @@ public final class Resources extends IndexedResourceBundle { public static final short OpenDataFile = 2; /** - * Publication date: + * Publication date */ public static final short PublicationDate = 21; /** - * Purpose: + * Purpose */ public static final short Purpose = 28; /** - * Reference system: + * Reference system */ public static final short ReferenceSystem = 22; @@ -321,12 +321,12 @@ public final class Resources extends IndexedResourceBundle { public static final short TabularData = 51; /** - * Topic category: + * Topic category */ public static final short TopicCategory = 25; /** - * Type of resource: + * Type of resource */ public static final short TypeOfResource = 26; 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 bcac615e..4ead37e 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 @@ -20,7 +20,7 @@ # For resources shared by all modules in the Apache SIS project, see "org.apache.sis.util.resources" package. # -Abstract = Abstract: +Abstract = Abstract AllFiles = All files CanNotFetchTile_2 = Can not fetch tile ({0}, {1}). CanNotReadFile_1 = Can not open \u201c{0}\u201d. @@ -28,16 +28,16 @@ CanNotClose_1 = Can not close \u201c{0}\u201d. Data may be lost. CanNotCreateCRS_1 = Can not create reference system \u201c{0}\u201d. CanNotCreateXML = Can not create XML document. CanNotReadResource = A resource contained in the file can not be read. The cause is given below. -CellGeometry = Cell geometry: +CellGeometry = Cell geometry Close = Close Copy = Copy CopyAs = Copy as -CreationDate = Creation date: -Credit = Credit: +CreationDate = Creation date +Credit = Credit CRSs = Coordinate Reference Systems Data = Data -Date = Date: -Dimensions = Dimensions: +Date = Date +Dimensions = Dimensions Display = Display DoesNotCoverAOI = Does not cover the area of interest. ErrorDetails = Details about error @@ -47,25 +47,25 @@ ErrorClosingFile = Error closing file ErrorCreatingCRS = Error creating reference system ErrorDataAccess = Error during data access Exit = Exit -Extent = Extent: +Extent = Extent File = File -Filter = Filter: -Format = Format: +Filter = Filter +Format = Format FromMetadata = From metadata FullScreen = Full screen GeospatialFiles = Geospatial data files -Identifiers = Identifiers: +Identifiers = Identifiers Loading = Loading\u2026 MainWindow = Main window Metadata = Metadata NewWindow = New window NoFeatureTypeInfo = No feature type information. -NumberOfDimensions = Number of dimensions: +NumberOfDimensions = Number of dimensions Open = Open\u2026 OpenDataFile = Open data file -PublicationDate = Publication date: -Purpose = Purpose: -ReferenceSystem = Reference system: +PublicationDate = Publication date +Purpose = Purpose +ReferenceSystem = Reference system ResourceIdentification = Resource identification SpatialRepresentation = Spatial representation SelectCRS = Select a coordinate reference system @@ -73,7 +73,7 @@ SendTo = Send to StandardErrorStream = Standard error stream Summary = Summary TabularData = Tabular data -TopicCategory = Topic category: -TypeOfResource = Type of resource: +TopicCategory = Topic category +TypeOfResource = Type of resource Visualize = Visualize Windows = Windows 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 37b1099..9659427 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 @@ -25,7 +25,7 @@ # U+00A0 NO-BREAK SPACE before : # -Abstract = R\u00e9sum\u00e9\u00a0: +Abstract = R\u00e9sum\u00e9 AllFiles = Tous les fichiers CanNotFetchTile_2 = Ne peut pas obtenir la tuile ({0}, {1}). CanNotReadFile_1 = Ne peut pas ouvrir \u00ab\u202f{0}\u202f\u00bb. @@ -33,16 +33,16 @@ CanNotClose_1 = Ne peut pas fermer \u00ab\u202f{0}\u202f\u00bb. Il pour CanNotCreateCRS_1 = Ne peut pas cr\u00e9er le syst\u00e8me de r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb. CanNotCreateXML = Ne peut pas cr\u00e9er le document XML. CanNotReadResource = Une ressource contenue dans le fichier ne peut pas \u00eatre lue. La cause est donn\u00e9e ci-dessous. -CellGeometry = G\u00e9om\u00e9trie des cellules\u00a0: +CellGeometry = G\u00e9om\u00e9trie des cellules Close = Fermer Copy = Copier CopyAs = Copier comme -CreationDate = Date de cr\u00e9ation\u00a0: -Credit = Cr\u00e9dit\u00a0: +CreationDate = Date de cr\u00e9ation +Credit = Cr\u00e9dit CRSs = Syst\u00e8mes de r\u00e9f\u00e9rence des coordonn\u00e9es Data = Donn\u00e9es -Date = Date\u00a0: -Dimensions = Dimensions\u00a0: +Date = Date +Dimensions = Dimensions Display = Affichage DoesNotCoverAOI = Ne couvre pas la r\u00e9gion d\u2019int\u00e9r\u00eat. ErrorDetails = D\u00e9tails \u00e0 propos de l\u2019erreur @@ -52,25 +52,25 @@ ErrorClosingFile = Erreur \u00e0 la fermeture du fichier ErrorCreatingCRS = Erreur \u00e0 la cr\u00e9ation du syst\u00e8me de r\u00e9f\u00e9rence ErrorDataAccess = Erreur lors de l\u2019acc\u00e8s \u00e0 la donn\u00e9e Exit = Quitter -Extent = \u00c9tendue\u00a0: +Extent = \u00c9tendue File = Fichier -Filter = Filtre\u00a0: -Format = Format\u00a0: +Filter = Filtre +Format = Format FromMetadata = Des m\u00e9ta-donn\u00e9es FullScreen = Plein \u00e9cran GeospatialFiles = Fichiers de donn\u00e9es g\u00e9ospatiales -Identifiers = Identifiants\u00a0: +Identifiers = Identifiants Loading = Chargement\u2026 MainWindow = Fen\u00eatre principale Metadata = Metadonn\u00e9es NewWindow = Nouvelle fen\u00eatre NoFeatureTypeInfo = Pas d\u2019information sur le type d\u2019entit\u00e9. -NumberOfDimensions = Nombre de dimensions\u00a0: +NumberOfDimensions = Nombre de dimensions Open = Ouvrir\u2026 OpenDataFile = Ouvrir un fichier de donn\u00e9es -PublicationDate = Date de publication\u00a0: -Purpose = Objectif\u00a0: -ReferenceSystem = Syst\u00e8me de r\u00e9f\u00e9rence\u00a0: +PublicationDate = Date de publication +Purpose = Objectif +ReferenceSystem = Syst\u00e8me de r\u00e9f\u00e9rence ResourceIdentification = Identification de la ressource SpatialRepresentation = Repr\u00e9sentation spatiale SelectCRS = Choisir un syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es @@ -78,7 +78,7 @@ SendTo = Envoyer vers StandardErrorStream = Flux d\u2019erreur standard Summary = R\u00e9sum\u00e9 TabularData = Tableau de valeurs -TopicCategory = Cat\u00e9gorie th\u00e9matique\u00a0: -TypeOfResource = Type de ressource\u00a0: +TopicCategory = Cat\u00e9gorie th\u00e9matique +TypeOfResource = Type de ressource Visualize = Visualiser Windows = Fen\u00eatres diff --git a/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java b/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java index 648ace5..4445ec2 100644 --- a/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java +++ b/application/sis-javafx/src/test/java/org/apache/sis/gui/referencing/CRSChooserApp.java @@ -73,7 +73,7 @@ public final strictfp class CRSChooserApp extends Application { final GeneralEnvelope bbox = new GeneralEnvelope(CommonCRS.defaultGeographic()); bbox.setRange(0, -140.99778, -52.6480987209); bbox.setRange(1, 41.6751050889, 83.23324); // Canada - final CRSChooser chooser = new CRSChooser(null, bbox); + final CRSChooser chooser = new CRSChooser(null, bbox, null); final Optional<CoordinateReferenceSystem> crs = chooser.showDialog(window); System.out.println("The selected CRS is: " + crs); diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java index 13a60f8..d64d8dc 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java +++ b/core/sis-feature/src/main/java/org/apache/sis/image/ComputedImage.java @@ -34,6 +34,7 @@ import org.apache.sis.internal.util.Numerics; import org.apache.sis.util.collection.Cache; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ArraysExt; +import org.apache.sis.util.Disposable; import org.apache.sis.util.Exceptions; import org.apache.sis.coverage.grid.GridExtent; // For javadoc import org.apache.sis.internal.feature.Resources; @@ -116,7 +117,7 @@ import org.apache.sis.internal.feature.Resources; * @since 1.1 * @module */ -public abstract class ComputedImage extends PlanarImage { +public abstract class ComputedImage extends PlanarImage implements Disposable { /** * The property for declaring the amount of additional source pixels needed on each side of a destination pixel. * This property can be used for calculations that require only a fixed rectangular source region around a source @@ -617,6 +618,7 @@ public abstract class ComputedImage extends PlanarImage { * <p><b>Note:</b> keep in mind that this image may be referenced as a source of other images. * In case of doubt, it may be safer to rely on the garbage collector instead than invoking this method.</p> */ + @Override public void dispose() { reference.dispose(); }
