sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 03/03: Add a "Open recent file" menu. It saves time when doing many test always on the same data file.
Date Fri, 23 Oct 2020 14:40:34 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 57dee2c180947b6b455c9906f5a2b0c8e9d5eaad
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Oct 23 16:39:44 2020 +0200

    Add a "Open recent file" menu. It saves time when doing many test always on the same data
file.
---
 .../main/java/org/apache/sis/gui/DataViewer.java   |   3 +-
 .../main/java/org/apache/sis/gui/RecentFiles.java  | 152 +++++++++++++++++++++
 .../java/org/apache/sis/gui/dataset/LoadEvent.java |  66 +++++++++
 .../apache/sis/gui/dataset/ResourceExplorer.java   |  20 +++
 .../org/apache/sis/gui/dataset/ResourceTree.java   |  40 +++++-
 .../org/apache/sis/internal/gui/RecentChoices.java |  24 ++++
 .../org/apache/sis/internal/gui/Resources.java     |   5 +
 .../apache/sis/internal/gui/Resources.properties   |   1 +
 .../sis/internal/gui/Resources_fr.properties       |   1 +
 .../sis/internal/storage/io/IOUtilities.java       |  24 ++++
 10 files changed, 334 insertions(+), 2 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java
index 1d92550..a9d622c 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/DataViewer.java
@@ -143,8 +143,9 @@ public class DataViewer extends Application {
         final Menu file = new Menu(vocabulary.getString(Vocabulary.Keys.File));
         {   // For keeping variables locale.
             final MenuItem open, close;
+            final Menu recentFiles = RecentFiles.create(content, localized);
             file.getItems().addAll(
-                    open  = localized.menu(Resources.Keys.Open,  (e) -> showOpenFileDialog()),
+                    open  = localized.menu(Resources.Keys.Open,  (e) -> showOpenFileDialog()),
recentFiles,
                     close = localized.menu(Resources.Keys.Close, (e) -> closeSelectedFile()),
                     new SeparatorMenuItem(),
                     localized.menu(Resources.Keys.Exit, (e) -> Platform.exit()));
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/RecentFiles.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/RecentFiles.java
new file mode 100644
index 0000000..fb82de7
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/RecentFiles.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.StringJoiner;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.scene.control.Menu;
+import javafx.scene.control.MenuItem;
+import javafx.collections.ObservableList;
+import org.apache.sis.gui.dataset.LoadEvent;
+import org.apache.sis.gui.dataset.ResourceExplorer;
+import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.internal.gui.RecentChoices;
+import org.apache.sis.util.ArraysExt;
+
+
+/**
+ * Manages a list of recently opened files. The list of files is initialized from user preferences.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ */
+final class RecentFiles implements EventHandler<ActionEvent> {
+    /**
+     * Maximum number of items to show.
+     */
+    private static final int MAX_COUNT = 10;
+
+    /**
+     * Menu items for each recently opened file.
+     * This list should have no more than {@value #MAX_COUNT} elements.
+     */
+    private final ObservableList<MenuItem> items;
+
+    /**
+     * The explorer where to open the file.
+     */
+    private final ResourceExplorer explorer;
+
+    /**
+     * Creates a new handler for a list of recently used files in the specified menu.
+     */
+    private RecentFiles(final ResourceExplorer explorer, final Menu menu) {
+        this.explorer = explorer;
+        items = menu.getItems();
+    }
+
+    /**
+     * Creates a menu for a list of recently used files. The menu is initialized with a list
of files
+     * fetched for user preferences. The user preferences are updated when {@link #opened(LoadEvent)}
+     * is invoked.
+     */
+    static Menu create(final ResourceExplorer explorer, final Resources localized) {
+        final Menu           menu    = new Menu(localized.getString(Resources.Keys.OpenRecentFile));
+        final RecentFiles    handler = new RecentFiles(explorer, menu);
+        final CharSequence[] files   = RecentChoices.getFiles();
+        final MenuItem[]     items   = new MenuItem[files.length];
+        int n = 0;
+        for (int i=0; i<files.length; i++) {
+            final String file = files[i].toString();
+            if (!file.isBlank()) {
+                items[n++] = handler.createItem(new File(file));
+            }
+        }
+        handler.items.setAll(ArraysExt.resize(items, n));
+        explorer.setOnResourceLoaded(handler::opened);
+        return menu;
+    }
+
+    /**
+     * Creates a new menu item for the specified file.
+     */
+    private MenuItem createItem(final File file) {
+        final MenuItem item = new MenuItem(file.getName());
+        item.setUserData(file);
+        item.setOnAction(this);
+        return item;
+    }
+
+    /**
+     * Notifies that a file has been opened. A new menu items is created for the specified
file
+     * and is inserted at the beginning of the list of recent files. If the list of recent
files
+     * has more than {@value #MAX_COUNT} elements, the latest item is discarded.
+     */
+    public void opened(final LoadEvent event) {
+        final Path path = event.getResourcePath();
+        final File file;
+        try {
+            file = path.toFile();
+        } catch (UnsupportedOperationException e) {
+            // Future version may have an "recently used URI" section. We don't do that for
now.
+            return;
+        }
+        final int size = items.size();
+        /*
+         * Verifies if an item already exists for the given file.
+         * If yes, we will just move it.
+         */
+        for (int i=0; i<size; i++) {
+            if (file.equals(items.get(i).getUserData())) {
+                items.add(0, items.remove(i));
+                return;
+            }
+        }
+        final MenuItem item;
+        if (size >= MAX_COUNT) {
+            item = items.remove(size-1);
+            item.setText(file.getName());
+            item.setUserData(file);
+        } else {
+            item = createItem(file);
+        }
+        items.add(0, item);
+        /*
+         * At this point the menu items has been updated.
+         * Now save the file list in user preferences.
+         */
+        final StringJoiner s = new StringJoiner(System.lineSeparator());
+        for (final MenuItem i : items) {
+            s.add(((File) i.getUserData()).getPath());
+        }
+        RecentChoices.setFiles(s.toString());
+    }
+
+    /**
+     * Invoked when the user selects a file.
+     */
+    @Override
+    public void handle(final ActionEvent event) {
+        final Object file = ((MenuItem) event.getSource()).getUserData();
+        explorer.loadResources(Collections.singleton(file));
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/LoadEvent.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/LoadEvent.java
new file mode 100644
index 0000000..6e0ed8d
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/LoadEvent.java
@@ -0,0 +1,66 @@
+/*
+ * 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.dataset;
+
+import java.nio.file.Path;
+import javafx.event.Event;
+import javafx.event.EventType;
+
+
+/**
+ * Event sent when a resource is loaded.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ */
+public final class LoadEvent extends Event {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 5935085957507976585L;
+
+    /**
+     * The only valid type for the load event.
+     */
+    private static final EventType<LoadEvent> LOAD = new EventType<>("LOAD");
+
+    /**
+     * Path to the resource being loaded.
+     */
+    private final Path path;
+
+    /**
+     * Creates a new event.
+     *
+     * @param  source  the source of this event.
+     * @param  path    path to the file being loaded.
+     */
+    LoadEvent(final ResourceTree source, final Path path) {
+        super(source, null, LOAD);
+        this.path = path;
+    }
+
+    /**
+     * Returns the path to the resource being loaded.
+     *
+     * @return path to the resource being loaded.
+     */
+    public Path getResourcePath() {
+        return path;
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
index 226a2e5..7cfd457 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceExplorer.java
@@ -22,6 +22,7 @@ import javafx.beans.property.ReadOnlyProperty;
 import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
+import javafx.event.EventHandler;
 import javafx.concurrent.Task;
 import javafx.geometry.Orientation;
 import javafx.scene.Node;
@@ -265,6 +266,25 @@ public class ResourceExplorer extends WindowManager {
     }
 
     /**
+     * Returns the a function to be called after a resource has been loaded from a file or
URL.
+     *
+     * @return current function to be called after a resource has been loaded, or {@code
null} if none.
+     */
+    public EventHandler<LoadEvent> getOnResourceLoaded() {
+        return resources.onResourceLoaded.get();
+    }
+
+    /**
+     * Specifies a function to be called after a resource has been loaded from a file or
URL.
+     * If this method is never invoked, then the default value is {@code null}.
+     *
+     * @param  handler  new function to be called after a resource has been loaded, or {@code
null} if none.
+     */
+    public void setOnResourceLoaded(final EventHandler<LoadEvent> handler) {
+        resources.onResourceLoaded.set(handler);
+    }
+
+    /**
      * Loads all given sources in background threads and add them to the resource tree.
      * The given collection typically contains files to load,
      * but may also contain {@link Resource} instances to add directly.
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
index b498e1d..0948fb3 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
@@ -17,8 +17,10 @@
 package org.apache.sis.gui.dataset;
 
 import java.io.File;
+import java.nio.file.Path;
 import java.net.URL;
 import java.net.MalformedURLException;
+import java.nio.file.FileSystemNotFoundException;
 import java.util.AbstractList;
 import java.util.Locale;
 import java.util.List;
@@ -29,6 +31,9 @@ import java.util.Optional;
 import javafx.application.Platform;
 import javafx.concurrent.Task;
 import javafx.collections.ObservableList;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.event.EventHandler;
 import javafx.scene.control.Button;
 import javafx.scene.control.ContextMenu;
 import javafx.scene.control.MenuItem;
@@ -101,6 +106,14 @@ public class ResourceTree extends TreeView<Resource> {
     final Locale locale;
 
     /**
+     * Function to be called after a resource has been loaded from a file or URL.
+     * The default value is {@code null}.
+     *
+     * @see #loadResource(Object)
+     */
+    public final ObjectProperty<EventHandler<LoadEvent>> onResourceLoaded;
+
+    /**
      * Creates a new tree of resources with initially no resource to show.
      * For showing a resource, invoke {@link #setResource(Resource)} after construction.
      */
@@ -109,6 +122,7 @@ public class ResourceTree extends TreeView<Resource> {
         setCellFactory((v) -> new Cell());
         setOnDragOver(ResourceTree::onDragOver);
         setOnDragDropped(this::onDragDropped);
+        onResourceLoaded = new SimpleObjectProperty<>(this, "onResourceLoaded");
     }
 
     /**
@@ -187,6 +201,8 @@ public class ResourceTree extends TreeView<Resource> {
      *
      * @param  source  the source of the resource to load. This is usually
      *                 a {@link java.io.File} or {@link java.nio.file.Path}.
+     *
+     * @see #onResourceLoaded
      */
     public void loadResource(final Object source) {
         if (source != null) {
@@ -198,7 +214,10 @@ public class ResourceTree extends TreeView<Resource> {
                 if (existing != null) {
                     addResource(existing);
                 } else {
-                    loader.setOnSucceeded((event) -> addResource((Resource) event.getSource().getValue()));
+                    loader.setOnSucceeded((event) -> {
+                        addResource((Resource) event.getSource().getValue());
+                        notifyLoaded(source);
+                    });
                     loader.setOnFailed((event) -> ExceptionReporter.show(this, event));
                     BackgroundThreads.execute(loader);
                 }
@@ -207,6 +226,25 @@ public class ResourceTree extends TreeView<Resource> {
     }
 
     /**
+     * Notifies {@link #onResourceLoaded} handler that a resource at the given path has been
loaded.
+     */
+    private void notifyLoaded(final Object source) {
+        final EventHandler<LoadEvent> handler = onResourceLoaded.getValue();
+        if (handler != null) {
+            final Path path;
+            try {
+                path = IOUtilities.toPathOrNull(source);
+            } catch (IllegalArgumentException | FileSystemNotFoundException e) {
+                Logging.recoverableException(Logging.getLogger(Modules.APPLICATION), ResourceTree.class,
"loadResource", e);
+                return;
+            }
+            if (path != null) {
+                handler.handle(new LoadEvent(this, path));
+            }
+        }
+    }
+
+    /**
      * Invoked when the user drops files or a URL on this resource tree.
      * This method starts the loading processes in a background thread.
      * The loading is started by calls to {@link #loadResource(Object)}.
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/RecentChoices.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/RecentChoices.java
index e8d7e8c..b151a7d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/RecentChoices.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/RecentChoices.java
@@ -56,6 +56,11 @@ public final class RecentChoices {
     private static final String OPEN = "Open";
 
     /**
+     * The node where to store recently opened files.
+     */
+    private static final String FILES = "RecentFiles";
+
+    /**
      * The node where to store authority (usually EPSG) codes of most recently used coordinate
reference systems.
      */
     private static final String CRS = "ReferenceSystems";
@@ -101,6 +106,25 @@ public final class RecentChoices {
     }
 
     /**
+     * Returns recently opened files.
+     *
+     * @return recently opened files.
+     */
+    public static CharSequence[] getFiles() {
+        return CharSequences.splitOnEOL(NODE.get(FILES, null));
+    }
+
+    /**
+     * Sets the list of recently opened files.
+     * The files shall be specified in a EOL-separated string.
+     *
+     * @param files recently opened files.
+     */
+    public static void setFiles(final String files) {
+        NODE.put(FILES, files);
+    }
+
+    /**
      * Returns the authority codes of most recently used reference systems.
      *
      * @return authority codes, or an empty array if none.
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 926913c..677f245 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
@@ -261,6 +261,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short OpenDataFile = 29;
 
         /**
+         * Open recent file
+         */
+        public static final short OpenRecentFile = 54;
+
+        /**
          * Orthographic
          */
         public static final short Orthographic = 52;
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 1fcd9ec..8c2abfe 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
@@ -61,6 +61,7 @@ NewWindow              = New window
 NoFeatureTypeInfo      = No feature type information.
 Open                   = Open\u2026
 OpenDataFile           = Open data file
+OpenRecentFile         = Open recent file
 Orthographic           = Orthographic
 SelectCRS              = Select a coordinate reference system
 SelectCrsByContextMenu = For changing the projection, use contextual menu on the map.
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 8a11636..b4ee8fa 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
@@ -66,6 +66,7 @@ NewWindow              = Nouvelle fen\u00eatre
 NoFeatureTypeInfo      = Pas d\u2019information sur le type d\u2019entit\u00e9.
 Open                   = Ouvrir\u2026
 OpenDataFile           = Ouvrir un fichier de donn\u00e9es
+OpenRecentFile         = Ouvrir un fichier r\u00e9cent
 Orthographic           = Orthographique
 SelectCRS              = Choisir un syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es
 SelectCrsByContextMenu = Pour changer la projection, utilisez le menu contextuel sur la carte.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
index fae94a7..4d95e78 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java
@@ -436,6 +436,30 @@ public final class IOUtilities extends Static {
     }
 
     /**
+     * Converts the given object to a {@link Path} if the object is a known type, or returns
{@code null} otherwise.
+     * Current implementation recognizes {@link CharSequence}, {@link Path}, {@link File},
{@link URI}
+     * but not {@link URL}, because conversion of URL requires to know the encoding.
+     *
+     * @param  path  the object to convert to a path.
+     * @return the given object as a path, or {@code null}.
+     * @throws IllegalArgumentException if the given object is an instance of a supported
type but can not be converted.
+     * @throws FileSystemNotFoundException if the file system identified by URI can not be
used.
+     */
+    public static Path toPathOrNull(final Object path) {
+        if (path instanceof Path) {
+            return (Path) path;
+        } else if (path instanceof File) {
+            return ((File) path).toPath();
+        } else if (path instanceof URI) {
+            return Paths.get((URI) path);
+        } else if (path instanceof CharSequence) {
+            return Paths.get(path.toString());
+        } else {
+            return null;
+        }
+    }
+
+    /**
      * Converts the given output stream to an input stream. It is caller's responsibility
to flush
      * the stream and reset its position to the beginning of file before to invoke this method.
      * The data read by the input stream will be the data that have been written in the output
stream


Mime
View raw message