/* * Copyright (C) 2011 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.core.ProgressMonitor; import com.bc.ceres.core.SubProgressMonitor; import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker; import org.esa.snap.core.dataio.ProductIO; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.gpf.GPF; import org.esa.snap.core.gpf.Operator; import org.esa.snap.core.gpf.OperatorCancelException; import org.esa.snap.core.gpf.OperatorException; import org.esa.snap.core.gpf.common.WriteOp; import org.esa.snap.core.gpf.internal.OperatorExecutor; import org.esa.snap.core.gpf.internal.OperatorProductReader; 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.actions.file.SaveProductAsAction; import org.esa.snap.rcp.util.Dialogs; import org.esa.snap.ui.AppContext; import org.esa.snap.ui.ModelessDialog; import javax.swing.AbstractButton; import javax.swing.JOptionPane; import java.awt.Toolkit; import java.io.File; import java.text.MessageFormat; import java.util.concurrent.ExecutionException; import java.util.prefs.Preferences; /** * WARNING: This class belongs to a preliminary API and may change in future releases. * * @author Norman Fomferra * @author Marco Peters */ public abstract class SingleTargetProductDialog extends ModelessDialog { protected TargetProductSelector targetProductSelector; protected AppContext appContext; private long createTargetProductTime; protected SingleTargetProductDialog(AppContext appContext, String title, String helpID) { this(appContext, title, ID_APPLY_CLOSE_HELP, helpID); } protected SingleTargetProductDialog(AppContext appContext, String title, int buttonMask, String helpID) { this(appContext, title, buttonMask, helpID, new TargetProductSelectorModel()); } protected SingleTargetProductDialog(AppContext appContext, String title, int buttonMask, String helpID, TargetProductSelectorModel model) { this(appContext, title, buttonMask, helpID, model, false); } protected SingleTargetProductDialog(AppContext appContext, String title, int buttonMask, String helpID, TargetProductSelectorModel model, boolean alwaysWriteOutput) { super(appContext.getApplicationWindow(), title, buttonMask, helpID); this.appContext = appContext; targetProductSelector = new TargetProductSelector(model, alwaysWriteOutput); String homeDirPath = SystemUtils.getUserHomeDir().getPath(); String saveDir = appContext.getPreferences().getPropertyString(SaveProductAsAction.PREFERENCES_KEY_LAST_PRODUCT_DIR, homeDirPath); targetProductSelector.getModel().setProductDir(new File(saveDir)); if (!alwaysWriteOutput) { targetProductSelector.getOpenInAppCheckBox().setText("Open in " + appContext.getApplicationName()); } targetProductSelector.getModel().getValueContainer().addPropertyChangeListener(evt -> { if (evt.getPropertyName().equals("saveToFileSelected") || evt.getPropertyName().equals("openInAppSelected")) { updateRunButton(); } }); AbstractButton button = getButton(ID_APPLY); button.setText("Run"); button.setMnemonic('R'); updateRunButton(); } private void updateRunButton() { AbstractButton button = getButton(ID_APPLY); boolean save = targetProductSelector.getModel().isSaveToFileSelected(); boolean open = targetProductSelector.getModel().isOpenInAppSelected(); String toolTipText = ""; boolean enabled = true; if (save && open) { toolTipText = "Save target product and open it in " + getAppContext().getApplicationName(); } else if (save) { toolTipText = "Save target product"; } else if (open) { toolTipText = "Open target product in " + getAppContext().getApplicationName(); } else { enabled = false; } button.setToolTipText(toolTipText); button.setEnabled(enabled); } public AppContext getAppContext() { return appContext; } public TargetProductSelector getTargetProductSelector() { return targetProductSelector; } @Override protected void onApply() { if (!canApply()) { return; } String productDir = targetProductSelector.getModel().getProductDir().getAbsolutePath(); appContext.getPreferences().setPropertyString(SaveProductAsAction.PREFERENCES_KEY_LAST_PRODUCT_DIR, productDir); Product targetProduct = null; try { long t0 = System.currentTimeMillis(); targetProduct = createTargetProduct(); createTargetProductTime = System.currentTimeMillis() - t0; if (targetProduct == null) { throw new NullPointerException("Target product is null."); } } catch (Throwable t) { handleInitialisationError(t); } if (targetProduct == null) { return; } targetProduct.setName(targetProductSelector.getModel().getProductName()); if (targetProductSelector.getModel().isSaveToFileSelected()) { targetProduct.setFileLocation(targetProductSelector.getModel().getProductFile()); final ProgressMonitorSwingWorker worker = new ProductWriterSwingWorker(targetProduct); worker.executeWithBlocking(); } else if (targetProductSelector.getModel().isOpenInAppSelected()) { appContext.getProductManager().addProduct(targetProduct); showOpenInAppInfo(); } } protected void handleInitialisationError(Throwable t) { String msg; if (t instanceof OperatorCancelException) { msg = MessageFormat.format("An internal error occurred during the target product initialisation.\n{0}", formatThrowable(t)); showErrorDialog(msg); return; } if (isInternalException(t)) { msg = MessageFormat.format("An internal error occurred during the target product initialisation.\n{0}", formatThrowable(t)); } else { msg = MessageFormat.format("A problem occurred during the target product initialisation.\n{0}", formatThrowable(t)); } appContext.handleError(msg, t); } protected void handleProcessingError(Throwable t) { String msg; if (t instanceof OperatorCancelException) { return; } if (isInternalException(t)) { msg = MessageFormat.format("An internal error occurred during the target product processing.\n{0}", formatThrowable(t)); } else { msg = MessageFormat.format("A problem occurred during processing the target product processing.\n{0}", formatThrowable(t)); } appContext.handleError(msg, t); } private boolean isInternalException(Throwable t) { return (t instanceof RuntimeException && !(t instanceof OperatorException)) || t instanceof Error; } private String formatThrowable(Throwable t) { return MessageFormat.format("Type: {0}\nMessage: {1}\n", t.getClass().getSimpleName(), t.getMessage()); } protected boolean canApply() { final String productName = targetProductSelector.getModel().getProductName(); if (productName == null || productName.isEmpty()) { showErrorDialog("Please specify a target product name."); targetProductSelector.getProductNameTextField().requestFocus(); return false; } if (targetProductSelector.getModel().isOpenInAppSelected()) { final Product existingProduct = appContext.getProductManager().getProduct(productName); if (existingProduct != null) { String message = MessageFormat.format( "<html>A product with the name ''{0}'' is already opened in {1}.<br><br>" + "Do you want to continue?", productName, appContext.getApplicationName() ); final int answer = JOptionPane.showConfirmDialog(getJDialog(), message, getTitle(), JOptionPane.YES_NO_OPTION); if (answer != JOptionPane.YES_OPTION) { return false; } } } if (targetProductSelector.getModel().isSaveToFileSelected()) { File productFile = targetProductSelector.getModel().getProductFile(); if (productFile.exists()) { String message = MessageFormat.format( "<html>The specified output file<br>\"{0}\"<br> already exists.<br><br>" + "Do you want to overwrite the existing file?", productFile.getPath() ); final int answer = JOptionPane.showConfirmDialog(getJDialog(), message, getTitle(), JOptionPane.YES_NO_OPTION); if (answer != JOptionPane.YES_OPTION) { return false; } } } return true; } private void showSaveInfo(long saveTime) { File productFile = getTargetProductSelector().getModel().getProductFile(); final String message = MessageFormat.format( "<html>The target product has been successfully written to<br>{0}<br>" + "Total time spend for processing: {1}", formatFile(productFile), formatDuration(saveTime) ); showSuppressibleInformationDialog(message, "saveInfo"); } protected void showOpenInAppInfo() { final String message = MessageFormat.format( "<html>The target product has successfully been created and opened in {0}.<br><br>" + "Actual processing of source to target data will be performed only on demand,<br>" + "for example, if the target product is saved or an image view is opened.", appContext.getApplicationName() ); showSuppressibleInformationDialog(message, "openInAppInfo"); } private void showSaveAndOpenInAppInfo(long saveTime) { File productFile = getTargetProductSelector().getModel().getProductFile(); final String message = MessageFormat.format( "<html>The target product has been successfully written to<br>" + "<p>{0}</p><br>" + "and has been opened in {1}.<br><br>" + "Total time spend for processing: {2}<br>", formatFile(productFile), appContext.getApplicationName(), formatDuration(saveTime) ); showSuppressibleInformationDialog(message, "saveAndOpenInAppInfo"); } String formatFile(File file) { return FileUtils.getDisplayText(file, 54); } String formatDuration(long millis) { long seconds = millis / 1000; millis -= seconds * 1000; long minutes = seconds / 60; seconds -= minutes * 60; long hours = minutes / 60; minutes -= hours * 60; return String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, millis); } /** * Shows an information dialog on top of this dialog. * The shown dialog will contain a user option allowing to not show the message anymore. * * @param infoMessage The message. * @param propertyName The (simple) property name used to store the user option in the application's preferences. */ public void showSuppressibleInformationDialog(String infoMessage, String propertyName) { Dialogs.showInformation(getTitle(), infoMessage, getQualifiedPropertyName(propertyName)); } private class ProductWriterSwingWorker extends ProgressMonitorSwingWorker<Product, Object> { private final Product targetProduct; private long saveTime; private ProductWriterSwingWorker(Product targetProduct) { super(getJDialog(), "Writing Target Product"); this.targetProduct = targetProduct; } @Override protected Product doInBackground(ProgressMonitor pm) throws Exception { final TargetProductSelectorModel model = getTargetProductSelector().getModel(); pm.beginTask("Writing...", model.isOpenInAppSelected() ? 100 : 95); saveTime = 0L; Product product = null; try { long t0 = System.currentTimeMillis(); Operator execOp = null; if (targetProduct.getProductReader() instanceof OperatorProductReader) { final OperatorProductReader opReader = (OperatorProductReader) targetProduct.getProductReader(); Operator operator = opReader.getOperatorContext().getOperator(); if (operator.getSpi().getOperatorDescriptor().isAutoWriteDisabled()) { execOp = operator; } } if (execOp == null) { WriteOp writeOp = new WriteOp(targetProduct, model.getProductFile(), model.getFormatName()); writeOp.setDeleteOutputOnFailure(true); writeOp.setWriteEntireTileRows(true); writeOp.setClearCacheAfterRowWrite(false); execOp = writeOp; } final OperatorExecutor executor = OperatorExecutor.create(execOp); executor.execute(SubProgressMonitor.create(pm, 95)); saveTime = System.currentTimeMillis() - t0; if (model.isOpenInAppSelected()) { File targetFile = model.getProductFile(); if (!targetFile.exists()) targetFile = targetProduct.getFileLocation(); if (targetFile.exists()) { product = ProductIO.readProduct(targetFile); if (product == null) { product = targetProduct; // todo - check - this cannot be ok!!! (nf) } } pm.worked(5); } } finally { pm.done(); if (product != targetProduct) { targetProduct.dispose(); } Preferences preferences = SnapApp.getDefault().getPreferences(); if (preferences.getBoolean(GPF.BEEP_AFTER_PROCESSING_PROPERTY, false)) { Toolkit.getDefaultToolkit().beep(); } } return product; } @Override protected void done() { final TargetProductSelectorModel model = getTargetProductSelector().getModel(); long totalSaveTime = saveTime + createTargetProductTime; try { final Product targetProduct = get(); if (model.isOpenInAppSelected()) { appContext.getProductManager().addProduct(targetProduct); showSaveAndOpenInAppInfo(totalSaveTime); } else { showSaveInfo(totalSaveTime); } } catch (InterruptedException e) { // ignore } catch (ExecutionException e) { handleProcessingError(e.getCause()); } catch (Throwable t) { handleProcessingError(t); } } } /** * Creates the desired target product. * Usually, this method will be implemented by invoking one of the multiple {@link GPF GPF} * {@code createProduct} methods. * <p> * The method should throw a {@link OperatorException} in order to signal "nominal" processing errors, * other exeption types are treated as internal errors. * * @return The target product. * @throws Exception if an error occurs, an {@link OperatorException} is signaling "nominal" processing errors. */ protected abstract Product createTargetProduct() throws Exception; }