/*
* Copyright (C) 2014 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.core.gpf.ui;
import com.bc.ceres.binding.dom.DomElement;
import com.bc.ceres.binding.dom.XppDomElement;
import com.thoughtworks.xstream.io.copy.HierarchicalStreamCopier;
import com.thoughtworks.xstream.io.xml.XppDomWriter;
import com.thoughtworks.xstream.io.xml.XppReader;
import com.thoughtworks.xstream.io.xml.xppdom.XppDom;
import org.apache.commons.lang.StringEscapeUtils;
import org.esa.snap.core.gpf.GPF;
import org.esa.snap.core.gpf.Operator;
import org.esa.snap.core.gpf.OperatorSpi;
import org.esa.snap.core.gpf.OperatorSpiRegistry;
import org.esa.snap.core.gpf.descriptor.OperatorDescriptor;
import org.esa.snap.core.util.Debug;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.ui.AbstractDialog;
import org.esa.snap.ui.AppContext;
import org.esa.snap.ui.ModalDialog;
import org.esa.snap.ui.UIUtils;
import org.openide.util.HelpCtx;
import org.xmlpull.mxp1.MXParser;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
/**
* WARNING: This class belongs to a preliminary API and may change in future releases.
* <p>
* Provides an operator menu with action for loading, saving and displaying the parameters of an operator
* in the file menu section and actions for help and about in the help menu section.
*
* @author Norman Fomferra
* @author Marco Zühlke
*/
public class OperatorMenu {
private final Component parentComponent;
private final OperatorParameterSupport parameterSupport;
private final OperatorDescriptor opDescriptor;
private final AppContext appContext;
private final String helpId;
private final Action loadParametersAction;
private final Action saveParametersAction;
private final Action displayParametersAction;
private final Action aboutAction;
private final String lastDirPreferenceKey;
/**
* @deprecated since BEAM 5, use {@link #OperatorMenu(Component, OperatorDescriptor, OperatorParameterSupport, AppContext, String)} instead
*/
@Deprecated
public OperatorMenu(Component parentComponent,
Class<? extends Operator> opType,
OperatorParameterSupport parameterSupport,
String helpId) {
this(parentComponent, getOperatorDescriptor(opType), parameterSupport, null, helpId);
}
public OperatorMenu(Component parentComponent,
OperatorDescriptor opDescriptor,
OperatorParameterSupport parameterSupport,
AppContext appContext,
String helpId) {
this.parentComponent = parentComponent;
this.parameterSupport = parameterSupport;
this.opDescriptor = opDescriptor;
this.appContext = appContext;
this.helpId = helpId;
lastDirPreferenceKey = opDescriptor.getName() + ".lastDir";
loadParametersAction = new LoadParametersAction();
saveParametersAction = new SaveParametersAction();
displayParametersAction = new DisplayParametersAction();
aboutAction = new AboutOperatorAction();
}
/**
* Creates the default menu.
*
* @return The menu
*/
public JMenuBar createDefaultMenu() {
JMenu fileMenu = new JMenu("File");
fileMenu.add(loadParametersAction);
fileMenu.add(saveParametersAction);
fileMenu.addSeparator();
fileMenu.add(displayParametersAction);
JMenu helpMenu = new JMenu("Help");
helpMenu.add(createHelpMenuItem());
helpMenu.add(aboutAction);
final JMenuBar menuBar = new JMenuBar();
menuBar.add(fileMenu);
menuBar.add(helpMenu);
return menuBar;
}
private JMenuItem createHelpMenuItem() {
JMenuItem menuItem = new JMenuItem("Help");
if (helpId != null && !helpId.isEmpty()) {
menuItem.addActionListener(e -> new HelpCtx(helpId).display());
} else {
menuItem.setEnabled(false);
}
return menuItem;
}
private class LoadParametersAction extends AbstractAction {
private static final String TITLE = "Load Parameters";
LoadParametersAction() {
super(TITLE + "...");
}
@Override
public void actionPerformed(ActionEvent event) {
JFileChooser fileChooser = new JFileChooser();
FileNameExtensionFilter parameterFileFilter = createParameterFileFilter();
fileChooser.addChoosableFileFilter(parameterFileFilter);
fileChooser.setFileFilter(parameterFileFilter);
fileChooser.setDialogTitle(TITLE);
fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
applyCurrentDirectory(fileChooser);
int response = fileChooser.showDialog(parentComponent, "Load");
if (JFileChooser.APPROVE_OPTION == response) {
try {
preserveCurrentDirectory(fileChooser);
readFromFile(fileChooser.getSelectedFile());
} catch (Exception e) {
Debug.trace(e);
AbstractDialog.showErrorDialog(parentComponent, "Could not load parameters.\n" + e.getMessage(), TITLE);
}
}
}
@Override
public boolean isEnabled() {
return super.isEnabled() && parameterSupport != null;
}
private void readFromFile(File selectedFile) throws Exception {
try (FileReader reader = new FileReader(selectedFile)) {
DomElement domElement = readXml(reader);
parameterSupport.fromDomElement(domElement);
}
}
private DomElement readXml(Reader reader) throws IOException {
try (BufferedReader br = new BufferedReader(reader)) {
StringBuilder sb = new StringBuilder();
String line = br.readLine();
while (line != null) {
sb.append(line);
line = br.readLine();
}
return new XppDomElement(createDom(sb.toString()));
}
}
}
static XppDom createDom(String xml) {
XppDomWriter domWriter = new XppDomWriter();
new HierarchicalStreamCopier().copy(new XppReader(new StringReader(xml), new MXParser()), domWriter);
return domWriter.getConfiguration();
}
private class SaveParametersAction extends AbstractAction {
private static final String TITLE = "Save Parameters";
SaveParametersAction() {
super(TITLE + "...");
}
@Override
public void actionPerformed(ActionEvent event) {
JFileChooser fileChooser = new JFileChooser();
final FileNameExtensionFilter parameterFileFilter = createParameterFileFilter();
fileChooser.addChoosableFileFilter(parameterFileFilter);
fileChooser.setFileFilter(parameterFileFilter);
fileChooser.setDialogTitle(TITLE);
fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
applyCurrentDirectory(fileChooser);
int response = fileChooser.showDialog(parentComponent, "Save");
if (JFileChooser.APPROVE_OPTION == response) {
try {
preserveCurrentDirectory(fileChooser);
File selectedFile = fileChooser.getSelectedFile();
selectedFile = FileUtils.ensureExtension(selectedFile,
"." + parameterFileFilter.getExtensions()[0]);
DomElement domElement = parameterSupport.toDomElement();
escapeXmlElements(domElement);
String xmlString = domElement.toXml();
writeToFile(xmlString, selectedFile);
} catch (Exception e) {
Debug.trace(e);
Dialogs.showError(TITLE, "Could not load parameters.\n" + e.getMessage());
}
}
}
@Override
public boolean isEnabled() {
return super.isEnabled() && parameterSupport != null;
}
private void writeToFile(String s, File outputFile) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile))) {
bw.write(s);
}
}
}
static void escapeXmlElements(DomElement domElement) {
domElement.setValue(StringEscapeUtils.escapeXml(domElement.getValue()));
String[] attributeNames = domElement.getAttributeNames();
for (String attributeName : attributeNames) {
domElement.setAttribute(attributeName, StringEscapeUtils.escapeXml(domElement.getAttribute(attributeName)));
}
DomElement[] children = domElement.getChildren();
for (DomElement child : children) {
escapeXmlElements(child);
}
}
private FileNameExtensionFilter createParameterFileFilter() {
return new FileNameExtensionFilter("GPF Parameter Files (XML)", "xml");
}
private class DisplayParametersAction extends AbstractAction {
private static final String TITLE = "Display Parameters";
DisplayParametersAction() {
super(TITLE + "...");
}
@Override
public void actionPerformed(ActionEvent event) {
String parameterXml;
try {
DomElement domElement = parameterSupport.toDomElement();
parameterXml = domElement.toXml();
} catch (Exception e) {
Debug.trace(e);
Dialogs.showError(TITLE, "Failed to convert parameters to XML." + e.getMessage());
return;
}
JTextArea textArea = new JTextArea(parameterXml);
textArea.setEditable(false);
JScrollPane textAreaScrollPane = new JScrollPane(textArea);
textAreaScrollPane.setPreferredSize(new Dimension(360, 360));
showInformationDialog(getOperatorName() + " Parameters", textAreaScrollPane);
}
@Override
public boolean isEnabled() {
return super.isEnabled() && parameterSupport != null;
}
}
private class AboutOperatorAction extends AbstractAction {
AboutOperatorAction() {
super("About...");
}
@Override
public void actionPerformed(ActionEvent event) {
showInformationDialog("About " + getOperatorName(), new JLabel(getOperatorAboutText()));
}
}
private void showInformationDialog(String title, Component component) {
final ModalDialog modalDialog = new ModalDialog(UIUtils.getRootWindow(parentComponent),
title,
AbstractDialog.ID_OK,
null); /*I18N*/
modalDialog.setContent(component);
modalDialog.show();
}
String getOperatorName() {
return opDescriptor.getAlias() != null ? opDescriptor.getAlias() : opDescriptor.getName();
}
String getOperatorAboutText() {
return makeHtmlConform(String.format("" +
"<html>" +
"<h2>%s Operator</h2>" +
"<table>" +
"<tr><td><b>Name:</b></td><td><code>%s</code></td></tr>" +
"<tr><td><b>Version:</b></td><td>%s</td></tr>" +
"<tr><td><b>Full name:</b></td><td><code>%s</code></td></tr>" +
"<tr><td><b>Description:</b></td><td>%s</td></tr>" +
"<tr><td><b>Authors:</b></td><td>%s</td></tr>" +
"<tr><td><b>Copyright:</b></td><td>%s</td></tr></table></html>",
getOperatorName(),
getOperatorName(),
opDescriptor.getVersion(),
opDescriptor.getName(),
opDescriptor.getDescription(),
opDescriptor.getAuthors(),
opDescriptor.getCopyright()
));
}
private static String makeHtmlConform(String text) {
return text.replace("\n", "<br/>");
}
private static OperatorDescriptor getOperatorDescriptor(Class<? extends Operator> opType) {
String operatorAlias = OperatorSpi.getOperatorAlias(opType);
OperatorDescriptor operatorDescriptor;
OperatorSpiRegistry spiRegistry = GPF.getDefaultInstance().getOperatorSpiRegistry();
operatorDescriptor = spiRegistry.getOperatorSpi(operatorAlias).getOperatorDescriptor();
if (operatorDescriptor == null) {
Class<?>[] declaredClasses = opType.getDeclaredClasses();
for (Class<?> declaredClass : declaredClasses) {
if (OperatorSpi.class.isAssignableFrom(declaredClass)) {
operatorDescriptor = spiRegistry.getOperatorSpi(declaredClass.getName()).getOperatorDescriptor();
}
}
}
if (operatorDescriptor == null) {
throw new IllegalStateException("Not able to find SPI for operator class '" + opType.getName() + "'");
}
return operatorDescriptor;
}
private void applyCurrentDirectory(JFileChooser fileChooser) {
if (appContext != null) {
String homeDirPath = SystemUtils.getUserHomeDir().getPath();
String lastDir = appContext.getPreferences().getPropertyString(lastDirPreferenceKey, homeDirPath);
fileChooser.setCurrentDirectory(new File(lastDir));
}
}
private void preserveCurrentDirectory(JFileChooser fileChooser) {
if (appContext != null) {
String lastDir = fileChooser.getCurrentDirectory().getAbsolutePath();
appContext.getPreferences().setPropertyString(lastDirPreferenceKey, lastDir);
}
}
}