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 bfd2d3c80b379d890ec314f82b7ada7b0ab7cc97 Author: Martin Desruisseaux <[email protected]> AuthorDate: Tue Jan 28 13:04:33 2020 +0100 Give a way to export metadata information, through clipboard for now. --- application/sis-javafx/pom.xml | 9 ++ .../org/apache/sis/gui/metadata/MetadataTree.java | 154 +++++++++++++++++++-- .../org/apache/sis/internal/gui/DataFormats.java | 47 +++++++ .../org/apache/sis/internal/gui/Resources.java | 15 ++ .../apache/sis/internal/gui/Resources.properties | 3 + .../sis/internal/gui/Resources_fr.properties | 3 + 6 files changed, 220 insertions(+), 11 deletions(-) diff --git a/application/sis-javafx/pom.xml b/application/sis-javafx/pom.xml index f5765ef..21f99c7 100644 --- a/application/sis-javafx/pom.xml +++ b/application/sis-javafx/pom.xml @@ -142,6 +142,15 @@ <version>${project.version}</version> <scope>runtime</scope> </dependency> + <dependency> + <groupId>jakarta.xml.bind</groupId> + <artifactId>jakarta.xml.bind-api</artifactId> + </dependency> + <dependency> + <groupId>org.glassfish.jaxb</groupId> + <artifactId>jaxb-runtime</artifactId> + <scope>runtime</scope> + </dependency> <!-- Test dependencies --> <dependency> diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java index e923015..ccce919 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataTree.java @@ -20,6 +20,10 @@ import java.util.Locale; import java.util.List; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.io.StringWriter; +import javax.xml.bind.JAXBException; +import javax.xml.transform.stream.StreamResult; import javafx.beans.DefaultProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; @@ -27,10 +31,18 @@ import javafx.beans.property.ReadOnlyStringWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableView; import javafx.scene.control.TreeTableColumn; import javafx.scene.control.TreeTableColumn.CellDataFeatures; +import javafx.scene.control.TreeTableRow; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; import org.opengis.metadata.Metadata; import org.opengis.util.InternationalString; import org.opengis.util.ControlledVocabulary; @@ -39,9 +51,14 @@ import org.apache.sis.metadata.AbstractMetadata; import org.apache.sis.metadata.MetadataStandard; import org.apache.sis.metadata.ValueExistencePolicy; import org.apache.sis.referencing.IdentifiedObjects; +import org.apache.sis.internal.xml.LegacyNamespaces; +import org.apache.sis.internal.gui.ExceptionReporter; +import org.apache.sis.internal.gui.DataFormats; +import org.apache.sis.internal.gui.Resources; import org.apache.sis.util.collection.TreeTable; import org.apache.sis.util.collection.TableColumn; import org.apache.sis.util.iso.Types; +import org.apache.sis.xml.XML; /** @@ -63,8 +80,7 @@ import org.apache.sis.util.iso.Types; * For changing content, use the {@link #contentProperty} instead.</li> * </ul> * - * @todo Add contextual menu for saving or copying in clipboard the XML starting from the selected node. - * Add contextual menu for showing a node in the summary pane (we would store in memory the path, + * @todo Add contextual menu for showing a node in the summary pane (we would store in memory the path, * including sequence number for multi-values property, and apply it to all opened resources). * Add a panel for controlling the number/date/angle format pattern. * @@ -130,6 +146,11 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> { private final Locale dataLocale; /** + * The "copy" and "copy as" localized string, used for contextual menus. + */ + private final String copy, copyAs; + + /** * Creates a new initially empty metadata tree. */ public MetadataTree() { @@ -152,11 +173,16 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> { textLocale = Locale.getDefault(Locale.Category.DISPLAY); dataLocale = Locale.getDefault(Locale.Category.FORMAT); } + final Resources localized = Resources.forLocale(textLocale); + copy = localized.getString(Resources.Keys.Copy); + copyAs = localized.getString(Resources.Keys.CopyAs); + contentProperty = new ContentProperty(this); nameColumn = new TreeTableColumn<>(TableColumn.NAME .getHeader().toString(textLocale)); valueColumn = new TreeTableColumn<>(TableColumn.VALUE.getHeader().toString(textLocale)); nameColumn .setCellValueFactory(MetadataTree::getPropertyName); valueColumn.setCellValueFactory(MetadataTree::getPropertyValue); + setRowFactory(Row::new); setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY); getColumns().setAll(nameColumn, valueColumn); @@ -167,21 +193,24 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> { } /** + * Returns the given metadata as a tree table. + */ + private static TreeTable toTree(final Object metadata) { + if (metadata instanceof AbstractMetadata) { + return ((AbstractMetadata) metadata).asTreeTable(); + } else { + return MetadataStandard.ISO_19115.asTreeTable(metadata, null, ValueExistencePolicy.COMPACT); + } + } + + /** * Sets the metadata to show in this tree table. This method gets a {@link TreeTable} view * of the given metadata, then delegates to {@link #setContent(TreeTable)}. * * @param metadata the metadata to show in this tree table view, or {@code null} if none. */ public void setContent(final Metadata metadata) { - TreeTable content = null; - if (metadata != null) { - if (metadata instanceof AbstractMetadata) { - content = ((AbstractMetadata) metadata).asTreeTable(); - } else { - content = MetadataStandard.ISO_19115.asTreeTable(metadata, null, ValueExistencePolicy.COMPACT); - } - } - setContent(content); + setContent(metadata == null ? null : toTree(metadata)); } /** @@ -234,6 +263,109 @@ public class MetadataTree extends TreeTableView<TreeTable.Node> { } /** + * A row in a metadata tree view, used for adding contextual menu on a row-by-row basis. + */ + private static final class Row extends TreeTableRow<TreeTable.Node> implements EventHandler<ActionEvent> { + /** + * The context menu, to be added only if this row is non-empty. + */ + private final ContextMenu menu; + + /** + * The menu items for legacy or current XML formats. + */ + private final MenuItem iso1, iso2; + + /** + * The menu items for copying in XML formats, to be disabled if we can not do this export. + */ + private final Menu copyAs; + + /** + * Creates a new row for the given tree table. + */ + @SuppressWarnings("ThisEscapedInObjectConstruction") + Row(final TreeTableView<TreeTable.Node> view) { + final MetadataTree md = (MetadataTree) view; + final MenuItem copy; + copy = new MenuItem(md.copy); + iso1 = new MenuItem("XML — ISO 19139:2007"); + iso2 = new MenuItem("XML — ISO 19115-3:2016"); + copyAs = new Menu(md.copyAs, null, iso2, iso1); + menu = new ContextMenu(copy, copyAs); + iso1.setOnAction(this); + iso2.setOnAction(this); + copy.setOnAction(this); + } + + /** + * Invoked when a new row is selected. This method enable or disable the "copy as" menu + * depending on whether or not we can format XML document for currently selected row. + */ + @Override + protected void updateItem(final TreeTable.Node item, final boolean empty) { + super.updateItem(item, empty); + copyAs.setDisable(empty || getMetadata() == null); + setContextMenu(empty ? null : menu); + } + + /** + * If the currently selected row is a metadata object, returns that object. + * Otherwise returns {@code null}. + */ + private Object getMetadata() { + final TreeTable.Node node = getItem(); + if (node != null) { + final Object md = node.getUserObject(); + if (md != null && MetadataStandard.ISO_19115.isMetadata(md.getClass())) { + return md; + } + } + return null; + } + + /** + * Invoked when user requested to copy metadata. The requested format (ISO 19115 versus ISO 19139) + * will be determined by comparing the event source with {@link #iso1} and {@link #iso2} menu items. + */ + @Override + public void handle(final ActionEvent event) { + final ClipboardContent content = new ClipboardContent(); + final Object md = getMetadata(); + if (md == null) { + final TreeTable.Node node = getItem(); + if (node != null) { + final Object value = node.getValue(TableColumn.VALUE); + if (value != null) content.putString(value.toString()); + } + } else { + final Object source = event.getSource(); + if (source != iso1 && source != iso2) { + content.putString(toTree(md).toString()); + } else try { + if (source == iso2) { + final String xml = XML.marshal(md); + content.put(DataFormats.XML, xml); + content.putString(xml); + } else { + final StringWriter output = new StringWriter(); + XML.marshal(md, new StreamResult(output), + Collections.singletonMap(XML.METADATA_VERSION, LegacyNamespaces.VERSION_2007)); + final String xml = output.toString(); + content.put(DataFormats.ISO_19139, xml); + content.putString(xml); + } + } catch (JAXBException e) { + final Resources localized = Resources.forLocale(((MetadataTree) getTreeTableView()).textLocale); + ExceptionReporter.show(localized.getString(Resources.Keys.ErrorExportingData), + localized.getString(Resources.Keys.CanNotCreateXML), e); + } + } + Clipboard.getSystemClipboard().setContent(content); + } + } + + /** * A simple node encapsulating a {@link TreeTable.Node} in a view. * The list of children is fetched when first needed. */ diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/DataFormats.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/DataFormats.java new file mode 100644 index 0000000..f1bd133 --- /dev/null +++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/DataFormats.java @@ -0,0 +1,47 @@ +/* + * 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.internal.gui; + +import javafx.scene.input.DataFormat; +import org.apache.sis.internal.storage.xml.AbstractProvider; + + +/** + * A central place where to declare data formats used by SIS application. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.1 + * @since 1.1 + * @module + */ +public final class DataFormats { + /** + * The data format for generic XML. + */ + public static final DataFormat XML = new DataFormat(AbstractProvider.MIME_TYPE); + + /** + * The data format for legacy ISO 19139:2007. + */ + public static final DataFormat ISO_19139 = new DataFormat("application/vnd.iso.19139+xml"); + + /** + * Do not allow instantiation of this class. + */ + private DataFormats() { + } +} 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 c864ce9..f5e3199 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 @@ -81,6 +81,11 @@ public final class Resources extends IndexedResourceBundle { public static final short CanNotCreateCRS_1 = 35; /** + * Can not create XML document. + */ + public static final short CanNotCreateXML = 47; + + /** * Can not fetch tile ({0}, {1}). */ public static final short CanNotFetchTile_2 = 45; @@ -106,6 +111,11 @@ public final class Resources extends IndexedResourceBundle { public static final short Copy = 31; /** + * Copy as + */ + public static final short CopyAs = 46; + + /** * Creation date: */ public static final short CreationDate = 16; @@ -151,6 +161,11 @@ public final class Resources extends IndexedResourceBundle { public static final short ErrorDataAccess = 40; /** + * Error exporting data + */ + public static final short ErrorExportingData = 48; + + /** * Error opening file */ public static final short ErrorOpeningFile = 6; 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 db5d34f..8328640 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 @@ -26,9 +26,11 @@ CanNotFetchTile_2 = Can not fetch tile ({0}, {1}). CanNotReadFile_1 = Can not open \u201c{0}\u201d. CanNotClose_1 = Can not close \u201c{0}\u201d. Data may be lost. CanNotCreateCRS_1 = Can not create reference system \u201c{0}\u201d. +CanNotCreateXML = Can not create XML document. CellGeometry = Cell geometry: Close = Close Copy = Copy +CopyAs = Copy as CreationDate = Creation date: Credit = Credit: CRSs = Coordinate Reference Systems @@ -36,6 +38,7 @@ Data = Data Date = Date: Dimensions = Dimensions: Display = Display +ErrorExportingData = Error exporting data ErrorOpeningFile = Error opening file ErrorClosingFile = Error closing file ErrorCreatingCRS = Error creating reference system 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 2699e37..a23fb87 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 @@ -31,9 +31,11 @@ CanNotFetchTile_2 = Ne peut pas obtenir la tuile ({0}, {1}). CanNotReadFile_1 = Ne peut pas ouvrir \u00ab\u202f{0}\u202f\u00bb. CanNotClose_1 = Ne peut pas fermer \u00ab\u202f{0}\u202f\u00bb. Il pourrait y avoir une perte de donn\u00e9es. CanNotCreateCRS_1 = Ne peut pas cr\u00e9er le syst\u00e8me de r\u00e9f\u00e9rence \u00ab\u202f{0}\u202f\u00bb. +CanNotCreateXML = Ne peut pas cr\u00e9er le document XML. CellGeometry = G\u00e9om\u00e9trie des cellules\u00a0: Close = Fermer Copy = Copier +CopyAs = Copier comme CreationDate = Date de cr\u00e9ation\u00a0: Credit = Cr\u00e9dit\u00a0: CRSs = Syst\u00e8mes de r\u00e9f\u00e9rence des coordonn\u00e9es @@ -41,6 +43,7 @@ Data = Donn\u00e9es Date = Date\u00a0: Dimensions = Dimensions\u00a0: Display = Affichage +ErrorExportingData = Erreur \u00e0 l\u2019exportation de donn\u00e9es ErrorOpeningFile = Erreur \u00e0 l\u2019ouverture du fichier ErrorClosingFile = Erreur \u00e0 la fermeture du fichier ErrorCreatingCRS = Erreur \u00e0 la cr\u00e9ation du syst\u00e8me de r\u00e9f\u00e9rence
