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

Reply via email to