This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 6b11704 CRSChooser builds its list of CRS in a background thread.
Filtering delegated to JavaFX FilteredList class.
6b11704 is described below
commit 6b117040a9289506f361c47cf84576a49e57b48a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Nov 11 17:38:09 2019 +0100
CRSChooser builds its list of CRS in a background thread.
Filtering delegated to JavaFX FilteredList class.
---
.../org/apache/sis/gui/dataset/FeatureTable.java | 2 +-
.../apache/sis/gui/referencing/AuthorityCodes.java | 389 +++++++++++++++++++++
.../org/apache/sis/gui/referencing/CRSChooser.java | 174 ++++-----
.../java/org/apache/sis/gui/referencing/Code.java | 92 +++--
.../org/apache/sis/internal/gui/Resources.java | 5 +
.../apache/sis/internal/gui/Resources.properties | 1 +
.../sis/internal/gui/Resources_fr.properties | 1 +
.../java/org/apache/sis/internal/gui/Styles.java | 5 +
.../referencing/factory/sql/EPSGDataAccess.java | 13 +-
.../sis/referencing/factory/sql/package-info.java | 2 +-
10 files changed, 547 insertions(+), 137 deletions(-)
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
index e55ca3c..1263ed1 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
@@ -112,13 +112,13 @@ public class FeatureTable extends TableView<Feature> {
* Creates an initially empty table.
*/
public FeatureTable() {
+ super(new FeatureList());
textLocale = Locale.getDefault(Locale.Category.DISPLAY);
dataLocale = Locale.getDefault(Locale.Category.FORMAT);
featuresProperty = new SimpleObjectProperty<>(this, "features");
featuresProperty.addListener(this::startFeaturesLoading);
setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY);
setTableMenuButtonVisible(true);
- setItems(new FeatureList());
}
/**
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
new file mode 100644
index 0000000..60e6e8d
--- /dev/null
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
@@ -0,0 +1,389 @@
+/*
+ * 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.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyStringWrapper;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.ObservableListBase;
+import javafx.scene.control.TableColumn;
+import javafx.concurrent.Task;
+import javafx.util.Callback;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.CRSAuthorityFactory;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.util.Exceptions;
+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.util.Constants;
+
+
+/**
+ * A list of authority codes (usually for CRS) which fetch code values in a
background thread
+ * and descriptions only when needed.
+ *
+ * @todo {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess}
internally uses a {@link java.util.Map}
+ * from codes to descriptions. We could open an access to this map for a
little bit more efficiency.
+ * It will be necessary if we want to use {@link AuthorityCodes} for
other kinds of objects than CRS.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+final class AuthorityCodes extends ObservableListBase<Code>
+ implements Callback<TableColumn.CellDataFeatures<Code,String>,
ObservableValue<String>>
+{
+ /**
+ * Delay in nanoseconds before to refresh the list with new content.
+ * Data will be transferred from background threads to JavaFX threads
every time this delay is elapsed.
+ * Delay value is a compromise between fast user experience and giving
enough time for allowing a few
+ * large data transfers instead than many small data transfers.
+ */
+ private static final long REFRESH_DELAY =
StandardDateFormat.NANOS_PER_SECOND / 10;
+
+ /**
+ * The type of object for which we want authority codes. Fixed to {@link
CoordinateReferenceSystem} for now,
+ * but could be made configurable in a future version. Making this field
configurable would require resolving
+ * the "todo" documented in class javadoc.
+ */
+ private static final Class<? extends IdentifiedObject> type =
CoordinateReferenceSystem.class;
+
+ /**
+ * The authority codes obtained from the factory. The list elements are
provided by a background thread.
+ * Elements are initially {@link String} instances and can be replaced
later by {@link Code} instances.
+ */
+ private Object[] codes;
+
+ /**
+ * The preferred locale of CRS descriptions.
+ */
+ 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.
+ */
+ private Loader loader;
+
+ /**
+ * Creates a new deferred list and starts a background process for loading
CRS codes.
+ */
+ AuthorityCodes(final CRSAuthorityFactory factory, final Locale locale) {
+ this.locale = locale;
+ codes = new Object[0];
+ loader = new Loader(factory);
+ BackgroundThreads.execute(loader);
+ }
+
+ /**
+ * 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.
+ */
+ @Override
+ public int size() {
+ return codes.length;
+ }
+
+ /**
+ * Returns the authority code at given index, eventually with its name.
+ */
+ @Override
+ public Code get(final int index) {
+ final Object value = codes[index];
+ if (value instanceof Code) {
+ return (Code) value;
+ }
+ // Wraps the String only when first needed.
+ final Code c = new Code((String) value);
+ codes[index] = c;
+ return c;
+ }
+
+ /**
+ * Adds a single code. This method should never be invoked except of an
error occurred
+ * while loading codes, in which case we add a single pseudo-code with
error message.
+ */
+ @Override
+ public boolean add(final Code code) {
+ final int i = codes.length;
+ codes = Arrays.copyOf(codes, i + 1);
+ codes[i] = code;
+ beginChange();
+ nextAdd(i, i+1);
+ endChange();
+ return true;
+ }
+
+ /**
+ * Invoked when the name or description of an authority code is requested.
+ * If the name is not available, then this method sends to the background
thread a
+ * request for fetching that name and update this property when name
become known.
+ */
+ @Override
+ public ObservableValue<String> call(final
TableColumn.CellDataFeatures<Code,String> cell) {
+ return getName(cell.getValue()).getReadOnlyProperty();
+ }
+
+ /**
+ * Returns the name (or description) for the given code.
+ * If the name is not available, then this method sends to the background
thread a
+ * request for fetching that name and update this property when name
become known.
+ */
+ final ReadOnlyStringWrapper getName(final Code code) {
+ final ReadOnlyStringWrapper p = code.name();
+ final String name = p.getValue();
+ if (name == null && loader != null) {
+ loader.requestName(code);
+ }
+ return p;
+ }
+
+ /**
+ * Adds new codes in this list and/or updates existing codes with CRS
names.
+ * This method is invoked after the background thread has loaded new codes,
+ * and/or after that thread has fetched names (descriptions) of some codes.
+ * We combine those two tasks in a single method in order to send a single
event.
+ *
+ * @param newCodes new codes as {@link String} instances, or {@code null}
if none.
+ * @param updated {@link Code} instances to update with new names, or
{@code null} if none.
+ */
+ private void update(final Object[] newCodes, final Map<Code,String>
updated) {
+ final int s = codes.length;
+ int n = s;
+ if (newCodes != null) {
+ codes = Arrays.copyOf(codes, n += newCodes.length);
+ System.arraycopy(newCodes, 0, codes, s, newCodes.length);
+ }
+ beginChange();
+ if (updated != null) {
+ for (int i=0; i<s; i++) { // Update
names first for having increasing indices.
+ final Object value = codes[i];
+ final String name = updated.remove(value);
+ if (name != null) {
+ ((Code) value).name().set(name); // The name
needs to be set in JavaFX thread.
+ nextUpdate(i);
+ }
+ }
+ }
+ nextAdd(s, n);
+ endChange();
+ }
+
+ /**
+ * Loads a {@link AuthorityCodes} codes in background thread. This
background thread may send tasks
+ * to be executed in JavaFX thread before the final result. The final
result contains only the codes
+ * that have not been processed by above-cited tasks or the codes for
which names need to be updated
+ * (see {@link #call()} for more information).
+ */
+ private final class Loader extends Task<Object> {
+ /**
+ * The factory to use for creating coordinate reference systems,
+ * or {@code null} if not yet determined.
+ */
+ private CRSAuthorityFactory factory;
+
+ /**
+ * The items for which {@link Code#name} has been requested.
+ * Completing those items have priority over completing {@link
AuthorityCodes} because
+ * those completion requests should happen only for cells that are
currently visible.
+ * This list is read and written by two different threads; usages must
be synchronized.
+ */
+ private final List<Code> toDescribe;
+
+ /**
+ * {@code true} for loading authority codes in addition of processing
{@link #toDescribe},
+ * or {@code false} if codes are already loaded. In later case this
task will only process
+ * the {@link #toDescribe} list.
+ */
+ private final boolean loadCodes;
+
+ /**
+ * Creates a new loader using the given factory. If the given factory
is null, then the
+ * {@linkplain CRS#getAuthorityFactory(String) Apache SIS default
factory} will be used.
+ */
+ Loader(final CRSAuthorityFactory factory) {
+ this.factory = factory;
+ toDescribe = new ArrayList<>();
+ loadCodes = true;
+ }
+
+ /**
+ * Invoked after a background thread finished its task. Prepares a new
background thread
+ * for loading names (descriptions) for authority codes listed in
{@link #toDescribe}.
+ */
+ private Loader(final Loader previous) {
+ factory = previous.factory;
+ toDescribe = previous.toDescribe;
+ loadCodes = false;
+ }
+
+ /**
+ * 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.
+ */
+ final void requestName(final Code code) {
+ synchronized (toDescribe) {
+ toDescribe.add(code);
+ }
+ if (!isRunning()) { // Include "scheduled"
state.
+ BackgroundThreads.execute(this);
+ }
+ }
+
+ /**
+ * Fetches the names of all objects in the {@link #toDescribe} array
and clears that array.
+ * The names are returned as a map with {@link Code} as keys and names
(descriptions) as values.
+ * This method is invoked from background thread and returned value
will be consumed in JavaFX thread.
+ */
+ private Map<Code,String> processNameRequests() throws FactoryException
{
+ final Code[] snapshot;
+ synchronized (toDescribe) {
+ final int size = toDescribe.size();
+ if (size == 0) return null;
+ snapshot = toDescribe.toArray(new Code[size]);
+ toDescribe.clear();
+ }
+ 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.
+ updated.put(code,
factory.getDescriptionText(code.code).toString(locale));
+ }
+ return updated;
+ }
+
+ /**
+ * Invoked in background thread for reading authority codes.
Intermediate results are sent
+ * to the JavaFX thread every {@value #REFRESH_DELAY} nanoseconds.
Requests for code names
+ * are also handled in priority since they are typically for visible
cells.
+ *
+ * @return one of the followings:
+ * <ul>
+ * <li>A {@code List<String>} which contains the remaining codes
that need to be
+ * sent to {@link AuthorityCodes} list.</li>
+ * <li>A {@code Map<Code,String>} which contains the codes for
which the names
+ * or descriptions have been updated.</li>
+ * </ul>
+ *
+ * @throws Exception if an error occurred while fetching the codes or
the names/descriptions.
+ */
+ @Override
+ protected Object call() throws Exception {
+ long lastTime = System.nanoTime();
+ List<String> codes = Collections.emptyList();
+ try {
+ if (factory == null) {
+ factory = CRS.getAuthorityFactory(Constants.EPSG);
+ }
+ if (loadCodes) {
+ codes = new ArrayList<>(100);
+ final Iterator<String> it =
factory.getAuthorityCodes(type).iterator();
+ while (it.hasNext()) {
+ codes.add(it.next());
+ if (System.nanoTime() - lastTime > REFRESH_DELAY) {
+ final Object[] newCodes = codes.toArray();
// Snapshot of current content.
+ codes.clear();
+ final Map<Code,String> updated =
processNameRequests(); // Must be outside lambda expression.
+ Platform.runLater(() -> update(newCodes, updated));
+ lastTime = System.nanoTime();
+ }
+ }
+ }
+ /*
+ * At this point we loaded all authority codes. If there is
some remaining codes,
+ * returns them immediately for allowing the user interface to
be updated quickly.
+ * If there is no more codes to return, wait a little bit for
giving a chance to
+ * the `toDescribe` list to be populated with more requests,
then process them.
+ */
+ if (codes.isEmpty()) {
+ Thread.sleep(REFRESH_DELAY /
StandardDateFormat.NANOS_PER_MILLISECOND);
+ return processNameRequests();
+ }
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(Exception.class);
+ }
+ return codes;
+ }
+
+ /**
+ * Invoked after the background thread finished to load authority
codes.
+ * This method adds the remaining codes to {@link AuthorityCodes} list,
+ * then prepare another background tasks for loading descriptions.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void succeeded() {
+ super.succeeded();
+ Object[] newCodes = null;
+ Map<Code,String> updated = null;
+ final Object result = getValue();
+ if (result instanceof List<?>){
+ final List<?> codes = (List<?>) result;
+ if (!codes.isEmpty()) {
+ newCodes = codes.toArray();
+ }
+ } else {
+ 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);
+ }
+ }
+
+ /**
+ * 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.
+ */
+ @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();
+ }
+ code.name().set(message);
+ add(code);
+ }
+ }
+}
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 765f837..73fd239 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
@@ -16,27 +16,31 @@
*/
package org.apache.sis.gui.referencing;
-import java.util.Set;
-import java.util.List;
-import java.util.ArrayList;
import java.util.Locale;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.collections.FXCollections;
-import javafx.concurrent.Task;
+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.control.ButtonType;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
-import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
-import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.internal.gui.IdentityValueFactory;
+import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.internal.util.Strings;
import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.CharSequences;
import org.apache.sis.referencing.CRS;
@@ -51,11 +55,6 @@ import org.apache.sis.referencing.CRS;
*/
public class CRSChooser {
/**
- * The preferred locale of CRS descriptions.
- */
- private final Locale locale;
-
- /**
* The pane where the controls for this CRS chooser will be put.
*/
final BorderPane content;
@@ -66,120 +65,99 @@ public class CRSChooser {
private final TextField searchField;
/**
- * The table showing CRS names together with their codes.
+ * The table showing CRS codes together with their names. Table items are
provided by a background thread.
+ * Items are initially authority codes as {@link Code} instances without
{@link Code#name} value.
+ * Names are completed later when needed.
*/
private final TableView<Code> table;
/**
- * Creates chooser proposing all coordinate reference systems from the
given factory.
+ * 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) {
- locale = Locale.getDefault();
- table = new TableView<>();
- /*
- * Loading of all CRS codes may take about one second.
- * Following put an animation will the CRS are loading.
- *
- * TODO: use deferred loading instead.
- */
- final ProgressIndicator loading = new ProgressIndicator();
- loading.setMaxWidth(60);
- loading.setMaxHeight(60);
- loading.setProgress(-1);
- table.setPlaceholder(loading);
+ final Locale locale = Locale.getDefault();
+ final AuthorityCodes codeList = new AuthorityCodes(factory, locale);
+ table = new TableView<>(codeList);
final Vocabulary vocabulary = Vocabulary.getResources(locale);
+ final TableColumn<Code,Code> codes = new
TableColumn<>(vocabulary.getString(Vocabulary.Keys.Code));
final TableColumn<Code,String> names = new
TableColumn<>(vocabulary.getString(Vocabulary.Keys.Name));
- final TableColumn<Code,String> codes = new
TableColumn<>(vocabulary.getString(Vocabulary.Keys.Code));
- codes.setPrefWidth(150);
- codes.setCellValueFactory((TableColumn.CellDataFeatures<Code,String>
p) -> new SimpleObjectProperty<>(p.getValue().code));
- names.setCellValueFactory((TableColumn.CellDataFeatures<Code,String>
p) -> new SimpleObjectProperty<>(p.getValue().name(locale)));
-
- table.getColumns().setAll(names, codes);
+ names.setCellValueFactory(codeList);
+ codes.setCellValueFactory(IdentityValueFactory.instance());
+ codes.setCellFactory(Code.Cell::new);
+ codes.setMinWidth ( 60); // Will be the initial size of
this column.
+ codes.setMaxWidth (120); // Seems to be required for
preventing `codes` to be as large as `names`.
+ table.setPrefWidth(500);
+ table.getColumns().setAll(codes, names);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
- table.setTableMenuButtonVisible(false);
- content = new BorderPane();
searchField = new TextField();
- searchField.addEventHandler(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
- searchCRS(searchField.getText());
+ searchField.setOnAction((ActionEvent event) -> {
+ filter(searchField.getText());
});
- content.setCenter(table);
-
- BackgroundThreads.execute(new Loader(factory));
- }
-
- private final class Loader extends Task<List<Code>> {
- private CRSAuthorityFactory factory;
-
- Loader(final CRSAuthorityFactory factory) {
- this.factory = factory;
- }
-
- @Override
- protected List<Code> call() throws Exception {
- if (factory == null) {
- factory = CRS.getAuthorityFactory(null);
- }
- final Set<String> strs =
factory.getAuthorityCodes(CoordinateReferenceSystem.class);
- final List<Code> codes = new ArrayList<>();
- for (final String code : strs) {
- codes.add(new Code(factory, code));
- }
- return codes;
- }
-
- @Override
- protected void succeeded() {
- table.setItems(FXCollections.observableArrayList(getValue()));
- table.setPlaceholder(new Label(""));
- }
-
- @Override
- protected void failed() {
- error(getException());
- }
- }
+ final Resources i18n = Resources.forLocale(locale);
+ 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));
- public void searchCRS(final String searchword){
- filter(searchword);
- }
-
- private static void error(final Throwable e) {
- // TODO
+ content = new BorderPane();
+ content.setCenter(table);
+ content.setTop(bar);
}
/**
- * Display only the CRS name that contains the specified keywords. The
{@code keywords}
- * argument is a space-separated list, usually provided by the user after
he pressed the
- * "Search" button.
+ * Displays only the CRS whose names contains the specified keywords. The
{@code keywords}
+ * argument is a space-separated list provided by the user after he
pressed "Enter" key.
*
- * @param keywords space-separated list of keywords to look for.
+ * @param keywords space-separated list of keywords to look for.
*/
private void filter(String keywords) {
- final List<Code> allValues = table.getItems();
- List<Code> model = allValues;
+ final ObservableList<Code> items = table.getItems();
+ final AuthorityCodes allCodes;
+ FilteredList<Code> filtered;
+ if (items instanceof AuthorityCodes) {
+ allCodes = (AuthorityCodes) items;
+ filtered = null;
+ } else {
+ filtered = (FilteredList<Code>) items;
+ allCodes = (AuthorityCodes) filtered.getSource();
+ }
+ keywords = Strings.trimOrNull(keywords);
if (keywords != null) {
- keywords = keywords.toLowerCase(locale).trim();
- final String[] tokens = keywords.split("\\s+");
+ keywords = keywords.toLowerCase(allCodes.locale);
+ final String[] tokens = (String[]) CharSequences.split(keywords, '
');
if (tokens.length != 0) {
- model = new ArrayList<>();
- scan:
- for (Code code : allValues) {
- final String name = code.toString().toLowerCase(locale);
- for (int j=0; j<tokens.length; j++) {
- if (!name.contains(tokens[j])) {
- continue scan;
+ final Predicate<Code> p = (code) -> {
+ String name = allCodes.getName(code).getValue();
+ if (name == null) {
+ return false;
+ }
+ name = name.toLowerCase(allCodes.locale);
+ for (final String token : tokens) {
+ if (!name.contains(token)) {
+ return false;
}
}
- model.add(code);
+ return true;
+ };
+ if (filtered == null) {
+ filtered = new FilteredList<>(allCodes, p);
+ table.setItems(filtered);
+ } else {
+ filtered.setPredicate(p);
}
+ return;
}
}
- table.getItems().setAll(model);
+ table.setItems(allCodes);
}
/**
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
index 67bc212..ad67412 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
@@ -16,16 +16,15 @@
*/
package org.apache.sis.gui.referencing;
-import java.util.Locale;
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.util.Exceptions;
+import javafx.geometry.Pos;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+import javafx.beans.property.ReadOnlyStringWrapper;
+import org.apache.sis.internal.gui.Styles;
/**
- * Stores the code of a coordinate reference system (CRS) together with its
description.
- * The description will be fetched when first needed and returned by {@link
#toString()}.
+ * Stores the code of a coordinate reference system (CRS) together with its
name or description.
*
* @author Martin Desruisseaux (Geomatys)
* @author Johann Sorel (Geomatys)
@@ -36,56 +35,77 @@ import org.apache.sis.util.Exceptions;
final class Code {
/**
* The CRS code. Usually defined by EPSG, but other authorities are
allowed.
+ * This is the value returned by {@link #toString()}.
*/
final String code;
/**
- * The CRS object name for the {@linkplain #code}, fetched when first
needed.
+ * The CRS object description for the {@linkplain #code}.
* In Apache SIS implementation of EPSG factory, this is the CRS name.
*/
- private String name;
+ private ReadOnlyStringWrapper name;
/**
- * The object returned by {@link #crs()}, cached for reuse.
+ * Creates a code from the specified value.
*/
- private CoordinateReferenceSystem crs;
+ Code(final String code) {
+ this.code = code;
+ }
/**
- * The authority factory to use for fetching the name.
+ * Returns the property where to store the name or description of this
authority code.
*/
- private final CRSAuthorityFactory factory;
+ final ReadOnlyStringWrapper name() {
+ if (name == null) {
+ name = new ReadOnlyStringWrapper();
+ }
+ return name;
+ }
/**
- * Creates a code from the specified value.
+ * Returns {@link #code}. This behavior is required for {@link CRSChooser}
since it
+ * will invoke {@link Object#toString()} directly for the column of
authority codes.
*/
- Code(final CRSAuthorityFactory factory, final String code) {
- this.factory = factory;
- this.code = code;
+ @Override
+ public String toString() {
+ return code;
}
- /**
- * Creates the object identified by code.
+ /*
+ * Do not override equals(Object) and hashCode(). We rely on identity
comparisons
+ * when using this object as keys in HashMap.
*/
- CoordinateReferenceSystem crs() throws FactoryException {
- if (crs == null) {
- crs = factory.createCoordinateReferenceSystem(code);
- }
- return crs;
- }
/**
- * Returns a description of the object. This method fetches the
description when first needed.
- * If the operation fails, the exception message will be used as a
description.
- *
- * @param locale the desired locale, or {@code null} for the default
locale.
- * @return the object name in the given locale if possible.
+ * A cell displaying a code value.
*/
- String name(final Locale locale) {
- if (name == null) try {
- name = factory.getDescriptionText(code).toString(locale);
- } catch (FactoryException e) {
- name = Exceptions.getLocalizedMessage(e, locale);
+ static final class Cell extends TableCell<Code,Code> {
+ /**
+ * Creates a new cell for feature property value.
+ *
+ * @param column the column where the cell will be shown.
+ */
+ Cell(final TableColumn<Code,Code> column) {
+ // Column not used at this time, but we need it in method
signature.
+ setAlignment(Pos.BASELINE_RIGHT);
+ setTextFill(Styles.CODE_TEXT);
+ }
+
+ /**
+ * Invoked when a new value needs to be show.
+ *
+ * @todo I didn't found how to get white text color when the row is
selected.
+ * Current color (blue~gray on blue) is hard to read.
+ */
+ @Override
+ protected void updateItem(final Code value, final boolean empty) {
+ if (value == getItem()) return;
+ super.updateItem(value, empty);
+ String text = null;
+ if (value != null) {
+ text = value.toString();
+ }
+ setText(text);
}
- return name;
}
}
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 7240f4d..6076310 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
@@ -146,6 +146,11 @@ public final class Resources extends IndexedResourceBundle
{
public static final short File = 10;
/**
+ * Filter:
+ */
+ public static final short Filter = 34;
+
+ /**
* Geospatial data files
*/
public static final short GeospatialFiles = 4;
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 425bf5d..9824bb6 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
@@ -38,6 +38,7 @@ ErrorOpeningFile = Error opening file
Exit = Exit
Extent = Extent:
File = File
+Filter = Filter:
GeospatialFiles = Geospatial data files
Loading = Loading\u2026
Metadata = Metadata
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 7be1eb0..bbce0ce 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
@@ -43,6 +43,7 @@ ErrorOpeningFile = Erreur \u00e0 l\u2019ouverture du
fichier
Exit = Quitter
Extent = \u00c9tendue\u00a0:
File = Fichier
+Filter = Filtrer\u00a0:
GeospatialFiles = Fichiers de donn\u00e9es g\u00e9ospatiales
Loading = Chargement\u2026
Metadata = Metadonn\u00e9es
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
index 3dfdbad..0f160cd 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
@@ -51,6 +51,11 @@ public final class Styles {
public static final Color LOADING_TEXT = Color.BLUE;
/**
+ * Color of text for authority codes.
+ */
+ public static final Color CODE_TEXT = Color.LIGHTSLATEGREY;
+
+ /**
* Color of text shown in place of data that we failed to load.
*/
public static final Color ERROR_TEXT = Color.RED;
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index f26d0d8..3f9ec4f 100644
---
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -103,6 +103,7 @@ import
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactor
import org.apache.sis.referencing.factory.FactoryDataException;
import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
+import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.util.iso.SimpleInternationalString;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.resources.Errors;
@@ -156,7 +157,7 @@ import static
org.apache.sis.internal.referencing.ServicesForMetadata.CONNECTION
* @author Matthias Basler
* @author Andrea Aime (TOPP)
* @author Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
*
* @see <a
href="http://sis.apache.org/tables/CoordinateReferenceSystems.html">List of
authority codes</a>
*
@@ -529,6 +530,14 @@ addURIs: for (int i=0; ; i++) {
/**
* Returns a map of EPSG authority codes as keys and object names as
values.
+ * The cautions documented in {@link #getAuthorityCodes(Class)} apply also
to this map.
+ *
+ * @todo We may need to give some public access to this map if callers
need descriptions
+ * for other kinds of object than CRS. Current {@link
#getDescriptionText(String)}
+ * implementation selects CRS if the same code is used by many kinds
of objects.
+ *
+ * @see #getAuthorityCodes(Class)
+ * @see #getDescriptionText(String)
*/
private synchronized Map<String,String> getCodeMap(final Class<?> type)
throws SQLException {
CloseableReference<AuthorityCodes> reference =
authorityCodes.get(type);
@@ -625,6 +634,8 @@ addURIs: for (int i=0; ; i++) {
}
} catch (SQLException exception) {
throw new FactoryException(exception.getLocalizedMessage(),
exception);
+ } catch (BackingStoreException exception) { // Cause is
SQLException.
+ throw new FactoryException(exception.getLocalizedMessage(),
exception.getCause());
}
throw noSuchAuthorityCode(IdentifiedObject.class, code);
}
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
index 6ff2f51..f900615 100644
---
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
+++
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
@@ -82,7 +82,7 @@
* @author Jody Garnett (Refractions)
* @author Didier Richard (IGN)
* @author John Grange
- * @version 1.0
+ * @version 1.1
*
* @see org.apache.sis.metadata.sql
*