diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index be5724c5d..87cb4f676 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -430,6 +430,8 @@ simpanel.col.Motors = Motors
simpanel.col.Configuration = Configuration
simpanel.col.Velocityoffrod = Velocity off rod
simpanel.col.Velocityatdeploy = Velocity at deployment
+simpanel.col.OptimumCoastTime = Optimum delay
+simpanel.col.OptimumCoastTime.ttip = The time between last motor burnout and maximum possible altitude.
simpanel.col.Apogee = Apogee
simpanel.col.Maxvelocity = Max. velocity
simpanel.col.Maxacceleration = Max. acceleration
diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
index a3f085331..a21354f7f 100644
--- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
+++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
@@ -536,6 +536,7 @@ public class OpenRocketSaver extends RocketSaver {
StringBuilder sb = new StringBuilder();
sb.append(" 0)
sb.append(",");
@@ -590,8 +603,6 @@ public class OpenRocketSaver extends RocketSaver {
writeln("");
}
-
-
/* TODO: LOW: This is largely duplicated from above! */
private int countFlightDataBranchPoints(FlightDataBranch branch, double timeSkip) {
int count = 0;
diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java
index 546ed02f6..e8ea6b80f 100644
--- a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java
+++ b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataBranchHandler.java
@@ -42,6 +42,22 @@ class FlightDataBranchHandler extends AbstractElementHandler {
branch = new FlightDataBranch(name, types);
}
+ /**
+ * @param timeToOptimumAltitude
+ * @see net.sf.openrocket.simulation.FlightDataBranch#setTimeToOptimumAltitude(double)
+ */
+ public void setTimeToOptimumAltitude(double timeToOptimumAltitude) {
+ branch.setTimeToOptimumAltitude(timeToOptimumAltitude);
+ }
+
+ /**
+ * @param optimumAltitude
+ * @see net.sf.openrocket.simulation.FlightDataBranch#setOptimumAltitude(double)
+ */
+ public void setOptimumAltitude(double optimumAltitude) {
+ branch.setOptimumAltitude(optimumAltitude);
+ }
+
// Find the full flight data type given name only
// Note: this way of doing it requires that custom expressions always come before flight data in the file,
// not the nicest but this is always the case anyway.
diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java
index bb2a78976..5883ba17e 100644
--- a/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java
+++ b/core/src/net/sf/openrocket/file/openrocket/importt/FlightDataHandler.java
@@ -48,6 +48,23 @@ class FlightDataHandler extends AbstractElementHandler {
dataHandler = new FlightDataBranchHandler(attributes.get("name"),
attributes.get("types"),
simHandler, context);
+
+ if (attributes.get("optimumAltitude") != null) {
+ double optimumAltitude = Double.NaN;
+ try {
+ optimumAltitude = Double.parseDouble(attributes.get("optimumAltitude"));
+ } catch (NumberFormatException ignore) {
+ }
+ dataHandler.setOptimumAltitude(optimumAltitude);
+ }
+ if (attributes.get("timeToOptimumAltitude") != null) {
+ double timeToOptimumAltitude = Double.NaN;
+ try {
+ timeToOptimumAltitude = Double.parseDouble(attributes.get("timeToOptimumAltitude"));
+ } catch (NumberFormatException ignore) {
+ }
+ dataHandler.setTimeToOptimumAltitude(timeToOptimumAltitude);
+ }
return dataHandler;
}
diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
index fb8d32020..ab3e35435 100644
--- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
+++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
@@ -21,6 +21,7 @@ import net.sf.openrocket.simulation.exception.MotorIgnitionException;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationLaunchException;
import net.sf.openrocket.simulation.listeners.SimulationListenerHelper;
+import net.sf.openrocket.simulation.listeners.system.OptimumCoastListener;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
@@ -120,8 +121,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
Coordinate originVelocity = status.getRocketVelocity();
try {
- double maxAlt = Double.NEGATIVE_INFINITY;
-
// Start the simulation
while (handleEvents()) {
@@ -149,8 +148,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.getConfiguration().getRocket(),
new Pair(oldAlt, status.getRocketPosition().z)));
- if (status.getRocketPosition().z > maxAlt) {
- maxAlt = status.getRocketPosition().z;
+ if (status.getRocketPosition().z > status.getMaxAlt()) {
+ status.setMaxAlt(status.getRocketPosition().z);
}
@@ -189,7 +188,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Check for apogee
- if (!status.isApogeeReached() && status.getRocketPosition().z < maxAlt - 0.01) {
+ if (!status.isApogeeReached() && status.getRocketPosition().z < status.getMaxAlt() - 0.01) {
+ status.setMaxAltTime(status.getSimulationTime());
addEvent(new FlightEvent(FlightEvent.Type.APOGEE, status.getSimulationTime(),
status.getConfiguration().getRocket()));
}
@@ -464,6 +464,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Mark apogee as reached
status.setApogeeReached(true);
status.getFlightData().addEvent(event);
+ // This apogee event might be the optimum if recovery has not already happened.
+ if (status.getSimulationConditions().isCalculateExtras() && status.getDeployedRecoveryDevices().size() == 0) {
+ status.getFlightData().setOptimumAltitude(status.getMaxAlt());
+ status.getFlightData().setTimeToOptimumAltitude(status.getMaxAltTime());
+ }
break;
case RECOVERY_DEVICE_DEPLOYMENT:
@@ -501,6 +506,14 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.setLiftoff(true);
status.getDeployedRecoveryDevices().add((RecoveryDevice) c);
+ // If we haven't already reached apogee, then we need to compute the actual coast time
+ // to determine the optimum altitude.
+ if (status.getSimulationConditions().isCalculateExtras() && !status.isApogeeReached()) {
+ FlightData coastStatus = computeCoastTime();
+ status.getFlightData().setOptimumAltitude(coastStatus.getMaxAltitude());
+ status.getFlightData().setTimeToOptimumAltitude(coastStatus.getTimeToApogee());
+ }
+
this.currentStepper = this.landingStepper;
this.status = currentStepper.initialize(status);
@@ -601,5 +614,17 @@ public class BasicEventSimulationEngine implements SimulationEngine {
}
}
-
+ private FlightData computeCoastTime() {
+ try {
+ SimulationConditions conds = status.getSimulationConditions().clone();
+ conds.getSimulationListenerList().add(OptimumCoastListener.INSTANCE);
+ BasicEventSimulationEngine e = new BasicEventSimulationEngine();
+
+ FlightData d = e.simulate(conds);
+ return d;
+ } catch (Exception e) {
+ log.warn("Exception computing coast time: ", e);
+ return null;
+ }
+ }
}
diff --git a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java
index 76bef7642..0f523b2f1 100644
--- a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java
+++ b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java
@@ -36,7 +36,15 @@ public class FlightDataBranch implements Monitorable {
private final Map maxValues = new HashMap();
private final Map minValues = new HashMap();
-
+ /**
+ * time for the rocket to reach apogee if the flight had been no recovery deployment
+ */
+ private double timeToOptimumAltitude = Double.NaN;
+ /**
+ * Altitude the rocket would reach if there had been no recovery deployment.
+ */
+ private double optimumAltitude = Double.NaN;
+
private final ArrayList events = new ArrayList();
private Mutable mutable = new Mutable();
@@ -73,12 +81,12 @@ public class FlightDataBranch implements Monitorable {
*/
public FlightDataBranch() {
branchName = "Empty branch";
- for (FlightDataType type : FlightDataType.ALL_TYPES){
+ for (FlightDataType type : FlightDataType.ALL_TYPES) {
this.setValue(type, Double.NaN);
}
this.immute();
}
-
+
/**
* Adds a new point into the data branch. The value for all types is set to NaN by default.
*
@@ -115,10 +123,10 @@ public class FlightDataBranch implements Monitorable {
}
values.put(type, list);
minValues.put(type, value);
- maxValues.put(type, value);
+ maxValues.put(type, value);
}
- if (list.size() > 0){
+ if (list.size() > 0) {
list.set(list.size() - 1, value);
}
@@ -219,6 +227,50 @@ public class FlightDataBranch implements Monitorable {
}
+ /**
+ * @return the timeToOptimumAltitude
+ */
+ public double getTimeToOptimumAltitude() {
+ return timeToOptimumAltitude;
+ }
+
+ /**
+ * @param timeToOptimumAltitude the timeToOptimumAltitude to set
+ */
+ public void setTimeToOptimumAltitude(double timeToOptimumAltitude) {
+ this.timeToOptimumAltitude = timeToOptimumAltitude;
+ }
+
+ /**
+ * @return the optimumAltitude
+ */
+ public double getOptimumAltitude() {
+ return optimumAltitude;
+ }
+
+ /**
+ * @param optimumAltitude the optimumAltitude to set
+ */
+ public void setOptimumAltitude(double optimumAltitude) {
+ this.optimumAltitude = optimumAltitude;
+ }
+
+ public double getOptimumDelay() {
+
+ if (Double.isNaN(timeToOptimumAltitude)) {
+ return Double.NaN;
+ }
+ // TODO - we really want the first burnout of this stage. which
+ // could be computed as the first burnout after the last stage separation event.
+ // however, that's not quite so concise
+ FlightEvent e = getLastEvent(FlightEvent.Type.BURNOUT);
+ if (e != null) {
+ return timeToOptimumAltitude - e.getTime();
+ }
+
+ return Double.NaN;
+ }
+
/**
* Add a flight event to this branch.
*
@@ -241,6 +293,34 @@ public class FlightDataBranch implements Monitorable {
return events.clone();
}
+ /**
+ * Return the first event of the given type.
+ * @param type
+ * @return
+ */
+ public FlightEvent getFirstEvent(FlightEvent.Type type) {
+ for (FlightEvent e : events) {
+ if (e.getType() == type) {
+ return e;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the last event of the given type.
+ * @param type
+ * @return
+ */
+ public FlightEvent getLastEvent(FlightEvent.Type type) {
+ FlightEvent retval = null;
+ for (FlightEvent e : events) {
+ if (e.getType() == type) {
+ retval = e;
+ }
+ }
+ return retval;
+ }
/**
* Make this FlightDataBranch immutable. Any calls to the set methods that would
diff --git a/core/src/net/sf/openrocket/simulation/SimulationConditions.java b/core/src/net/sf/openrocket/simulation/SimulationConditions.java
index ffd9e003a..61bad44c6 100644
--- a/core/src/net/sf/openrocket/simulation/SimulationConditions.java
+++ b/core/src/net/sf/openrocket/simulation/SimulationConditions.java
@@ -29,7 +29,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
private String motorID = null;
private Simulation simulation; // The parent simulation
-
+
private double launchRodLength = 1;
/** Launch rod angle >= 0, radians from vertical */
@@ -45,7 +45,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
private WorldCoordinate launchSite = new WorldCoordinate(0, 0, 0);
private GeodeticComputationStrategy geodeticComputation = GeodeticComputationStrategy.SPHERICAL;
-
+
private WindModel windModel;
private AtmosphericModel atmosphericModel;
private GravityModel gravityModel;
@@ -53,25 +53,25 @@ public class SimulationConditions implements Monitorable, Cloneable {
private AerodynamicCalculator aerodynamicCalculator;
private MassCalculator massCalculator;
-
+
private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
private double maximumAngleStep = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
/* Whether to calculate additional data or only primary simulation figures */
private boolean calculateExtras = true;
-
+
private List simulationListeners = new ArrayList();
-
+
private int randomSeed = 0;
private int modID = 0;
private int modIDadd = 0;
-
-
+
+
public AerodynamicCalculator getAerodynamicCalculator() {
return aerodynamicCalculator;
}
@@ -253,7 +253,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
}
-
+
public int getRandomSeed() {
return randomSeed;
}
@@ -267,8 +267,8 @@ public class SimulationConditions implements Monitorable, Cloneable {
public void setSimulation(Simulation sim) {
this.simulation = sim;
}
-
- public Simulation getSimulation(){
+
+ public Simulation getSimulation() {
return this.simulation;
}
@@ -291,7 +291,12 @@ public class SimulationConditions implements Monitorable, Cloneable {
public SimulationConditions clone() {
try {
// TODO: HIGH: Deep clone models
- return (SimulationConditions) super.clone();
+ SimulationConditions clone = (SimulationConditions) super.clone();
+ clone.simulationListeners = new ArrayList(this.simulationListeners.size());
+ for (SimulationListener listener : this.simulationListeners) {
+ clone.simulationListeners.add(listener.clone());
+ }
+ return clone;
} catch (CloneNotSupportedException e) {
throw new BugException(e);
}
diff --git a/core/src/net/sf/openrocket/simulation/SimulationStatus.java b/core/src/net/sf/openrocket/simulation/SimulationStatus.java
index 30fce5772..79b266a5a 100644
--- a/core/src/net/sf/openrocket/simulation/SimulationStatus.java
+++ b/core/src/net/sf/openrocket/simulation/SimulationStatus.java
@@ -27,10 +27,6 @@ import net.sf.openrocket.util.WorldCoordinate;
*/
public class SimulationStatus implements Monitorable {
- /*
- * NOTE! All fields must be added to copyFrom() method!!
- */
-
private SimulationConditions simulationConditions;
private Configuration configuration;
private MotorInstanceConfiguration motorConfiguration;
@@ -51,12 +47,12 @@ public class SimulationStatus implements Monitorable {
// Set of burnt out motors
Set motorBurntOut = new HashSet();
-
-
+
+
/** Nanosecond time when the simulation was started. */
private long simulationStartWallTime = Long.MIN_VALUE;
-
+
/** Set to true when a motor has ignited. */
private boolean motorIgnited = false;
@@ -83,38 +79,40 @@ public class SimulationStatus implements Monitorable {
/** Available for special purposes by the listeners. */
private final Map extraData = new HashMap();
-
+ double maxAlt = Double.NEGATIVE_INFINITY;
+ double maxAltTime = 0;
+
private int modID = 0;
private int modIDadd = 0;
- public SimulationStatus( Configuration configuration,
+ public SimulationStatus(Configuration configuration,
MotorInstanceConfiguration motorConfiguration,
- SimulationConditions simulationConditions ) {
+ SimulationConditions simulationConditions) {
this.simulationConditions = simulationConditions;
this.configuration = configuration;
this.motorConfiguration = motorConfiguration;
-
+
this.time = 0;
this.previousTimeStep = this.simulationConditions.getTimeStep();
this.position = Coordinate.NUL;
this.velocity = Coordinate.NUL;
this.worldPosition = this.simulationConditions.getLaunchSite();
-
+
// Initialize to roll angle with least stability w.r.t. the wind
Quaternion o;
FlightConditions cond = new FlightConditions(this.configuration);
this.simulationConditions.getAerodynamicCalculator().getWorstCP(this.configuration, cond, null);
double angle = -cond.getTheta() - this.simulationConditions.getLaunchRodDirection();
o = Quaternion.rotation(new Coordinate(0, 0, angle));
-
+
// Launch rod angle and direction
o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, this.simulationConditions.getLaunchRodAngle(), 0)));
o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, 0, this.simulationConditions.getLaunchRodDirection())));
-
+
this.orientation = o;
this.rotationVelocity = Coordinate.NUL;
-
+
/*
* Calculate the effective launch rod length taking into account launch lugs.
* If no lugs are found, assume a tower launcher of full length.
@@ -140,16 +138,16 @@ public class SimulationStatus implements Monitorable {
}
}
this.effectiveLaunchRodLength = length;
-
+
this.simulationStartWallTime = System.nanoTime();
-
+
this.motorIgnited = false;
this.liftoff = false;
this.launchRodCleared = false;
this.apogeeReached = false;
-
+
this.warnings = new WarningSet();
-
+
}
/**
@@ -163,7 +161,7 @@ public class SimulationStatus implements Monitorable {
*
* @param orig the object from which to copy
*/
- public SimulationStatus( SimulationStatus orig ) {
+ public SimulationStatus(SimulationStatus orig) {
this.simulationConditions = orig.simulationConditions.clone();
this.configuration = orig.configuration.clone();
this.motorConfiguration = orig.motorConfiguration.clone();
@@ -292,11 +290,11 @@ public class SimulationStatus implements Monitorable {
}
- public boolean addBurntOutMotor( MotorId motor ) {
+ public boolean addBurntOutMotor(MotorId motor) {
return motorBurntOut.add(motor);
}
-
-
+
+
public Quaternion getRocketOrientationQuaternion() {
return orientation;
}
@@ -384,7 +382,7 @@ public class SimulationStatus implements Monitorable {
}
- public void setTumbling( boolean tumbling ) {
+ public void setTumbling(boolean tumbling) {
this.tumbling = tumbling;
this.modID++;
}
@@ -393,6 +391,24 @@ public class SimulationStatus implements Monitorable {
return tumbling;
}
+ public double getMaxAlt() {
+ return maxAlt;
+ }
+
+ public void setMaxAlt(double maxAlt) {
+ this.maxAlt = maxAlt;
+ this.modID++;
+ }
+
+ public double getMaxAltTime() {
+ return maxAltTime;
+ }
+
+ public void setMaxAltTime(double maxAltTime) {
+ this.maxAltTime = maxAltTime;
+ this.modID++;
+ }
+
public Set getDeployedRecoveryDevices() {
return deployedRecoveryDevices;
}
@@ -477,5 +493,5 @@ public class SimulationStatus implements Monitorable {
eventQueue.getModID() + warnings.getModID());
}
-
+
}
diff --git a/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java b/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java
index c3211f90c..0c8a3d32d 100644
--- a/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java
+++ b/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java
@@ -72,7 +72,7 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
* Return an array of any flight data types this listener creates.
*/
@Override
- public FlightDataType[] getFlightDataTypes(){
+ public FlightDataType[] getFlightDataTypes() {
return new FlightDataType[] {};
}
@@ -183,4 +183,9 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
return null;
}
+ @Override
+ public AbstractSimulationListener clone() throws CloneNotSupportedException {
+ return (AbstractSimulationListener) super.clone();
+ }
+
}
diff --git a/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java b/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java
index ba727f2bb..bdbf90ffc 100644
--- a/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java
+++ b/core/src/net/sf/openrocket/simulation/listeners/SimulationListener.java
@@ -4,9 +4,13 @@ import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
-
-
-public interface SimulationListener {
+/**
+ * Listen to simulation events and possibly take action.
+ *
+ * If the implementation maintains any state, it should be properly cloned.
+ *
+ */
+public interface SimulationListener extends Cloneable {
/**
* Get the name of this simulation listener. Ideally this should be localized, as
@@ -83,5 +87,5 @@ public interface SimulationListener {
*/
public FlightDataType[] getFlightDataTypes();
-
+ public SimulationListener clone() throws CloneNotSupportedException;
}
diff --git a/core/src/net/sf/openrocket/simulation/listeners/system/OptimumCoastListener.java b/core/src/net/sf/openrocket/simulation/listeners/system/OptimumCoastListener.java
new file mode 100644
index 000000000..e6dcdc7aa
--- /dev/null
+++ b/core/src/net/sf/openrocket/simulation/listeners/system/OptimumCoastListener.java
@@ -0,0 +1,38 @@
+package net.sf.openrocket.simulation.listeners.system;
+
+import net.sf.openrocket.rocketcomponent.RecoveryDevice;
+import net.sf.openrocket.simulation.FlightEvent;
+import net.sf.openrocket.simulation.SimulationStatus;
+import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
+
+/**
+ * Simulation listener which ignores recovery deployment events and ends the simulation
+ * when apogee is reached.
+ *
+ * @author kevin
+ *
+ */
+public class OptimumCoastListener extends AbstractSimulationListener {
+
+ public static final OptimumCoastListener INSTANCE = new OptimumCoastListener();
+
+ @Override
+ public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) {
+ if (event.getType() == FlightEvent.Type.APOGEE) {
+ status.getEventQueue().add(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime()));
+ }
+ return true;
+ }
+
+ @Override
+ public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException {
+ return false;
+ }
+
+ @Override
+ public boolean isSystemListener() {
+ return true;
+ }
+
+}
diff --git a/swing/resources/datafiles/examples/A simple model rocket.ork b/swing/resources/datafiles/examples/A simple model rocket.ork
index febaadaeb..2932f25e3 100644
Binary files a/swing/resources/datafiles/examples/A simple model rocket.ork and b/swing/resources/datafiles/examples/A simple model rocket.ork differ
diff --git a/swing/resources/datafiles/examples/Apocalypse with decals.ork b/swing/resources/datafiles/examples/Apocalypse with decals.ork
index 0b368faad..57c63fdc8 100644
Binary files a/swing/resources/datafiles/examples/Apocalypse with decals.ork and b/swing/resources/datafiles/examples/Apocalypse with decals.ork differ
diff --git a/swing/resources/datafiles/examples/Boosted Dart.ork b/swing/resources/datafiles/examples/Boosted Dart.ork
index 6638ec6b9..8bd8df078 100644
Binary files a/swing/resources/datafiles/examples/Boosted Dart.ork and b/swing/resources/datafiles/examples/Boosted Dart.ork differ
diff --git a/swing/resources/datafiles/examples/Clustered rocket design.ork b/swing/resources/datafiles/examples/Clustered rocket design.ork
index 825059deb..b16f089c5 100644
Binary files a/swing/resources/datafiles/examples/Clustered rocket design.ork and b/swing/resources/datafiles/examples/Clustered rocket design.ork differ
diff --git a/swing/resources/datafiles/examples/High Power Airstart.ork b/swing/resources/datafiles/examples/High Power Airstart.ork
index 87ce4e0d1..edfb6e125 100644
Binary files a/swing/resources/datafiles/examples/High Power Airstart.ork and b/swing/resources/datafiles/examples/High Power Airstart.ork differ
diff --git a/swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork b/swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork
index 19fece7dd..d3c120da4 100644
Binary files a/swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork and b/swing/resources/datafiles/examples/Hybrid rocket with dual parachute deployment.ork differ
diff --git a/swing/resources/datafiles/examples/Preset Usage.ork b/swing/resources/datafiles/examples/Preset Usage.ork
index 932e24dcd..358f2f079 100644
Binary files a/swing/resources/datafiles/examples/Preset Usage.ork and b/swing/resources/datafiles/examples/Preset Usage.ork differ
diff --git a/swing/resources/datafiles/examples/Roll-stabilized rocket.ork b/swing/resources/datafiles/examples/Roll-stabilized rocket.ork
index 35cd43d95..4d7d84a03 100644
Binary files a/swing/resources/datafiles/examples/Roll-stabilized rocket.ork and b/swing/resources/datafiles/examples/Roll-stabilized rocket.ork differ
diff --git a/swing/resources/datafiles/examples/Simulation listeners.ork b/swing/resources/datafiles/examples/Simulation listeners.ork
index 8c1070fd5..d4fbae528 100644
Binary files a/swing/resources/datafiles/examples/Simulation listeners.ork and b/swing/resources/datafiles/examples/Simulation listeners.ork differ
diff --git a/swing/resources/datafiles/examples/TARC Payloader.ork b/swing/resources/datafiles/examples/TARC Payloader.ork
index 8508ea1a3..2d9ecbf0f 100644
Binary files a/swing/resources/datafiles/examples/TARC Payloader.ork and b/swing/resources/datafiles/examples/TARC Payloader.ork differ
diff --git a/swing/resources/datafiles/examples/Three-stage rocket.ork b/swing/resources/datafiles/examples/Three-stage rocket.ork
index 1775c6774..39b3a02f5 100644
Binary files a/swing/resources/datafiles/examples/Three-stage rocket.ork and b/swing/resources/datafiles/examples/Three-stage rocket.ork differ
diff --git a/swing/src/net/sf/openrocket/gui/adaptors/Column.java b/swing/src/net/sf/openrocket/gui/adaptors/Column.java
index 36efe2ace..1c51f98af 100644
--- a/swing/src/net/sf/openrocket/gui/adaptors/Column.java
+++ b/swing/src/net/sf/openrocket/gui/adaptors/Column.java
@@ -6,6 +6,7 @@ import javax.swing.table.TableColumnModel;
public abstract class Column {
private final String name;
+ private final String toolTip;
/**
* Create a new column with specified name. Additionally, the {@link #getValueAt(int)}
@@ -15,6 +16,17 @@ public abstract class Column {
*/
public Column(String name) {
this.name = name;
+ this.toolTip = null;
+ }
+
+ /**
+ * Create a new column with specified name and toolTip.
+ *
+ *
+ */
+ public Column(String name, String toolTip ) {
+ this.name = name;
+ this.toolTip = toolTip;
}
/**
@@ -87,5 +99,9 @@ public abstract class Column {
public Comparator getComparator() {
return null;
}
+
+ public String getToolTip() {
+ return toolTip;
+ }
}
diff --git a/swing/src/net/sf/openrocket/gui/adaptors/ColumnTable.java b/swing/src/net/sf/openrocket/gui/adaptors/ColumnTable.java
new file mode 100644
index 000000000..a1c54c2f7
--- /dev/null
+++ b/swing/src/net/sf/openrocket/gui/adaptors/ColumnTable.java
@@ -0,0 +1,28 @@
+package net.sf.openrocket.gui.adaptors;
+
+import java.awt.event.MouseEvent;
+
+import javax.swing.JTable;
+import javax.swing.table.JTableHeader;
+
+public class ColumnTable extends JTable {
+
+ public ColumnTable( ColumnTableModel model ) {
+ super(model);
+ }
+
+ @Override
+ protected JTableHeader createDefaultTableHeader() {
+ return new JTableHeader( columnModel ) {
+ public String getToolTipText(MouseEvent e) {
+ String tip = null;
+ java.awt.Point p = e.getPoint();
+ int index = columnModel.getColumnIndexAtX(p.x);
+ int realIndex = columnModel.getColumn(index).getModelIndex();
+ tip = ((ColumnTableModel) getModel()).getColumn(realIndex).getToolTip();
+ return tip;
+ }
+ };
+ }
+
+}
diff --git a/swing/src/net/sf/openrocket/gui/adaptors/ValueColumn.java b/swing/src/net/sf/openrocket/gui/adaptors/ValueColumn.java
index e2922f0af..052f9b975 100644
--- a/swing/src/net/sf/openrocket/gui/adaptors/ValueColumn.java
+++ b/swing/src/net/sf/openrocket/gui/adaptors/ValueColumn.java
@@ -15,6 +15,11 @@ public abstract class ValueColumn extends Column {
this.unit = unit;
}
+ public ValueColumn( String name, String toolTip, UnitGroup unit ) {
+ super( name, toolTip );
+ this.unit = unit;
+ }
+
@Override
public Object getValueAt(int row) {
Double d = valueAt(row);
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java
index 9a2320bf7..5f2d90b3a 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java
@@ -43,6 +43,7 @@ import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.gui.adaptors.Column;
+import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.adaptors.FlightConfigurationModel;
@@ -243,7 +244,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
}
};
- table = new JTable(cpTableModel);
+ table = new ColumnTable(cpTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK);
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java
index 706014fcf..b39d683bb 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java
@@ -40,6 +40,7 @@ import javax.swing.table.TableRowSorter;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.adaptors.Column;
+import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.components.SelectableLabel;
import net.sf.openrocket.gui.util.GUIUtil;
@@ -266,7 +267,7 @@ public class DebugLogDialog extends JDialog {
}
};
- table = new JTable(model);
+ table = new ColumnTable(model);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK);
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java
index 41aecb430..e3b1d1278 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java
@@ -23,6 +23,7 @@ import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.database.Database;
import net.sf.openrocket.database.Databases;
import net.sf.openrocket.gui.adaptors.Column;
+import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style;
@@ -108,7 +109,7 @@ public class MaterialEditPanel extends JPanel {
}
};
- table = new JTable(model);
+ table = new ColumnTable(model);
model.setColumnWidths(table.getColumnModel());
table.setAutoCreateRowSorter(true);
table.setDefaultRenderer(Object.class, new MaterialCellRenderer());
diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
index 4a238463a..c25d19eb1 100644
--- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
+++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
@@ -35,6 +35,7 @@ import net.sf.openrocket.document.events.DocumentChangeListener;
import net.sf.openrocket.document.events.SimulationChangeEvent;
import net.sf.openrocket.formatting.RocketDescriptor;
import net.sf.openrocket.gui.adaptors.Column;
+import net.sf.openrocket.gui.adaptors.ColumnTable;
import net.sf.openrocket.gui.adaptors.ColumnTableModel;
import net.sf.openrocket.gui.adaptors.ColumnTableRowSorter;
import net.sf.openrocket.gui.adaptors.ValueColumn;
@@ -48,6 +49,7 @@ import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.unit.UnitGroup;
@@ -59,37 +61,37 @@ import org.slf4j.LoggerFactory;
public class SimulationPanel extends JPanel {
private static final Logger log = LoggerFactory.getLogger(SimulationPanel.class);
private static final Translator trans = Application.getTranslator();
-
-
+
+
private static final Color WARNING_COLOR = Color.RED;
private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark
-
+
private static final Color OK_COLOR = new Color(60, 150, 0);
private static final String OK_TEXT = "\u2714"; // Heavy check mark
-
-
+
+
private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class);
-
-
+
+
private final OpenRocketDocument document;
-
+
private final ColumnTableModel simulationTableModel;
private final JTable simulationTable;
-
+
private final JButton editButton;
private final JButton runButton;
private final JButton deleteButton;
private final JButton plotButton;
-
+
public SimulationPanel(OpenRocketDocument doc) {
super(new MigLayout("fill", "[grow][][][][][][grow]"));
-
+
this.document = doc;
-
-
-
+
+
+
//////// The simulation action buttons
-
+
//// New simulation button
{
JButton button = new JButton(trans.get("simpanel.but.newsimulation"));
@@ -100,19 +102,19 @@ public class SimulationPanel extends JPanel {
public void actionPerformed(ActionEvent e) {
Simulation sim = new Simulation(document.getRocket());
sim.setName(document.getNextSimulationName());
-
+
int n = document.getSimulationCount();
document.addSimulation(sim);
simulationTableModel.fireTableDataChanged();
simulationTable.clearSelection();
simulationTable.addRowSelectionInterval(n, n);
-
+
openDialog(false, sim);
}
});
this.add(button, "skip 1, gapright para");
}
-
+
//// Edit simulation button
editButton = new JButton(trans.get("simpanel.but.editsimulation"));
//// Edit the selected simulation
@@ -133,7 +135,7 @@ public class SimulationPanel extends JPanel {
}
});
this.add(editButton, "gapright para");
-
+
//// Run simulations
runButton = new JButton(trans.get("simpanel.but.runsimulations"));
//// Re-run the selected simulations
@@ -150,7 +152,7 @@ public class SimulationPanel extends JPanel {
selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
sims[i] = document.getSimulation(selection[i]);
}
-
+
long t = System.currentTimeMillis();
new SimulationRunDialog(SwingUtilities.getWindowAncestor(
SimulationPanel.this), document, sims).setVisible(true);
@@ -159,7 +161,7 @@ public class SimulationPanel extends JPanel {
}
});
this.add(runButton, "gapright para");
-
+
//// Delete simulations button
deleteButton = new JButton(trans.get("simpanel.but.deletesimulations"));
//// Delete the selected simulations
@@ -174,34 +176,34 @@ public class SimulationPanel extends JPanel {
// Verify deletion
boolean verify = Application.getPreferences().getBoolean(Preferences.CONFIRM_DELETE_SIMULATION, true);
if (verify) {
-
+
JPanel panel = new JPanel(new MigLayout());
//// Do not ask me again
JCheckBox dontAsk = new JCheckBox(trans.get("simpanel.checkbox.donotask"));
panel.add(dontAsk, "wrap");
//// You can change the default operation in the preferences.
panel.add(new StyledLabel(trans.get("simpanel.lbl.defpref"), -2));
-
+
int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
new Object[] {
- //// Delete the selected simulations?
- trans.get("simpanel.dlg.lbl.DeleteSim1"),
- //// This operation cannot be undone.
- trans.get("simpanel.dlg.lbl.DeleteSim2"),
- "",
- panel },
+ //// Delete the selected simulations?
+ trans.get("simpanel.dlg.lbl.DeleteSim1"),
+ //// This operation cannot be undone.
+ trans.get("simpanel.dlg.lbl.DeleteSim2"),
+ "",
+ panel },
//// Delete simulations
trans.get("simpanel.dlg.lbl.DeleteSim3"),
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE);
if (ret != JOptionPane.OK_OPTION)
return;
-
+
if (dontAsk.isSelected()) {
Application.getPreferences().putBoolean(Preferences.CONFIRM_DELETE_SIMULATION, false);
}
}
-
+
// Delete simulations
for (int i = 0; i < selection.length; i++) {
selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
@@ -214,7 +216,7 @@ public class SimulationPanel extends JPanel {
}
});
this.add(deleteButton, "gapright para");
-
+
//// Plot / export button
plotButton = new JButton(trans.get("simpanel.but.plotexport"));
// button = new JButton("Plot flight");
@@ -228,58 +230,58 @@ public class SimulationPanel extends JPanel {
selected = simulationTable.convertRowIndexToModel(selected);
simulationTable.clearSelection();
simulationTable.addRowSelectionInterval(selected, selected);
-
-
+
+
Simulation sim = document.getSimulations().get(selected);
-
+
if (!sim.hasSimulationData()) {
new SimulationRunDialog(SwingUtilities.getWindowAncestor(
SimulationPanel.this), document, sim).setVisible(true);
}
-
+
fireMaintainSelection();
-
+
openDialog(true, sim);
-
+
}
});
this.add(plotButton, "wrap para");
-
-
-
+
+
+
//////// The simulation table
-
+
simulationTableModel = new ColumnTableModel(
-
+
//// Status and warning column
new Column("") {
private JLabel label = null;
-
+
@Override
public Object getValueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
-
+
// Initialize the label
if (label == null) {
label = new StyledLabel(2f);
label.setIconTextGap(1);
// label.setFont(label.getFont().deriveFont(Font.BOLD));
}
-
+
// Set simulation status icon
Simulation.Status status = document.getSimulation(row).getStatus();
label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
-
-
+
+
// Set warning marker
if (status == Simulation.Status.NOT_SIMULATED ||
status == Simulation.Status.EXTERNAL) {
-
+
label.setText("");
-
+
} else {
-
+
WarningSet w = document.getSimulation(row).getSimulatedWarnings();
if (w == null) {
label.setText("");
@@ -291,21 +293,21 @@ public class SimulationPanel extends JPanel {
label.setText(WARNING_TEXT);
}
}
-
+
return label;
}
-
+
@Override
public int getExactWidth() {
return 36;
}
-
+
@Override
public Class> getColumnClass() {
return JLabel.class;
}
},
-
+
//// Simulation name
//// Name
new Column(trans.get("simpanel.col.Name")) {
@@ -315,18 +317,18 @@ public class SimulationPanel extends JPanel {
return null;
return document.getSimulation(row).getName();
}
-
+
@Override
public int getDefaultWidth() {
return 125;
}
-
+
@Override
public Comparator getComparator() {
return new AlphanumComparator();
}
},
-
+
//// Simulation configuration
new Column(trans.get("simpanel.col.Configuration")) {
@Override
@@ -336,144 +338,165 @@ public class SimulationPanel extends JPanel {
Configuration c = document.getSimulation(row).getConfiguration();
return descriptor.format(c.getRocket(), c.getFlightConfigurationID());
}
-
+
@Override
public int getDefaultWidth() {
return 125;
}
},
-
+
//// Launch rod velocity
new ValueColumn(trans.get("simpanel.col.Velocityoffrod"), UnitGroup.UNITS_VELOCITY) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
-
+
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null)
return null;
-
+
return data.getLaunchRodVelocity();
}
-
+
},
-
+
//// Apogee
new ValueColumn(trans.get("simpanel.col.Apogee"), UnitGroup.UNITS_DISTANCE) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
-
+
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null)
return null;
-
+
return data.getMaxAltitude();
}
},
-
+
//// Velocity at deployment
new ValueColumn(trans.get("simpanel.col.Velocityatdeploy"), UnitGroup.UNITS_VELOCITY) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
-
+
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null)
return null;
-
+
return data.getDeploymentVelocity();
}
},
-
+
+ //// Deployment Time from Apogee
+ new ValueColumn(trans.get("simpanel.col.OptimumCoastTime"),
+ trans.get("simpanel.col.OptimumCoastTime.ttip"),
+ UnitGroup.UNITS_SHORT_TIME) {
+ @Override
+ public Double valueAt(int row) {
+ if (row < 0 || row >= document.getSimulationCount())
+ return null;
+
+ FlightData data = document.getSimulation(row).getSimulatedData();
+ if (data == null || data.getBranchCount() == 0)
+ return null;
+
+ double val = data.getBranch(0).getOptimumDelay();
+ if ( Double.isNaN(val) ) {
+ return null;
+ }
+ return val;
+ }
+ },
+
//// Maximum velocity
new ValueColumn(trans.get("simpanel.col.Maxvelocity"), UnitGroup.UNITS_VELOCITY) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
-
+
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null)
return null;
-
+
return data.getMaxVelocity();
}
},
-
+
//// Maximum acceleration
new ValueColumn(trans.get("simpanel.col.Maxacceleration"), UnitGroup.UNITS_ACCELERATION) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
-
+
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null)
return null;
-
+
return data.getMaxAcceleration();
}
},
-
+
//// Time to apogee
new ValueColumn(trans.get("simpanel.col.Timetoapogee"), UnitGroup.UNITS_FLIGHT_TIME) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
-
+
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null)
return null;
-
+
return data.getTimeToApogee();
}
},
-
+
//// Flight time
new ValueColumn(trans.get("simpanel.col.Flighttime"), UnitGroup.UNITS_FLIGHT_TIME) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
-
+
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null)
return null;
-
+
return data.getFlightTime();
}
},
-
+
//// Ground hit velocity
new ValueColumn(trans.get("simpanel.col.Groundhitvelocity"), UnitGroup.UNITS_VELOCITY) {
@Override
public Double valueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
-
+
FlightData data = document.getSimulation(row).getSimulatedData();
if (data == null)
return null;
-
+
return data.getGroundHitVelocity();
}
}
-
+
) {
- @Override
- public int getRowCount() {
- return document.getSimulationCount();
- }
- };
-
+ @Override
+ public int getRowCount() {
+ return document.getSimulationCount();
+ }
+ };
+
// Override processKeyBinding so that the JTable does not catch
// key bindings used in menu accelerators
- simulationTable = new JTable(simulationTableModel) {
+ simulationTable = new ColumnTable(simulationTableModel) {
@Override
protected boolean processKeyBinding(KeyStroke ks,
KeyEvent e,
@@ -487,8 +510,8 @@ public class SimulationPanel extends JPanel {
simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
-
-
+
+
// Mouse listener to act on double-clicks
simulationTable.addMouseListener(new MouseAdapter() {
@Override
@@ -499,15 +522,15 @@ public class SimulationPanel extends JPanel {
return;
}
int selected = simulationTable.convertRowIndexToModel(selectedRow);
-
+
int column = simulationTable.columnAtPoint(e.getPoint());
if (column == 0) {
SimulationWarningDialog.showWarningDialog(SimulationPanel.this, document.getSimulations().get(selected));
} else {
-
+
simulationTable.clearSelection();
simulationTable.addRowSelectionInterval(selectedRow, selectedRow);
-
+
openDialog(document.getSimulations().get(selected));
}
} else {
@@ -515,7 +538,7 @@ public class SimulationPanel extends JPanel {
}
}
});
-
+
document.addDocumentChangeListener(new DocumentChangeListener() {
@Override
public void documentChanged(DocumentChangeEvent event) {
@@ -524,10 +547,10 @@ public class SimulationPanel extends JPanel {
simulationTableModel.fireTableDataChanged();
}
});
-
-
-
-
+
+
+
+
// Fire table change event when the rocket changes
document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
@Override
@@ -535,14 +558,14 @@ public class SimulationPanel extends JPanel {
fireMaintainSelection();
}
});
-
-
+
+
JScrollPane scrollpane = new JScrollPane(simulationTable);
this.add(scrollpane, "spanx, grow, wrap rel");
-
+
updateButtonStates();
}
-
+
private void updateButtonStates() {
int[] selection = simulationTable.getSelectedRows();
if (selection.length == 0) {
@@ -560,13 +583,13 @@ public class SimulationPanel extends JPanel {
runButton.setEnabled(true);
deleteButton.setEnabled(true);
}
-
+
}
-
+
public ListSelectionModel getSimulationListSelectionModel() {
return simulationTable.getSelectionModel();
}
-
+
private void openDialog(boolean plotMode, final Simulation... sims) {
SimulationEditDialog d = new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, sims);
if (plotMode) {
@@ -575,7 +598,7 @@ public class SimulationPanel extends JPanel {
d.setVisible(true);
fireMaintainSelection();
}
-
+
private void openDialog(final Simulation sim) {
boolean plotMode = false;
if (sim.hasSimulationData() && (sim.getStatus() == Status.UPTODATE || sim.getStatus() == Status.EXTERNAL)) {
@@ -583,7 +606,7 @@ public class SimulationPanel extends JPanel {
}
openDialog(plotMode, sim);
}
-
+
private void fireMaintainSelection() {
int[] selection = simulationTable.getSelectedRows();
simulationTableModel.fireTableDataChanged();
@@ -593,24 +616,24 @@ public class SimulationPanel extends JPanel {
simulationTable.addRowSelectionInterval(row, row);
}
}
-
+
private enum SimulationTableColumns {
-
+
}
-
+
private class JLabelRenderer extends DefaultTableCellRenderer {
-
+
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
-
+
if (row < 0 || row >= document.getSimulationCount())
return super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
row = table.getRowSorter().convertRowIndexToModel(row);
-
+
// A JLabel is self-contained and has set its own tool tip
if (value instanceof JLabel) {
JLabel label = (JLabel) value;
@@ -619,66 +642,66 @@ public class SimulationPanel extends JPanel {
else
label.setBackground(table.getBackground());
label.setOpaque(true);
-
+
label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
return label;
}
-
+
Component component = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
-
+
if (component instanceof JComponent) {
((JComponent) component).setToolTipText(getSimulationToolTip(
document.getSimulation(row)));
}
return component;
}
-
+
private String getSimulationToolTip(Simulation sim) {
String tip;
FlightData data = sim.getSimulatedData();
-
+
tip = "" + sim.getName() + "
";
switch (sim.getStatus()) {
case UPTODATE:
tip += trans.get("simpanel.ttip.uptodate") + "
";
break;
-
+
case LOADED:
tip += trans.get("simpanel.ttip.loaded") + "
";
break;
-
+
case OUTDATED:
tip += trans.get("simpanel.ttip.outdated") + "
";
break;
-
+
case EXTERNAL:
tip += trans.get("simpanel.ttip.external") + "
";
return tip;
-
+
case NOT_SIMULATED:
tip += trans.get("simpanel.ttip.notSimulated");
return tip;
}
-
+
if (data == null) {
tip += trans.get("simpanel.ttip.noData");
return tip;
}
WarningSet warnings = data.getWarningSet();
-
+
if (warnings.isEmpty()) {
tip += trans.get("simpanel.ttip.noWarnings");
return tip;
}
-
+
tip += trans.get("simpanel.ttip.warnings");
for (Warning w : warnings) {
tip += "
" + w.toString();
}
-
+
return tip;
}
-
+
}
}
diff --git a/swing/src/net/sf/openrocket/gui/print/DesignReport.java b/swing/src/net/sf/openrocket/gui/print/DesignReport.java
index 776a79379..550381a1c 100644
--- a/swing/src/net/sf/openrocket/gui/print/DesignReport.java
+++ b/swing/src/net/sf/openrocket/gui/print/DesignReport.java
@@ -125,6 +125,7 @@ public class DesignReport {
private static final String SIZE = "Size";
private static final String ALTITUDE = "Altitude";
private static final String FLIGHT_TIME = "Flight Time";
+ private static final String OPTIMUM_DELAY = "Optimum Delay";
private static final String TIME_TO_APOGEE = "Time to Apogee";
private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
private static final String MAX_VELOCITY = "Max Velocity";
@@ -468,6 +469,9 @@ public class DesignReport {
labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
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(VELOCITY_OFF_PAD, 2, 2));
labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));