This commit is contained in:
Sampo Niskanen 2009-06-30 15:08:46 +00:00
parent 6e14883374
commit 9c6c1b4895
12 changed files with 691 additions and 151 deletions

View File

@ -1,3 +1,8 @@
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

2
TODO
View File

@ -9,7 +9,7 @@ Must-have:
- 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:

View File

@ -6,6 +6,7 @@ import java.io.OutputStream;
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;
@ -14,6 +15,7 @@ import net.sf.openrocket.aerodynamics.Warning;
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;
@ -34,6 +36,17 @@ public class OpenRocketSaver extends RocketSaver {
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;
@ -83,6 +96,55 @@ public class OpenRocketSaver extends RocketSaver {
((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")
@ -231,8 +293,9 @@ public class OpenRocketSaver extends RocketSaver {
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;
@ -297,6 +360,53 @@ public class OpenRocketSaver extends RocketSaver {
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);

View File

@ -1,6 +1,5 @@
package net.sf.openrocket.file;
import java.awt.Component;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
@ -8,8 +7,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.swing.ProgressMonitorInputStream;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument;
@ -18,29 +15,28 @@ public abstract class RocketLoader {
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();
}
}
}
}

View File

@ -68,6 +68,16 @@ public abstract class RocketSaver {
/**
* 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);

View File

@ -1,5 +1,8 @@
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;
@ -17,6 +20,8 @@ import net.miginfocom.swing.MigLayout;
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;
@ -24,6 +29,8 @@ public class StorageOptionChooser extends JPanel {
public static final double DEFAULT_SAVE_TIME_SKIP = 0.20;
private final OpenRocketDocument document;
private JRadioButton allButton;
private JRadioButton someButton;
private JRadioButton noneButton;
@ -32,11 +39,30 @@ public class StorageOptionChooser extends JPanel {
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;
@ -47,6 +73,7 @@ public class StorageOptionChooser extends JPanel {
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");
@ -55,6 +82,7 @@ public class StorageOptionChooser extends JPanel {
"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));
@ -68,6 +96,7 @@ public class StorageOptionChooser extends JPanel {
}
});
this.add(timeSpinner, "wmin 55lp");
timeSpinner.addChangeListener(changeUpdater);
JLabel label = new JLabel("seconds");
label.setToolTipText(tip);
@ -78,13 +107,22 @@ public class StorageOptionChooser extends JPanel {
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(
@ -117,6 +155,8 @@ public class StorageOptionChooser extends JPanel {
// Compression checkbox
compressButton.setSelected(opts.isCompressionEnabled());
updateEstimate();
}
@ -140,6 +180,33 @@ public class StorageOptionChooser extends JPanel {
// 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.
@ -187,7 +254,7 @@ public class StorageOptionChooser extends JPanel {
}
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) !=
@ -200,5 +267,4 @@ public class StorageOptionChooser extends JPanel {
return true;
}
}

View File

@ -1,6 +1,9 @@
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;
@ -12,7 +15,7 @@ import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.util.Pair;
import net.sf.openrocket.util.MathUtil;
/**
@ -25,94 +28,135 @@ import net.sf.openrocket.util.Pair;
*/
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;
private int position;
private Pair<String, SwingWorker<?,?>>[] workers;
/** 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;
/** 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);
}
nextWorker();
this.setVisible(true); // Waits until all have ended
}
/**
* 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.
*/
private void nextWorker() {
if ((position >= workers.length-1) || cancelled) {
close();
return;
}
position++;
label.setText(workers[position].getU());
workers[position].getV().execute();
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (workers[position].getV().getState() == SwingWorker.StateValue.DONE) {
nextWorker();
if (worker.getState() == SwingWorker.StateValue.DONE) {
close();
}
int value = workers[position].getV().getProgress();
value = (value + position*100 ) / workers.length;
progressBar.setValue(value);
progressBar.setValue(worker.getProgress());
}
private void close() {
for (int i=0; i < workers.length; i++) {
workers[i].getV().removePropertyChangeListener(this);
}
worker.removePropertyChangeListener(this);
this.setVisible(false);
}
/**
* 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
*/
public static boolean runWorker(Window parent, String title, String label,
SwingWorker<?,?> worker) {
// Start timing the worker
final long startTime = System.currentTimeMillis();
worker.execute();
// 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;
}
// Dialog is required
SwingWorkerDialog dialog = new SwingWorkerDialog(parent, title, label, worker);
dialog.setVisible(true);
return !dialog.cancelled;
}
}

View File

@ -1,9 +1,9 @@
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;
@ -15,10 +15,11 @@ import java.awt.event.MouseListener;
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;
@ -40,7 +41,6 @@ import javax.swing.ListSelectionModel;
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;
@ -65,16 +65,17 @@ import net.sf.openrocket.gui.dialogs.BugDialog;
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;
@ -83,6 +84,9 @@ public class BasicFrame extends JFrame {
* 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();
/**
@ -241,7 +245,6 @@ public class BasicFrame extends JFrame {
}
});
frames.add(this);
}
@ -590,19 +593,18 @@ public class BasicFrame extends JFrame {
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();
}
}
@ -614,23 +616,58 @@ public class BasicFrame extends JFrame {
* @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:");
@ -652,29 +689,8 @@ public class BasicFrame extends JFrame {
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;
}
}
@ -688,11 +704,12 @@ public class BasicFrame extends JFrame {
}
}
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());
@ -738,18 +755,39 @@ public class BasicFrame extends JFrame {
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;
}
@ -787,6 +825,16 @@ public class BasicFrame extends JFrame {
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.
*/

View File

@ -36,10 +36,26 @@ public class ConcurrentProgressMonitor extends ProgressMonitor {
}
});
}
}
@Override
public void close() {
if (SwingUtilities.isEventDispatchThread()) {
super.close();
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
ConcurrentProgressMonitor.super.close();
}
});
}
}
}

View File

@ -130,6 +130,7 @@ public class ConcurrentProgressMonitorInputStream extends FilterInputStream {
@Override
public void close() throws IOException {
in.close();
monitor.close();
}

View File

@ -0,0 +1,145 @@
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);
}
}
}
}

View File

@ -0,0 +1,99 @@
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);
}
}
}
}