package org.esa.snap.rcp.actions.file;
import com.bc.ceres.swing.TableLayout;
import org.esa.snap.core.dataio.DecodeQualification;
import org.esa.snap.core.dataio.ProductIOPlugInManager;
import org.esa.snap.core.dataio.ProductReaderPlugIn;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.SnapFileFilter;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.util.Dialogs;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.RequestProcessor;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import java.awt.Component;
import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.prefs.Preferences;
/**
* @author Marco Peters
*/
public class ProductOpener {
public static final String PREFERENCES_KEY_LAST_PRODUCT_DIR = "last_product_open_dir";
private static final String PREFERENCES_KEY_PREFIX_ALTERNATIVE_READER = "open_alternative_reader.";
private static final String PREFERENCES_KEY_DONT_SHOW_DIALOG = "multipleReadersDialog.dontShow";
private static final int IMMEDIATELY = 0;
private String fileFormat;
private boolean useAllFileFilter;
private boolean subsetImportEnabled;
private File[] files;
private boolean multiSelectionEnabled;
public void setFiles(File... files) {
this.files = files;
}
public File[] getFiles() {
return files;
}
void setFileFormat(String format) {
fileFormat = format;
}
public String getFileFormat() {
return fileFormat;
}
void setUseAllFileFilter(boolean useAllFileFilter) {
this.useAllFileFilter = useAllFileFilter;
}
public boolean isUseAllFileFilter() {
return useAllFileFilter;
}
void setSubsetImportEnabled(boolean subsetImportEnabled) {
this.subsetImportEnabled = subsetImportEnabled;
}
public boolean isSubsetImportEnabled() {
return subsetImportEnabled;
}
public void setMultiSelectionEnabled(boolean multiSelectionEnabled) {
this.multiSelectionEnabled = multiSelectionEnabled;
}
public boolean isMultiSelectionEnabled() {
return multiSelectionEnabled;
}
public Boolean openProduct() {
File[] configuredFiles = getFiles();
if (configuredFiles != null) {
return openProductFilesCheckOpened(getFileFormat(), configuredFiles);
}
Iterator<ProductReaderPlugIn> readerPlugIns;
if (getFileFormat() != null) {
readerPlugIns = ProductIOPlugInManager.getInstance().getReaderPlugIns(getFileFormat());
if (!readerPlugIns.hasNext()) {
Dialogs.showError(
Bundle.LBL_NoReaderFoundText() + String.format("%nCan't find reader for the given format '%s'.", getFileFormat()));
return false;
}
} else {
readerPlugIns = ProductIOPlugInManager.getInstance().getAllReaderPlugIns();
}
List<SnapFileFilter> filters = new ArrayList<>();
while (readerPlugIns.hasNext()) {
ProductReaderPlugIn readerPlugIn = readerPlugIns.next();
SnapFileFilter snapFileFilter = readerPlugIn.getProductFileFilter();
if (snapFileFilter != null) {
filters.add(snapFileFilter);
}
}
Collections.sort(filters, (f1, f2) -> {
String d1 = f1.getDescription();
String d2 = f2.getDescription();
return d1 != null ? d1.compareTo(d2) : d2 == null ? 0 : 1;
});
if (filters.isEmpty()) {
Dialogs.showError(Bundle.LBL_NoReaderFoundText());
return false;
}
Preferences preferences = SnapApp.getDefault().getPreferences();
String userHomePath = SystemUtils.getUserHomeDir().getAbsolutePath();
ProductFileChooser fc = new ProductFileChooser(new File(preferences.get(PREFERENCES_KEY_LAST_PRODUCT_DIR, userHomePath)));
fc.setSubsetEnabled(isSubsetImportEnabled());
fc.setDialogTitle(SnapApp.getDefault().getInstanceName() + " - " + Bundle.CTL_OpenProductActionName());
fc.setAcceptAllFileFilterUsed(isUseAllFileFilter());
filters.forEach((filter) -> {
fc.addChoosableFileFilter(filter);
if (getFileFormat() != null && getFileFormat().equals(filter.getFormatName())) {
fc.setFileFilter(filter);
}
});
fc.setMultiSelectionEnabled(isMultiSelectionEnabled());
fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
int returnVal = fc.showOpenDialog(SnapApp.getDefault().getMainFrame());
if (returnVal != JFileChooser.APPROVE_OPTION) {
// cancelled
return null;
}
File[] files = getSelectedFiles(fc);
if (files == null || files.length == 0) {
// cancelled
return null;
}
File currentDirectory = fc.getCurrentDirectory();
if (currentDirectory != null) {
preferences.put(PREFERENCES_KEY_LAST_PRODUCT_DIR, currentDirectory.toString());
}
if (fc.getSubsetProduct() != null) {
SnapApp.getDefault().getProductManager().addProduct(fc.getSubsetProduct());
return true;
}
String formatName = (fc.getFileFilter() instanceof SnapFileFilter)
? ((SnapFileFilter) fc.getFileFilter()).getFormatName()
: null;
return openProductFilesCheckOpened(formatName, files);
}
private File[] getSelectedFiles(ProductFileChooser fc) {
File[] files = new File[0];
if (isMultiSelectionEnabled()) {
files = fc.getSelectedFiles();
} else {
File file = fc.getSelectedFile();
if (file != null) {
files = new File[]{file};
}
}
return files;
}
private static Boolean openProductFilesCheckOpened(final String formatName, final File... files) {
List<File> openedFiles = OpenProductAction.getOpenedProductFiles();
List<File> fileList = new ArrayList<>(Arrays.asList(files));
for (File file : files) {
if (openedFiles.contains(file)) {
Dialogs.Answer answer = Dialogs.requestDecision(Bundle.CTL_OpenProductActionName(),
MessageFormat.format("Product\n" +
"{0}\n" +
"is already opened.\n" +
"Do you want to open another instance?", file),
true, null);
if (answer == Dialogs.Answer.NO) {
fileList.remove(file);
} else if (answer == Dialogs.Answer.CANCELLED) {
return null;
}
}
}
RequestProcessor rp = new RequestProcessor("Opening Products", 4, true, true);
for (File file : fileList) {
String fileFormatName;
if (formatName == null) {
final List<PluginEntry> intendedPlugIns = getPluginsForFile(file, DecodeQualification.INTENDED);
List<PluginEntry> suitablePlugIns = new ArrayList<>();
if (intendedPlugIns.size() == 0) { // check for suitable readers only if no intended reader was found
suitablePlugIns.addAll(getPluginsForFile(file, DecodeQualification.SUITABLE));
}
if (intendedPlugIns.isEmpty() && suitablePlugIns.isEmpty()) {
Dialogs.showError(Bundle.LBL_NoReaderFoundText() + String.format("%nFile '%s' can not be opened.", file));
continue;
} else if (intendedPlugIns.size() == 1) {
PluginEntry entry = intendedPlugIns.get(0);
fileFormatName = entry.plugin.getFormatNames()[0];
} else if (intendedPlugIns.size() == 0 && suitablePlugIns.size() == 1) {
PluginEntry entry = suitablePlugIns.get(0);
fileFormatName = entry.plugin.getFormatNames()[0];
} else {
Collections.sort(intendedPlugIns);
Collections.sort(suitablePlugIns);
fileFormatName = getUserSelection(intendedPlugIns, suitablePlugIns);
if (fileFormatName == null) { // User clicked cancel
return null;
}
}
} else {
fileFormatName = formatName;
}
ReadProductOperation operation = new ReadProductOperation(file, fileFormatName);
RequestProcessor.Task task = rp.create(operation);
// TODO (mp/20160830) - Cancellation is not working; the thread is not interrupted. Why?
ProgressHandle handle = ProgressHandleFactory.createHandle("Reading " + file.getName()/*, operation.createCancellable(task)*/);
operation.attacheProgressHandle(handle);
task.schedule(IMMEDIATELY);
}
return true;
}
private static List<PluginEntry> getPluginsForFile(File file, DecodeQualification desiredQualification) {
final Iterator<ProductReaderPlugIn> allReaderPlugIns = ProductIOPlugInManager.getInstance().getAllReaderPlugIns();
final List<PluginEntry> possiblePlugIns = new ArrayList<>();
allReaderPlugIns.forEachRemaining(plugIn -> {
final DecodeQualification qualification = plugIn.getDecodeQualification(file);
if (qualification == desiredQualification) {
possiblePlugIns.add(new PluginEntry(plugIn, qualification));
}
});
return possiblePlugIns;
}
private static String getUserSelection(List<PluginEntry> intendedPlugins, List<PluginEntry> suitablePlugIns) {
final PluginEntry leadPlugin;
if (!intendedPlugins.isEmpty()) {
leadPlugin = intendedPlugins.get(0);
} else {
leadPlugin = suitablePlugIns.get(0);
}
final boolean dontShowDialog = SnapApp.getDefault().getPreferences().getBoolean(PREFERENCES_KEY_DONT_SHOW_DIALOG, false);
String prefKeyFormat = PREFERENCES_KEY_PREFIX_ALTERNATIVE_READER + leadPlugin.plugin.getClass().getSimpleName();
final String storedSelection = SnapApp.getDefault().getPreferences().get(prefKeyFormat, null);
if (dontShowDialog && storedSelection != null) {
return storedSelection;
}
final TableLayout layout = new TableLayout(1);
layout.setTableAnchor(TableLayout.Anchor.WEST);
layout.setTableFill(TableLayout.Fill.HORIZONTAL);
layout.setTablePadding(4, 4);
final JPanel readerSelectionPanel = new JPanel(layout);
readerSelectionPanel.add(new JLabel("<html>Multiple readers are available for the selected file.<br>" +
"The readers might interpret the data differently.<br>" +
"Please select one of the following:"));
final JComboBox<ProductReaderPlugIn> pluginsCombobox = new JComboBox<>();
DefaultListCellRenderer cellRenderer = new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
setText(((ProductReaderPlugIn) value).getDescription(Locale.getDefault()));
return this;
}
};
pluginsCombobox.setRenderer(cellRenderer);
for (PluginEntry plugin : intendedPlugins) {
pluginsCombobox.addItem(plugin.plugin);
}
for (PluginEntry plugin : suitablePlugIns) {
pluginsCombobox.addItem(plugin.plugin);
}
readerSelectionPanel.add(pluginsCombobox);
JCheckBox decisionCheckBox = new JCheckBox("Remember my decision and don't ask again.", false);
decisionCheckBox.setHorizontalAlignment(SwingConstants.RIGHT);
readerSelectionPanel.add(decisionCheckBox);
NotifyDescriptor d = new NotifyDescriptor(readerSelectionPanel,
Dialogs.getDialogTitle("Multiple Readers Available"),
NotifyDescriptor.OK_CANCEL_OPTION, NotifyDescriptor.QUESTION_MESSAGE, null,
NotifyDescriptor.OK_OPTION);
Object answer = DialogDisplayer.getDefault().notify(d);
if (NotifyDescriptor.OK_OPTION.equals(answer)) {
boolean storeResult = decisionCheckBox.isSelected();
String selectedFormatName = ((ProductReaderPlugIn) pluginsCombobox.getSelectedItem()).getFormatNames()[0];
if (storeResult) {
SnapApp.getDefault().getPreferences().put(prefKeyFormat, selectedFormatName);
SnapApp.getDefault().getPreferences().put(PREFERENCES_KEY_DONT_SHOW_DIALOG, "true");
}
return selectedFormatName;
}
return null;
}
private static class PluginEntry implements Comparable<PluginEntry> {
ProductReaderPlugIn plugin;
DecodeQualification qualification;
public PluginEntry(ProductReaderPlugIn plugin, DecodeQualification qualification) {
this.plugin = plugin;
this.qualification = qualification;
}
@Override
public int compareTo(PluginEntry other) {
final int qualificationComparison = this.qualification.compareTo(other.qualification);
if (qualificationComparison == 0) {
final String description1 = this.plugin.getDescription(Locale.getDefault());
final String description2 = other.plugin.getDescription(Locale.getDefault());
return description1.compareTo(description2);
} else {
return qualificationComparison;
}
}
}
}