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 dfec2ae06d0a05a8c382b06687ed85592e42aa77 Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Sep 2 12:05:45 2020 +0200 Add a menu item for coyping the path to a file. --- .../org/apache/sis/gui/dataset/ResourceTree.java | 81 ++++++++++++++++++++-- .../org/apache/sis/gui/dataset/WindowManager.java | 3 +- .../main/java/org/apache/sis/gui/map/MapMenu.java | 7 +- .../org/apache/sis/internal/gui/Resources.java | 15 ++-- .../apache/sis/internal/gui/Resources.properties | 3 +- .../sis/internal/gui/Resources_fr.properties | 3 +- .../apache/sis/internal/storage/URIDataStore.java | 44 +++++++++++- .../sis/internal/storage/io/IOUtilities.java | 17 ++++- 8 files changed, 151 insertions(+), 22 deletions(-) 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 70d1133..12c1e1b 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,6 +17,8 @@ package org.apache.sis.gui.dataset; import java.io.File; +import java.nio.file.Path; +import java.net.URI; import java.net.URL; import java.net.MalformedURLException; import java.util.AbstractList; @@ -31,12 +33,15 @@ import javafx.concurrent.Task; import javafx.collections.ObservableList; import javafx.scene.control.Button; import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; import javafx.scene.control.TreeCell; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; import javafx.scene.paint.Color; import org.opengis.util.GenericName; import org.opengis.util.InternationalString; @@ -53,13 +58,17 @@ import org.apache.sis.storage.DataStore; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.event.StoreEvent; import org.apache.sis.storage.event.StoreListener; +import org.apache.sis.internal.storage.URIDataStore; +import org.apache.sis.internal.storage.io.IOUtilities; import org.apache.sis.internal.gui.ResourceLoader; import org.apache.sis.internal.gui.BackgroundThreads; import org.apache.sis.internal.gui.ExceptionReporter; import org.apache.sis.internal.gui.LogHandler; import org.apache.sis.internal.gui.Resources; import org.apache.sis.internal.gui.Styles; +import org.apache.sis.internal.system.Modules; import org.apache.sis.internal.util.Strings; +import org.apache.sis.util.logging.Logging; /** @@ -251,6 +260,46 @@ public class ResourceTree extends TreeView<Resource> { } /** + * Performs a "copy" action on the given resource. Current implementation performs only + * a "copy file path" action, but future versions may add more kinds of copy actions. + * + * @param resource the resource to copy. + */ + private static void copy(final Resource resource) { + Object path; + try { + path = URIDataStore.location(resource); + } catch (DataStoreException e) { + ExceptionReporter.show(null, null, e); + return; + } + final ClipboardContent content = new ClipboardContent(); + final boolean isKindOfPath = IOUtilities.isKindOfPath(path); + if (isKindOfPath || path instanceof CharSequence) { + String uri = path.toString(); + String text = uri; + try { + if (path instanceof URI) { + path = new File((URI) path); + } else if (path instanceof Path) { + path = ((Path) path).toFile(); + } + } catch (IllegalArgumentException | UnsupportedOperationException e) { + // Ignore + } + if (path instanceof File) { + content.putFiles(Collections.singletonList((File) path)); + text = path.toString(); + } + if (isKindOfPath) { + content.putUrl(uri); + } + content.putString(text); + } + Clipboard.getSystemClipboard().setContent(content); + } + + /** * Removes the given resource from the tree and closes it if it is a {@link DataStore}. * It is caller's responsibility to ensure that the given resource is not used anymore. * A resource can be removed only if it is a root. If the given resource is not in this @@ -481,9 +530,7 @@ public class ResourceTree extends TreeView<Resource> { setTextFill(isSelected() ? Styles.SELECTED_TEXT : color); /* * If the resource is at the root, add a menu for removing it. - * If we find that the cell already has a menu, we do not need - * to build it again (it may change in a future SIS version if - * the menu is not always the same). + * If we find that the cell already has a menu, we do not need to build it again. */ if (tree != null) { setText(text); @@ -493,14 +540,38 @@ public class ResourceTree extends TreeView<Resource> { menu = getContextMenu(); if (menu == null) { menu = new ContextMenu(); - menu.getItems().add(tree.localized().menu(Resources.Keys.Close, (e) -> { + final Resources localized = tree.localized(); + final MenuItem[] items = new MenuItem[CLOSE + 1]; + items[COPY_PATH] = localized.menu(Resources.Keys.CopyFilePath, (e) -> { + copy(getItem()); + }); + items[CLOSE] = localized.menu(Resources.Keys.Close, (e) -> { ((ResourceTree) getTreeView()).removeAndClose(getItem()); - })); + }); + menu.getItems().setAll(items); } + /* + * "Copy file path" menu item should be enabled only if we can + * get some kind of file path or URI from the specified resource. + */ + Object path; + try { + path = URIDataStore.location(resource); + } catch (DataStoreException e) { + path = null; + Logging.unexpectedException(Logging.getLogger(Modules.APPLICATION), URIDataStore.class, "location", e); + } + menu.getItems().get(COPY_PATH).setDisable(!(IOUtilities.isKindOfPath(path) || path instanceof CharSequence)); } setContextMenu(menu); } } + + /** + * Position of menu items in the contextual menu built by {@link #updateItem(Resource, boolean)}. + * Above method assumes that {@link #CLOSE} is the last menu item. + */ + private static final int COPY_PATH = 0, CLOSE = 1; } /** diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java index fcbf302..a19e4d8 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/WindowManager.java @@ -106,8 +106,7 @@ abstract class WindowManager extends Widget { * @see #hasWindowsProperty */ public final MenuItem createNewWindowMenu() { - final MenuItem menu = new MenuItem(localized().getString(Resources.Keys.NewWindow)); - menu.setOnAction(this::newDataWindow); + final MenuItem menu = localized().menu(Resources.Keys.NewWindow, this::newDataWindow); menu.setDisable(true); newWindowMenus.add(menu); return menu; diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java index 1af6a55..4e4f41c 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapMenu.java @@ -159,8 +159,7 @@ public class MapMenu extends ContextMenu { ArgumentChecks.ensureNonNull("format", format); final MapCanvas.MenuHandler handler = startNewMenuItems(COPY); final Resources resources = Resources.forLocale(canvas.getLocale()); - final MenuItem coordinates = new MenuItem(resources.getString(Resources.Keys.CoordinatesOfHere)); - coordinates.setOnAction((event) -> { + final MenuItem coordinates = resources.menu(Resources.Keys.CopyCoordinates, (event) -> { try { final String text = format.formatCoordinates(handler.x, handler.y); final ClipboardContent content = new ClipboardContent(); @@ -170,9 +169,7 @@ public class MapMenu extends ContextMenu { ExceptionReporter.show(((MenuItem) event.getSource()).getText(), null, e); } }); - final Menu group = new Menu(resources.getString(Resources.Keys.Copy)); - group.getItems().setAll(coordinates); - getItems().add(group); + getItems().add(coordinates); } 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 16be644..4d4fb96 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 @@ -126,11 +126,6 @@ public final class Resources extends IndexedResourceBundle { public static final short Close = 10; /** - * Coordinates of here - */ - public static final short CoordinatesOfHere = 50; - - /** * Copy */ public static final short Copy = 11; @@ -141,6 +136,16 @@ public final class Resources extends IndexedResourceBundle { public static final short CopyAs = 12; /** + * Copy coordinates + */ + public static final short CopyCoordinates = 50; + + /** + * Copy file path + */ + public static final short CopyFilePath = 51; + + /** * Display start */ public static final short DisplayStart = 38; 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 f61ccc7..0c63818 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 @@ -34,9 +34,10 @@ CanNotRender = An error occurred while rendering the data. CanNotUseRefSys_1 = Can not use the \u201c{0}\u201d reference system. CenteredProjection = Centered projection Close = Close -CoordinatesOfHere = Coordinates of here Copy = Copy CopyAs = Copy as +CopyCoordinates = Copy coordinates +CopyFilePath = Copy file path DisplayedSize = Displayed size DisplayStart = Display start DoesNotCoverAOI = Does not cover the area of interest. 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 fdda04f..cade986 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 @@ -39,9 +39,10 @@ CanNotRender = Une erreur est survenue lors de l\u2019affichage des do CanNotUseRefSys_1 = Ne peut pas utiliser le syst\u00e8me de r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb. CenteredProjection = Projection centr\u00e9e Close = Fermer -CoordinatesOfHere = Coordonn\u00e9es d\u2019ici Copy = Copier CopyAs = Copier comme +CopyCoordinates = Copier les coordonn\u00e9es +CopyFilePath = Copier le chemin du fichier DisplayedSize = Taille affich\u00e9e DisplayStart = D\u00e9but de l\u2019affichage DoesNotCoverAOI = Ne couvre pas la r\u00e9gion d\u2019int\u00e9r\u00eat. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java index 069e503..19fc4d3 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/URIDataStore.java @@ -28,6 +28,7 @@ import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterNotFoundException; import org.apache.sis.parameter.ParameterBuilder; import org.apache.sis.storage.StorageConnector; +import org.apache.sis.storage.Resource; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.DataStoreProvider; import org.apache.sis.storage.DataStoreException; @@ -36,6 +37,8 @@ import org.apache.sis.storage.event.StoreEvent; import org.apache.sis.storage.event.StoreListener; import org.apache.sis.storage.event.WarningEvent; import org.apache.sis.internal.storage.io.IOUtilities; +import org.apache.sis.internal.system.Modules; +import org.apache.sis.util.logging.Logging; /** @@ -45,7 +48,7 @@ import org.apache.sis.internal.storage.io.IOUtilities; * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.1 * @since 0.8 * @module */ @@ -100,7 +103,6 @@ public abstract class URIDataStore extends DataStore implements StoreResource, R return new Path[] {path}; } - /** * Returns the parameters used to open this data store. * @@ -251,6 +253,44 @@ public abstract class URIDataStore extends DataStore implements StoreResource, R } /** + * Returns the location (path, URL, URI, <i>etc.</i>) of the given resource. + * The type of the returned object can be any of the types documented in {@link DataStoreProvider#LOCATION}. + * The main ones are {@link java.net.URI}, {@link java.nio.file.Path} and JDBC {@linkplain javax.sql.DataSource}. + * + * @param resource the resource for which to get the location, or {@code null}. + * @return location of the given resource, or {@code null} if none. + * @throws DataStoreException if an error on the file system prevent the creation of the path. + * + * @since 1.1 + */ + public static Object location(final Resource resource) throws DataStoreException { + if (resource instanceof DataStore) { + final Optional<ParameterValueGroup> p = ((DataStore) resource).getOpenParameters(); + if (p.isPresent()) try { + return p.get().parameter(DataStoreProvider.LOCATION).getValue(); + } catch (ParameterNotFoundException e) { + /* + * This exception should not happen often since the "location" parameter is recommended. + * Note that it does not mean the same thing than "parameter provided but value is null". + * In that later case we want to return the null value as specified in the parameters. + */ + Logging.recoverableException(Logging.getLogger(Modules.STORAGE), URIDataStore.class, "location", e); + } + } + /* + * This fallback should not happen with `URIDataStore` implementation because the "location" parameter + * is always present even if null. This fallback is for resources implementated by different classes. + */ + if (resource instanceof ResourceOnFileSystem) { + final Path[] paths = ((ResourceOnFileSystem) resource).getComponentFiles(); + if (paths != null && paths.length != 0) { + return paths[0]; // First path is presumed the main file. + } + } + return null; + } + + /** * Adds the filename (without extension) as the citation title if there is no title, or as the identifier otherwise. * This method should be invoked last, after {@code DataStore} implementation did its best effort for adding a title. * The intent is actually to provide an identifier, but since the title is mandatory in ISO 19115 metadata, providing 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 0a2acc9..7ce731b 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 @@ -59,7 +59,7 @@ import org.apache.sis.internal.storage.Resources; * * @author Martin Desruisseaux (Geomatys) * @author Johann Sorel (Geomatys) - * @version 0.8 + * @version 1.1 * @since 0.3 * @module */ @@ -71,6 +71,21 @@ public final class IOUtilities extends Static { } /** + * Returns {@code true} if the given object is a {@link Path}, {@link File}, {@link URL} or {@link URI}. + * Note that this method does not returns {@code true} for {@link CharSequence}; that type needs to be + * verified by the caller if desired. + * + * @param path the object to verify. + * @return whether the given object is of known type. + * + * @since 1.1 + */ + public static boolean isKindOfPath(final Object path) { + return (path instanceof URI) || (path instanceof URL) || // Test final classes first. + (path instanceof Path) || (path instanceof File); + } + + /** * Returns the filename from a {@link Path}, {@link File}, {@link URL}, {@link URI} or {@link CharSequence} * instance. If the given argument is specialized type like {@code Path} or {@code File}, then this method uses * dedicated API like {@link Path#getFileName()}. Otherwise this method gets a string representation of the path
