Updates
This commit is contained in:
parent
6e14883374
commit
9c6c1b4895
@ -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
2
TODO
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ public class ConcurrentProgressMonitorInputStream extends FilterInputStream {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
monitor.close();
|
||||
}
|
||||
|
||||
|
||||
|
145
src/net/sf/openrocket/util/OpenFileWorker.java
Normal file
145
src/net/sf/openrocket/util/OpenFileWorker.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
99
src/net/sf/openrocket/util/SaveFileWorker.java
Normal file
99
src/net/sf/openrocket/util/SaveFileWorker.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user