/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.snap.rcp.actions.file.export;
import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.swing.progress.DialogProgressMonitor;
import org.esa.snap.core.datamodel.MetadataAttribute;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.metadata.MetadataViewTopComponent;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.ui.SelectExportMethodDialog;
import org.esa.snap.ui.UIUtils;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.util.ContextAwareAction;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import java.awt.Dialog;
import java.awt.event.ActionEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
@ActionID(
category = "File",
id = "org.esa.snap.rcp.actions.file.export.ExportMetadataAction"
)
@ActionRegistration(
displayName = "#CTL_ ExportMetadataAction_MenuText",
popupText = "#CTL_ ExportMetadataAction_PopupText",
lazy = false
)
@ActionReference(path = "Menu/File/Export/Other", position = 60)
@NbBundle.Messages({
"CTL_ExportMetadataAction_MenuText=Product Metadata",
"CTL_ExportMetadataAction_PopupText=Export Product Metadata...",
"CTL_ExportMetadataAction_HelpID=exportMetadata",
"CTL_ExportMetadataAction_ShortDescription=Export the currently displayed metadata as tab-separated text."
})
public class ExportMetadataAction extends AbstractAction implements HelpCtx.Provider, LookupListener, ContextAwareAction {
private static final String ERR_MSG_BASE = "Metadata could not be exported:\n";
private static final String DLG_TITLE = "Export Product Metadata";
private final Lookup.Result<MetadataElement> result;
private final Lookup lookup;
private MetadataElement productMetadata;
public ExportMetadataAction() {
this(Utilities.actionsGlobalContext());
}
public ExportMetadataAction(Lookup lookup) {
super(Bundle.CTL_ExportMetadataAction_MenuText());
this.lookup = lookup;
result = lookup.lookupResult(MetadataElement.class);
result.addLookupListener(WeakListeners.create(LookupListener.class, this, result));
setEnabled(false);
}
private static String getWindowTitle() {
return SnapApp.getDefault().getInstanceName() + " - " + DLG_TITLE;
}
/**
* Opens a modal file chooser dialog that prompts the user to select the output file name.
*
* @param defaultFileName The default file name.
* @return The selected file, {@code null} means "Cancel".
*/
private static File promptForFile(String defaultFileName) {
// Loop while the user does not want to overwrite a selected, existing file
// or if the user presses "Cancel"
File file = null;
while (file == null) {
file = Dialogs.requestFileForSave(DLG_TITLE,
false,
null,
".txt",
defaultFileName, null,
"exportMetadata.lastDir");
if (file == null) {
return null; // Cancel
} else if (file.exists()) {
int status = JOptionPane.showConfirmDialog(SnapApp.getDefault().getMainFrame(),
"The file '" + file + "' already exists.\n" + /*I18N*/
"Overwrite it?",
MessageFormat.format("{0} - {1}", SnapApp.getDefault().getInstanceName(), DLG_TITLE),
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE);
if (status == JOptionPane.CANCEL_OPTION) {
return null; // Cancel
} else if (status == JOptionPane.NO_OPTION) {
file = null; // No, do not overwrite, let user select other file
}
}
}
return file;
}
/**
* Invoked when a command action is performed.
*
* @param event the command event
*/
@Override
public void actionPerformed(ActionEvent event) {
exportMetadata();
}
@Override
public HelpCtx getHelpCtx() {
return new HelpCtx(Bundle.CTL_ExportMetadataAction_MenuText());
}
/////////////////////////////////////////////////////////////////////////
// Private implementations for the "Export Metadata" command
/////////////////////////////////////////////////////////////////////////
@Override
public Action createContextAwareInstance(Lookup actionContext) {
return new ExportMetadataAction(actionContext);
}
@Override
public void resultChanged(LookupEvent lookupEvent) {
MetadataElement metadataElement = lookup.lookup(MetadataElement.class);
setEnabled(metadataElement != null);
}
private void exportMetadata() {
productMetadata = lookup.lookup(MetadataElement.class);
final String msgText = "How do you want to export the metadata?\n" +
productMetadata.getName() + "Element will be exported.\n";
final int method = SelectExportMethodDialog.run(SnapApp.getDefault().getMainFrame(), getWindowTitle(),
msgText, getHelpCtx().getHelpID());
final PrintWriter out;
final StringBuffer clipboardText;
final int initialBufferSize = 256000;
if (method == SelectExportMethodDialog.EXPORT_TO_CLIPBOARD) {
// Write into string buffer
final StringWriter stringWriter = new StringWriter(initialBufferSize);
out = new PrintWriter(stringWriter);
clipboardText = stringWriter.getBuffer();
} else if (method == SelectExportMethodDialog.EXPORT_TO_FILE) {
// Write into file, get file from user
MetadataViewTopComponent metadataViewTopComponent = new MetadataViewTopComponent(productMetadata);
final File file = promptForFile(createDefaultFileName(metadataViewTopComponent));
if (file == null) {
return; // Cancel
}
final FileWriter fileWriter;
try {
fileWriter = new FileWriter(file);
} catch (IOException e) {
Dialogs.showError(DLG_TITLE,
ERR_MSG_BASE + "Failed to create file '" + file + "':\n" + e.getMessage());
return; // Error
}
out = new PrintWriter(new BufferedWriter(fileWriter, initialBufferSize));
clipboardText = null;
} else {
return; // Cancel
}
// Create a progress monitor and adds them to the progress controller pool in order to show export progress
// Create a new swing worker instance (as instance of an anonymous class).
// When the swing worker's start() method is called, a new separate thread is started.
// The swing worker's construct() method is executed in that thread, so that
// VISAT can keep on handling user events.
//
final SwingWorker swingWorker = new SwingWorker<Exception, Object>() {
@Override
protected Exception doInBackground() throws Exception {
boolean success;
Exception returnValue = null;
try {
ProgressMonitor pm = new DialogProgressMonitor(SnapApp.getDefault().getMainFrame(), DLG_TITLE,
Dialog.ModalityType.APPLICATION_MODAL);
final MetadataExporter exporter = new MetadataExporter(productMetadata);
success = exporter.exportMetadata(out, pm);
if (success && clipboardText != null) {
SystemUtils.copyToClipboard(clipboardText.toString());
clipboardText.setLength(0);
}
} catch (Exception e) {
returnValue = e;
} finally {
out.close();
}
return returnValue;
}
/**
* Called on the event dispatching thread (not on the worker thread) after the {@code construct} method
* has returned.
*/
@Override
public void done() {
// clear status bar
SnapApp.getDefault().setStatusBarMessage("");
// show default-cursor
UIUtils.setRootFrameDefaultCursor(SnapApp.getDefault().getMainFrame());
// On error, show error message
Exception exception;
try {
exception = get();
} catch (Exception e) {
exception = e;
}
if (exception != null) {
Dialogs.showError(DLG_TITLE, ERR_MSG_BASE + exception.getMessage());
}
}
};
// show wait-cursor
// show message in status bar
SnapApp.getDefault().setStatusBarMessage("Exporting Product Metadata..."); /*I18N*/
// Start separate worker thread.
swingWorker.execute();
}
private String createDefaultFileName(MetadataViewTopComponent productMetadataView) {
return FileUtils.getFilenameWithoutExtension(productMetadataView.getDocument().getProduct().getName()) + "_" +
productMetadata.getName() +
".txt";
}
private static class MetadataExporter {
private final MetadataElement rootElement;
private MetadataExporter(MetadataElement rootElement) {
this.rootElement = rootElement;
}
public boolean exportMetadata(final PrintWriter out, ProgressMonitor pm) {
pm.beginTask("Export Metadata", 1);
try {
writeHeaderLine(out);
writeAttributes(out, rootElement);
pm.worked(1);
} finally {
pm.done();
}
return true;
}
private void writeHeaderLine(final PrintWriter out) {
out.print("Value\t");
out.print("Type\t");
out.print("Unit\t");
out.print("Description\t\n");
}
private void writeAttributes(PrintWriter out, MetadataElement element) {
final MetadataAttribute[] attributes = element.getAttributes();
for (MetadataAttribute attribute : attributes) {
out.print(createAttributeName(attribute) + "\t");
out.print(attribute.getData().getElemString() + "\t");
out.print(attribute.getUnit() + "\t");
out.print(attribute.getDescription() + "\t\n");
}
final MetadataElement[] subElements = element.getElements();
for (MetadataElement subElement : subElements) {
writeAttributes(out, subElement);
}
}
private String createAttributeName(MetadataAttribute attribute) {
StringBuilder sb = new StringBuilder();
MetadataElement metadataElement = attribute.getParentElement();
if (metadataElement != null) {
prependParentName(metadataElement, sb);
}
sb.append(attribute.getName());
return sb.toString();
}
private void prependParentName(MetadataElement element, StringBuilder sb) {
final MetadataElement owner = element.getParentElement();
if (owner != null) {
if (owner != rootElement) {
prependParentName(owner, sb);
} else if (owner.getName() != null) {
sb.insert(0, owner.getName()).append(".");
}
}
if (element.getName() != null) {
sb.append(element.getName()).append(".");
}
}
}
}