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
|
2009-06-20 Sampo Niskanen
|
||||||
|
|
||||||
* New edit motor configurations dialog
|
* 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
|
- Read more thrust curve formats / go through thrust curves and correct errors
|
||||||
- Create application icon and take into use
|
- Create application icon and take into use
|
||||||
- Fix engine block icons
|
- Fix engine block icons
|
||||||
- Progress and error dialogs when reading/writing files
|
- Better error/warning dialogs when reading/writing files
|
||||||
|
|
||||||
|
|
||||||
Maybe:
|
Maybe:
|
||||||
|
@ -6,6 +6,7 @@ import java.io.OutputStream;
|
|||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.zip.GZIPOutputStream;
|
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.OpenRocketDocument;
|
||||||
import net.sf.openrocket.document.Simulation;
|
import net.sf.openrocket.document.Simulation;
|
||||||
import net.sf.openrocket.document.StorageOptions;
|
import net.sf.openrocket.document.StorageOptions;
|
||||||
|
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
import net.sf.openrocket.simulation.FlightData;
|
import net.sf.openrocket.simulation.FlightData;
|
||||||
import net.sf.openrocket.simulation.FlightDataBranch;
|
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_PACKAGE = "net.sf.openrocket.file.openrocket";
|
||||||
private static final String METHOD_SUFFIX = "Saver";
|
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 int indent;
|
||||||
private Writer dest;
|
private Writer dest;
|
||||||
|
|
||||||
@ -85,6 +98,55 @@ public class OpenRocketSaver extends RocketSaver {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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")
|
@SuppressWarnings("unchecked")
|
||||||
private void saveComponent(RocketComponent component) throws IOException {
|
private void saveComponent(RocketComponent component) throws IOException {
|
||||||
|
|
||||||
@ -231,8 +293,9 @@ public class OpenRocketSaver extends RocketSaver {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip) throws IOException {
|
private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
|
||||||
double previousTime = -100;
|
throws IOException {
|
||||||
|
double previousTime = -100000;
|
||||||
|
|
||||||
if (branch == null)
|
if (branch == null)
|
||||||
return;
|
return;
|
||||||
@ -297,6 +360,53 @@ public class OpenRocketSaver extends RocketSaver {
|
|||||||
writeln("</databranch>");
|
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)
|
private void writeDataPointString(List<List<Double>> data, int index, StringBuilder sb)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
sb.setLength(0);
|
sb.setLength(0);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.sf.openrocket.file;
|
package net.sf.openrocket.file;
|
||||||
|
|
||||||
import java.awt.Component;
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@ -8,8 +7,6 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import javax.swing.ProgressMonitorInputStream;
|
|
||||||
|
|
||||||
import net.sf.openrocket.aerodynamics.WarningSet;
|
import net.sf.openrocket.aerodynamics.WarningSet;
|
||||||
import net.sf.openrocket.document.OpenRocketDocument;
|
import net.sf.openrocket.document.OpenRocketDocument;
|
||||||
|
|
||||||
@ -18,29 +15,28 @@ public abstract class RocketLoader {
|
|||||||
protected final WarningSet warnings = new WarningSet();
|
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.
|
* Loads a rocket from the specified File object.
|
||||||
*/
|
*/
|
||||||
public final OpenRocketDocument load(File source) throws RocketLoadException {
|
public final OpenRocketDocument load(File source) throws RocketLoadException {
|
||||||
warnings.clear();
|
warnings.clear();
|
||||||
|
InputStream stream = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return load(new BufferedInputStream(new FileInputStream(source)));
|
|
||||||
|
stream = new BufferedInputStream(new FileInputStream(source));
|
||||||
|
return load(stream);
|
||||||
|
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
throw new RocketLoadException("File not found: " + source);
|
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;
|
package net.sf.openrocket.gui;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
import javax.swing.BorderFactory;
|
import javax.swing.BorderFactory;
|
||||||
import javax.swing.ButtonGroup;
|
import javax.swing.ButtonGroup;
|
||||||
import javax.swing.JCheckBox;
|
import javax.swing.JCheckBox;
|
||||||
@ -17,6 +20,8 @@ import net.miginfocom.swing.MigLayout;
|
|||||||
import net.sf.openrocket.document.OpenRocketDocument;
|
import net.sf.openrocket.document.OpenRocketDocument;
|
||||||
import net.sf.openrocket.document.Simulation;
|
import net.sf.openrocket.document.Simulation;
|
||||||
import net.sf.openrocket.document.StorageOptions;
|
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.FlightData;
|
||||||
import net.sf.openrocket.simulation.FlightDataBranch;
|
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;
|
public static final double DEFAULT_SAVE_TIME_SKIP = 0.20;
|
||||||
|
|
||||||
|
private final OpenRocketDocument document;
|
||||||
|
|
||||||
private JRadioButton allButton;
|
private JRadioButton allButton;
|
||||||
private JRadioButton someButton;
|
private JRadioButton someButton;
|
||||||
private JRadioButton noneButton;
|
private JRadioButton noneButton;
|
||||||
@ -32,12 +39,31 @@ public class StorageOptionChooser extends JPanel {
|
|||||||
|
|
||||||
private JCheckBox compressButton;
|
private JCheckBox compressButton;
|
||||||
|
|
||||||
|
private JLabel estimateLabel;
|
||||||
|
|
||||||
|
|
||||||
private boolean artificialEvent = false;
|
private boolean artificialEvent = false;
|
||||||
|
|
||||||
public StorageOptionChooser(StorageOptions opts) {
|
public StorageOptionChooser(OpenRocketDocument doc, StorageOptions opts) {
|
||||||
super(new MigLayout());
|
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();
|
ButtonGroup buttonGroup = new ButtonGroup();
|
||||||
String tip;
|
String tip;
|
||||||
|
|
||||||
@ -47,6 +73,7 @@ public class StorageOptionChooser extends JPanel {
|
|||||||
allButton.setToolTipText("<html>Store all simulated data.<br>" +
|
allButton.setToolTipText("<html>Store all simulated data.<br>" +
|
||||||
"This can result in very large files!");
|
"This can result in very large files!");
|
||||||
buttonGroup.add(allButton);
|
buttonGroup.add(allButton);
|
||||||
|
allButton.addActionListener(actionUpdater);
|
||||||
this.add(allButton, "spanx, wrap rel");
|
this.add(allButton, "spanx, wrap rel");
|
||||||
|
|
||||||
|
|
||||||
@ -55,6 +82,7 @@ public class StorageOptionChooser extends JPanel {
|
|||||||
"Larger values result in smaller files.";
|
"Larger values result in smaller files.";
|
||||||
someButton.setToolTipText(tip);
|
someButton.setToolTipText(tip);
|
||||||
buttonGroup.add(someButton);
|
buttonGroup.add(someButton);
|
||||||
|
someButton.addActionListener(actionUpdater);
|
||||||
this.add(someButton, "");
|
this.add(someButton, "");
|
||||||
|
|
||||||
timeSpinner = new JSpinner(new SpinnerNumberModel(0.0, 0.0, 5.0, 0.1));
|
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");
|
this.add(timeSpinner, "wmin 55lp");
|
||||||
|
timeSpinner.addChangeListener(changeUpdater);
|
||||||
|
|
||||||
JLabel label = new JLabel("seconds");
|
JLabel label = new JLabel("seconds");
|
||||||
label.setToolTipText(tip);
|
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>" +
|
noneButton.setToolTipText("<html>Store only the values shown in the summary table.<br>" +
|
||||||
"This results in the smallest files.");
|
"This results in the smallest files.");
|
||||||
buttonGroup.add(noneButton);
|
buttonGroup.add(noneButton);
|
||||||
|
noneButton.addActionListener(actionUpdater);
|
||||||
this.add(noneButton, "spanx, wrap 20lp");
|
this.add(noneButton, "spanx, wrap 20lp");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
compressButton = new JCheckBox("Compress file");
|
compressButton = new JCheckBox("Compress file");
|
||||||
compressButton.setToolTipText("Using compression reduces the file size significantly.");
|
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(
|
this.setBorder(BorderFactory.createCompoundBorder(
|
||||||
@ -117,6 +155,8 @@ public class StorageOptionChooser extends JPanel {
|
|||||||
|
|
||||||
// Compression checkbox
|
// Compression checkbox
|
||||||
compressButton.setSelected(opts.isCompressionEnabled());
|
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
|
* 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.
|
* 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",
|
if (JOptionPane.showConfirmDialog(parent, chooser, "Save options",
|
||||||
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) !=
|
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) !=
|
||||||
@ -200,5 +267,4 @@ public class StorageOptionChooser extends JPanel {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package net.sf.openrocket.gui.dialogs;
|
package net.sf.openrocket.gui.dialogs;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
|
||||||
@ -12,7 +15,7 @@ import javax.swing.JProgressBar;
|
|||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
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 {
|
public class SwingWorkerDialog extends JDialog implements PropertyChangeListener {
|
||||||
|
|
||||||
private final JLabel label;
|
/** Number of milliseconds to wait at a time between checking worker status */
|
||||||
private final JProgressBar progressBar;
|
private static final int DELAY = 100;
|
||||||
|
|
||||||
private int position;
|
/** Minimum number of milliseconds to wait before estimating work length */
|
||||||
private Pair<String, SwingWorker<?,?>>[] workers;
|
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;
|
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);
|
super(parent, title, ModalityType.APPLICATION_MODAL);
|
||||||
|
|
||||||
|
this.worker = w;
|
||||||
|
w.addPropertyChangeListener(this);
|
||||||
|
|
||||||
JPanel panel = new JPanel(new MigLayout("fill"));
|
JPanel panel = new JPanel(new MigLayout("fill"));
|
||||||
|
|
||||||
label = new JLabel("");
|
if (label != null) {
|
||||||
panel.add(label, "wrap para");
|
panel.add(new JLabel(label), "wrap para");
|
||||||
|
}
|
||||||
|
|
||||||
progressBar = new JProgressBar();
|
progressBar = new JProgressBar();
|
||||||
panel.add(progressBar, "growx, wrap para");
|
panel.add(progressBar, "growx, wrap para");
|
||||||
|
|
||||||
JButton cancel = new JButton("Cancel");
|
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");
|
panel.add(cancel, "right");
|
||||||
|
|
||||||
this.add(panel);
|
this.add(panel);
|
||||||
|
this.setMinimumSize(new Dimension(250,100));
|
||||||
this.pack();
|
this.pack();
|
||||||
|
this.setLocationRelativeTo(parent);
|
||||||
this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
|
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
|
@Override
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
if (workers[position].getV().getState() == SwingWorker.StateValue.DONE) {
|
if (worker.getState() == SwingWorker.StateValue.DONE) {
|
||||||
nextWorker();
|
close();
|
||||||
}
|
}
|
||||||
|
progressBar.setValue(worker.getProgress());
|
||||||
int value = workers[position].getV().getProgress();
|
|
||||||
value = (value + position*100 ) / workers.length;
|
|
||||||
progressBar.setValue(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void close() {
|
private void close() {
|
||||||
for (int i=0; i < workers.length; i++) {
|
worker.removePropertyChangeListener(this);
|
||||||
workers[i].getV().removePropertyChangeListener(this);
|
|
||||||
}
|
|
||||||
this.setVisible(false);
|
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;
|
package net.sf.openrocket.gui.main;
|
||||||
|
|
||||||
import java.awt.Component;
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.Toolkit;
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.Window;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.ComponentAdapter;
|
import java.awt.event.ComponentAdapter;
|
||||||
@ -15,10 +15,11 @@ import java.awt.event.MouseListener;
|
|||||||
import java.awt.event.WindowAdapter;
|
import java.awt.event.WindowAdapter;
|
||||||
import java.awt.event.WindowEvent;
|
import java.awt.event.WindowEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
import javax.swing.InputMap;
|
import javax.swing.InputMap;
|
||||||
@ -40,7 +41,6 @@ import javax.swing.ListSelectionModel;
|
|||||||
import javax.swing.LookAndFeel;
|
import javax.swing.LookAndFeel;
|
||||||
import javax.swing.ScrollPaneConstants;
|
import javax.swing.ScrollPaneConstants;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.SwingWorker;
|
|
||||||
import javax.swing.ToolTipManager;
|
import javax.swing.ToolTipManager;
|
||||||
import javax.swing.UIManager;
|
import javax.swing.UIManager;
|
||||||
import javax.swing.border.TitledBorder;
|
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.ComponentAnalysisDialog;
|
||||||
import net.sf.openrocket.gui.dialogs.LicenseDialog;
|
import net.sf.openrocket.gui.dialogs.LicenseDialog;
|
||||||
import net.sf.openrocket.gui.dialogs.PreferencesDialog;
|
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.gui.scalefigure.RocketPanel;
|
||||||
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
|
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
|
||||||
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
|
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
|
||||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
import net.sf.openrocket.rocketcomponent.Stage;
|
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.Icons;
|
||||||
|
import net.sf.openrocket.util.OpenFileWorker;
|
||||||
import net.sf.openrocket.util.Prefs;
|
import net.sf.openrocket.util.Prefs;
|
||||||
|
import net.sf.openrocket.util.SaveFileWorker;
|
||||||
|
|
||||||
public class BasicFrame extends JFrame {
|
public class BasicFrame extends JFrame {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
@ -84,6 +85,9 @@ public class BasicFrame extends JFrame {
|
|||||||
*/
|
*/
|
||||||
private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader();
|
private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader();
|
||||||
|
|
||||||
|
// TODO: Always uses OpenRocketSaver
|
||||||
|
private static final RocketSaver ROCKET_SAVER = new OpenRocketSaver();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File filter for filtering only rocket designs.
|
* File filter for filtering only rocket designs.
|
||||||
@ -241,7 +245,6 @@ public class BasicFrame extends JFrame {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
frames.add(this);
|
frames.add(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -590,18 +593,17 @@ public class BasicFrame extends JFrame {
|
|||||||
Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
|
Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
|
||||||
|
|
||||||
File[] files = chooser.getSelectedFiles();
|
File[] files = chooser.getSelectedFiles();
|
||||||
boolean opened = false;
|
|
||||||
|
|
||||||
for (File file: files) {
|
for (File file: files) {
|
||||||
System.out.println("Opening file: " + file);
|
System.out.println("Opening file: " + file);
|
||||||
if (open(file, this)) {
|
if (open(file, this)) {
|
||||||
opened = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close this frame if replaceable and file opened successfully
|
// Close previous window if replacing
|
||||||
if (replaceable && opened) {
|
if (replaceable && document.isSaved()) {
|
||||||
closeAction();
|
closeAction();
|
||||||
|
replaceable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,23 +616,58 @@ public class BasicFrame extends JFrame {
|
|||||||
* @param parent the parent component for which a progress dialog is opened.
|
* @param parent the parent component for which a progress dialog is opened.
|
||||||
* @return whether the file was successfully loaded and opened.
|
* @return whether the file was successfully loaded and opened.
|
||||||
*/
|
*/
|
||||||
private static boolean open(File file, Component parent) {
|
private static boolean open(File file, Window parent) {
|
||||||
OpenRocketDocument doc = null;
|
|
||||||
|
|
||||||
|
// Open the file in a Swing worker thread
|
||||||
|
OpenFileWorker worker = new OpenFileWorker(file);
|
||||||
|
if (!SwingWorkerDialog.runWorker(parent, "Opening file",
|
||||||
|
"Reading " + file.getName() + "...", worker)) {
|
||||||
|
|
||||||
try {
|
// User cancelled the operation
|
||||||
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();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc == null) {
|
|
||||||
throw new RuntimeException("BUG: Rocket loader returned null");
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Show warnings
|
// Show warnings
|
||||||
Iterator<Warning> warns = ROCKET_LOADER.getWarnings().iterator();
|
Iterator<Warning> warns = ROCKET_LOADER.getWarnings().iterator();
|
||||||
System.out.println("Warnings:");
|
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() {
|
private boolean saveAsAction() {
|
||||||
File file = null;
|
File file = null;
|
||||||
while (file == null) {
|
while (file == null) {
|
||||||
StorageOptionChooser storageChooser =
|
StorageOptionChooser storageChooser =
|
||||||
new StorageOptionChooser(document.getDefaultStorageOptions());
|
new StorageOptionChooser(document, document.getDefaultStorageOptions());
|
||||||
JFileChooser chooser = new JFileChooser();
|
JFileChooser chooser = new JFileChooser();
|
||||||
chooser.setFileFilter(ROCKET_DESIGN_FILTER);
|
chooser.setFileFilter(ROCKET_DESIGN_FILTER);
|
||||||
chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
|
chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
|
||||||
@ -738,18 +755,39 @@ public class BasicFrame extends JFrame {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RocketSaver saver = new OpenRocketSaver();
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saver.save(file, document);
|
worker.get();
|
||||||
document.setFile(file);
|
document.setFile(file);
|
||||||
document.setSaved(true);
|
document.setSaved(true);
|
||||||
saved = true;
|
saved = true;
|
||||||
} catch (IOException e) {
|
setTitle();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
Throwable cause = e.getCause();
|
||||||
|
|
||||||
|
if (cause instanceof IOException) {
|
||||||
JOptionPane.showMessageDialog(this, new String[] {
|
JOptionPane.showMessageDialog(this, new String[] {
|
||||||
"An I/O error occurred while saving:",
|
"An I/O error occurred while saving:",
|
||||||
e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
|
e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Unknown error when saving file", e);
|
||||||
}
|
}
|
||||||
setTitle();
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException("EDT was interrupted", e);
|
||||||
|
}
|
||||||
|
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -787,6 +825,16 @@ public class BasicFrame extends JFrame {
|
|||||||
return true;
|
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.
|
* 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
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
in.close();
|
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