Merge pull request #665 from wolsen/add-print-progress-dialog
[Fixes 637] Update print dialog to allow simulation control
This commit is contained in:
commit
16300941c7
@ -115,6 +115,7 @@ AboutDialog.lbl.translatorIcon =
|
||||
PrintDialog.title = Print or export
|
||||
PrintDialog.but.previewAndPrint = Preview & Print
|
||||
PrintDialog.checkbox.showByStage = Show by stage
|
||||
PrintDialog.checkbox.updateSimulations = Update simulation data
|
||||
PrintDialog.lbl.selectElements = Select elements to include:
|
||||
printdlg.but.saveaspdf = Save as PDF
|
||||
printdlg.but.preview = Preview
|
||||
|
@ -50,6 +50,8 @@ import net.sf.openrocket.startup.Application;
|
||||
*/
|
||||
public class PrintDialog extends JDialog implements TreeSelectionListener {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PrintDialog.class);
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
@ -65,6 +67,8 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
|
||||
private JButton cancel;
|
||||
|
||||
private double rotation = 0d;
|
||||
|
||||
private boolean updateSimulations = true;
|
||||
|
||||
private final static SwingPreferences prefs = (SwingPreferences) Application.getPreferences();
|
||||
|
||||
@ -122,6 +126,19 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
|
||||
|
||||
|
||||
// Checkboxes and buttons
|
||||
final JPanel optionsPanel = new JPanel(new MigLayout());
|
||||
|
||||
final JCheckBox updateSimulationsCheckbox = new JCheckBox(trans.get("checkbox.updateSimulations"));
|
||||
updateSimulationsCheckbox.setEnabled(true);
|
||||
updateSimulationsCheckbox.setSelected(this.updateSimulations);
|
||||
updateSimulationsCheckbox.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
updateSimulations = updateSimulationsCheckbox.isSelected();
|
||||
}
|
||||
});
|
||||
optionsPanel.add(updateSimulationsCheckbox, "pad 0, grow, wrap");
|
||||
|
||||
final JCheckBox sortByStage = new JCheckBox(trans.get("checkbox.showByStage"));
|
||||
sortByStage.setEnabled(stages > 1);
|
||||
sortByStage.setSelected(stages > 1);
|
||||
@ -142,10 +159,10 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
|
||||
}
|
||||
}
|
||||
});
|
||||
panel.add(sortByStage, "aligny top, split");
|
||||
optionsPanel.add(sortByStage);
|
||||
panel.add(optionsPanel, "pad 0, aligny top, split");
|
||||
|
||||
|
||||
panel.add(new JPanel(), "growx");
|
||||
panel.add(new JPanel(), "pad 0, aligny top, growx");
|
||||
|
||||
|
||||
JButton settingsButton = new JButton(trans.get("printdlg.but.settings"));
|
||||
@ -159,8 +176,8 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
|
||||
setPrintSettings(settings);
|
||||
}
|
||||
});
|
||||
panel.add(settingsButton, "wrap para");
|
||||
|
||||
panel.add(settingsButton, "aligny top, wrap para");
|
||||
|
||||
|
||||
previewButton = new JButton(trans.get("but.previewAndPrint"));
|
||||
previewButton.addActionListener(new ActionListener() {
|
||||
@ -286,7 +303,10 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
|
||||
*/
|
||||
private File generateReport(File f, PrintSettings settings) throws IOException {
|
||||
Iterator<PrintableContext> toBePrinted = currentTree.getToBePrinted();
|
||||
new PrintController().print(document, toBePrinted, new FileOutputStream(f), settings, rotation);
|
||||
PrintController controller = new PrintController();
|
||||
controller.setWindow(this.getOwner());
|
||||
controller.print(document, toBePrinted, new FileOutputStream(f),
|
||||
settings, rotation, updateSimulations);
|
||||
return f;
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,15 @@
|
||||
package net.sf.openrocket.gui.print;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Window;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -27,6 +34,7 @@ import net.sf.openrocket.formatting.RocketDescriptor;
|
||||
import net.sf.openrocket.gui.figureelements.FigureElement;
|
||||
import net.sf.openrocket.gui.figureelements.RocketInfo;
|
||||
import net.sf.openrocket.gui.scalefigure.RocketPanel;
|
||||
import net.sf.openrocket.gui.simulation.SimulationRunDialog;
|
||||
import net.sf.openrocket.masscalc.MassCalculator;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.motor.Motor;
|
||||
@ -38,6 +46,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.simulation.FlightData;
|
||||
import net.sf.openrocket.simulation.FlightDataBranch;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.unit.Unit;
|
||||
@ -107,6 +116,21 @@ public class DesignReport {
|
||||
*/
|
||||
private double rotation = 0d;
|
||||
|
||||
/**
|
||||
* Determines whether or not to run out of date simulations.
|
||||
*/
|
||||
private boolean runOutOfDateSimulations = true;
|
||||
|
||||
/**
|
||||
* Determines whether or not to update existing simulations.
|
||||
*/
|
||||
private boolean updateExistingSimulations = false;
|
||||
|
||||
/**
|
||||
* Parent window for showing simulation run dialog as necessary
|
||||
*/
|
||||
private Window window = null;
|
||||
|
||||
/** The displayed strings. */
|
||||
private static final String STAGES = "Stages: ";
|
||||
private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
|
||||
@ -135,17 +159,45 @@ public class DesignReport {
|
||||
private static final double GRAVITY_CONSTANT = 9.80665d;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Creates a new DesignReport in the iTextPDF Document based on the
|
||||
* OpenRocketDocument specified. All out of date simulations will be
|
||||
* run as part of generating the iTextPDF report.
|
||||
*
|
||||
* This is for backwards API compatibility and will copy existing
|
||||
* simulations before running them.
|
||||
*
|
||||
* @param theRocDoc the OpenRocketDocument which serves as the source
|
||||
* of the rocket information
|
||||
* @param theIDoc the iTextPDF Document where the DesignReport is written
|
||||
* @param figureRotation the rotation of the figure used for displaying
|
||||
* the profile view.
|
||||
*/
|
||||
public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc, Double figureRotation) {
|
||||
this(theRocDoc, theIDoc, figureRotation, true, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DesignReport in the iTextPDF Document based on the
|
||||
* OpenRocketDocument specified. Out of date simulations will be run
|
||||
* when the runOutOfDateSims parameter is set to true.
|
||||
*
|
||||
* @param theRocDoc the OR document
|
||||
* @param theIDoc the iText document
|
||||
* @param figureRotation the angle the figure is rotated on the screen; printed report will mimic
|
||||
* @param runOutOfDateSims whether or not to run simulations that are not up to date.
|
||||
* @param updateExistingSims whether or not to update existing simulations or to copy the simulations.
|
||||
* Previous behavior was to copy existing simulations.
|
||||
* @param window the base AWT window to use
|
||||
*/
|
||||
public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc, Double figureRotation) {
|
||||
public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc, Double figureRotation,
|
||||
boolean runOutOfDateSims, boolean updateExistingSims, Window window) {
|
||||
document = theIDoc;
|
||||
rocketDocument = theRocDoc;
|
||||
panel = new RocketPanel(rocketDocument);
|
||||
rotation = figureRotation;
|
||||
this.runOutOfDateSimulations = runOutOfDateSims;
|
||||
this.updateExistingSimulations = updateExistingSims;
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -226,7 +278,7 @@ public class DesignReport {
|
||||
paragraph.setSpacingAfter(heightOfDiagramAndText);
|
||||
document.add(paragraph);
|
||||
|
||||
List<Simulation> simulations = rocketDocument.getSimulations();
|
||||
List<Simulation> simulations = getSimulations();
|
||||
|
||||
boolean firstMotor = true;
|
||||
for (FlightConfigurationId fcid : rocket.getIds()) {
|
||||
@ -242,7 +294,7 @@ public class DesignReport {
|
||||
*/
|
||||
int leading = (firstMotor) ? 0 : 25;
|
||||
|
||||
FlightData flight = findSimulation( fcid, simulations);
|
||||
FlightData flight = findSimulation(fcid, simulations);
|
||||
addFlightData(flight, rocket, fcid, parent, leading);
|
||||
addMotorData(rocket, fcid, parent);
|
||||
document.add(parent);
|
||||
@ -456,6 +508,10 @@ public class DesignReport {
|
||||
// Output the flight data
|
||||
if (flight != null) {
|
||||
try {
|
||||
FlightDataBranch branch = new FlightDataBranch();
|
||||
if (flight.getBranchCount() > 0) {
|
||||
branch = flight.getBranch(0);
|
||||
}
|
||||
final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
|
||||
final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
|
||||
final Unit flightTimeUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
|
||||
@ -482,7 +538,7 @@ public class DesignReport {
|
||||
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2));
|
||||
|
||||
labelTable.addCell(ITextHelper.createCell(OPTIMUM_DELAY, 2, 2));
|
||||
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getBranch(0).getOptimumDelay()), 2, 2));
|
||||
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(branch.getOptimumDelay()), 2, 2));
|
||||
|
||||
labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
|
||||
labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));
|
||||
@ -520,22 +576,160 @@ public class DesignReport {
|
||||
private FlightData findSimulation(final FlightConfigurationId motorId, List<Simulation> simulations) {
|
||||
// Perform flight simulation
|
||||
FlightData flight = null;
|
||||
try {
|
||||
for (int i = 0; i < simulations.size(); i++) {
|
||||
Simulation simulation = simulations.get(i);
|
||||
if (Utils.equals(simulation.getId(), motorId)) {
|
||||
simulation = simulation.copy();
|
||||
simulation.simulate();
|
||||
flight = simulation.getSimulatedData();
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < simulations.size(); i++) {
|
||||
Simulation simulation = simulations.get(i);
|
||||
if (Utils.equals(simulation.getId(), motorId)) {
|
||||
flight = simulation.getSimulatedData();
|
||||
break;
|
||||
}
|
||||
} catch (SimulationException e1) {
|
||||
// Ignore
|
||||
}
|
||||
return flight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Simulations to use for printing the design report
|
||||
* for the rocket and optionally re-run out of date simulations.
|
||||
*
|
||||
* If the user has selected to not run any simulations, this method will
|
||||
* simply return the simulations found in the OpenRocketDocument.
|
||||
*
|
||||
* If the user has selected to run simulations, this method will identify
|
||||
* any simulations which are not up to date and re-run them.
|
||||
*
|
||||
* @return a list of Simulations to include in the DesignReport.
|
||||
*/
|
||||
protected List<Simulation> getSimulations() {
|
||||
List<Simulation> simulations = rocketDocument.getSimulations();
|
||||
if (!runOutOfDateSimulations) {
|
||||
log.debug("Using current simulations for rocket.");
|
||||
return simulations;
|
||||
}
|
||||
|
||||
ArrayList<Simulation> simulationsToRun = new ArrayList<Simulation>();
|
||||
ArrayList<Simulation> upToDateSimulations = new ArrayList<Simulation>();
|
||||
for (Simulation simulation : simulations) {
|
||||
boolean simulate = false;
|
||||
boolean copy = !this.updateExistingSimulations;
|
||||
|
||||
switch (simulation.getStatus()) {
|
||||
case CANT_RUN:
|
||||
log.warn("Simulation " + simulation.getId() + " has no motors, skipping");
|
||||
// Continue so we don't simulate
|
||||
continue;
|
||||
case UPTODATE:
|
||||
log.trace("Simulation " + simulation.getId() + "is up to date, not running simulation");
|
||||
simulate = false;
|
||||
break;
|
||||
case NOT_SIMULATED:
|
||||
case OUTDATED:
|
||||
case LOADED:
|
||||
log.trace("Running simulation for " + simulation.getId());
|
||||
simulate = true;
|
||||
break;
|
||||
case EXTERNAL:
|
||||
log.trace("Simulation " + simulation.getId() + " is external. Using data provided");
|
||||
simulate = false;
|
||||
break;
|
||||
default:
|
||||
log.trace("Running simulation for " + simulation.getId());
|
||||
simulate = true;
|
||||
copy = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!simulate) {
|
||||
upToDateSimulations.add(simulation);
|
||||
} else if (copy) {
|
||||
simulationsToRun.add(simulation.copy());
|
||||
} else {
|
||||
simulationsToRun.add(simulation);
|
||||
}
|
||||
}
|
||||
|
||||
/* Run any simulations that are pending a run. This is done via the
|
||||
* SimulationRunDialog in order to provide user feedback.
|
||||
*/
|
||||
if (!simulationsToRun.isEmpty()) {
|
||||
runSimulations(simulationsToRun);
|
||||
upToDateSimulations.addAll(simulationsToRun);
|
||||
}
|
||||
|
||||
return upToDateSimulations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the selected set of simulations. If a valid Window was provided when
|
||||
* creating this DesignReport, this method will run simulations using the
|
||||
* SimulationRunDialog in order to present status to the user.
|
||||
*
|
||||
* @param simulations a list of Simulations to run
|
||||
*/
|
||||
protected void runSimulations(List<Simulation> simulations) {
|
||||
if (window != null) {
|
||||
log.debug("Updating " + simulations.size() + "simulations using SimulationRunDialog");
|
||||
Simulation[] runMe = simulations.toArray(new Simulation[simulations.size()]);
|
||||
new SimulationRunDialog(window, rocketDocument, runMe).setVisible(true);
|
||||
} else {
|
||||
/* This code is left for compatibility with any developers who are
|
||||
* using the API to generate design reports. This may not be running
|
||||
* graphically and the SimulationRunDialog may not be available for
|
||||
* displaying progress information/updating simulations.
|
||||
*/
|
||||
log.debug("Updating simulations using thread pool");
|
||||
int cores = Runtime.getRuntime().availableProcessors();
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(cores, cores, 0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
new SimulationRunnerThreadFactory());
|
||||
for (Simulation simulation : simulations) {
|
||||
executor.execute(new RunSimulationTask(simulation));
|
||||
}
|
||||
executor.shutdown();
|
||||
try {
|
||||
/* Arbitrarily wait for at most 5 minutes for the simulation
|
||||
* to complete. This seems like a long time, but in case there
|
||||
* is a really long running simulation
|
||||
*/
|
||||
executor.awaitTermination(5, TimeUnit.MINUTES);
|
||||
} catch (InterruptedException ie) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SimulationRunnerThreadFactory implements ThreadFactory {
|
||||
private ThreadFactory factory = Executors.defaultThreadFactory();
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = factory.newThread(r);
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The RunSimulationTask is responsible for running simulations within the
|
||||
* DesignReport when run outside of the SimulationRunDialog.
|
||||
*/
|
||||
private static class RunSimulationTask implements Runnable {
|
||||
|
||||
private final Simulation simulation;
|
||||
|
||||
public RunSimulationTask(final Simulation simulation) {
|
||||
this.simulation = simulation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
simulation.simulate();
|
||||
} catch (SimulationException ex) {
|
||||
log.error("Error simulating " + simulation.getId(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip [] brackets from a string.
|
||||
*
|
||||
|
@ -20,6 +20,7 @@ import net.sf.openrocket.gui.print.visitor.PageFitPrintStrategy;
|
||||
import net.sf.openrocket.gui.print.visitor.PartsDetailVisitorStrategy;
|
||||
import net.sf.openrocket.gui.print.visitor.TransitionStrategy;
|
||||
|
||||
import java.awt.Window;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
@ -30,6 +31,33 @@ import java.util.Set;
|
||||
* file.
|
||||
*/
|
||||
public class PrintController {
|
||||
|
||||
/**
|
||||
* Used for displaying progress information when the Window
|
||||
* is not null.
|
||||
*/
|
||||
private Window window = null;
|
||||
|
||||
/**
|
||||
* Sets the reference Window for displaying progress information
|
||||
* for long running print operations.
|
||||
*
|
||||
* @param window the reference window
|
||||
*/
|
||||
public void setWindow(final Window window) {
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reference Window for displaying progress information
|
||||
* for long running print operations.
|
||||
*
|
||||
* @return the reference Window for displaying progress. May be null
|
||||
* if no reference Window has been set.
|
||||
*/
|
||||
public Window getWindow() {
|
||||
return this.window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the selected components to a PDF document.
|
||||
@ -39,9 +67,10 @@ public class PrintController {
|
||||
* @param outputFile the file being written to
|
||||
* @param settings the print settings
|
||||
* @param rotation the angle the rocket figure is rotated
|
||||
* @param runSims determines whether to re-run out of date simulations or not
|
||||
*/
|
||||
public void print(OpenRocketDocument doc, Iterator<PrintableContext> toBePrinted, OutputStream outputFile,
|
||||
PrintSettings settings, double rotation) {
|
||||
PrintSettings settings, double rotation, boolean runSims) {
|
||||
|
||||
Document idoc = new Document(getSize(settings));
|
||||
PdfWriter writer = null;
|
||||
@ -51,12 +80,7 @@ public class PrintController {
|
||||
|
||||
writer.addViewerPreference(PdfName.PRINTSCALING, PdfName.NONE);
|
||||
writer.addViewerPreference(PdfName.PICKTRAYBYPDFSIZE, PdfBoolean.PDFTRUE);
|
||||
try {
|
||||
idoc.open();
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
idoc.open();
|
||||
|
||||
// Used to combine multiple components onto fewer sheets of paper
|
||||
PageFitPrintStrategy pageFitPrint = new PageFitPrintStrategy(idoc, writer);
|
||||
@ -70,7 +94,7 @@ public class PrintController {
|
||||
|
||||
switch (printableContext.getPrintable()) {
|
||||
case DESIGN_REPORT:
|
||||
DesignReport dp = new DesignReport(doc, idoc, rotation);
|
||||
DesignReport dp = new DesignReport(doc, idoc, rotation, runSims, true, this.window);
|
||||
dp.writeToDocument(writer);
|
||||
idoc.newPage();
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user