+2009-06-26 Sampo Niskanen
+
+ * Progress dialogs for file open/save
+ * File size estimate in save dialog
+
2009-06-20 Sampo Niskanen
* New edit motor configurations dialog
- Read more thrust curve formats / go through thrust curves and correct errors
- Create application icon and take into use
- Fix engine block icons
-- Progress and error dialogs when reading/writing files
+- Better error/warning dialogs when reading/writing files
Maybe:
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.zip.GZIPOutputStream;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.document.StorageOptions;
+import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
private static final String METHOD_PACKAGE = "net.sf.openrocket.file.openrocket";
private static final String METHOD_SUFFIX = "Saver";
+
+ // Estimated storage used by different portions
+ // These have been hand-estimated from saved files
+ private static final int BYTES_PER_COMPONENT_UNCOMPRESSED = 590;
+ private static final int BYTES_PER_COMPONENT_COMPRESSED = 80;
+ private static final int BYTES_PER_SIMULATION_UNCOMPRESSED = 1000;
+ private static final int BYTES_PER_SIMULATION_COMPRESSED = 100;
+ private static final int BYTES_PER_DATAPOINT_UNCOMPRESSED = 350;
+ private static final int BYTES_PER_DATAPOINT_COMPRESSED = 100;
+
+
private int indent;
private Writer dest;
((GZIPOutputStream)output).finish();
}
+
+
+ @Override
+ public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
+
+ long size = 0;
+
+ // Size per component
+ int componentCount = 0;
+ Rocket rocket = doc.getRocket();
+ Iterator<RocketComponent> iterator = rocket.deepIterator(true);
+ while (iterator.hasNext()) {
+ iterator.next();
+ componentCount++;
+ }
+
+ if (options.isCompressionEnabled())
+ size += componentCount * BYTES_PER_COMPONENT_COMPRESSED;
+ else
+ size += componentCount * BYTES_PER_COMPONENT_UNCOMPRESSED;
+
+
+ // Size per simulation
+ if (options.isCompressionEnabled())
+ size += doc.getSimulationCount() * BYTES_PER_SIMULATION_COMPRESSED;
+ else
+ size += doc.getSimulationCount() * BYTES_PER_SIMULATION_UNCOMPRESSED;
+
+
+ // Size per flight data point
+ int pointCount = 0;
+ double timeSkip = options.getSimulationTimeSkip();
+ if (timeSkip != StorageOptions.SIMULATION_DATA_NONE) {
+ for (Simulation s: doc.getSimulations()) {
+ FlightData data = s.getSimulatedData();
+ for (int i=0; i < data.getBranchCount(); i++) {
+ pointCount += countFlightDataBranchPoints(data.getBranch(i), timeSkip);
+ }
+ }
+ }
+
+ if (options.isCompressionEnabled())
+ size += pointCount * BYTES_PER_DATAPOINT_COMPRESSED;
+ else
+ size += pointCount * BYTES_PER_DATAPOINT_UNCOMPRESSED;
+
+ return size;
+ }
+
@SuppressWarnings("unchecked")
- private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip) throws IOException {
- double previousTime = -100;
+ private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
+ throws IOException {
+ double previousTime = -100000;
if (branch == null)
return;
writeln("</databranch>");
}
+
+
+ /* TODO: LOW: This is largely duplicated from above! */
+ private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
+ int count = 0;
+
+ double previousTime = -100000;
+
+ if (branch == null)
+ return 0;
+
+ // Retrieve the types from the branch
+ FlightDataBranch.Type[] types = branch.getTypes();
+
+ if (types.length == 0)
+ return 0;
+
+ List<Double> timeData = branch.get(FlightDataBranch.TYPE_TIME);
+ if (timeData == null) {
+ // TODO: MEDIUM: External data may not have time data
+ throw new IllegalArgumentException("Data did not contain time data");
+ }
+
+ // Write the data
+ int length = branch.getLength();
+ if (length > 0) {
+ count++;
+ previousTime = timeData.get(0);
+ }
+
+ for (int i=1; i < length-1; i++) {
+ if (Math.abs(timeData.get(i) - previousTime - timeSkip) <
+ Math.abs(timeData.get(i+1) - previousTime - timeSkip)) {
+ count++;
+ previousTime = timeData.get(i);
+ }
+ }
+
+ if (length > 1) {
+ count++;
+ }
+
+ return count;
+ }
+
+
+
private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
throws IOException {
sb.setLength(0);
package net.sf.openrocket.file;
-import java.awt.Component;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import javax.swing.ProgressMonitorInputStream;
-
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument;
protected final WarningSet warnings = new WarningSet();
- public final OpenRocketDocument load(File source, Component parent)
- throws RocketLoadException {
- warnings.clear();
-
- try {
- return load(new BufferedInputStream(new ProgressMonitorInputStream(
- parent, "Loading " + source.getName(),
- new FileInputStream(source))));
- } catch (FileNotFoundException e) {
- throw new RocketLoadException("File not found: " + source);
- }
- }
-
/**
* Loads a rocket from the specified File object.
*/
public final OpenRocketDocument load(File source) throws RocketLoadException {
warnings.clear();
+ InputStream stream = null;
try {
- return load(new BufferedInputStream(new FileInputStream(source)));
+
+ stream = new BufferedInputStream(new FileInputStream(source));
+ return load(stream);
+
} catch (FileNotFoundException e) {
throw new RocketLoadException("File not found: " + source);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
}
}
+ /**
+ * Provide an estimate of the file size when saving the document with the
+ * specified options. This is used as an indication to the user and when estimating
+ * file save progress.
+ *
+ * @param doc the document.
+ * @param options the save options, compression must be taken into account.
+ * @return the estimated number of bytes the storage would take.
+ */
+ public abstract long estimateFileSize(OpenRocketDocument doc, StorageOptions options);
package net.sf.openrocket.gui;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.document.StorageOptions;
+import net.sf.openrocket.file.OpenRocketSaver;
+import net.sf.openrocket.file.RocketSaver;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
public static final double DEFAULT_SAVE_TIME_SKIP = 0.20;
+ private final OpenRocketDocument document;
+
private JRadioButton allButton;
private JRadioButton someButton;
private JRadioButton noneButton;
private JCheckBox compressButton;
+ private JLabel estimateLabel;
+
private boolean artificialEvent = false;
- public StorageOptionChooser(StorageOptions opts) {
+ public StorageOptionChooser(OpenRocketDocument doc, StorageOptions opts) {
super(new MigLayout());
+
+ this.document = doc;
+
+
+ ChangeListener changeUpdater = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ updateEstimate();
+ }
+ };
+ ActionListener actionUpdater = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateEstimate();
+ }
+ };
+
ButtonGroup buttonGroup = new ButtonGroup();
String tip;
allButton.setToolTipText("<html>Store all simulated data.<br>" +
"This can result in very large files!");
buttonGroup.add(allButton);
+ allButton.addActionListener(actionUpdater);
this.add(allButton, "spanx, wrap rel");
"Larger values result in smaller files.";
someButton.setToolTipText(tip);
buttonGroup.add(someButton);
+ someButton.addActionListener(actionUpdater);
this.add(someButton, "");
timeSpinner = new JSpinner(new SpinnerNumberModel(0.0, 0.0, 5.0, 0.1));
}
});
this.add(timeSpinner, "wmin 55lp");
+ timeSpinner.addChangeListener(changeUpdater);
JLabel label = new JLabel("seconds");
label.setToolTipText(tip);
noneButton.setToolTipText("<html>Store only the values shown in the summary table.<br>" +
"This results in the smallest files.");
buttonGroup.add(noneButton);
-
+ noneButton.addActionListener(actionUpdater);
this.add(noneButton, "spanx, wrap 20lp");
+
compressButton = new JCheckBox("Compress file");
compressButton.setToolTipText("Using compression reduces the file size significantly.");
- this.add(compressButton, "spanx");
+ compressButton.addActionListener(actionUpdater);
+ this.add(compressButton, "spanx, wrap para");
+
+
+ // Estimate is updated in loadOptions(opts)
+ estimateLabel = new JLabel("");
+ estimateLabel.setToolTipText("An estimate on how large the resulting file would " +
+ "be with the present options.");
+ this.add(estimateLabel, "spanx");
this.setBorder(BorderFactory.createCompoundBorder(
// Compression checkbox
compressButton.setSelected(opts.isCompressionEnabled());
+
+ updateEstimate();
}
+ // TODO: MEDIUM: The estimation method always uses OpenRocketSaver!
+ private static final RocketSaver ROCKET_SAVER = new OpenRocketSaver();
+
+ private void updateEstimate() {
+ StorageOptions opts = new StorageOptions();
+
+ storeOptions(opts);
+ long size = ROCKET_SAVER.estimateFileSize(document, opts);
+ size = Math.max((size+512)/1024, 1);
+
+ String formatted;
+
+ if (size >= 10000) {
+ formatted = (size/1000) + " MB";
+ } else if (size >= 1000){
+ formatted = (size/1000) + "." + ((size/100)%10) + " MB";
+ } else if (size >= 100) {
+ formatted = ((size/10)*10) + " kB";
+ } else {
+ formatted = size + " kB";
+ }
+
+ estimateLabel.setText("Estimated file size: " + formatted);
+ }
+
+
+
/**
* Asks the user the storage options using a modal dialog window if the document
* contains simulated data and the user has not explicitly set how to store the data.
}
- StorageOptionChooser chooser = new StorageOptionChooser(options);
+ StorageOptionChooser chooser = new StorageOptionChooser(document, options);
if (JOptionPane.showConfirmDialog(parent, chooser, "Save options",
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) !=
return true;
}
-
}
package net.sf.openrocket.gui.dialogs;
+import java.awt.Dimension;
import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.SwingWorker;
import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.util.Pair;
+import net.sf.openrocket.util.MathUtil;
/**
*/
public class SwingWorkerDialog extends JDialog implements PropertyChangeListener {
- private final JLabel label;
- private final JProgressBar progressBar;
+ /** Number of milliseconds to wait at a time between checking worker status */
+ private static final int DELAY = 100;
+
+ /** Minimum number of milliseconds to wait before estimating work length */
+ private static final int ESTIMATION_DELAY = 190;
+
+ /** Open the dialog if estimated remaining time is longer than this */
+ private static final int REMAINING_TIME_FOR_DIALOG = 1000;
- private int position;
- private Pair<String, SwingWorker<?,?>>[] workers;
+ /** Open the dialog if estimated total time is longed than this */
+ private static final int TOTAL_TIME_FOR_DIALOG = 2000;
+
+
+ private final SwingWorker<?,?> worker;
+ private final JProgressBar progressBar;
private boolean cancelled = false;
- public SwingWorkerDialog(Window parent, String title) {
+
+ private SwingWorkerDialog(Window parent, String title, String label,
+ SwingWorker<?,?> w) {
super(parent, title, ModalityType.APPLICATION_MODAL);
+
+ this.worker = w;
+ w.addPropertyChangeListener(this);
JPanel panel = new JPanel(new MigLayout("fill"));
- label = new JLabel("");
- panel.add(label, "wrap para");
+ if (label != null) {
+ panel.add(new JLabel(label), "wrap para");
+ }
progressBar = new JProgressBar();
panel.add(progressBar, "growx, wrap para");
JButton cancel = new JButton("Cancel");
- // TODO: CRITICAL: Implement cancel
+ cancel.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ cancelled = true;
+ worker.cancel(true);
+ close();
+ }
+ });
panel.add(cancel, "right");
this.add(panel);
+ this.setMinimumSize(new Dimension(250,100));
this.pack();
+ this.setLocationRelativeTo(parent);
this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
}
-
- /**
- * Execute the provided workers one after another. When this call returns
- * the workers will all have completed.
- *
- * @param workers pairs of description texts and workers to run.
- */
- public void runWorkers(Pair<String, SwingWorker<?,?>> ... workers) {
- if (workers.length == 0) {
- throw new IllegalArgumentException("No workers provided.");
- }
-
- this.workers = workers;
- position = -1;
-
- for (int i=0; i < workers.length; i++) {
- workers[i].getV().addPropertyChangeListener(this);
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (worker.getState() == SwingWorker.StateValue.DONE) {
+ close();
}
-
- nextWorker();
- this.setVisible(true); // Waits until all have ended
+ progressBar.setValue(worker.getProgress());
+ }
+
+ private void close() {
+ worker.removePropertyChangeListener(this);
+ this.setVisible(false);
}
-
/**
- * Starts the execution of the next worker in the queue. If the last worker
- * has completed or the operation has been cancelled, closes the dialog.
+ * Run a SwingWorker and if necessary show a dialog displaying the progress of
+ * the worker. The progress information is obtained from the SwingWorker's
+ * progress property. The dialog is shown only if the worker is estimated to
+ * take a notable amount of time.
+ * <p>
+ * The dialog contains a cancel button. Clicking it will call
+ * <code>worker.cancel(true)</code> and close the dialog immediately.
+ *
+ * @param parent the parent window for the dialog, or <code>null</code>.
+ * @param title the title for the dialog.
+ * @param label an additional label for the dialog, or <code>null</code>.
+ * @param worker the SwingWorker to execute.
+ * @return <code>true</code> if the worker has completed normally,
+ * <code>false</code> if the user cancelled the operation
*/
- private void nextWorker() {
- if ((position >= workers.length-1) || cancelled) {
- close();
- return;
- }
-
- position++;
+ public static boolean runWorker(Window parent, String title, String label,
+ SwingWorker<?,?> worker) {
- label.setText(workers[position].getU());
- workers[position].getV().execute();
- }
-
-
+ // Start timing the worker
+ final long startTime = System.currentTimeMillis();
+ worker.execute();
- @Override
- public void propertyChange(PropertyChangeEvent evt) {
- if (workers[position].getV().getState() == SwingWorker.StateValue.DONE) {
- nextWorker();
+ // Monitor worker thread before opening the dialog
+ while (true) {
+
+ try {
+ Thread.sleep(DELAY);
+ } catch (InterruptedException e) {
+ // Should never occur
+ e.printStackTrace();
+ }
+
+ if (worker.isDone()) {
+ // Worker has completed within time limits
+ return true;
+ }
+
+ // Check whether enough time has gone to get realistic estimate
+ long elapsed = System.currentTimeMillis() - startTime;
+ if (elapsed < ESTIMATION_DELAY)
+ continue;
+
+
+ // Calculate and check estimated remaining time
+ int progress = MathUtil.clamp(worker.getProgress(), 1, 100); // Avoid div-by-zero
+ long estimate = elapsed * 100 / progress;
+ long remaining = estimate - elapsed;
+
+ if (estimate >= TOTAL_TIME_FOR_DIALOG)
+ break;
+
+ if (remaining >= REMAINING_TIME_FOR_DIALOG)
+ break;
}
- int value = workers[position].getV().getProgress();
- value = (value + position*100 ) / workers.length;
- progressBar.setValue(value);
- }
-
-
-
- private void close() {
- for (int i=0; i < workers.length; i++) {
- workers[i].getV().removePropertyChangeListener(this);
- }
- this.setVisible(false);
+
+ // Dialog is required
+
+ SwingWorkerDialog dialog = new SwingWorkerDialog(parent, title, label, worker);
+ dialog.setVisible(true);
+
+ return !dialog.cancelled;
}
}
package net.sf.openrocket.gui.main;
-import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
+import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
-import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.concurrent.ExecutionException;
import javax.swing.Action;
import javax.swing.InputMap;
import javax.swing.LookAndFeel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
-import javax.swing.SwingWorker;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
import net.sf.openrocket.gui.dialogs.LicenseDialog;
import net.sf.openrocket.gui.dialogs.PreferencesDialog;
+import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.Stage;
-import net.sf.openrocket.util.ConcurrentProgressMonitor;
-import net.sf.openrocket.util.ConcurrentProgressMonitorInputStream;
import net.sf.openrocket.util.Icons;
+import net.sf.openrocket.util.OpenFileWorker;
import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.SaveFileWorker;
public class BasicFrame extends JFrame {
private static final long serialVersionUID = 1L;
* The RocketLoader instance used for loading all rocket designs.
*/
private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader();
+
+ // TODO: Always uses OpenRocketSaver
+ private static final RocketSaver ROCKET_SAVER = new OpenRocketSaver();
/**
}
});
frames.add(this);
-
}
Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
File[] files = chooser.getSelectedFiles();
- boolean opened = false;
for (File file: files) {
System.out.println("Opening file: " + file);
if (open(file, this)) {
- opened = true;
+
+ // Close previous window if replacing
+ if (replaceable && document.isSaved()) {
+ closeAction();
+ replaceable = false;
+ }
}
}
-
- // Close this frame if replaceable and file opened successfully
- if (replaceable && opened) {
- closeAction();
- }
}
* @param parent the parent component for which a progress dialog is opened.
* @return whether the file was successfully loaded and opened.
*/
- private static boolean open(File file, Component parent) {
- OpenRocketDocument doc = null;
-
-
- try {
- doc = ROCKET_LOADER.load(file, parent);
- } catch (RocketLoadException e) {
- JOptionPane.showMessageDialog(null, "Unable to open file '" + file.getName()
- +"': " + e.getMessage(), "Error opening file", JOptionPane.ERROR_MESSAGE);
- e.printStackTrace();
+ private static boolean open(File file, Window parent) {
+
+ // Open the file in a Swing worker thread
+ OpenFileWorker worker = new OpenFileWorker(file);
+ if (!SwingWorkerDialog.runWorker(parent, "Opening file",
+ "Reading " + file.getName() + "...", worker)) {
+
+ // User cancelled the operation
return false;
}
+
+
+ // Handle the document
+ OpenRocketDocument doc = null;
+ try {
+
+ doc = worker.get();
+
+ } catch (ExecutionException e) {
+
+ Throwable cause = e.getCause();
+
+ if (cause instanceof FileNotFoundException) {
+
+ JOptionPane.showMessageDialog(parent,
+ "File not found: " + file.getName(),
+ "Error opening file", JOptionPane.ERROR_MESSAGE);
+ return false;
+
+ } else if (cause instanceof RocketLoadException) {
+
+ JOptionPane.showMessageDialog(parent,
+ "Unable to open file '" + file.getName() +"': "
+ + cause.getMessage(),
+ "Error opening file", JOptionPane.ERROR_MESSAGE);
+ return false;
+
+ } else {
+
+ throw new RuntimeException("Unknown error when opening file", e);
+
+ }
+
+ } catch (InterruptedException e) {
+ throw new RuntimeException("EDT was interrupted", e);
+ }
+
+ if (doc == null) {
+ throw new RuntimeException("BUG: Document loader returned null");
+ }
+
- if (doc == null) {
- throw new RuntimeException("BUG: Rocket loader returned null");
- }
-
// Show warnings
Iterator<Warning> warns = ROCKET_LOADER.getWarnings().iterator();
System.out.println("Warnings:");
- private static class OpenWorker extends SwingWorker<OpenRocketDocument, Void> {
- private final File file;
- private final Component parent;
- private ConcurrentProgressMonitor monitor = null;
-
- public OpenWorker(File file, Component parent) {
- this.file = file;
- this.parent = parent;
- }
-
- @Override
- protected OpenRocketDocument doInBackground() throws Exception {
- ConcurrentProgressMonitorInputStream is =
- new ConcurrentProgressMonitorInputStream(parent,
- "Loading " + file.getName(), new FileInputStream(file));
- monitor = is.getProgressMonitor();
- return ROCKET_LOADER.load(is);
- }
-
- public ConcurrentProgressMonitor getMonitor() {
- return monitor;
- }
- }
+
+
}
}
+
private boolean saveAsAction() {
File file = null;
while (file == null) {
StorageOptionChooser storageChooser =
- new StorageOptionChooser(document.getDefaultStorageOptions());
+ new StorageOptionChooser(document, document.getDefaultStorageOptions());
JFileChooser chooser = new JFileChooser();
chooser.setFileFilter(ROCKET_DESIGN_FILTER);
chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
return false;
}
- RocketSaver saver = new OpenRocketSaver();
- try {
- saver.save(file, document);
- document.setFile(file);
- document.setSaved(true);
- saved = true;
- } catch (IOException e) {
- JOptionPane.showMessageDialog(this, new String[] {
- "An I/O error occurred while saving:",
- e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
+
+ SaveFileWorker worker = new SaveFileWorker(document, file, ROCKET_SAVER);
+
+ if (!SwingWorkerDialog.runWorker(this, "Saving file",
+ "Writing " + file.getName() + "...", worker)) {
+
+ // User cancelled the save
+ file.delete();
+ return false;
}
- setTitle();
+
+ try {
+ worker.get();
+ document.setFile(file);
+ document.setSaved(true);
+ saved = true;
+ setTitle();
+ } catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+
+ if (cause instanceof IOException) {
+ JOptionPane.showMessageDialog(this, new String[] {
+ "An I/O error occurred while saving:",
+ e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
+ return false;
+ } else {
+ throw new RuntimeException("Unknown error when saving file", e);
+ }
+
+ } catch (InterruptedException e) {
+ throw new RuntimeException("EDT was interrupted", e);
+ }
+
return saved;
}
return true;
}
+
+ /**
+ * Closes this frame if it is replaceable.
+ */
+ public void closeIfReplaceable() {
+ if (this.replaceable && document.isSaved()) {
+ closeAction();
+ }
+ }
+
/**
* Open a new design window with a basic rocket+stage.
*/
}
});
-
}
}
+ @Override
+ public void close() {
+ if (SwingUtilities.isEventDispatchThread()) {
+ super.close();
+ } else {
+
+ SwingUtilities.invokeLater(new Runnable() {
+
+ @Override
+ public void run() {
+ ConcurrentProgressMonitor.super.close();
+ }
+
+ });
+ }
+ }
+
}
@Override
public void close() throws IOException {
in.close();
+ monitor.close();
}
--- /dev/null
+package net.sf.openrocket.util;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+
+import javax.swing.SwingWorker;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.GeneralRocketLoader;
+import net.sf.openrocket.file.RocketLoader;
+
+
+/**
+ * A SwingWorker thread that opens a rocket design file.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class OpenFileWorker extends SwingWorker<OpenRocketDocument, Void> {
+
+ private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader();
+
+ private final File file;
+
+ public OpenFileWorker(File file) {
+ this.file = file;
+ }
+
+
+ @Override
+ protected OpenRocketDocument doInBackground() throws Exception {
+ ProgressInputStream is = new ProgressInputStream(
+ new BufferedInputStream(new FileInputStream(file)));
+ try {
+ return ROCKET_LOADER.load(is);
+ } finally {
+ try {
+ is.close();
+ } catch (Exception e) {
+ System.err.println("Error closing file: ");
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+
+
+ private class ProgressInputStream extends FilterInputStream {
+
+ private final int size;
+ private int readBytes = 0;
+ private int progress = -1;
+
+ protected ProgressInputStream(InputStream in) {
+ super(in);
+ int s;
+ try {
+ s = in.available();
+ } catch (IOException e) {
+ System.err.println("ERROR estimating available bytes!");
+ s = 0;
+ }
+ size = s;
+ }
+
+
+
+ @Override
+ public int read() throws IOException {
+ int c = in.read();
+ if (c >= 0) {
+ readBytes++;
+ setProgress();
+ }
+ if (isCancelled()) {
+ throw new InterruptedIOException("OpenFileWorker was cancelled");
+ }
+ return c;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int n = in.read(b, off, len);
+ if (n > 0) {
+ readBytes += n;
+ setProgress();
+ }
+ if (isCancelled()) {
+ throw new InterruptedIOException("OpenFileWorker was cancelled");
+ }
+ return n;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ int n = in.read(b);
+ if (n > 0) {
+ readBytes += n;
+ setProgress();
+ }
+ if (isCancelled()) {
+ throw new InterruptedIOException("OpenFileWorker was cancelled");
+ }
+ return n;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ long nr = in.skip(n);
+ if (nr > 0) {
+ readBytes += nr;
+ setProgress();
+ }
+ if (isCancelled()) {
+ throw new InterruptedIOException("OpenFileWorker was cancelled");
+ }
+ return nr;
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ in.reset();
+ readBytes = size - in.available();
+ setProgress();
+ if (isCancelled()) {
+ throw new InterruptedIOException("OpenFileWorker was cancelled");
+ }
+ }
+
+
+
+ private void setProgress() {
+ int p = MathUtil.clamp(readBytes * 100 / size, 0, 100);
+ if (progress != p) {
+ progress = p;
+ OpenFileWorker.this.setProgress(progress);
+ }
+ }
+ }
+}
--- /dev/null
+package net.sf.openrocket.util;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+
+import javax.swing.SwingWorker;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.RocketSaver;
+
+public class SaveFileWorker extends SwingWorker<Void, Void> {
+
+ private final OpenRocketDocument document;
+ private final File file;
+ private final RocketSaver saver;
+
+ public SaveFileWorker(OpenRocketDocument document, File file, RocketSaver saver) {
+ this.document = document;
+ this.file = file;
+ this.saver = saver;
+ }
+
+
+ @Override
+ protected Void doInBackground() throws Exception {
+ ProgressOutputStream os = new ProgressOutputStream(
+ new BufferedOutputStream(new FileOutputStream(file)),
+ (int)saver.estimateFileSize(document, document.getDefaultStorageOptions()));
+
+ try {
+ saver.save(os, document);
+ } finally {
+ try {
+ os.close();
+ } catch (Exception e) {
+ System.err.println("Error closing file: ");
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+
+ private class ProgressOutputStream extends FilterOutputStream {
+
+ private final int totalBytes;
+ private int writtenBytes = 0;
+ private int progress = -1;
+
+ public ProgressOutputStream(OutputStream out, int estimate) {
+ super(out);
+ this.totalBytes = estimate;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ out.write(b, off, len);
+ writtenBytes += len;
+ setProgress();
+ if (isCancelled()) {
+ throw new InterruptedIOException("SaveFileWorker was cancelled");
+ }
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ out.write(b);
+ writtenBytes += b.length;
+ setProgress();
+ if (isCancelled()) {
+ throw new InterruptedIOException("SaveFileWorker was cancelled");
+ }
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ out.write(b);
+ writtenBytes++;
+ setProgress();
+ if (isCancelled()) {
+ throw new InterruptedIOException("SaveFileWorker was cancelled");
+ }
+ }
+
+
+ private void setProgress() {
+ int p = MathUtil.clamp(writtenBytes * 100 / totalBytes, 0, 100);
+ if (progress != p) {
+ progress = p;
+ SaveFileWorker.this.setProgress(progress);
+ }
+ }
+ }
+}