</classpathentry>
<classpathentry kind="lib" path="lib-extra/RXTXcomm.jar"/>
<classpathentry kind="lib" path="lib-test/junit-4.7.jar"/>
- <classpathentry kind="lib" path="lib/jfreechart-1.0.13.jar"/>
+ <classpathentry kind="lib" path="lib/jfreechart-1.0.13.jar" sourcepath="/home/sampo/Projects/lib/jfreechart-1.0.13/source"/>
<classpathentry kind="lib" path="lib/jcommon-1.0.16.jar">
<accessrules>
<accessrule kind="nonaccessible" pattern="org/jfree/util/Log"/>
+2010-07-21 Sampo Niskanen
+
+ * Implemented enhanced motor selection dialog
+ * Background motor loading & startup time optimization
+
+2010-07-20 Doug Pedrick
+
+ * [BUG] Exception when loading Rocksim files
+
2010-07-19 Sampo Niskanen
- * Small bug fixes
+ * [BUG] Various small bug fixes
2010-07-18 Sampo Niskanen
openrocket.debug.menu
If defined the "Debug" menu will be displayed in the main application window.
+openrocket.debug.prefs
+ If defined a new, clean set of preferences will be used (does not overwrite the existing preferences).
+
openrocket.debug.bugurl
URL used for sending bug reports.
--- /dev/null
+#!/bin/bash
+
+#
+# This script runs the version of OpenRocket compiled by Eclipse from
+# the bin/ directory. You can provide Java arguments and OpenRocket
+# arguments.
+#
+
+JAVAOPTS=""
+
+while echo "$1" | grep -q "^-" ; do
+ JAVAOPTS="$JAVAOPTS $1"
+ shift
+done
+
+
+java -cp bin/:lib/miglayout15-swing.jar:lib/jcommon-1.0.16.jar:lib/jfreechart-1.0.13.jar:. $JAVAOPTS net.sf.openrocket.startup.Startup "$@"
+
package net.sf.openrocket.database;
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.util.ArrayList;
-
-import net.sf.openrocket.file.GeneralMotorLoader;
-import net.sf.openrocket.file.Loader;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.material.MaterialStorage;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.util.ConfigurationException;
-import net.sf.openrocket.util.JarUtil;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Prefs;
private static final LogHelper log = Application.getLogger();
/* Static implementations of specific databases: */
- /**
- * The motor database.
- * TODO: HIGH: Must cast to (Loader) to works, very ugly. In practice returns only ThrustCurveMotors currently.
- */
- public static final Database<ThrustCurveMotor> MOTOR = new Database<ThrustCurveMotor>((Loader) new GeneralMotorLoader());
-
+
/**
* A database of bulk materials (with bulk densities).
*/
- // TODO: HIGH: loading the thrust curves and other databases
- static {
- final String filePattern = ".*\\.([eE][nN][gG]|[rR][sS][eE])$";
- try {
- MOTOR.loadJarDirectory("datafiles/thrustcurves/", filePattern);
- } catch (Exception e) {
- System.out.println("Could not read thrust curves from JAR: " + e.getMessage());
-
- // Try to find directory as a system resource
- File dir;
- URL url = ClassLoader.getSystemResource("datafiles/thrustcurves/");
-
- try {
- dir = JarUtil.urlToFile(url);
- } catch (Exception e1) {
- dir = new File("datafiles/thrustcurves/");
- }
-
- try {
- MOTOR.loadDirectory(dir, filePattern);
- } catch (IOException e1) {
- System.out.println("Could not read thrust curves from directory either.");
- throw new ConfigurationException("Couldn't read thrust curves from either " +
- "JAR file or system resource directory", e1);
- }
- }
- }
-
static {
// Add default materials
return Material.newMaterial(type, name, density, userDefined);
}
-
- /**
- * Return all motor in the database matching a search criteria. Any search criteria that
- * is null or NaN is ignored.
- *
- * @param type the motor type, or null.
- * @param manufacturer the manufacturer, or null.
- * @param designation the designation, or null.
- * @param diameter the diameter, or NaN.
- * @param length the length, or NaN.
- * @return an array of all the matching motors.
- */
- public static Motor[] findMotors(Motor.Type type, String manufacturer, String designation, double diameter, double length) {
- ArrayList<Motor> results = new ArrayList<Motor>();
-
- for (ThrustCurveMotor m : MOTOR) {
- boolean match = true;
- if (type != null && type != m.getMotorType())
- match = false;
- else if (manufacturer != null && !m.getManufacturer().matches(manufacturer))
- match = false;
- else if (designation != null && !designation.equalsIgnoreCase(m.getDesignation()))
- match = false;
- else if (!Double.isNaN(diameter) && (Math.abs(diameter - m.getDiameter()) > 0.0015))
- match = false;
- else if (!Double.isNaN(length) && (Math.abs(length - m.getLength()) > 0.0015))
- match = false;
-
- if (match)
- results.add(m);
- }
-
- return results.toArray(new Motor[0]);
- }
-
}
+++ /dev/null
-package net.sf.openrocket.database;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.motor.ThrustCurveMotor;
-import net.sf.openrocket.startup.Application;
-
-/**
- * A database containing ThrustCurveMotorSet objects and allowing adding a motor
- * to the database.
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public abstract class MotorSetDatabase {
-
- private static final LogHelper logger = Application.getLogger();
-
- private List<ThrustCurveMotorSet> motorSets;
-
- private volatile boolean startedLoading = false;
- private volatile boolean endedLoading = false;
- private final boolean asynchronous;
-
-
- /**
- * Sole constructor.
- *
- * @param asynchronous whether to load motors asynchronously in a background thread.
- */
- public MotorSetDatabase(boolean asynchronous) {
- this.asynchronous = asynchronous;
- }
-
-
- /**
- * Return a list of the ThrustCurveMotorSet objects. The list is in sorted order and
- * is unmodifiable.
- *
- * @return the list of all ThrustCurveMotorSets.
- */
- public List<ThrustCurveMotorSet> getMotorSets() {
- blockUntilLoaded();
- return motorSets;
- }
-
-
-
-
-
- /**
- * Add a motor to the database. If a matching ThrustCurveMototSet is found,
- * the motor is added to that set, otherwise a new set is created and added to the
- * database.
- *
- * @param motor the motor to add
- */
- protected void addMotor(ThrustCurveMotor motor) {
- // Iterate from last to first, as this is most likely to hit early when loading files
- for (int i = motorSets.size()-1; i>= 0; i--) {
- ThrustCurveMotorSet set = motorSets.get(i);
- if (set.matches(motor)) {
- set.addMotor(motor);
- return;
- }
- }
-
- ThrustCurveMotorSet newSet = new ThrustCurveMotorSet();
- newSet.addMotor(motor);
- motorSets.add(newSet);
- }
-
-
-
-
-
- /**
- * Start loading the motors. If asynchronous
- *
- * @throws IllegalStateException if this method has already been called.
- */
- public void startLoading() {
- if (startedLoading) {
- throw new IllegalStateException("Already called startLoading");
- }
- startedLoading = true;
- if (asynchronous) {
- new LoadingThread().start();
- } else {
- performMotorLoading();
- }
- }
-
-
- /**
- * Return whether loading the database has ended.
- *
- * @return whether background loading has ended.
- */
- public boolean isLoaded() {
- return endedLoading;
- }
-
-
- /**
- * Block the current thread until loading of the motors has been completed.
- *
- * @throws IllegalStateException if startLoading() has not been called.
- */
- public void blockUntilLoaded() {
- if (!startedLoading) {
- throw new IllegalStateException("startLoading() has not been called");
- }
- if (!endedLoading) {
- synchronized (this) {
- while (!endedLoading) {
- try {
- this.wait();
- } catch (InterruptedException e) {
- logger.warn("InterruptedException occurred, ignoring", e);
- }
- }
- }
- }
- }
-
-
- /**
- * Used for loading the motor database. This method will be called in a background
- * thread to load the motors asynchronously. This method should call
- * {@link #addMotor(ThrustCurveMotor)} to add the motors to the database.
- */
- protected abstract void loadMotors();
-
-
-
- /**
- * Creates the motor list, calls {@link #loadMotors()}, sorts the list and marks
- * the motors as loaded. This method is called either synchronously or from the
- * background thread.
- */
- private void performMotorLoading() {
- motorSets = new ArrayList<ThrustCurveMotorSet>();
- try {
- loadMotors();
- } catch (Exception e) {
- logger.error("Loading motors failed", e);
- }
- Collections.sort(motorSets);
- motorSets = Collections.unmodifiableList(motorSets);
- synchronized (MotorSetDatabase.this) {
- endedLoading = true;
- MotorSetDatabase.this.notifyAll();
- }
- }
-
-
- /**
- * Background thread for loading the motors. This creates the motor list,
- * calls loadMotors(), sorts the database, makes it unmodifiable, and finally
- * marks the database as loaded and notifies any blocked threads.
- */
- private class LoadingThread extends Thread {
- @Override
- public void run() {
- performMotorLoading();
- }
- }
-
-}
import net.sf.openrocket.motor.DesignationComparator;
import net.sf.openrocket.motor.Manufacturer;
-import net.sf.openrocket.motor.MotorDigest;
import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.MotorDigest;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.motor.Motor.Type;
import net.sf.openrocket.util.MathUtil;
public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> {
-
+
// Comparators:
private static final Collator COLLATOR = Collator.getInstance(Locale.US);
static {
}
private static final DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
private static final ThrustCurveMotorComparator comparator = new ThrustCurveMotorComparator();
-
-
+
+
private final ArrayList<ThrustCurveMotor> motors = new ArrayList<ThrustCurveMotor>();
private final Map<ThrustCurveMotor, String> digestMap =
- new IdentityHashMap<ThrustCurveMotor, String>();
+ new IdentityHashMap<ThrustCurveMotor, String>();
private final List<Double> delays = new ArrayList<Double>();
private Motor.Type type = Motor.Type.UNKNOWN;
-
+
public void addMotor(ThrustCurveMotor motor) {
// Check for first insertion
// Verify that the motor can be added
if (!matches(motor)) {
throw new IllegalArgumentException("Motor does not match the set:" +
- " manufacturer="+manufacturer +
- " designation="+designation +
+ " manufacturer=" + manufacturer +
+ " designation=" + designation +
" diameter=" + diameter +
" length=" + length +
" set_size=" + motors.size() +
// Update the type if now known
if (type == Motor.Type.UNKNOWN) {
type = motor.getMotorType();
+ // Add "Plugged" option if hybrid
+ if (type == Motor.Type.HYBRID) {
+ if (!delays.contains(Motor.PLUGGED)) {
+ delays.add(Motor.PLUGGED);
+ }
+ }
}
// Change the simplified designation if necessary
}
// Add the standard delays
- for (double d: motor.getStandardDelays()) {
+ for (double d : motor.getStandardDelays()) {
d = Math.rint(d);
- delays.add(d);
+ if (!delays.contains(d)) {
+ delays.add(d);
+ }
}
Collections.sort(delays);
-
+
// Check whether to add as new motor or overwrite existing
final String digest = MotorDigest.digestMotor(motor);
for (int index = 0; index < motors.size(); index++) {
Motor m = motors.get(index);
-
+
if (digest.equals(digestMap.get(m)) &&
motor.getDesignation().equals(m.getDesignation())) {
-
+
// Match found, check which one to keep (or both) based on comment
- String newCmt = motor.getDescription().replaceAll("\\s+"," ").trim();
+ String newCmt = motor.getDescription().replaceAll("\\s+", " ").trim();
String oldCmt = m.getDescription().replaceAll("\\s+", " ").trim();
if (newCmt.length() == 0 || newCmt.equals(oldCmt)) {
public boolean matches(ThrustCurveMotor m) {
if (motors.isEmpty())
return true;
-
+
if (manufacturer != m.getManufacturer())
return false;
if (!MathUtil.equals(length, m.getLength()))
return false;
- if ((type != Type.UNKNOWN) && (m.getMotorType()!= Type.UNKNOWN) &&
+ if ((type != Type.UNKNOWN) && (m.getMotorType() != Type.UNKNOWN) &&
(type != m.getMotorType())) {
return false;
}
return true;
}
-
+
@SuppressWarnings("unchecked")
public List<ThrustCurveMotor> getMotors() {
}
+ public int getMotorCount() {
+ return motors.size();
+ }
+
/**
* Return the standard delays applicable to this motor type. This is a union of
public List<Double> getDelays() {
return Collections.unmodifiableList(delays);
}
-
-
+
+
/**
* Return the manufacturer of this motor type.
* @return the manufacturer
public Manufacturer getManufacturer() {
return manufacturer;
}
-
-
+
+
/**
* Return the designation of this motor type. This is either the exact or simplified
* designation, depending on what motors have been added.
public String getDesignation() {
return designation;
}
-
-
+
+
/**
* Return the diameter of this motor type.
* @return the diameter
public double getDiameter() {
return diameter;
}
-
-
+
+
/**
* Return the length of this motor type.
* @return the length
public double getLength() {
return length;
}
-
-
+
+
/**
* Return the type of this motor type. If any of the added motors has a type different
* from UNKNOWN, then that type will be returned.
public Motor.Type getType() {
return type;
}
+
+
-
+ @Override
+ public String toString() {
+ return "ThrustCurveMotorSet[" + manufacturer + " " + designation +
+ ", type=" + type + ", count=" + motors.size() + "]";
+ }
+
+
private static final Pattern SIMPLIFY_PATTERN = Pattern.compile("^[0-9]*[ -]*([A-Z][0-9]+).*");
+
/**
* Simplify a motor designation, if possible. This attempts to reduce the designation
* into a simple letter + number notation for the impulse class and average thrust.
* Comparator for deciding in which order to display matching motors.
*/
private static class ThrustCurveMotorComparator implements Comparator<ThrustCurveMotor> {
-
+
@Override
public int compare(ThrustCurveMotor o1, ThrustCurveMotor o2) {
// 1. Designation
}
}
-
-
+
+
@Override
public int compareTo(ThrustCurveMotorSet other) {
-
+
int value;
// 1. Manufacturer
- value = COLLATOR.compare(this.manufacturer.getDisplayName(),
+ value = COLLATOR.compare(this.manufacturer.getDisplayName(),
other.manufacturer.getDisplayName());
if (value != 0)
return value;
return value;
// 3. Diameter
- value = (int)((this.diameter - other.diameter)*1000000);
+ value = (int) ((this.diameter - other.diameter) * 1000000);
if (value != 0)
return value;
-
+
// 4. Length
- value = (int)((this.length - other.length)*1000000);
+ value = (int) ((this.length - other.length) * 1000000);
return value;
}
--- /dev/null
+package net.sf.openrocket.database;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A database containing ThrustCurveMotorSet objects and allowing adding a motor
+ * to the database.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class ThrustCurveMotorSetDatabase {
+
+ private static final LogHelper logger = Application.getLogger();
+
+ protected List<ThrustCurveMotorSet> motorSets;
+
+ private volatile boolean startedLoading = false;
+ private volatile boolean endedLoading = false;
+ private final boolean asynchronous;
+
+ /** Set to true the first time {@link #blockUntilLoaded()} is called. */
+ protected volatile boolean inUse = false;
+
+ /**
+ * Sole constructor.
+ *
+ * @param asynchronous whether to load motors asynchronously in a background thread.
+ */
+ public ThrustCurveMotorSetDatabase(boolean asynchronous) {
+ this.asynchronous = asynchronous;
+ }
+
+
+ /**
+ * Return a list of the ThrustCurveMotorSet objects. The list is in sorted order and
+ * is unmodifiable.
+ *
+ * @return the list of all ThrustCurveMotorSets.
+ */
+ public List<ThrustCurveMotorSet> getMotorSets() {
+ blockUntilLoaded();
+ return motorSets;
+ }
+
+
+
+ /**
+ * Return all motors in the database matching a search criteria. Any search criteria that
+ * is null or NaN is ignored.
+ *
+ * @param type the motor type, or null.
+ * @param manufacturer the manufacturer, or null.
+ * @param designation the designation, or null.
+ * @param diameter the diameter, or NaN.
+ * @param length the length, or NaN.
+ * @return a list of all the matching motors.
+ */
+ public List<ThrustCurveMotor> findMotors(Motor.Type type, String manufacturer, String designation,
+ double diameter, double length) {
+ blockUntilLoaded();
+ ArrayList<ThrustCurveMotor> results = new ArrayList<ThrustCurveMotor>();
+
+ for (ThrustCurveMotorSet set : motorSets) {
+ for (ThrustCurveMotor m : set.getMotors()) {
+ boolean match = true;
+ if (type != null && type != set.getType())
+ match = false;
+ else if (manufacturer != null && !m.getManufacturer().matches(manufacturer))
+ match = false;
+ else if (designation != null && !designation.equalsIgnoreCase(m.getDesignation()))
+ match = false;
+ else if (!Double.isNaN(diameter) && (Math.abs(diameter - m.getDiameter()) > 0.0015))
+ match = false;
+ else if (!Double.isNaN(length) && (Math.abs(length - m.getLength()) > 0.0015))
+ match = false;
+
+ if (match)
+ results.add(m);
+ }
+ }
+
+ return results;
+ }
+
+
+ /**
+ * Add a motor to the database. If a matching ThrustCurveMototSet is found,
+ * the motor is added to that set, otherwise a new set is created and added to the
+ * database.
+ *
+ * @param motor the motor to add
+ */
+ protected void addMotor(ThrustCurveMotor motor) {
+ // Iterate from last to first, as this is most likely to hit early when loading files
+ for (int i = motorSets.size() - 1; i >= 0; i--) {
+ ThrustCurveMotorSet set = motorSets.get(i);
+ if (set.matches(motor)) {
+ set.addMotor(motor);
+ return;
+ }
+ }
+
+ ThrustCurveMotorSet newSet = new ThrustCurveMotorSet();
+ newSet.addMotor(motor);
+ motorSets.add(newSet);
+ }
+
+
+
+
+
+ /**
+ * Start loading the motors. If asynchronous
+ *
+ * @throws IllegalStateException if this method has already been called.
+ */
+ public void startLoading() {
+ if (startedLoading) {
+ throw new IllegalStateException("Already called startLoading");
+ }
+ startedLoading = true;
+ if (asynchronous) {
+ new LoadingThread().start();
+ } else {
+ performMotorLoading();
+ }
+ }
+
+
+ /**
+ * Return whether loading the database has ended.
+ *
+ * @return whether background loading has ended.
+ */
+ public boolean isLoaded() {
+ return endedLoading;
+ }
+
+
+ /**
+ * Mark that this database is in use or a place is waiting for the database to
+ * become loaded. This can be used in conjunction with {@link #isLoaded()} to load
+ * the database without blocking.
+ */
+ public void setInUse() {
+ inUse = true;
+ }
+
+
+ /**
+ * Block the current thread until loading of the motors has been completed.
+ *
+ * @throws IllegalStateException if startLoading() has not been called.
+ */
+ public void blockUntilLoaded() {
+ inUse = true;
+ if (!startedLoading) {
+ throw new IllegalStateException("startLoading() has not been called");
+ }
+ if (!endedLoading) {
+ synchronized (this) {
+ while (!endedLoading) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ logger.warn("InterruptedException occurred, ignoring", e);
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Used for loading the motor database. This method will be called in a background
+ * thread to load the motors asynchronously. This method should call
+ * {@link #addMotor(ThrustCurveMotor)} to add the motors to the database.
+ */
+ protected abstract void loadMotors();
+
+
+
+ /**
+ * Creates the motor list, calls {@link #loadMotors()}, sorts the list and marks
+ * the motors as loaded. This method is called either synchronously or from the
+ * background thread.
+ */
+ private void performMotorLoading() {
+ motorSets = new ArrayList<ThrustCurveMotorSet>();
+ try {
+ loadMotors();
+ } catch (Exception e) {
+ logger.error("Loading motors failed", e);
+ }
+ Collections.sort(motorSets);
+ motorSets = Collections.unmodifiableList(motorSets);
+ synchronized (ThrustCurveMotorSetDatabase.this) {
+ endedLoading = true;
+ ThrustCurveMotorSetDatabase.this.notifyAll();
+ }
+ }
+
+
+ /**
+ * Background thread for loading the motors. This creates the motor list,
+ * calls loadMotors(), sorts the database, makes it unmodifiable, and finally
+ * marks the database as loaded and notifies any blocked threads.
+ */
+ private class LoadingThread extends Thread {
+ @Override
+ public void run() {
+ performMotorLoading();
+ }
+ }
+
+}
import net.sf.openrocket.file.simplesax.SimpleSAX;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.rocketcomponent.BodyComponent;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.Bulkhead;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.GUISimulationConditions;
import net.sf.openrocket.simulation.FlightEvent.Type;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
warnings.add(Warning.fromString("No motor specified, ignoring."));
return null;
}
- Motor[] motors = Databases.findMotors(type, manufacturer, designation, diameter, length);
- if (motors.length == 0) {
+ List<ThrustCurveMotor> motors = Application.getMotorSetDatabase().findMotors(type, manufacturer,
+ designation, diameter, length);
+ if (motors.size() == 0) {
String str = "No motor with designation '" + designation + "'";
if (manufacturer != null)
str += " for manufacturer '" + manufacturer + "'";
warnings.add(Warning.fromString(str + " found."));
return null;
}
- if (motors.length > 1) {
+ if (motors.size() > 1) {
String str = "Multiple motors with designation '" + designation + "'";
if (manufacturer != null)
str += " for manufacturer '" + manufacturer + "'";
warnings.add(Warning.fromString(str + " found, one chosen arbitrarily."));
}
- return motors[0];
+ return motors.get(0);
}
private static final String CONFIGDIALOGPACKAGE = "net.sf.openrocket.gui.configdialog";
private static final String CONFIGDIALOGPOSTFIX = "Config";
-
- private static ComponentConfigDialog dialog = null;
+ private static ComponentConfigDialog dialog = null;
+
private OpenRocketDocument document = null;
private RocketComponent component = null;
private RocketComponentConfig configurator = null;
private final Window parent;
- private ComponentConfigDialog(Window parent, OpenRocketDocument document,
+ private ComponentConfigDialog(Window parent, OpenRocketDocument document,
RocketComponent component) {
super(parent);
this.parent = parent;
setComponent(document, component);
+ GUIUtil.setDisposableDialogOptions(this, null);
+
// Set window position according to preferences, and set prefs when moving
Point position = Prefs.getWindowPosition(this.getClass());
- if (position == null)
- this.setLocationByPlatform(true);
- else
+ if (position != null) {
+ this.setLocationByPlatform(false);
this.setLocation(position);
-
+ }
+
this.addComponentListener(new ComponentAdapter() {
@Override
public void componentMoved(ComponentEvent e) {
- Prefs.setWindowPosition(ComponentConfigDialog.this.getClass(),
+ Prefs.setWindowPosition(ComponentConfigDialog.this.getClass(),
ComponentConfigDialog.this.getLocation());
}
});
- GUIUtil.setDisposableDialogOptions(this, null);
}
-
+
/**
* Set the component being configured. The listening connections of the old configurator
* will be removed and the new ones created.
if (this.document != null) {
this.document.getRocket().removeComponentChangeListener(this);
}
-
+
if (configurator != null) {
// Remove listeners by setting all applicable models to null
- GUIUtil.setNullModels(configurator); // null-safe
+ GUIUtil.setNullModels(configurator); // null-safe
}
this.document = document;
this.setContentPane(configurator);
configurator.updateFields();
- setTitle(component.getComponentName()+" configuration");
-
-// Dimension pref = getPreferredSize();
-// Dimension real = getSize();
-// if (pref.width > real.width || pref.height > real.height)
+ setTitle(component.getComponentName() + " configuration");
+
+ // Dimension pref = getPreferredSize();
+ // Dimension real = getSize();
+ // if (pref.width > real.width || pref.height > real.height)
this.pack();
}
* Return the configurator panel of the current component.
*/
private RocketComponentConfig getDialogContents() {
- Constructor<? extends RocketComponentConfig> c =
- findDialogContentsConstructor(component);
+ Constructor<? extends RocketComponentConfig> c =
+ findDialogContentsConstructor(component);
if (c != null) {
try {
return (RocketComponentConfig) c.newInstance(component);
} catch (InstantiationException e) {
- throw new BugException("BUG in constructor reflection",e);
+ throw new BugException("BUG in constructor reflection", e);
} catch (IllegalAccessException e) {
- throw new BugException("BUG in constructor reflection",e);
+ throw new BugException("BUG in constructor reflection", e);
} catch (InvocationTargetException e) {
throw Reflection.handleWrappedException(e);
}
// Should never be reached, since RocketComponentConfig should catch all
// components without their own configurator.
- throw new BugException("Unable to find any configurator for "+component);
+ throw new BugException("Unable to find any configurator for " + component);
}
-
+
/**
* Finds the Constructor of the given component's config dialog panel in
* CONFIGDIALOGPACKAGE.
*/
@SuppressWarnings("unchecked")
- private static Constructor<? extends RocketComponentConfig>
- findDialogContentsConstructor(RocketComponent component) {
+ private static Constructor<? extends RocketComponentConfig> findDialogContentsConstructor(RocketComponent component) {
Class<?> currentclass;
String currentclassname;
String configclassname;
int index = currentclassname.lastIndexOf('.');
if (index >= 0)
currentclassname = currentclassname.substring(index + 1);
- configclassname = CONFIGDIALOGPACKAGE + "." + currentclassname +
- CONFIGDIALOGPOSTFIX;
+ configclassname = CONFIGDIALOGPACKAGE + "." + currentclassname +
+ CONFIGDIALOGPOSTFIX;
try {
configclass = Class.forName(configclassname);
c = (Constructor<? extends RocketComponentConfig>)
- configclass.getConstructor(RocketComponent.class);
+ configclass.getConstructor(RocketComponent.class);
return c;
- } catch (Exception ignore) { }
-
+ } catch (Exception ignore) {
+ }
+
currentclass = currentclass.getSuperclass();
}
return null;
}
-
+
////////// Static dialog /////////
* @param document the document to configure.
* @param component the component to configure.
*/
- public static void showDialog(Window parent, OpenRocketDocument document,
+ public static void showDialog(Window parent, OpenRocketDocument document,
RocketComponent component) {
if (dialog != null)
dialog.dispose();
dialog = new ComponentConfigDialog(parent, document, component);
dialog.setVisible(true);
- document.addUndoPosition("Modify "+component.getComponentName());
+ document.addUndoPosition("Modify " + component.getComponentName());
}
- /* package */
+ /* package */
static void showDialog(RocketComponent component) {
showDialog(dialog.parent, dialog.document, component);
}
if (dialog != null)
dialog.setVisible(false);
}
-
+
/**
* Add an undo position for the current document. This is intended for use only
*
* @param description Description of the undoable action
*/
- /*package*/ static void addUndoPosition(String description) {
+ /*package*/static void addUndoPosition(String description) {
if (dialog == null) {
throw new IllegalStateException("Dialog not open, report bug!");
}
* Returns whether the singleton configuration dialog is currently visible or not.
*/
public static boolean isDialogVisible() {
- return (dialog!=null) && (dialog.isVisible());
+ return (dialog != null) && (dialog.isVisible());
}
-
-
+
+
public void componentChanged(ComponentChangeEvent e) {
if (e.isTreeChange() || e.isUndoChange()) {
// Hide dialog in case of tree or undo change
dialog.setVisible(false);
-
+
} else {
/*
* TODO: HIGH: The line below has caused a NullPointerException (without null check)
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
+import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
-import net.sf.openrocket.gui.dialogs.MotorChooserDialog;
+import net.sf.openrocket.gui.dialogs.motor.MotorChooserDialog;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.rocketcomponent.Configuration;
public void actionPerformed(ActionEvent e) {
String id = configuration.getMotorConfigurationID();
- // TODO: HIGH: Assumes only ThrustCurveMotors exist
- MotorChooserDialog dialog = new MotorChooserDialog((ThrustCurveMotor) mount.getMotor(id),
- mount.getMotorDelay(id), mount.getMotorMountDiameter());
+ MotorChooserDialog dialog = new MotorChooserDialog(mount.getMotor(id),
+ mount.getMotorDelay(id), mount.getMotorMountDiameter(),
+ SwingUtilities.getWindowAncestor(MotorConfig.this));
dialog.setVisible(true);
Motor m = dialog.getSelectedMotor();
double d = dialog.getSelectedDelay();
} else {
String str = "";
if (m instanceof ThrustCurveMotor)
- str = ((ThrustCurveMotor) m).getManufacturer() + "";
+ str = ((ThrustCurveMotor) m).getManufacturer() + " ";
str += m.getDesignation(mount.getMotorDelay(id));
motorLabel.setText(str);
}
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.gui.dialogs.motor.MotorChooserDialog;
import net.sf.openrocket.gui.main.BasicFrame;
import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
if (currentID == null || currentMount == null)
return;
- // TODO: HIGH: Assumes only ThrustCurveMotors exist
- MotorChooserDialog dialog = new MotorChooserDialog((ThrustCurveMotor) currentMount.getMotor(currentID),
- currentMount.getMotorDelay(currentID), currentMount.getMotorMountDiameter(),
- this);
+ MotorChooserDialog dialog = new MotorChooserDialog(currentMount.getMotor(currentID),
+ currentMount.getMotorDelay(currentID), currentMount.getMotorMountDiameter(), this);
dialog.setVisible(true);
Motor m = dialog.getSelectedMotor();
double d = dialog.getSelectedDelay();
+++ /dev/null
-package net.sf.openrocket.gui.dialogs;
-
-
-import java.awt.Dialog;
-import java.awt.Font;
-import java.awt.Rectangle;
-import java.awt.Window;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Comparator;
-
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTable;
-import javax.swing.JTextField;
-import javax.swing.ListSelectionModel;
-import javax.swing.RowFilter;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-import javax.swing.table.AbstractTableModel;
-import javax.swing.table.TableModel;
-import javax.swing.table.TableRowSorter;
-
-import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.database.Databases;
-import net.sf.openrocket.gui.components.StyledLabel;
-import net.sf.openrocket.motor.DesignationComparator;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.motor.ThrustCurveMotor;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.unit.Value;
-import net.sf.openrocket.unit.ValueComparator;
-import net.sf.openrocket.util.GUIUtil;
-import net.sf.openrocket.util.Prefs;
-
-/*
- * TODO: HIGH: Only supports ThrustCurveMotors
- */
-public class MotorChooserDialog extends JDialog {
-
- private static final int SHOW_ALL = 0;
- private static final int SHOW_SMALLER = 1;
- private static final int SHOW_EXACT = 2;
- private static final String[] SHOW_DESCRIPTIONS = {
- "Show all motors",
- "Show motors with diameter less than that of the motor mount",
- "Show motors with diameter equal to that of the motor mount"
- };
- private static final int SHOW_MAX = 2;
-
- private final JTextField searchField;
- private String[] searchTerms = new String[0];
-
- private final double diameter;
-
- private ThrustCurveMotor selectedMotor = null;
- private double selectedDelay = 0;
-
- private JTable table;
- private TableRowSorter<TableModel> sorter;
- private JComboBox delayBox;
- private MotorDatabaseModel model;
-
- private boolean okClicked = false;
-
-
- public MotorChooserDialog(double diameter) {
- this(null, 5, diameter, null);
- }
-
- public MotorChooserDialog(ThrustCurveMotor current, double delay, double diameter) {
- this(current, delay, diameter, null);
- }
-
- public MotorChooserDialog(ThrustCurveMotor current, double delay, double diameter, Window owner) {
- super(owner, "Select a rocket motor", Dialog.ModalityType.APPLICATION_MODAL);
-
- JButton button;
-
- this.selectedMotor = current;
- this.selectedDelay = delay;
- this.diameter = diameter;
-
- JPanel panel = new JPanel(new MigLayout("fill", "[grow][]"));
-
- // Label
- JLabel label = new JLabel("Select a rocket motor:");
- label.setFont(label.getFont().deriveFont(Font.BOLD));
- panel.add(label, "growx");
-
- label = new JLabel("Motor mount diameter: " +
- UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
- panel.add(label, "gapleft para, wrap paragraph");
-
-
- // Diameter selection
- JComboBox combo = new JComboBox(SHOW_DESCRIPTIONS);
- combo.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- JComboBox cb = (JComboBox) e.getSource();
- int sel = cb.getSelectedIndex();
- if ((sel < 0) || (sel > SHOW_MAX))
- sel = SHOW_ALL;
- switch (sel) {
- case SHOW_ALL:
- sorter.setRowFilter(new MotorRowFilterAll());
- break;
-
- case SHOW_SMALLER:
- sorter.setRowFilter(new MotorRowFilterSmaller());
- break;
-
- case SHOW_EXACT:
- sorter.setRowFilter(new MotorRowFilterExact());
- break;
-
- default:
- assert (false) : "Should not occur.";
- }
- Prefs.putChoise("MotorDiameterMatch", sel);
- setSelectionVisible();
- }
- });
- panel.add(combo, "growx 1000");
-
-
-
- label = new JLabel("Search:");
- panel.add(label, "gapleft para, split 2");
-
- searchField = new JTextField();
- searchField.getDocument().addDocumentListener(new DocumentListener() {
- @Override
- public void changedUpdate(DocumentEvent e) {
- update();
- }
-
- @Override
- public void insertUpdate(DocumentEvent e) {
- update();
- }
-
- @Override
- public void removeUpdate(DocumentEvent e) {
- update();
- }
-
- private void update() {
- String text = searchField.getText().trim();
- String[] split = text.split("\\s+");
- ArrayList<String> list = new ArrayList<String>();
- for (String s : split) {
- s = s.trim().toLowerCase();
- if (s.length() > 0) {
- list.add(s);
- }
- }
- searchTerms = list.toArray(new String[0]);
- sorter.sort();
- }
- });
- panel.add(searchField, "growx 1, wrap");
-
-
-
- // Table, overridden to show meaningful tooltip texts
- model = new MotorDatabaseModel(current);
- table = new JTable(model) {
- @Override
- public String getToolTipText(MouseEvent e) {
- java.awt.Point p = e.getPoint();
- int colIndex = columnAtPoint(p);
- int viewRow = rowAtPoint(p);
- if (viewRow < 0)
- return null;
- int rowIndex = convertRowIndexToModel(viewRow);
- ThrustCurveMotor motor = model.getMotor(rowIndex);
-
- if (colIndex < 0 || colIndex >= MotorColumns.values().length)
- return null;
-
- return MotorColumns.values()[colIndex].getToolTipText(motor);
- }
- };
-
- // Set comparators and widths
- table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
- sorter = new TableRowSorter<TableModel>(model);
- for (int i = 0; i < MotorColumns.values().length; i++) {
- MotorColumns column = MotorColumns.values()[i];
- sorter.setComparator(i, column.getComparator());
- table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
- }
- table.setRowSorter(sorter);
-
- // Set selection and double-click listeners
- table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
- @Override
- public void valueChanged(ListSelectionEvent e) {
- int row = table.getSelectedRow();
- if (row >= 0) {
- row = table.convertRowIndexToModel(row);
- ThrustCurveMotor m = model.getMotor(row);
- // TODO: HIGH: equals or == ?
- if (!m.equals(selectedMotor)) {
- selectedMotor = model.getMotor(row);
- setDelays(true); // Reset delay times
- }
- }
- }
- });
- table.addMouseListener(new MouseAdapter() {
- @Override
- public void mouseClicked(MouseEvent e) {
- if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
- okClicked = true;
- MotorChooserDialog.this.setVisible(false);
- }
- }
- });
- // (Current selection and scrolling performed later)
-
- JScrollPane scrollpane = new JScrollPane();
- scrollpane.setViewportView(table);
- panel.add(scrollpane, "spanx, grow, width :700:, height :300:, wrap paragraph");
-
-
- // Ejection delay
- panel.add(new JLabel("Select ejection charge delay:"), "spanx, split 3, gap rel");
-
- delayBox = new JComboBox();
- delayBox.setEditable(true);
- delayBox.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- JComboBox cb = (JComboBox) e.getSource();
- String sel = (String) cb.getSelectedItem();
- if (sel.equalsIgnoreCase("None")) {
- selectedDelay = Motor.PLUGGED;
- } else {
- try {
- selectedDelay = Double.parseDouble(sel);
- } catch (NumberFormatException ignore) {
- }
- }
- setDelays(false);
- }
- });
- panel.add(delayBox, "gapright unrel");
- panel.add(new StyledLabel("(Number of seconds or \"None\")", -1), "wrap para");
- setDelays(false);
-
-
- JButton okButton = new JButton("OK");
- okButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- okClicked = true;
- MotorChooserDialog.this.setVisible(false);
- }
- });
- panel.add(okButton, "spanx, split, tag ok");
-
- button = new JButton("Cancel");
- button.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- MotorChooserDialog.this.setVisible(false);
- }
- });
- panel.add(button, "tag cancel");
-
-
- // Sets the filter:
- int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
- combo.setSelectedIndex(showMode);
-
-
- this.add(panel);
- this.pack();
- // this.setAlwaysOnTop(true);
-
- this.setLocationByPlatform(true);
- GUIUtil.setDisposableDialogOptions(this, okButton);
-
- // Table can be scrolled only after pack() has been called
- setSelectionVisible();
-
- // Focus the search field
- searchField.grabFocus();
- }
-
- private void setSelectionVisible() {
- if (selectedMotor != null) {
- int index = table.convertRowIndexToView(model.getIndex(selectedMotor));
- table.getSelectionModel().setSelectionInterval(index, index);
- Rectangle rect = table.getCellRect(index, 0, true);
- rect = new Rectangle(rect.x, rect.y - 100, rect.width, rect.height + 200);
- table.scrollRectToVisible(rect);
- }
- }
-
-
- /**
- * Set the values in the delay combo box. If <code>reset</code> is <code>true</code>
- * then sets the selected value as the value closest to selectedDelay, otherwise
- * leaves selection alone.
- */
- private void setDelays(boolean reset) {
- if (selectedMotor == null) {
-
- delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
- delayBox.setSelectedIndex(0);
-
- } else {
-
- double[] delays = selectedMotor.getStandardDelays();
- String[] delayStrings = new String[delays.length];
- double currentDelay = selectedDelay; // Store current setting locally
-
- for (int i = 0; i < delays.length; i++) {
- delayStrings[i] = ThrustCurveMotor.getDelayString(delays[i], "None");
- }
- delayBox.setModel(new DefaultComboBoxModel(delayStrings));
-
- if (reset) {
-
- // Find and set the closest value
- double closest = Double.NaN;
- for (int i = 0; i < delays.length; i++) {
- // if-condition to always become true for NaN
- if (!(Math.abs(delays[i] - currentDelay) > Math.abs(closest - currentDelay))) {
- closest = delays[i];
- }
- }
- if (!Double.isNaN(closest)) {
- selectedDelay = closest;
- delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(closest, "None"));
- } else {
- delayBox.setSelectedItem("None");
- }
-
- } else {
-
- selectedDelay = currentDelay;
- delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, "None"));
-
- }
-
- }
- }
-
-
-
- public ThrustCurveMotor getSelectedMotor() {
- if (!okClicked)
- return null;
- return selectedMotor;
- }
-
-
- public double getSelectedDelay() {
- return selectedDelay;
- }
-
-
-
-
- //////////////// JTable elements ////////////////
-
-
- /**
- * Enum defining the table columns.
- */
- private enum MotorColumns {
- MANUFACTURER("Manufacturer", 100) {
- @Override
- public String getValue(ThrustCurveMotor m) {
- return m.getManufacturer().getDisplayName();
- }
-
- @Override
- public Comparator<?> getComparator() {
- return Collator.getInstance();
- }
- },
- DESIGNATION("Designation") {
- @Override
- public String getValue(ThrustCurveMotor m) {
- return m.getDesignation();
- }
-
- @Override
- public Comparator<?> getComparator() {
- return new DesignationComparator();
- }
- },
- TYPE("Type") {
- @Override
- public String getValue(ThrustCurveMotor m) {
- return m.getMotorType().getName();
- }
-
- @Override
- public Comparator<?> getComparator() {
- return Collator.getInstance();
- }
- },
- DIAMETER("Diameter") {
- @Override
- public Object getValue(ThrustCurveMotor m) {
- return new Value(m.getDiameter(), UnitGroup.UNITS_MOTOR_DIMENSIONS);
- }
-
- @Override
- public Comparator<?> getComparator() {
- return ValueComparator.INSTANCE;
- }
- },
- LENGTH("Length") {
- @Override
- public Object getValue(ThrustCurveMotor m) {
- return new Value(m.getLength(), UnitGroup.UNITS_MOTOR_DIMENSIONS);
- }
-
- @Override
- public Comparator<?> getComparator() {
- return ValueComparator.INSTANCE;
- }
- },
- IMPULSE("Impulse") {
- @Override
- public Object getValue(ThrustCurveMotor m) {
- return new Value(m.getTotalImpulseEstimate(), UnitGroup.UNITS_IMPULSE);
- }
-
- @Override
- public Comparator<?> getComparator() {
- return ValueComparator.INSTANCE;
- }
- },
- TIME("Burn time") {
- @Override
- public Object getValue(ThrustCurveMotor m) {
- return new Value(m.getBurnTimeEstimate(), UnitGroup.UNITS_SHORT_TIME);
- }
-
- @Override
- public Comparator<?> getComparator() {
- return ValueComparator.INSTANCE;
- }
- };
-
-
- private final String title;
- private final int width;
-
- MotorColumns(String title) {
- this(title, 50);
- }
-
- MotorColumns(String title, int width) {
- this.title = title;
- this.width = width;
- }
-
-
- public abstract Object getValue(ThrustCurveMotor m);
-
- public abstract Comparator<?> getComparator();
-
- public String getTitle() {
- return title;
- }
-
- public int getWidth() {
- return width;
- }
-
- public String getToolTipText(ThrustCurveMotor m) {
- String tip = "<html>";
- tip += "<b>" + m.toString() + "</b>";
- tip += " (" + m.getMotorType().getDescription() + ")<br><hr>";
-
- String desc = m.getDescription().trim();
- if (desc.length() > 0) {
- tip += "<i>" + desc.replace("\n", "<br>") + "</i><br><hr>";
- }
-
- tip += ("Diameter: " +
- UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getDiameter()) +
- "<br>");
- tip += ("Length: " +
- UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getLength()) +
- "<br>");
- tip += ("Maximum thrust: " +
- UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getMaxThrustEstimate()) +
- "<br>");
- tip += ("Average thrust: " +
- UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getAverageThrustEstimate()) +
- "<br>");
- tip += ("Burn time: " +
- UnitGroup.UNITS_SHORT_TIME.getDefaultUnit()
- .toStringUnit(m.getBurnTimeEstimate()) + "<br>");
- tip += ("Total impulse: " +
- UnitGroup.UNITS_IMPULSE.getDefaultUnit()
- .toStringUnit(m.getTotalImpulseEstimate()) + "<br>");
- tip += ("Launch mass: " +
- UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getLaunchCG().weight) +
- "<br>");
- tip += ("Empty mass: " +
- UnitGroup.UNITS_MASS.getDefaultUnit()
- .toStringUnit(m.getEmptyCG().weight));
- return tip;
- }
-
- }
-
-
- /**
- * The JTable model. Includes an extra motor, given in the constructor,
- * if it is not already in the database.
- */
- private class MotorDatabaseModel extends AbstractTableModel {
- private final ThrustCurveMotor extra;
-
- public MotorDatabaseModel(ThrustCurveMotor current) {
- if (Databases.MOTOR.contains(current))
- extra = null;
- else
- extra = current;
- }
-
- @Override
- public int getColumnCount() {
- return MotorColumns.values().length;
- }
-
- @Override
- public int getRowCount() {
- if (extra == null)
- return Databases.MOTOR.size();
- else
- return Databases.MOTOR.size() + 1;
- }
-
- @Override
- public Object getValueAt(int rowIndex, int columnIndex) {
- MotorColumns column = getColumn(columnIndex);
- if (extra == null) {
- return column.getValue(Databases.MOTOR.get(rowIndex));
- } else {
- if (rowIndex == 0)
- return column.getValue(extra);
- else
- return column.getValue(Databases.MOTOR.get(rowIndex - 1));
- }
- }
-
- @Override
- public String getColumnName(int columnIndex) {
- return getColumn(columnIndex).getTitle();
- }
-
-
- public ThrustCurveMotor getMotor(int rowIndex) {
- if (extra == null) {
- return Databases.MOTOR.get(rowIndex);
- } else {
- if (rowIndex == 0)
- return extra;
- else
- return Databases.MOTOR.get(rowIndex - 1);
- }
- }
-
- public int getIndex(ThrustCurveMotor m) {
- if (extra == null) {
- return Databases.MOTOR.indexOf(m);
- } else {
- if (extra.equals(m))
- return 0;
- else
- return Databases.MOTOR.indexOf(m) + 1;
- }
- }
-
- private MotorColumns getColumn(int index) {
- return MotorColumns.values()[index];
- }
- }
-
-
- //////// Row filters
-
- /**
- * Abstract adapter class.
- */
- private abstract class MotorRowFilter extends RowFilter<TableModel, Integer> {
- @Override
- public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
- int index = entry.getIdentifier();
- ThrustCurveMotor m = model.getMotor(index);
- return filterByDiameter(m) && filterByString(m);
- }
-
- public abstract boolean filterByDiameter(ThrustCurveMotor m);
-
-
- public boolean filterByString(ThrustCurveMotor m) {
- main: for (String s : searchTerms) {
- for (MotorColumns col : MotorColumns.values()) {
- String str = col.getValue(m).toString().toLowerCase();
- if (str.indexOf(s) >= 0)
- continue main;
- }
- return false;
- }
- return true;
- }
- }
-
- /**
- * Show all motors.
- */
- private class MotorRowFilterAll extends MotorRowFilter {
- @Override
- public boolean filterByDiameter(ThrustCurveMotor m) {
- return true;
- }
- }
-
- /**
- * Show motors smaller than the mount.
- */
- private class MotorRowFilterSmaller extends MotorRowFilter {
- @Override
- public boolean filterByDiameter(ThrustCurveMotor m) {
- return (m.getDiameter() <= diameter + 0.0004);
- }
- }
-
- /**
- * Show motors that fit the mount.
- */
- private class MotorRowFilterExact extends MotorRowFilter {
- @Override
- public boolean filterByDiameter(ThrustCurveMotor m) {
- return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015));
- }
- }
-
-}
--- /dev/null
+package net.sf.openrocket.gui.dialogs;
+
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.Timer;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.GUIUtil;
+
+public class MotorDatabaseLoadingDialog extends JDialog {
+ private static final LogHelper log = Application.getLogger();
+
+
+ private MotorDatabaseLoadingDialog(Window parent) {
+ super(parent, "Loading motors", ModalityType.APPLICATION_MODAL);
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+ panel.add(new JLabel("Loading motors..."), "wrap para");
+
+ JProgressBar progress = new JProgressBar();
+ progress.setIndeterminate(true);
+ panel.add(progress, "growx");
+
+ this.add(panel);
+ this.pack();
+ this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ this.setLocationByPlatform(true);
+ GUIUtil.setWindowIcons(this);
+ }
+
+
+ /**
+ * Check whether the motor database is loaded and block until it is.
+ * An uncloseable modal dialog window is opened while loading.
+ *
+ * @param parent the parent window for the dialog, or <code>null</code>
+ */
+ public static void check(Window parent) {
+ final ThrustCurveMotorSetDatabase db = Application.getMotorSetDatabase();
+ if (db.isLoaded())
+ return;
+
+ log.info(1, "Motor database not loaded yet, displaying dialog");
+
+ final MotorDatabaseLoadingDialog dialog = new MotorDatabaseLoadingDialog(parent);
+
+ final Timer timer = new Timer(100, new ActionListener() {
+ private int count = 0;
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ count++;
+ if (db.isLoaded()) {
+ log.debug("Database loaded, closing dialog");
+ dialog.setVisible(false);
+ } else if (count % 10 == 0) {
+ log.debug("Database not loaded, count=" + count);
+ }
+ }
+ });
+
+ db.setInUse();
+ timer.start();
+ dialog.setVisible(true);
+ timer.stop();
+
+ log.debug("Motor database now loaded");
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.motor;
+
+public interface CloseableDialog {
+
+ /**
+ * Close this dialog.
+ *
+ * @param ok whether "OK" should be considered to have been clicked.
+ */
+ public void close(boolean ok);
+
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.motor;
+
+
+import java.awt.Dialog;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.dialogs.MotorDatabaseLoadingDialog;
+import net.sf.openrocket.gui.dialogs.motor.thrustcurve.ThrustCurveMotorSelectionPanel;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+import net.sf.openrocket.util.GUIUtil;
+
+public class MotorChooserDialog extends JDialog implements CloseableDialog {
+
+ private final ThrustCurveMotorSelectionPanel selectionPanel;
+
+ private boolean okClicked = false;
+
+
+ public MotorChooserDialog(Motor current, double delay, double diameter, Window owner) {
+ super(owner, "Select a rocket motor", Dialog.ModalityType.APPLICATION_MODAL);
+
+ // Check that the motor database has been loaded properly
+ MotorDatabaseLoadingDialog.check(null);
+
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+ selectionPanel = new ThrustCurveMotorSelectionPanel((ThrustCurveMotor) current, delay, diameter);
+
+ panel.add(selectionPanel, "grow, wrap para");
+
+
+ // OK / Cancel buttons
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ close(true);
+ }
+ });
+ panel.add(okButton, "tag ok, spanx, split");
+
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ close(false);
+ }
+ });
+ panel.add(cancelButton, "tag cancel");
+
+ this.add(panel);
+
+ this.setModal(true);
+ this.pack();
+ this.setLocationByPlatform(true);
+ GUIUtil.setDisposableDialogOptions(this, okButton);
+
+ JComponent focus = selectionPanel.getDefaultFocus();
+ if (focus != null) {
+ focus.grabFocus();
+ }
+
+ // Set the closeable dialog after all initialization
+ selectionPanel.setCloseableDialog(this);
+ }
+
+
+ /**
+ * Return the motor selected by this chooser dialog, or <code>null</code> if the selection has been aborted.
+ *
+ * @return the selected motor, or <code>null</code> if no motor has been selected or the selection was canceled.
+ */
+ public Motor getSelectedMotor() {
+ if (!okClicked)
+ return null;
+ return selectionPanel.getSelectedMotor();
+ }
+
+ /**
+ * Return the selected ejection charge delay.
+ *
+ * @return the selected ejection charge delay.
+ */
+ public double getSelectedDelay() {
+ return selectionPanel.getSelectedDelay();
+ }
+
+
+
+ @Override
+ public void close(boolean ok) {
+ okClicked = ok;
+ this.setVisible(false);
+
+ Motor selected = getSelectedMotor();
+ if (okClicked && selected != null) {
+ selectionPanel.selectedMotor(selected);
+ }
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.motor;
+
+import javax.swing.JComponent;
+
+import net.sf.openrocket.motor.Motor;
+
+public interface MotorSelector {
+
+ /**
+ * Return the currently selected motor.
+ *
+ * @return the currently selected motor, or <code>null</code> if no motor is selected.
+ */
+ public Motor getSelectedMotor();
+
+ /**
+ * Return the currently selected ejection charge delay.
+ *
+ * @return the currently selected ejection charge delay.
+ */
+ public double getSelectedDelay();
+
+ /**
+ * Return the component that should have the default focus in this motor selector panel.
+ *
+ * @return the component that should have default focus, or <code>null</code> for none.
+ */
+ public JComponent getDefaultFocus();
+
+ /**
+ * Notify that the provided motor has been selected. This can be used to store preference
+ * data for later usage.
+ *
+ * @param m the motor that was selected.
+ */
+ public void selectedMotor(Motor m);
+
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
+
+import net.sf.openrocket.motor.ThrustCurveMotor;
+
+class MotorHolder {
+
+ private final ThrustCurveMotor motor;
+ private final int index;
+
+ public MotorHolder(ThrustCurveMotor motor, int index) {
+ this.motor = motor;
+ this.index = index;
+ }
+
+ public ThrustCurveMotor getMotor() {
+ return motor;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public String toString() {
+ return motor.getDesignation();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MotorHolder))
+ return false;
+ MotorHolder other = (MotorHolder) obj;
+ return this.motor.equals(other.motor);
+ }
+
+ @Override
+ public int hashCode() {
+ return motor.hashCode();
+ }
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
+
+import java.text.Collator;
+import java.util.Comparator;
+
+import net.sf.openrocket.database.ThrustCurveMotorSet;
+import net.sf.openrocket.motor.DesignationComparator;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.unit.Value;
+import net.sf.openrocket.unit.ValueComparator;
+
+/**
+ * Enum defining the table columns.
+ */
+enum ThrustCurveMotorColumns {
+ MANUFACTURER("Manufacturer", 100) {
+ @Override
+ public String getValue(ThrustCurveMotorSet m) {
+ return m.getManufacturer().getDisplayName();
+ }
+
+ @Override
+ public Comparator<?> getComparator() {
+ return Collator.getInstance();
+ }
+ },
+ DESIGNATION("Designation") {
+ @Override
+ public String getValue(ThrustCurveMotorSet m) {
+ return m.getDesignation();
+ }
+
+ @Override
+ public Comparator<?> getComparator() {
+ return new DesignationComparator();
+ }
+ },
+ TYPE("Type") {
+ @Override
+ public String getValue(ThrustCurveMotorSet m) {
+ return m.getType().getName();
+ }
+
+ @Override
+ public Comparator<?> getComparator() {
+ return Collator.getInstance();
+ }
+ },
+ DIAMETER("Diameter") {
+ @Override
+ public Object getValue(ThrustCurveMotorSet m) {
+ return new Value(m.getDiameter(), UnitGroup.UNITS_MOTOR_DIMENSIONS);
+ }
+
+ @Override
+ public Comparator<?> getComparator() {
+ return ValueComparator.INSTANCE;
+ }
+ },
+ LENGTH("Length") {
+ @Override
+ public Object getValue(ThrustCurveMotorSet m) {
+ return new Value(m.getLength(), UnitGroup.UNITS_MOTOR_DIMENSIONS);
+ }
+
+ @Override
+ public Comparator<?> getComparator() {
+ return ValueComparator.INSTANCE;
+ }
+ };
+
+
+ private final String title;
+ private final int width;
+
+ ThrustCurveMotorColumns(String title) {
+ this(title, 50);
+ }
+
+ ThrustCurveMotorColumns(String title, int width) {
+ this.title = title;
+ this.width = width;
+ }
+
+
+ public abstract Object getValue(ThrustCurveMotorSet m);
+
+ public abstract Comparator<?> getComparator();
+
+ public String getTitle() {
+ return title;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public String getToolTipText(ThrustCurveMotor m) {
+ String tip = "<html>";
+ tip += "<b>" + m.toString() + "</b>";
+ tip += " (" + m.getMotorType().getDescription() + ")<br><hr>";
+
+ String desc = m.getDescription().trim();
+ if (desc.length() > 0) {
+ tip += "<i>" + desc.replace("\n", "<br>") + "</i><br><hr>";
+ }
+
+ tip += ("Diameter: " +
+ UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getDiameter()) +
+ "<br>");
+ tip += ("Length: " +
+ UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getLength()) +
+ "<br>");
+ tip += ("Maximum thrust: " +
+ UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getMaxThrustEstimate()) +
+ "<br>");
+ tip += ("Average thrust: " +
+ UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getAverageThrustEstimate()) +
+ "<br>");
+ tip += ("Burn time: " +
+ UnitGroup.UNITS_SHORT_TIME.getDefaultUnit()
+ .toStringUnit(m.getBurnTimeEstimate()) + "<br>");
+ tip += ("Total impulse: " +
+ UnitGroup.UNITS_IMPULSE.getDefaultUnit()
+ .toStringUnit(m.getTotalImpulseEstimate()) + "<br>");
+ tip += ("Launch mass: " +
+ UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getLaunchCG().weight) +
+ "<br>");
+ tip += ("Empty mass: " +
+ UnitGroup.UNITS_MASS.getDefaultUnit()
+ .toStringUnit(m.getEmptyCG().weight));
+ return tip;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
+
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+import net.sf.openrocket.database.ThrustCurveMotorSet;
+
+class ThrustCurveMotorDatabaseModel extends AbstractTableModel {
+ private final List<ThrustCurveMotorSet> database;
+
+ public ThrustCurveMotorDatabaseModel(List<ThrustCurveMotorSet> database) {
+ this.database = database;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return ThrustCurveMotorColumns.values().length;
+ }
+
+ @Override
+ public int getRowCount() {
+ return database.size();
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ ThrustCurveMotorColumns column = getColumn(columnIndex);
+ return column.getValue(database.get(rowIndex));
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ return getColumn(columnIndex).getTitle();
+ }
+
+
+ public ThrustCurveMotorSet getMotorSet(int rowIndex) {
+ return database.get(rowIndex);
+ }
+
+
+ public int getIndex(ThrustCurveMotorSet m) {
+ return database.indexOf(m);
+ }
+
+ private ThrustCurveMotorColumns getColumn(int index) {
+ return ThrustCurveMotorColumns.values()[index];
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.database.ThrustCurveMotorSet;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.GUIUtil;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+public class ThrustCurveMotorPlotDialog extends JDialog {
+
+ public ThrustCurveMotorPlotDialog(ThrustCurveMotorSet motorSet, ThrustCurveMotor selectedMotor, Window parent) {
+ super(parent, "Motor thrust curves", ModalityType.APPLICATION_MODAL);
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+ // Thrust curve plot
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ "Motor thrust curves", // title
+ "Time / " + UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().getUnit(), // xAxisLabel
+ "Thrust / " + UnitGroup.UNITS_FORCE.getDefaultUnit().getUnit(), // yAxisLabel
+ null, // dataset
+ PlotOrientation.VERTICAL,
+ true, // legend
+ true, // tooltips
+ false // urls
+ );
+
+
+ // Add the data and formatting to the plot
+ XYPlot plot = chart.getXYPlot();
+
+ chart.setBackgroundPaint(panel.getBackground());
+ plot.setBackgroundPaint(Color.WHITE);
+ plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
+ plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
+
+ ChartPanel chartPanel = new ChartPanel(chart,
+ false, // properties
+ true, // save
+ false, // print
+ true, // zoom
+ true); // tooltips
+ chartPanel.setMouseWheelEnabled(true);
+ chartPanel.setEnforceFileExtensions(true);
+ chartPanel.setInitialDelay(500);
+
+ StandardXYItemRenderer renderer = new StandardXYItemRenderer();
+ renderer.setBaseShapesVisible(true);
+ renderer.setBaseShapesFilled(true);
+ plot.setRenderer(renderer);
+
+
+ // Create the plot data set
+ XYSeriesCollection dataset = new XYSeriesCollection();
+ List<ThrustCurveMotor> motors = motorSet.getMotors();
+
+ // Selected thrust curve
+ int index = motors.indexOf(selectedMotor);
+ int n = 0;
+ dataset.addSeries(generateSeries(selectedMotor));
+ renderer.setSeriesStroke(n, new BasicStroke(1.5f));
+ if (index >= 0) {
+ renderer.setSeriesPaint(n, ThrustCurveMotorSelectionPanel.getColor(index));
+ }
+ n++;
+
+ // Other thrust curves
+ for (int i = 0; i < motors.size(); i++) {
+ if (i == index)
+ continue;
+
+ ThrustCurveMotor m = motors.get(i);
+ dataset.addSeries(generateSeries(m));
+ renderer.setSeriesStroke(n, new BasicStroke(1.5f));
+ renderer.setSeriesPaint(n, ThrustCurveMotorSelectionPanel.getColor(i));
+ renderer.setSeriesShape(n, new Rectangle());
+ n++;
+ }
+
+ plot.setDataset(dataset);
+
+ panel.add(chartPanel, "width 600:600:, height 400:400:, grow, wrap para");
+
+
+ // Close button
+ JButton close = new JButton("Close");
+ close.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ ThrustCurveMotorPlotDialog.this.setVisible(false);
+ }
+ });
+ panel.add(close, "right, tag close");
+
+
+ this.add(panel);
+
+ this.pack();
+ GUIUtil.setDisposableDialogOptions(this, null);
+ }
+
+
+ private XYSeries generateSeries(ThrustCurveMotor motor) {
+ XYSeries series = new XYSeries(motor.getManufacturer() + " " + motor.getDesignation());
+ double[] time = motor.getTimePoints();
+ double[] thrust = motor.getThrustPoints();
+
+ for (int j = 0; j < time.length; j++) {
+ series.add(time[j], thrust[j]);
+ }
+ return series;
+ }
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Font;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JLayeredPane;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListSelectionModel;
+import javax.swing.RowFilter;
+import javax.swing.SwingUtilities;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableModel;
+import javax.swing.table.TableRowSorter;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.database.ThrustCurveMotorSet;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
+import net.sf.openrocket.gui.dialogs.motor.CloseableDialog;
+import net.sf.openrocket.gui.dialogs.motor.MotorSelector;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.MotorDigest;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Icons;
+import net.sf.openrocket.util.Prefs;
+
+import org.jfree.chart.ChartColor;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.title.TextTitle;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelector {
+ private static final LogHelper log = Application.getLogger();
+
+ private static final int SHOW_ALL = 0;
+ private static final int SHOW_SMALLER = 1;
+ private static final int SHOW_EXACT = 2;
+ private static final String[] SHOW_DESCRIPTIONS = {
+ "Show all motors",
+ "Show motors with diameter less than that of the motor mount",
+ "Show motors with diameter equal to that of the motor mount"
+ };
+ private static final int SHOW_MAX = 2;
+
+ private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50;
+ private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12;
+
+ private static final Paint[] CURVE_COLORS = ChartColor.createDefaultPaintArray();
+
+ private static final Color NO_COMMENT_COLOR = Color.GRAY;
+ private static final Color WITH_COMMENT_COLOR = Color.BLACK;
+
+ private final List<ThrustCurveMotorSet> database;
+
+ private final double diameter;
+ private CloseableDialog dialog = null;
+
+
+ private final ThrustCurveMotorDatabaseModel model;
+ private final JTable table;
+ private final TableRowSorter<TableModel> sorter;
+
+ private final JTextField searchField;
+ private String[] searchTerms = new String[0];
+
+
+ private final JLabel curveSelectionLabel;
+ private final JComboBox curveSelectionBox;
+ private final DefaultComboBoxModel curveSelectionModel;
+
+ private final JLabel totalImpulseLabel;
+ private final JLabel avgThrustLabel;
+ private final JLabel maxThrustLabel;
+ private final JLabel burnTimeLabel;
+ private final JLabel launchMassLabel;
+ private final JLabel emptyMassLabel;
+ private final JLabel dataPointsLabel;
+
+ private final JTextArea comment;
+ private final Font noCommentFont;
+ private final Font withCommentFont;
+
+ private final JFreeChart chart;
+ private final ChartPanel chartPanel;
+ private final JLabel zoomIcon;
+
+ private final JComboBox delayBox;
+
+ private ThrustCurveMotor selectedMotor;
+ private ThrustCurveMotorSet selectedMotorSet;
+ private double selectedDelay;
+
+
+ /**
+ * Sole constructor.
+ *
+ * @param current the currently selected ThrustCurveMotor, or <code>null</code> for none.
+ * @param delay the currently selected ejection charge delay.
+ * @param diameter the diameter of the motor mount.
+ */
+ public ThrustCurveMotorSelectionPanel(ThrustCurveMotor current, double delay, double diameter) {
+ super(new MigLayout("fill", "[grow][]"));
+
+ this.diameter = diameter;
+
+
+ // Construct the database (adding the current motor if not in the db already)
+ List<ThrustCurveMotorSet> db;
+ db = Application.getMotorSetDatabase().getMotorSets();
+
+ // If current motor is not found in db, add a new ThrustCurveMotorSet containing it
+ if (current != null) {
+ selectedMotor = current;
+ for (ThrustCurveMotorSet motorSet : db) {
+ if (motorSet.getMotors().contains(current)) {
+ selectedMotorSet = motorSet;
+ break;
+ }
+ }
+ if (selectedMotorSet == null) {
+ db = new ArrayList<ThrustCurveMotorSet>(db);
+ ThrustCurveMotorSet extra = new ThrustCurveMotorSet();
+ extra.addMotor(current);
+ selectedMotorSet = extra;
+ db.add(extra);
+ Collections.sort(db);
+ }
+ }
+ database = db;
+
+
+
+ //// GUI
+
+ JPanel panel;
+ JLabel label;
+
+ panel = new JPanel(new MigLayout("fill"));
+ this.add(panel, "grow");
+
+
+
+ // Selection label
+ label = new StyledLabel("Select rocket motor:", Style.BOLD);
+ panel.add(label, "spanx, wrap para");
+
+ // Diameter selection
+ JComboBox filterComboBox = new JComboBox(SHOW_DESCRIPTIONS);
+ filterComboBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JComboBox cb = (JComboBox) e.getSource();
+ int sel = cb.getSelectedIndex();
+ if ((sel < 0) || (sel > SHOW_MAX))
+ sel = SHOW_ALL;
+ switch (sel) {
+ case SHOW_ALL:
+ sorter.setRowFilter(new MotorRowFilterAll());
+ break;
+
+ case SHOW_SMALLER:
+ sorter.setRowFilter(new MotorRowFilterSmaller());
+ break;
+
+ case SHOW_EXACT:
+ sorter.setRowFilter(new MotorRowFilterExact());
+ break;
+
+ default:
+ assert (false) : "Should not occur.";
+ }
+ Prefs.putChoise("MotorDiameterMatch", sel);
+ scrollSelectionVisible();
+ }
+ });
+ panel.add(filterComboBox, "spanx, growx, wrap para");
+
+
+
+ // Motor selection table
+ model = new ThrustCurveMotorDatabaseModel(database);
+ table = new JTable(model);
+
+
+ // Set comparators and widths
+ table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ sorter = new TableRowSorter<TableModel>(model);
+ for (int i = 0; i < ThrustCurveMotorColumns.values().length; i++) {
+ ThrustCurveMotorColumns column = ThrustCurveMotorColumns.values()[i];
+ sorter.setComparator(i, column.getComparator());
+ table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
+ }
+ table.setRowSorter(sorter);
+
+ // Set selection and double-click listeners
+ table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ int row = table.getSelectedRow();
+ if (row >= 0) {
+ row = table.convertRowIndexToModel(row);
+ ThrustCurveMotorSet motorSet = model.getMotorSet(row);
+ log.user("Selected table row " + row + ": " + motorSet);
+ if (motorSet != selectedMotorSet) {
+ select(selectMotor(motorSet));
+ }
+ } else {
+ log.user("Selected table row " + row + ", nothing selected");
+ }
+ }
+ });
+ table.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
+ if (dialog != null) {
+ dialog.close(true);
+ }
+ }
+ }
+ });
+
+
+ JScrollPane scrollpane = new JScrollPane();
+ scrollpane.setViewportView(table);
+ panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para");
+
+
+
+
+ // Motor mount diameter label
+ label = new StyledLabel("Motor mount diameter: " +
+ UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
+ panel.add(label, "gapright 30lp, spanx, split");
+
+
+
+ // Search field
+ label = new StyledLabel("Search:");
+ panel.add(label, "");
+
+ searchField = new JTextField();
+ searchField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ update();
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ update();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ update();
+ }
+
+ private void update() {
+ String text = searchField.getText().trim();
+ String[] split = text.split("\\s+");
+ ArrayList<String> list = new ArrayList<String>();
+ for (String s : split) {
+ s = s.trim().toLowerCase();
+ if (s.length() > 0) {
+ list.add(s);
+ }
+ }
+ searchTerms = list.toArray(new String[0]);
+ sorter.sort();
+ scrollSelectionVisible();
+ }
+ });
+ panel.add(searchField, "growx, wrap");
+
+
+
+ // Vertical split
+ this.add(panel, "grow");
+ this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para");
+ panel = new JPanel(new MigLayout("fill"));
+
+
+
+ // Thrust curve selection
+ curveSelectionLabel = new JLabel("Select thrust curve:");
+ panel.add(curveSelectionLabel);
+
+ curveSelectionModel = new DefaultComboBoxModel();
+ curveSelectionBox = new JComboBox(curveSelectionModel);
+ curveSelectionBox.setRenderer(new CurveSelectionRenderer(curveSelectionBox.getRenderer()));
+ curveSelectionBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Object value = curveSelectionBox.getSelectedItem();
+ if (value != null) {
+ select(((MotorHolder) value).getMotor());
+ }
+ }
+ });
+ panel.add(curveSelectionBox, "growx, wrap para");
+
+
+
+
+
+ // Ejection charge delay
+ panel.add(new JLabel("Ejection charge delay:"));
+
+ delayBox = new JComboBox();
+ delayBox.setEditable(true);
+ delayBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JComboBox cb = (JComboBox) e.getSource();
+ String sel = (String) cb.getSelectedItem();
+ if (sel.equalsIgnoreCase("None")) {
+ selectedDelay = Motor.PLUGGED;
+ } else {
+ try {
+ selectedDelay = Double.parseDouble(sel);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ setDelays(false);
+ }
+ });
+ panel.add(delayBox, "growx, wrap rel");
+ panel.add(new StyledLabel("(Number of seconds or \"None\")", -3), "skip, wrap para");
+ setDelays(false);
+
+
+ panel.add(new JSeparator(), "spanx, growx, wrap para");
+
+
+
+ // Thrust curve info
+ panel.add(new JLabel("Total impulse:"));
+ totalImpulseLabel = new JLabel();
+ panel.add(totalImpulseLabel, "wrap");
+
+ panel.add(new JLabel("Avg. thrust:"));
+ avgThrustLabel = new JLabel();
+ panel.add(avgThrustLabel, "wrap");
+
+ panel.add(new JLabel("Max. thrust:"));
+ maxThrustLabel = new JLabel();
+ panel.add(maxThrustLabel, "wrap");
+
+ panel.add(new JLabel("Burn time:"));
+ burnTimeLabel = new JLabel();
+ panel.add(burnTimeLabel, "wrap");
+
+ panel.add(new JLabel("Launch mass:"));
+ launchMassLabel = new JLabel();
+ panel.add(launchMassLabel, "wrap");
+
+ panel.add(new JLabel("Empty mass:"));
+ emptyMassLabel = new JLabel();
+ panel.add(emptyMassLabel, "wrap");
+
+ panel.add(new JLabel("Data points:"));
+ dataPointsLabel = new JLabel();
+ panel.add(dataPointsLabel, "wrap para");
+
+
+ comment = new JTextArea(5, 5);
+ GUIUtil.changeFontSize(comment, -2);
+ withCommentFont = comment.getFont();
+ noCommentFont = withCommentFont.deriveFont(Font.ITALIC);
+ comment.setLineWrap(true);
+ comment.setWrapStyleWord(true);
+ comment.setEditable(false);
+ scrollpane = new JScrollPane(comment);
+ panel.add(scrollpane, "spanx, growx, wrap para");
+
+
+
+
+ // Thrust curve plot
+ chart = ChartFactory.createXYLineChart(
+ null, // title
+ null, // xAxisLabel
+ null, // yAxisLabel
+ null, // dataset
+ PlotOrientation.VERTICAL,
+ false, // legend
+ false, // tooltips
+ false // urls
+ );
+
+
+ // Add the data and formatting to the plot
+ XYPlot plot = chart.getXYPlot();
+
+ changeLabelFont(plot.getRangeAxis(), -2);
+ changeLabelFont(plot.getDomainAxis(), -2);
+
+ chart.setTitle(new TextTitle("Thrust curve:", this.getFont()));
+ chart.setBackgroundPaint(this.getBackground());
+ plot.setBackgroundPaint(Color.WHITE);
+ plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
+ plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
+
+ chartPanel = new ChartPanel(chart,
+ false, // properties
+ false, // save
+ false, // print
+ false, // zoom
+ false); // tooltips
+ chartPanel.setMouseZoomable(false);
+ chartPanel.setPopupMenu(null);
+ chartPanel.setMouseWheelEnabled(false);
+ chartPanel.setRangeZoomable(false);
+ chartPanel.setDomainZoomable(false);
+
+ chartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ chartPanel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (selectedMotor == null || selectedMotorSet == null)
+ return;
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ // Open plot dialog
+ ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(selectedMotorSet, selectedMotor,
+ SwingUtilities.getWindowAncestor(ThrustCurveMotorSelectionPanel.this));
+ plotDialog.setVisible(true);
+ }
+ }
+ });
+
+ JLayeredPane layer = new CustomLayeredPane();
+
+ layer.setBorder(BorderFactory.createLineBorder(Color.BLUE));
+
+ layer.add(chartPanel, (Integer) 0);
+
+ zoomIcon = new JLabel(Icons.ZOOM_IN);
+ zoomIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ layer.add(zoomIcon, (Integer) 1);
+
+
+ panel.add(layer, "width 300:300:, height 180:180:, grow, spanx");
+
+
+
+ this.add(panel, "grow");
+
+
+
+ // Sets the filter:
+ int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
+ filterComboBox.setSelectedIndex(showMode);
+
+
+ // Update the panel data
+ updateData();
+ selectedDelay = delay;
+ setDelays(false);
+
+ }
+
+
+
+ @Override
+ public Motor getSelectedMotor() {
+ return selectedMotor;
+ }
+
+
+ @Override
+ public double getSelectedDelay() {
+ return selectedDelay;
+ }
+
+
+ @Override
+ public JComponent getDefaultFocus() {
+ return searchField;
+ }
+
+ @Override
+ public void selectedMotor(Motor motorSelection) {
+ if (!(motorSelection instanceof ThrustCurveMotor)) {
+ log.error("Received argument that was not ThrustCurveMotor: " + motorSelection);
+ return;
+ }
+
+ ThrustCurveMotor motor = (ThrustCurveMotor) motorSelection;
+ ThrustCurveMotorSet set = findMotorSet(motor);
+ if (set == null) {
+ log.error("Could not find set for motor:" + motorSelection);
+ return;
+ }
+
+ // Store selected motor in preferences node, set all others to false
+ Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
+ for (ThrustCurveMotor m : set.getMotors()) {
+ String digest = MotorDigest.digestMotor(m);
+ prefs.putBoolean(digest, m == motor);
+ }
+ }
+
+ public void setCloseableDialog(CloseableDialog dialog) {
+ this.dialog = dialog;
+ }
+
+
+
+ private void changeLabelFont(ValueAxis axis, float size) {
+ Font font = axis.getTickLabelFont();
+ font = font.deriveFont(font.getSize2D() + size);
+ axis.setTickLabelFont(font);
+ }
+
+
+ /**
+ * Called when a different motor is selected from within the panel.
+ */
+ private void select(ThrustCurveMotor motor) {
+ if (selectedMotor == motor)
+ return;
+
+ ThrustCurveMotorSet set = findMotorSet(motor);
+ if (set == null) {
+ throw new BugException("Could not find motor from database, motor=" + motor);
+ }
+
+ boolean updateDelays = (selectedMotorSet != set);
+
+ selectedMotor = motor;
+ selectedMotorSet = set;
+ updateData();
+ if (updateDelays) {
+ setDelays(true);
+ }
+ }
+
+
+ private void updateData() {
+
+ if (selectedMotorSet == null) {
+ // No motor selected
+ curveSelectionModel.removeAllElements();
+ curveSelectionBox.setEnabled(false);
+ curveSelectionLabel.setEnabled(false);
+ totalImpulseLabel.setText("");
+ avgThrustLabel.setText("");
+ maxThrustLabel.setText("");
+ burnTimeLabel.setText("");
+ launchMassLabel.setText("");
+ emptyMassLabel.setText("");
+ dataPointsLabel.setText("");
+ setComment("");
+ chart.getXYPlot().setDataset(new XYSeriesCollection());
+ return;
+ }
+
+
+ List<ThrustCurveMotor> motors = selectedMotorSet.getMotors();
+ final int index = motors.indexOf(selectedMotor);
+
+ curveSelectionModel.removeAllElements();
+ for (int i = 0; i < motors.size(); i++) {
+ curveSelectionModel.addElement(new MotorHolder(motors.get(i), i));
+ }
+ curveSelectionBox.setSelectedIndex(index);
+
+ if (motors.size() > 1) {
+ curveSelectionBox.setEnabled(true);
+ curveSelectionLabel.setEnabled(true);
+ } else {
+ curveSelectionBox.setEnabled(false);
+ curveSelectionLabel.setEnabled(false);
+ }
+
+ totalImpulseLabel.setText(UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
+ selectedMotor.getTotalImpulseEstimate()));
+ avgThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
+ selectedMotor.getAverageThrustEstimate()));
+ maxThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
+ selectedMotor.getMaxThrustEstimate()));
+ burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
+ selectedMotor.getBurnTimeEstimate()));
+ launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
+ selectedMotor.getLaunchCG().weight));
+ emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
+ selectedMotor.getEmptyCG().weight));
+ dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1));
+
+ setComment(selectedMotor.getDescription());
+
+
+ // Update the plot
+ XYPlot plot = chart.getXYPlot();
+
+ XYSeriesCollection dataset = new XYSeriesCollection();
+ for (int i = 0; i < motors.size(); i++) {
+ ThrustCurveMotor m = motors.get(i);
+
+ XYSeries series = new XYSeries("Thrust");
+ double[] time = m.getTimePoints();
+ double[] thrust = m.getThrustPoints();
+
+ for (int j = 0; j < time.length; j++) {
+ series.add(time[j], thrust[j]);
+ }
+
+ dataset.addSeries(series);
+
+ boolean selected = (i == index);
+ plot.getRenderer().setSeriesStroke(i, new BasicStroke(selected ? 3 : 1));
+ plot.getRenderer().setSeriesPaint(i, getColor(i));
+ }
+
+ plot.setDataset(dataset);
+ }
+
+
+ private void setComment(String s) {
+ s = s.trim();
+ if (s.length() == 0) {
+ comment.setText("No description available.");
+ comment.setFont(noCommentFont);
+ comment.setForeground(NO_COMMENT_COLOR);
+ } else {
+ comment.setText(s);
+ comment.setFont(withCommentFont);
+ comment.setForeground(WITH_COMMENT_COLOR);
+ }
+ comment.setCaretPosition(0);
+ }
+
+ private void scrollSelectionVisible() {
+ if (selectedMotorSet != null) {
+ int index = table.convertRowIndexToView(model.getIndex(selectedMotorSet));
+ System.out.println("index=" + index);
+ table.getSelectionModel().setSelectionInterval(index, index);
+ Rectangle rect = table.getCellRect(index, 0, true);
+ rect = new Rectangle(rect.x, rect.y - 100, rect.width, rect.height + 200);
+ table.scrollRectToVisible(rect);
+ }
+ }
+
+
+ public static Color getColor(int index) {
+ return (Color) CURVE_COLORS[index % CURVE_COLORS.length];
+ }
+
+
+ /**
+ * Find the ThrustCurveMotorSet that contains a motor.
+ *
+ * @param motor the motor to look for.
+ * @return the ThrustCurveMotorSet, or null if not found.
+ */
+ private ThrustCurveMotorSet findMotorSet(ThrustCurveMotor motor) {
+ for (ThrustCurveMotorSet set : database) {
+ if (set.getMotors().contains(motor)) {
+ return set;
+ }
+ }
+
+ return null;
+ }
+
+
+
+ /**
+ * Select the default motor from this ThrustCurveMotorSet. This uses primarily motors
+ * that the user has previously used, and secondarily a heuristic method of selecting which
+ * thrust curve seems to be better or more reliable.
+ *
+ * @param set the motor set
+ * @return the default motor in this set
+ */
+ private ThrustCurveMotor selectMotor(ThrustCurveMotorSet set) {
+ if (set.getMotorCount() == 0) {
+ throw new BugException("Attempting to select motor from empty ThrustCurveMotorSet: " + set);
+ }
+ if (set.getMotorCount() == 1) {
+ return set.getMotors().get(0);
+ }
+
+ // Find which motor has been used the most recently
+ Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
+ for (ThrustCurveMotor m : set.getMotors()) {
+ String digest = MotorDigest.digestMotor(m);
+ if (prefs.getBoolean(digest, false)) {
+ return m;
+ }
+ }
+
+ // No motor has been used, use heuristics to select motor
+ // TODO: CRITICAL: Heuristics
+ return set.getMotors().get(0);
+ }
+
+
+ /**
+ * Set the values in the delay combo box. If <code>reset</code> is <code>true</code>
+ * then sets the selected value as the value closest to selectedDelay, otherwise
+ * leaves selection alone.
+ */
+ private void setDelays(boolean reset) {
+ if (selectedMotor == null) {
+
+ delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
+ delayBox.setSelectedIndex(0);
+
+ } else {
+
+ List<Double> delays = selectedMotorSet.getDelays();
+ String[] delayStrings = new String[delays.size()];
+ double currentDelay = selectedDelay; // Store current setting locally
+
+ for (int i = 0; i < delays.size(); i++) {
+ delayStrings[i] = ThrustCurveMotor.getDelayString(delays.get(i), "None");
+ }
+ delayBox.setModel(new DefaultComboBoxModel(delayStrings));
+
+ if (reset) {
+
+ // Find and set the closest value
+ double closest = Double.NaN;
+ for (int i = 0; i < delays.size(); i++) {
+ // if-condition to always become true for NaN
+ if (!(Math.abs(delays.get(i) - currentDelay) > Math.abs(closest - currentDelay))) {
+ closest = delays.get(i);
+ }
+ }
+ if (!Double.isNaN(closest)) {
+ selectedDelay = closest;
+ delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(closest, "None"));
+ } else {
+ delayBox.setSelectedItem("None");
+ }
+
+ } else {
+
+ selectedDelay = currentDelay;
+ delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, "None"));
+
+ }
+
+ }
+ }
+
+
+
+
+ //////////////////////
+
+
+ private class CurveSelectionRenderer implements ListCellRenderer {
+
+ private final ListCellRenderer renderer;
+
+ public CurveSelectionRenderer(ListCellRenderer renderer) {
+ this.renderer = renderer;
+ }
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value, int index,
+ boolean isSelected, boolean cellHasFocus) {
+
+ Component c = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ if (value instanceof MotorHolder) {
+ MotorHolder m = (MotorHolder) value;
+ c.setForeground(getColor(m.getIndex()));
+ }
+
+ return c;
+ }
+
+ }
+
+
+ //////// Row filters
+
+ /**
+ * Abstract adapter class.
+ */
+ private abstract class MotorRowFilter extends RowFilter<TableModel, Integer> {
+ @Override
+ public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
+ int index = entry.getIdentifier();
+ ThrustCurveMotorSet m = model.getMotorSet(index);
+ return filterByDiameter(m) && filterByString(m);
+ }
+
+ public abstract boolean filterByDiameter(ThrustCurveMotorSet m);
+
+
+ public boolean filterByString(ThrustCurveMotorSet m) {
+ main: for (String s : searchTerms) {
+ for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) {
+ String str = col.getValue(m).toString().toLowerCase();
+ if (str.indexOf(s) >= 0)
+ continue main;
+ }
+ return false;
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Show all motors.
+ */
+ private class MotorRowFilterAll extends MotorRowFilter {
+ @Override
+ public boolean filterByDiameter(ThrustCurveMotorSet m) {
+ return true;
+ }
+ }
+
+ /**
+ * Show motors smaller than the mount.
+ */
+ private class MotorRowFilterSmaller extends MotorRowFilter {
+ @Override
+ public boolean filterByDiameter(ThrustCurveMotorSet m) {
+ return (m.getDiameter() <= diameter + 0.0004);
+ }
+ }
+
+ /**
+ * Show motors that fit the mount.
+ */
+ private class MotorRowFilterExact extends MotorRowFilter {
+ @Override
+ public boolean filterByDiameter(ThrustCurveMotorSet m) {
+ return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015));
+ }
+ }
+
+
+ /**
+ * Custom layered pane that sets the bounds of the components on every layout.
+ */
+ public class CustomLayeredPane extends JLayeredPane {
+ @Override
+ public void doLayout() {
+ synchronized (getTreeLock()) {
+ int w = getWidth();
+ int h = getHeight();
+ chartPanel.setBounds(0, 0, w, h);
+ zoomIcon.setBounds(w - ZOOM_ICON_POSITION_NEGATIVE_X, ZOOM_ICON_POSITION_POSITIVE_Y, 50, 50);
+ }
+ }
+ }
+}
import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
import net.sf.openrocket.gui.dialogs.ExampleDesignDialog;
import net.sf.openrocket.gui.dialogs.LicenseDialog;
+import net.sf.openrocket.gui.dialogs.MotorDatabaseLoadingDialog;
import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
import net.sf.openrocket.gui.dialogs.WarningDialog;
import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.Stage;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.GUIUtil;
import net.sf.openrocket.util.Icons;
import net.sf.openrocket.util.TestRockets;
public class BasicFrame extends JFrame {
- private static final long serialVersionUID = 1L;
-
+ private static final LogHelper log = Application.getLogger();
+
/**
* The RocketLoader instance used for loading all rocket designs.
*/
private static final RocketLoader ROCKET_LOADER = new GeneralRocketLoader();
private static final RocketSaver ROCKET_SAVER = new OpenRocketSaver();
-
+
// FileFilters for different types of rocket design files
private static final FileFilter ALL_DESIGNS_FILTER =
- new SimpleFileFilter("All rocket designs (*.ork; *.rkt)",
- ".ork", ".ork.gz", ".rkt", ".rkt.gz");
+ new SimpleFileFilter("All rocket designs (*.ork; *.rkt)",
+ ".ork", ".ork.gz", ".rkt", ".rkt.gz");
- private static final FileFilter OPENROCKET_DESIGN_FILTER =
- new SimpleFileFilter("OpenRocket designs (*.ork)", ".ork", ".ork.gz");
+ private static final FileFilter OPENROCKET_DESIGN_FILTER =
+ new SimpleFileFilter("OpenRocket designs (*.ork)", ".ork", ".ork.gz");
- private static final FileFilter ROCKSIM_DESIGN_FILTER =
- new SimpleFileFilter("RockSim designs (*.rkt)", ".rkt", ".rkt.gz");
-
+ private static final FileFilter ROCKSIM_DESIGN_FILTER =
+ new SimpleFileFilter("RockSim designs (*.rkt)", ".rkt", ".rkt.gz");
+
+
+
+ public static final int COMPONENT_TAB = 0;
+ public static final int SIMULATION_TAB = 1;
-
- public static final int COMPONENT_TAB = 0;
- public static final int SIMULATION_TAB = 1;
-
/**
* List of currently open frames. When the list goes empty
*/
private static final ArrayList<BasicFrame> frames = new ArrayList<BasicFrame>();
-
-
-
-
+
+
+
+
/**
* Whether "New" and "Open" should replace this frame.
* Should be set to false on the first rocket modification.
*/
private boolean replaceable = false;
-
-
+
+
private final OpenRocketDocument document;
private final Rocket rocket;
private final RocketActions actions;
-
+
/**
* Sole constructor. Creates a new frame based on the supplied document
* and adds it to the current frames list.
* @param document the document to show.
*/
public BasicFrame(OpenRocketDocument document) {
-
+ log.debug("Instantiating new BasicFrame");
+
this.document = document;
this.rocket = document.getRocket();
this.rocket.getDefaultConfiguration().setAllStages();
-
+
// Set replaceable flag to false at first modification
rocket.addComponentChangeListener(new ComponentChangeListener() {
public void componentChanged(ComponentChangeEvent e) {
}
});
-
+
// Create the component tree selection model that will be used
componentSelectionModel = new DefaultTreeSelectionModel();
componentSelectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
selectionModel.attachComponentTreeSelectionModel(componentSelectionModel);
selectionModel.attachSimulationListSelectionModel(simulationSelectionModel);
-
+
actions = new RocketActions(document, selectionModel, this);
+
+ log.debug("Constructing the BasicFrame UI");
// The main vertical split pane
JSplitPane vertical = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
vertical.setResizeWeight(0.5);
this.add(vertical);
-
+
// The top tabbed pane
tabbedPane = new JTabbedPane();
tabbedPane.addTab("Rocket design", null, designTab());
rocketpanel = new RocketPanel(document);
vertical.setBottomComponent(rocketpanel);
-
+
rocketpanel.setSelectionModel(tree.getSelectionModel());
+
-
createMenu();
-
+
rocket.addComponentChangeListener(new ComponentChangeListener() {
public void componentChanged(ComponentChangeEvent e) {
setTitle();
setTitle();
this.pack();
-
+
Dimension size = Prefs.getWindowSize(this.getClass());
if (size == null) {
size = Toolkit.getDefaultToolkit().getScreenSize();
- size.width = size.width*9/10;
- size.height = size.height*9/10;
+ size.width = size.width * 9 / 10;
+ size.height = size.height * 9 / 10;
}
this.setSize(size);
this.addComponentListener(new ComponentAdapter() {
}
});
this.setLocationByPlatform(true);
-
+
GUIUtil.setWindowIcons(this);
this.validate();
}
});
frames.add(this);
+
+ log.debug("BasicFrame instantiation complete");
}
* for adding components.
*/
private JComponent designTab() {
- JSplitPane horizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true);
+ JSplitPane horizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
horizontal.setResizeWeight(0.5);
-
+
// Upper-left segment, component tree
-
- JPanel panel = new JPanel(new MigLayout("fill, flowy","","[grow]"));
-
+
+ JPanel panel = new JPanel(new MigLayout("fill, flowy", "", "[grow]"));
+
tree = new ComponentTree(rocket);
tree.setSelectionModel(componentSelectionModel);
-
+
// Remove JTree key events that interfere with menu accelerators
InputMap im = SwingUtilities.getUIInputMap(tree, JComponent.WHEN_FOCUSED);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK), null);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK), null);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK), null);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK), null);
+
-
// Double-click opens config dialog
MouseListener ml = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
int selRow = tree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
- if(selRow != -1) {
- if((e.getClickCount() == 2) && !ComponentConfigDialog.isDialogVisible()) {
+ if (selRow != -1) {
+ if ((e.getClickCount() == 2) && !ComponentConfigDialog.isDialogVisible()) {
// Double-click
- RocketComponent c = (RocketComponent)selPath.getLastPathComponent();
- ComponentConfigDialog.showDialog(BasicFrame.this,
+ RocketComponent c = (RocketComponent) selPath.getLastPathComponent();
+ ComponentConfigDialog.showDialog(BasicFrame.this,
BasicFrame.this.document, c);
}
}
}
};
tree.addMouseListener(ml);
-
+
// Update dialog when selection is changed
componentSelectionModel.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
if (!ComponentConfigDialog.isDialogVisible())
return;
- RocketComponent c = (RocketComponent)path.getLastPathComponent();
- ComponentConfigDialog.showDialog(BasicFrame.this,
+ RocketComponent c = (RocketComponent) path.getLastPathComponent();
+ ComponentConfigDialog.showDialog(BasicFrame.this,
BasicFrame.this.document, c);
}
});
-
+
// Place tree inside scroll pane
JScrollPane scroll = new JScrollPane(tree);
- panel.add(scroll,"spany, grow, wrap");
-
+ panel.add(scroll, "spany, grow, wrap");
+
// Buttons
JButton button = new JButton(actions.getMoveUpAction());
- panel.add(button,"sizegroup buttons, aligny 65%");
+ panel.add(button, "sizegroup buttons, aligny 65%");
button = new JButton(actions.getMoveDownAction());
- panel.add(button,"sizegroup buttons, aligny 0%");
+ panel.add(button, "sizegroup buttons, aligny 0%");
button = new JButton(actions.getEditAction());
panel.add(button, "sizegroup buttons");
button = new JButton(actions.getNewStageAction());
- panel.add(button,"sizegroup buttons");
+ panel.add(button, "sizegroup buttons");
button = new JButton(actions.getDeleteAction());
button.setIcon(null);
button.setMnemonic(0);
- panel.add(button,"sizegroup buttons");
-
+ panel.add(button, "sizegroup buttons");
+
horizontal.setLeftComponent(panel);
-
+
// Upper-right segment, component addition buttons
-
- panel = new JPanel(new MigLayout("fill, insets 0","[0::]"));
-
+
+ panel = new JPanel(new MigLayout("fill, insets 0", "[0::]"));
+
scroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setViewportView(new ComponentAddButtons(document, componentSelectionModel,
scroll.getViewport()));
scroll.setBorder(null);
scroll.setViewportBorder(null);
-
+
TitledBorder border = new TitledBorder("Add new component");
border.setTitleFont(border.getTitleFont().deriveFont(Font.BOLD));
scroll.setBorder(border);
-
- panel.add(scroll,"grow");
-
+
+ panel.add(scroll, "grow");
+
horizontal.setRightComponent(panel);
-
+
return horizontal;
}
-
+
/**
* Creates the menu for the window.
*/
menu.getAccessibleContext().setAccessibleDescription("File-handling related tasks");
menubar.add(menu);
- item = new JMenuItem("New",KeyEvent.VK_N);
+ item = new JMenuItem("New", KeyEvent.VK_N);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
item.setMnemonic(KeyEvent.VK_N);
item.getAccessibleContext().setAccessibleDescription("Create a new rocket design");
});
menu.add(item);
- item = new JMenuItem("Open...",KeyEvent.VK_O);
+ item = new JMenuItem("Open...", KeyEvent.VK_O);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
item.getAccessibleContext().setAccessibleDescription("Open a rocket design");
item.setIcon(Icons.FILE_OPEN);
item = new JMenuItem("Open example...");
item.getAccessibleContext().setAccessibleDescription("Open an example rocket design");
- item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK));
item.setIcon(Icons.FILE_OPEN_EXAMPLE);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
URL[] urls = ExampleDesignDialog.selectExampleDesigns(BasicFrame.this);
if (urls != null) {
- for (URL u: urls) {
+ for (URL u : urls) {
open(u, BasicFrame.this);
}
}
menu.addSeparator();
- item = new JMenuItem("Save",KeyEvent.VK_S);
+ item = new JMenuItem("Save", KeyEvent.VK_S);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
item.getAccessibleContext().setAccessibleDescription("Save the current rocket design");
item.setIcon(Icons.FILE_SAVE);
});
menu.add(item);
- item = new JMenuItem("Save as...",KeyEvent.VK_A);
- item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
+ item = new JMenuItem("Save as...", KeyEvent.VK_A);
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK));
- item.getAccessibleContext().setAccessibleDescription("Save the current rocket design "+
+ item.getAccessibleContext().setAccessibleDescription("Save the current rocket design " +
"to a new file");
item.setIcon(Icons.FILE_SAVE_AS);
item.addActionListener(new ActionListener() {
});
menu.add(item);
-// menu.addSeparator();
+ // menu.addSeparator();
menu.add(new JSeparator());
- item = new JMenuItem("Close",KeyEvent.VK_C);
+ item = new JMenuItem("Close", KeyEvent.VK_C);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK));
item.getAccessibleContext().setAccessibleDescription("Close the current rocket design");
item.setIcon(Icons.FILE_CLOSE);
menu.add(item);
menu.addSeparator();
-
- item = new JMenuItem("Quit",KeyEvent.VK_Q);
+
+ item = new JMenuItem("Quit", KeyEvent.VK_Q);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
item.getAccessibleContext().setAccessibleDescription("Quit the program");
item.setIcon(Icons.FILE_QUIT);
});
menu.add(item);
-
+
//// Edit
menu = new JMenu("Edit");
menu.getAccessibleContext().setAccessibleDescription("Rocket editing");
menubar.add(menu);
-
+
Action action = document.getUndoAction();
item = new JMenuItem(action);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
item.setMnemonic(KeyEvent.VK_U);
item.getAccessibleContext().setAccessibleDescription("Undo the previous operation");
-
+
menu.add(item);
-
+
action = document.getRedoAction();
item = new JMenuItem(action);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, ActionEvent.CTRL_MASK));
menu.addSeparator();
-
+
item = new JMenuItem(actions.getCutAction());
menu.add(item);
-
+
item = new JMenuItem(actions.getCopyAction());
menu.add(item);
-
+
item = new JMenuItem(actions.getPasteAction());
menu.add(item);
item = new JMenuItem("Preferences");
item.setIcon(Icons.PREFERENCES);
- item.getAccessibleContext().setAccessibleDescription("Setup the application "+
+ item.getAccessibleContext().setAccessibleDescription("Setup the application " +
"preferences");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
}
});
menu.add(item);
-
+
//// Analyze
menu = new JMenu("Analyze");
menu.setMnemonic(KeyEvent.VK_A);
menu.getAccessibleContext().setAccessibleDescription("Analyzing the rocket");
menubar.add(menu);
- item = new JMenuItem("Component analysis",KeyEvent.VK_C);
+ item = new JMenuItem("Component analysis", KeyEvent.VK_C);
item.getAccessibleContext().setAccessibleDescription("Analyze the rocket components " +
"separately");
item.addActionListener(new ActionListener() {
});
menu.add(item);
-
+
//// Debug
// (shown if openrocket.debug.menu is defined)
if (System.getProperty("openrocket.debug.menu") != null) {
menubar.add(makeDebugMenu());
}
-
-
+
+
//// Help
menu = new JMenu("Help");
menu.getAccessibleContext().setAccessibleDescription("Information about OpenRocket");
menubar.add(menu);
-
-
- item = new JMenuItem("License",KeyEvent.VK_L);
+
+
+ item = new JMenuItem("License", KeyEvent.VK_L);
item.getAccessibleContext().setAccessibleDescription("OpenRocket license information");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
});
menu.add(item);
- item = new JMenuItem("Bug report",KeyEvent.VK_B);
+ item = new JMenuItem("Bug report", KeyEvent.VK_B);
item.getAccessibleContext().setAccessibleDescription("Information about reporting " +
"bugs in OpenRocket");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
-// new BugDialog(BasicFrame.this).setVisible(true);
+ // new BugDialog(BasicFrame.this).setVisible(true);
BugReportDialog.showBugReportDialog(BasicFrame.this);
}
});
menu.add(item);
- item = new JMenuItem("About",KeyEvent.VK_A);
+ item = new JMenuItem("About", KeyEvent.VK_A);
item.getAccessibleContext().setAccessibleDescription("About OpenRocket");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
});
menu.add(item);
-
+
this.setJMenuBar(menubar);
}
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(BasicFrame.this,
new Object[] {
- "The 'Debug' menu includes actions for testing and debugging " +
- "OpenRocket.", " ",
- "The menu is made visible by defining the system property " +
- "'openrocket.debug.menu' when starting OpenRocket.",
- "It should not be visible by default." },
+ "The 'Debug' menu includes actions for testing and debugging " +
+ "OpenRocket.", " ",
+ "The menu is made visible by defining the system property " +
+ "'openrocket.debug.menu' when starting OpenRocket.",
+ "It should not be visible by default." },
"Debug menu", JOptionPane.INFORMATION_MESSAGE);
}
});
int sel = JOptionPane.showOptionDialog(BasicFrame.this, new Object[] {
"Input text key to generate random rocket:",
field
- }, "Generate random test rocket", JOptionPane.DEFAULT_OPTION,
- JOptionPane.QUESTION_MESSAGE, null, new Object[] {
- "Random", "OK"
+ }, "Generate random test rocket", JOptionPane.DEFAULT_OPTION,
+ JOptionPane.QUESTION_MESSAGE, null, new Object[] {
+ "Random", "OK"
}, "OK");
Rocket r;
});
menu.add(item);
-
+
item = new JMenuItem("Create 'Iso-Haisu'");
item.addActionListener(new ActionListener() {
});
menu.add(item);
-
-
+
+
menu.addSeparator();
item = new JMenuItem("Exception here");
});
menu.add(item);
-
-
+
+
return menu;
}
-
+
/**
* Select the tab on the main pane.
*
public void selectTab(int tab) {
tabbedPane.setSelectedIndex(tab);
}
-
+
private void openAction() {
- JFileChooser chooser = new JFileChooser();
-
- chooser.addChoosableFileFilter(ALL_DESIGNS_FILTER);
- chooser.addChoosableFileFilter(OPENROCKET_DESIGN_FILTER);
- chooser.addChoosableFileFilter(ROCKSIM_DESIGN_FILTER);
- chooser.setFileFilter(ALL_DESIGNS_FILTER);
-
- chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
- chooser.setMultiSelectionEnabled(true);
- chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
- if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION)
- return;
-
- Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
-
- File[] files = chooser.getSelectedFiles();
-
- for (File file: files) {
- System.out.println("Opening file: " + file);
- if (open(file, this)) {
-
- // Close previous window if replacing
- if (replaceable && document.isSaved()) {
- closeAction();
- replaceable = false;
- }
- }
- }
+ JFileChooser chooser = new JFileChooser();
+
+ chooser.addChoosableFileFilter(ALL_DESIGNS_FILTER);
+ chooser.addChoosableFileFilter(OPENROCKET_DESIGN_FILTER);
+ chooser.addChoosableFileFilter(ROCKSIM_DESIGN_FILTER);
+ chooser.setFileFilter(ALL_DESIGNS_FILTER);
+
+ chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ chooser.setMultiSelectionEnabled(true);
+ chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
+ if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION)
+ return;
+
+ Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
+
+ File[] files = chooser.getSelectedFiles();
+
+ for (File file : files) {
+ System.out.println("Opening file: " + file);
+ if (open(file, this)) {
+
+ // Close previous window if replacing
+ if (replaceable && document.isSaved()) {
+ closeAction();
+ replaceable = false;
+ }
+ }
+ }
}
-
+
private static boolean open(URL url, BasicFrame parent) {
String filename = null;
try {
URI uri = url.toURI();
filename = uri.getPath();
- } catch (URISyntaxException ignore) { }
-
+ } catch (URISyntaxException ignore) {
+ }
+
// Try URL-decoding the URL
if (filename == null) {
try {
filename = URLDecoder.decode(url.toString(), "UTF-8");
- } catch (UnsupportedEncodingException ignore) { }
+ } catch (UnsupportedEncodingException ignore) {
+ }
}
// Last resort
// Remove path from filename
if (filename.lastIndexOf('/') >= 0) {
- filename = filename.substring(filename.lastIndexOf('/')+1);
+ filename = filename.substring(filename.lastIndexOf('/') + 1);
}
try {
InputStream is = url.openStream();
if (open(is, filename, parent)) {
- // Close previous window if replacing
- if (parent.replaceable && parent.document.isSaved()) {
- parent.closeAction();
- parent.replaceable = false;
- }
+ // Close previous window if replacing
+ if (parent.replaceable && parent.document.isSaved()) {
+ parent.closeAction();
+ parent.replaceable = false;
+ }
}
} catch (IOException e) {
- JOptionPane.showMessageDialog(parent,
+ JOptionPane.showMessageDialog(parent,
"An error occurred while opening the file " + filename,
"Error loading file", JOptionPane.ERROR_MESSAGE);
}
return open(worker, filename, null, parent);
}
-
+
/**
* Open the specified file in a new design frame. If an error occurs, an error
* dialog is shown and <code>false</code> is returned.
return open(worker, file.getName(), file, parent);
}
-
+
/**
* Open the specified file using the provided worker.
*
* @param parent
* @return
*/
- private static boolean open(OpenFileWorker worker, String filename, File file,
+ private static boolean open(OpenFileWorker worker, String filename, File file,
Window parent) {
-
+
+ MotorDatabaseLoadingDialog.check(null);
+
// Open the file in a Swing worker thread
- if (!SwingWorkerDialog.runWorker(parent, "Opening file",
+ if (!SwingWorkerDialog.runWorker(parent, "Opening file",
"Reading " + filename + "...", worker)) {
-
+
// User cancelled the operation
return false;
}
-
+
// Handle the document
OpenRocketDocument doc = null;
try {
-
+
doc = worker.get();
-
+
} catch (ExecutionException e) {
-
+
Throwable cause = e.getCause();
-
+
if (cause instanceof FileNotFoundException) {
-
- JOptionPane.showMessageDialog(parent,
+
+ JOptionPane.showMessageDialog(parent,
"File not found: " + filename,
"Error opening file", JOptionPane.ERROR_MESSAGE);
return false;
-
+
} else if (cause instanceof RocketLoadException) {
-
- JOptionPane.showMessageDialog(parent,
- "Unable to open file '" + filename +"': "
- + cause.getMessage(),
+
+ JOptionPane.showMessageDialog(parent,
+ "Unable to open file '" + filename + "': "
+ + cause.getMessage(),
"Error opening file", JOptionPane.ERROR_MESSAGE);
return false;
-
+
} else {
-
+
throw new BugException("Unknown error when opening file", e);
-
+
}
-
+
} catch (InterruptedException e) {
throw new BugException("EDT was interrupted", e);
}
throw new BugException("BUG: Document loader returned null");
}
-
- // Show warnings
+
+ // Show warnings
WarningSet warnings = worker.getRocketLoader().getWarnings();
if (!warnings.isEmpty()) {
WarningDialog.showWarnings(parent,
new Object[] {
- "The following problems were encountered while opening " + filename + ".",
- "Some design features may not have been loaded correctly."
+ "The following problems were encountered while opening " + filename + ".",
+ "Some design features may not have been loaded correctly."
},
"Warnings while opening file", warnings);
}
-
- // Set document state
- doc.setFile(file);
- doc.setSaved(true);
- // Open the frame
- BasicFrame frame = new BasicFrame(doc);
- frame.setVisible(true);
-
- return true;
+ // Set document state
+ doc.setFile(file);
+ doc.setSaved(true);
+
+ // Open the frame
+ BasicFrame frame = new BasicFrame(doc);
+ frame.setVisible(true);
+
+ return true;
}
-
-
-
-
-
-
-
+
+
+
private boolean saveAction() {
File file = document.getFile();
- if (file==null) {
+ if (file == null) {
return saveAsAction();
}
// Saving RockSim designs is not supported
if (ROCKSIM_DESIGN_FILTER.accept(file)) {
- file = new File(file.getAbsolutePath().replaceAll(".[rR][kK][tT](.[gG][zZ])?$",
+ file = new File(file.getAbsolutePath().replaceAll(".[rR][kK][tT](.[gG][zZ])?$",
".ork"));
-
+
int option = JOptionPane.showConfirmDialog(this, new Object[] {
"Saving designs in RockSim format is not supported.",
- "Save in OpenRocket format instead ("+file.getName()+")?"
- }, "Save "+file.getName(), JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE, null);
+ "Save in OpenRocket format instead (" + file.getName() + ")?"
+ }, "Save " + file.getName(), JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE, null);
if (option != JOptionPane.YES_OPTION)
return false;
document.setFile(file);
- }
+ }
return saveAs(file);
}
File file = null;
while (file == null) {
// TODO: HIGH: what if *.rkt chosen?
- StorageOptionChooser storageChooser =
- new StorageOptionChooser(document, document.getDefaultStorageOptions());
+ StorageOptionChooser storageChooser =
+ new StorageOptionChooser(document, document.getDefaultStorageOptions());
JFileChooser chooser = new JFileChooser();
chooser.setFileFilter(OPENROCKET_DESIGN_FILTER);
chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
file = chooser.getSelectedFile();
if (file == null)
return false;
-
+
Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
storageChooser.storeOptions(document.getDefaultStorageOptions());
}
if (file.exists()) {
- int result = JOptionPane.showConfirmDialog(this,
- "File '"+file.getName()+"' exists. Do you want to overwrite it?",
+ int result = JOptionPane.showConfirmDialog(this,
+ "File '" + file.getName() + "' exists. Do you want to overwrite it?",
"File exists", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (result != JOptionPane.YES_OPTION)
return false;
}
}
- saveAs(file);
- return true;
+ saveAs(file);
+ return true;
}
private boolean saveAs(File file) {
- System.out.println("Saving to file: " + file.getName());
- boolean saved = false;
-
- if (!StorageOptionChooser.verifyStorageOptions(document, this)) {
- // User cancelled the dialog
- return false;
- }
-
-
- 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 {
+ System.out.println("Saving to file: " + file.getName());
+ boolean saved = false;
+
+ if (!StorageOptionChooser.verifyStorageOptions(document, this)) {
+ // User cancelled the dialog
+ return false;
+ }
+
+
+ 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 {
worker.get();
document.setFile(file);
document.setSaved(true);
saved = true;
- setTitle();
+ setTitle();
} catch (ExecutionException e) {
-
+
Throwable cause = e.getCause();
if (cause instanceof IOException) {
- JOptionPane.showMessageDialog(this, new String[] {
- "An I/O error occurred while saving:",
- e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
- return false;
+ JOptionPane.showMessageDialog(this, new String[] {
+ "An I/O error occurred while saving:",
+ e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
+ return false;
} else {
Reflection.handleWrappedException(e);
}
} catch (InterruptedException e) {
throw new BugException("EDT was interrupted", e);
}
-
- return saved;
+
+ return saved;
}
private boolean closeAction() {
if (!document.isSaved()) {
ComponentConfigDialog.hideDialog();
- int result = JOptionPane.showConfirmDialog(this,
- "Design '"+rocket.getName()+"' has not been saved. " +
- "Do you want to save it?",
- "Design not saved", JOptionPane.YES_NO_CANCEL_OPTION,
+ int result = JOptionPane.showConfirmDialog(this,
+ "Design '" + rocket.getName() + "' has not been saved. " +
+ "Do you want to save it?",
+ "Design not saved", JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (result == JOptionPane.YES_OPTION) {
// Save
if (!saveAction())
- return false; // If save was interrupted
+ return false; // If save was interrupted
} else if (result == JOptionPane.NO_OPTION) {
// Don't save: No-op
} else {
// Rocket has been saved or discarded
this.dispose();
-
+
// TODO: LOW: Close only dialogs that have this frame as their parent
ComponentConfigDialog.hideDialog();
ComponentAnalysisDialog.hideDialog();
* Open a new design window with a basic rocket+stage.
*/
public static void newAction() {
+ log.debug("New action initiated");
Rocket rocket = new Rocket();
Stage stage = new Stage();
stage.setName("Sustainer");
* Quit the application. Confirms saving unsaved designs. The action of File->Quit.
*/
public static void quitAction() {
- for (int i=frames.size()-1; i>=0; i--) {
+ for (int i = frames.size() - 1; i >= 0; i--) {
if (!frames.get(i).closeAction()) {
// Close canceled
return;
String title;
title = rocket.getName();
- if (file!=null) {
- title = title + " ("+file.getName()+")";
+ if (file != null) {
+ title = title + " (" + file.getName() + ")";
}
if (!saved)
title = "*" + title;
}
-
+
/**
* Find a currently open BasicFrame containing the specified rocket. This method
* can be used to map a Rocket to a BasicFrame from GUI methods.
* @return the corresponding BasicFrame, or <code>null</code> if none found.
*/
public static BasicFrame findFrame(Rocket rocket) {
- for (BasicFrame f: frames) {
+ for (BasicFrame f : frames) {
if (f.rocket == rocket)
return f;
}
* @return the corresponding OpenRocketDocument, or <code>null</code> if not found.
*/
public static OpenRocketDocument findDocument(Rocket rocket) {
- for (BasicFrame f: frames) {
+ for (BasicFrame f : frames) {
if (f.rocket == rocket)
return f.document;
}
// Initialize the splash screen with version info
Splash.init();
-
+
// Start update info fetching
final UpdateInfoRetriever updateInfo;
if (Prefs.getCheckUpdates()) {
updateInfo = null;
}
-
+
// Set the best available look-and-feel
GUIUtil.setBestLAF();
-
+
// Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively.
ToolTipManager.sharedInstance().setDismissDelay(30000);
-
+
// Setup the uncaught exception handler
ExceptionHandler.registerExceptionHandler();
-
+
// Load defaults
Prefs.loadDefaultUnits();
newAction();
}
-
+
// Check whether update info has been fetched or whether it needs more time
checkUpdateStatus(updateInfo);
}
private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
if (updateInfo == null)
return;
-
+
int delay = 1000;
if (!updateInfo.isRunning())
delay = 100;
-
+
final Timer timer = new Timer(delay, null);
-
+
ActionListener listener = new ActionListener() {
private int count = 5;
+
@Override
public void actionPerformed(ActionEvent e) {
if (!updateInfo.isRunning()) {
String current = Prefs.getVersion();
String last = Prefs.getString(Prefs.LAST_UPDATE, "");
-
+
UpdateInfo info = updateInfo.getUpdateInfo();
if (info != null && info.getLatestVersion() != null &&
!current.equals(info.getLatestVersion()) &&
// Check command-line for files
boolean opened = false;
- for (String file: args) {
+ for (String file : args) {
if (open(new File(file), null)) {
opened = true;
}
}
return opened;
}
-
+
}
package net.sf.openrocket.gui.plot;
import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
public class PlotDialog extends JDialog {
+ private static final float PLOT_STROKE_WIDTH = 1.5f;
+
private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0);
private static final Map<FlightEvent.Type, Color> EVENT_COLORS =
new HashMap<FlightEvent.Type, Color>();
ModifiedXYItemRenderer r = new ModifiedXYItemRenderer();
r.setBaseShapesVisible(initialShowPoints);
r.setBaseShapesFilled(true);
+ for (int j = 0; j < data[i].getSeriesCount(); j++) {
+ r.setSeriesStroke(j, new BasicStroke(PLOT_STROKE_WIDTH));
+ }
renderers.add(r);
plot.setRenderer(axisno, r);
plot.mapDatasetToRangeAxis(axisno, axisno);
import java.util.Arrays;
import java.util.Locale;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.Inertia;
public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> {
+ private static final LogHelper log = Application.getLogger();
public static final double MAX_THRUST = 10e6;
}
-
+ /**
+ * {@inheritDoc}
+ * <p>
+ * NOTE: In most cases you want to examine the motor type of the ThrustCurveMotorSet,
+ * not the ThrustCurveMotor itself.
+ */
@Override
public Type getMotorType() {
return type;
private int modID = 0;
public ThrustCurveMotorInstance() {
+ log.debug("ThrustCurveMotor: Creating motor instance of " + ThrustCurveMotor.this);
position = 0;
prevTime = 0;
instThrust = 0;
@Override
public void step(double nextTime, double acceleration, AtmosphericConditions cond) {
- System.out.println("MOTOR: Stepping instance " + this + " to time " + nextTime);
-
if (!(nextTime >= prevTime)) {
// Also catches NaN
throw new IllegalArgumentException("Stepping backwards in time, current=" +
prevTime + " new=" + nextTime);
}
if (MathUtil.equals(prevTime, nextTime)) {
- System.out.println("Same time as earlier");
return;
}
modID++;
if (position >= time.length - 1) {
- System.out.println("Thrust has ended");
// Thrust has ended
prevTime = nextTime;
stepThrust = 0;
if (position < time.length - 1) {
nextCG = MathUtil.map(nextTime, time[position], time[position + 1],
cg[position], cg[position + 1]);
-
- System.out.println("nextTime=" + nextTime +
- " time[position]=" + time[position] +
- " time[position+1]=" + time[position + 1] +
- " mass[position]=" + cg[position].weight * 1000 +
- " mass[position+1]=" + cg[position + 1].weight * 1000 +
- " result=" + nextCG.weight * 1000 +
- " position=" + position);
} else {
nextCG = cg[cg.length - 1];
}
stepCG = instCG.add(nextCG).multiply(0.5);
- System.out.println("instMass=" + instCG.weight + " nextMass=" + nextCG.weight + " stepMass=" + stepCG.weight);
instCG = nextCG;
// Update time
if (status.getSimulationTime() < status.getStartWarningTime())
warnings = null;
- System.out.println("flightConditions=" + store.flightConditions);
-
+
// Calculate aerodynamic forces
store.forces = status.getSimulationConditions().getAerodynamicCalculator()
.getAerodynamicForces(status.getConfiguration(), store.flightConditions, warnings);
- System.out.println("CP=" + store.forces.getCP());
-
+
// Add very small randomization to yaw & pitch moments to prevent over-perfect flight
// TODO: HIGH: This should rather be performed as a listener
store.forces.setCm(store.forces.getCm() + (PITCH_YAW_RANDOM * 2 * (random.nextDouble() - 0.5)));
package net.sf.openrocket.startup;
-import net.sf.openrocket.database.MotorSetDatabase;
+import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.logging.LogLevel;
import net.sf.openrocket.logging.LogLevelBufferLogger;
private static LogHelper logger;
private static LogLevelBufferLogger logBuffer;
- private static MotorSetDatabase motorSetDatabase;
+ private static ThrustCurveMotorSetDatabase motorSetDatabase;
// Initialize the logger to something sane for testing without executing Startup
static {
/**
* Return the database of all thrust curves loaded into the system.
*/
- public static MotorSetDatabase getMotorSetDatabase() {
+ public static ThrustCurveMotorSetDatabase getMotorSetDatabase() {
return motorSetDatabase;
}
/**
* Set the database of thrust curves loaded into the system.
*/
- public static void setMotorSetDatabase(MotorSetDatabase motorSetDatabase) {
+ public static void setMotorSetDatabase(ThrustCurveMotorSetDatabase motorSetDatabase) {
Application.motorSetDatabase = motorSetDatabase;
}
import java.io.InputStream;
import java.io.PrintStream;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import net.sf.openrocket.communication.UpdateInfo;
import net.sf.openrocket.communication.UpdateInfoRetriever;
import net.sf.openrocket.database.Databases;
-import net.sf.openrocket.database.MotorSetDatabase;
+import net.sf.openrocket.database.ThrustCurveMotorSet;
+import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
import net.sf.openrocket.file.DirectoryIterator;
import net.sf.openrocket.file.GeneralMotorLoader;
import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
private static final String THRUSTCURVE_DIRECTORY = "datafiles/thrustcurves/";
-
-
+ /** Block motor loading for this many milliseconds */
+ private static AtomicInteger blockLoading = new AtomicInteger(Integer.MAX_VALUE);
+
+
public static void main(final String[] args) throws Exception {
// Initialize logging first so we can use it
checkHead();
// Check that we're running a good version of a JRE
+ log.info("Checking JRE compatibility");
VersionHelper.checkVersion();
VersionHelper.checkOpenJDK();
// Run the actual startup method in the EDT since it can use progress dialogs etc.
+ log.info("Running main");
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
}
});
+ log.info("Startup complete");
+
+ // Block motor loading for 2 seconds to allow window painting
+ blockLoading.set(2000);
}
private static void runMain(String[] args) {
// Initialize the splash screen with version info
+ log.info("Initializing the splash screen");
Splash.init();
// Setup the uncaught exception handler
+ log.info("Registering exception handler");
ExceptionHandler.registerExceptionHandler();
// Start update info fetching
final UpdateInfoRetriever updateInfo;
if (Prefs.getCheckUpdates()) {
+ log.info("Starting update check");
updateInfo = new UpdateInfoRetriever();
updateInfo.start();
} else {
+ log.info("Update check disabled");
updateInfo = null;
}
// Set the best available look-and-feel
+ log.info("Setting best LAF");
GUIUtil.setBestLAF();
// Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively.
// Load motors etc.
// TODO: HIGH: Use new motor loading
- // loadMotor();
+ log.info("Loading databases");
+ loadMotor();
Databases.fakeMethod();
// Starting action (load files or open new document)
+ log.info("Opening main application window");
if (!handleCommandLine(args)) {
BasicFrame.newAction();
}
// Check whether update info has been fetched or whether it needs more time
+ log.info("Checking update status");
checkUpdateStatus(updateInfo);
}
log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY +
" in background thread.");
- MotorSetDatabase db = new MotorSetDatabase(true) {
+ ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
@Override
protected void loadMotors() {
+
+ log.info("Blocking motor loading while starting up");
+
+ // Block for 100ms a time until timeout or database in use
+ while (!inUse && blockLoading.addAndGet(-100) > 0) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ log.info("Blocking ended, inUse=" + inUse + " slowLoadingCount=" + blockLoading.get());
+
+ log.info("Started to load motors from " + THRUSTCURVE_DIRECTORY);
+ long t0 = System.currentTimeMillis();
+
+ int fileCount = 0;
+ int thrustCurveCount = 0;
+ int distinctMotorCount = 0;
+ int distinctThrustCurveCount = 0;
+
GeneralMotorLoader loader = new GeneralMotorLoader();
- DirectoryIterator iterator =
- DirectoryIterator.findDirectory(THRUSTCURVE_DIRECTORY,
- new SimpleFileFilter("", false, "eng", "rkt"));
+ DirectoryIterator iterator = DirectoryIterator.findDirectory(THRUSTCURVE_DIRECTORY,
+ new SimpleFileFilter("", false, "eng", "rse"));
if (iterator == null) {
- throw new IllegalStateException("No thrust curves found, " +
- "distribution built wrong");
+ throw new IllegalStateException("No thrust curves found, distribution built wrong");
}
while (iterator.hasNext()) {
final Pair<String, InputStream> input = iterator.next();
+ log.debug("Loading motors from file " + input.getU());
+ fileCount++;
try {
List<Motor> motors = loader.load(input.getV(), input.getU());
+ if (motors.size() == 0) {
+ log.warn("No motors found in file " + input.getU());
+ }
for (Motor m : motors) {
+ thrustCurveCount++;
this.addMotor((ThrustCurveMotor) m);
}
} catch (IOException e) {
log.error("IOException when closing InputStream", e);
}
}
+
+ }
+
+ long t1 = System.currentTimeMillis();
+
+ // Count statistics
+ distinctMotorCount = motorSets.size();
+ for (ThrustCurveMotorSet set : motorSets) {
+ distinctThrustCurveCount += set.getMotorCount();
}
+ log.info("Motor loading done, took " + (t1 - t0) + " ms to load "
+ + fileCount + " files containing " + thrustCurveCount + " thrust curves which contained "
+ + distinctMotorCount + " distinct motors with " + distinctThrustCurveCount + " thrust curves.");
}
};
*/
private static void checkHead() {
+ log.info("Checking for graphics head");
+
if (GraphicsEnvironment.isHeadless()) {
log.error("Application is headless.");
System.err.println();
public class VersionHelper {
- private static final LogHelper logger = Application.getLogger();
-
+ private static final LogHelper log = Application.getLogger();
+
private static final int REQUIRED_MAJOR_VERSION = 1;
private static final int REQUIRED_MINOR_VERSION = 6;
// OpenJDK 1.6.0_0-b16 is known to work, 1.6.0_0-b12 does not
private static final String BAD_OPENJDK_VERSION = "^1.6.0_0-b([0-9]|1[1-5])$";
-
-
+
+
/**
* Check that the JRE version is high enough.
*/
static void checkVersion() {
+
String[] version = System.getProperty("java.specification.version", "").split("\\.");
String jreName = System.getProperty("java.vm.name", "(unknown)");
String jreVersion = System.getProperty("java.runtime.version", "(unknown)");
String jreVendor = System.getProperty("java.vendor", "(unknown)");
- logger.info("Running JRE " + jreName + " version " + jreVersion + " by " + jreVendor);
+ log.info("Running JRE " + jreName + " version " + jreVersion + " by " + jreVendor);
int major, minor;
-
+
try {
major = Integer.parseInt(version[0]);
minor = Integer.parseInt(version[1]);
- if (major < REQUIRED_MAJOR_VERSION ||
+ if (major < REQUIRED_MAJOR_VERSION ||
(major == REQUIRED_MAJOR_VERSION && minor < REQUIRED_MINOR_VERSION)) {
- Startup.error(new String[] {"Java SE version 6 is required to run OpenRocket.",
+ Startup.error(new String[] { "Java SE version 6 is required to run OpenRocket.",
"You are currently running " + jreName + " version " +
- jreVersion + " by " + jreVendor});
+ jreVersion + " by " + jreVendor });
}
} catch (RuntimeException e) {
- Startup.confirm(new String[] {"The Java version in use could not be detected.",
+ Startup.confirm(new String[] { "The Java version in use could not be detected.",
"OpenRocket requires at least Java SE 6.",
- "Continue anyway?"});
+ "Continue anyway?" });
}
}
-
+
/**
* Check whether OpenJDK is being used, and if it is warn the user about
* problems and confirm whether to continue.
*/
static void checkOpenJDK() {
- if (System.getProperty("java.runtime.name", "").toLowerCase().indexOf("icedtea")>=0 ||
- System.getProperty("java.vm.name", "").toLowerCase().indexOf("openjdk")>=0) {
-
+ if (System.getProperty("java.runtime.name", "").toLowerCase().indexOf("icedtea") >= 0 ||
+ System.getProperty("java.vm.name", "").toLowerCase().indexOf("openjdk") >= 0) {
+
String jreName = System.getProperty("java.vm.name", "(unknown)");
String jreVersion = System.getProperty("java.runtime.version", "(unknown)");
String jreVendor = System.getProperty("java.vendor", "(unknown)");
-
- if (jreVersion.matches(BAD_OPENJDK_VERSION)) {
- Startup.confirm(new String[] {"Old versions of OpenJDK are known to have problems " +
+ if (jreVersion.matches(BAD_OPENJDK_VERSION)) {
+
+ Startup.confirm(new String[] { "Old versions of OpenJDK are known to have problems " +
"running OpenRocket.",
" ",
"You are currently running " + jreName + " version " +
- jreVersion + " by " + jreVendor,
- "Do you want to continue?"});
+ jreVersion + " by " + jreVendor,
+ "Do you want to continue?" });
}
}
}
-
+
}
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class BugException extends FatalException {
-
- public BugException() {
- }
-
+
public BugException(String message) {
super(message);
}
-
+
public BugException(Throwable cause) {
super(cause);
}
-
+
public BugException(String message, Throwable cause) {
super(message, cause);
}
-
+
}
import java.awt.Component;
import java.awt.Container;
+import java.awt.Font;
import java.awt.Image;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
/**
* Set suitable options for a single-use disposable dialog. This includes
- * setting ESC to close the dialog and adding the appropriate window icons.
- * If defaultButton is provided, it is set to the default button action.
+ * setting ESC to close the dialog, adding the appropriate window icons and
+ * setting the location based on the platform. If defaultButton is provided,
+ * it is set to the default button action.
* <p>
* The default button must be already attached to the dialog.
*
installEscapeCloseOperation(dialog);
setWindowIcons(dialog);
addModelNullingListener(dialog);
+ dialog.setLocationByPlatform(true);
if (defaultButton != null) {
setDefaultButton(defaultButton);
}
}
+ /**
+ * Changes the size of the font of the specified component by the given amount.
+ *
+ * @param component the component for which to change the font
+ * @param size the change in the font size
+ */
+ public static void changeFontSize(JComponent component, float size) {
+ Font font = component.getFont();
+ font = font.deriveFont(font.getSize2D() + size);
+ component.setFont(font);
+ }
+
+
/**
* Traverses recursively the component tree, and sets all applicable component
* models to null, so as to remove the listener connections. After calling this
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.main.ExceptionHandler;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
public class Icons {
-
+ private static final LogHelper log = Application.getLogger();
+
+ static {
+ log.debug("Starting to load icons");
+ }
+
/**
* Icons used for showing the status of a simulation (up to date, out of date, etc).
*/
SIMULATION_LISTENER_OK = SIMULATION_STATUS_ICON_MAP.get(Simulation.Status.UPTODATE);
SIMULATION_LISTENER_ERROR = SIMULATION_STATUS_ICON_MAP.get(Simulation.Status.OUTDATED);
}
-
+
public static final Icon FILE_NEW = loadImageIcon("pix/icons/document-new.png", "New document");
public static final Icon FILE_OPEN = loadImageIcon("pix/icons/document-open.png", "Open document");
public static final Icon EDIT_COPY = loadImageIcon("pix/icons/edit-copy.png", "Copy");
public static final Icon EDIT_PASTE = loadImageIcon("pix/icons/edit-paste.png", "Paste");
public static final Icon EDIT_DELETE = loadImageIcon("pix/icons/edit-delete.png", "Delete");
-
+
public static final Icon ZOOM_IN = loadImageIcon("pix/icons/zoom-in.png", "Zoom in");
public static final Icon ZOOM_OUT = loadImageIcon("pix/icons/zoom-out.png", "Zoom out");
-
+
public static final Icon PREFERENCES = loadImageIcon("pix/icons/preferences.png", "Preferences");
-
- public static final Icon DELETE = loadImageIcon("pix/icons/delete.png", "Delete");
+ public static final Icon DELETE = loadImageIcon("pix/icons/delete.png", "Delete");
+ static {
+ log.debug("Icons loaded");
+ }
/**
* Load an ImageIcon from the specified file. The file is obtained as a system
/**
* Whether to use the debug-node instead of the normal node.
*/
- public static final boolean DEBUG = false;
+ private static final boolean DEBUG;
+ static {
+ DEBUG = (System.getProperty("openrocket.debug.prefs") != null);
+ }
/**
* Whether to clear all preferences at application startup. This has an effect only
* if DEBUG is true.
*/
- public static final boolean CLEARPREFS = true;
+ private static final boolean CLEARPREFS = true;
/**
* The node name to use in the Java preferences storage.
*/
- public static final String NODENAME = (DEBUG ? "OpenRocket-debug" : "OpenRocket");
-
-
- public static final String DEFAULT_BUILD_SOURCE = "default";
+ private static final String NODENAME = (DEBUG ? "OpenRocket-debug" : "OpenRocket");
/*
private static final String CHECK_UPDATES = "CheckUpdates";
public static final String LAST_UPDATE = "LastUpdateVersion";
+
+ // Node names
+ public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "preferredThrustCurveMotors";
+
+
/**
* Node to this application's preferences.
* @deprecated Use the static methods instead.
private static final Preferences PREFNODE;
+ // Clear the preferences if debug mode and clearprefs is defined
static {
Preferences root = Preferences.userRoot();
if (DEBUG && CLEARPREFS) {
//////////////////////
+ /**
+ * Return the OpenRocket version number.
+ */
public static String getVersion() {
return BuildPropertyHolder.BUILD_VERSION;
}
+ /**
+ * Return the OpenRocket build source (e.g. "default" or "Debian")
+ */
public static String getBuildSource() {
return BuildPropertyHolder.BUILD_SOURCE;
}
+ /**
+ * Return the OpenRocket unique ID.
+ *
+ * @return a random ID string that stays constant between OpenRocket executions
+ */
public static String getUniqueID() {
String id = PREFNODE.get("id", null);
if (id == null) {
- public static void storeVersion() {
+ /**
+ * Store the current OpenRocket version into the preferences to allow for preferences migration.
+ */
+ private static void storeVersion() {
PREFNODE.put("OpenRocketVersion", getVersion());
}
}
-
+ /**
+ * Return a string preference.
+ *
+ * @param key the preference key.
+ * @param def the default if no preference is stored
+ * @return the preference value
+ */
public static String getString(String key, String def) {
return PREFNODE.get(key, def);
}
+ /**
+ * Set a string preference.
+ *
+ * @param key the preference key
+ * @param value the value to set
+ */
public static void putString(String key, String value) {
PREFNODE.put(key, value);
storeVersion();
}
-
+ /**
+ * Return a boolean preference.
+ *
+ * @param key the preference key
+ * @param def the default if no preference is stored
+ * @return the preference value
+ */
public static boolean getBoolean(String key, boolean def) {
return PREFNODE.getBoolean(key, def);
}
+ /**
+ * Set a boolean preference.
+ *
+ * @param key the preference key
+ * @param value the value to set
+ */
public static void putBoolean(String key, boolean value) {
PREFNODE.putBoolean(key, value);
storeVersion();
}
+ /**
+ * Return a preferences object for the specified node name.
+ *
+ * @param nodeName the node name
+ * @return the preferences object for that node
+ */
+ public static Preferences getNode(String nodeName) {
+ return PREFNODE.node(nodeName);
+ }
+
+
+ //////////////////
+
+
+
public static boolean getCheckUpdates() {
return PREFNODE.getBoolean(CHECK_UPDATES, BuildPropertyHolder.DEFAULT_CHECK_UPDATES);
storeVersion();
}
-
- //////////////////
-
public static File getDefaultDirectory() {
String file = PREFNODE.get("defaultDirectory", null);
if (file == null)
import java.awt.Color;
import java.util.Random;
-import net.sf.openrocket.database.Databases;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.material.Material.Type;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.MotorMount.IgnitionEvent;
import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
import net.sf.openrocket.rocketcomponent.Transition.Shape;
+import net.sf.openrocket.startup.Application;
public class TestRockets {
public TestRockets(String key) {
-
+
if (key == null) {
Random rnd = new Random();
StringBuilder sb = new StringBuilder();
- for (int i=0; i<6; i++) {
+ for (int i = 0; i < 6; i++) {
int n = rnd.nextInt(62);
if (n < 10) {
- sb.append((char)('0'+n));
+ sb.append((char) ('0' + n));
} else if (n < 36) {
- sb.append((char)('A'+n-10));
+ sb.append((char) ('A' + n - 10));
} else {
- sb.append((char)('a'+n-36));
+ sb.append((char) ('a' + n - 36));
}
}
key = sb.toString();
this.key = key;
this.rnd = new Random(key.hashCode());
-
+
}
-
-
+
+
/**
* Create a new test rocket based on the value 'key'. The rocket utilizes most of the
* properties and features available. The same key always returns the same rocket,
rocket.setRevision("Rocket revision " + key);
rocket.setName(key);
-
+
Stage stage = new Stage();
setBasics(stage);
rocket.addChild(stage);
-
+
NoseCone nose = new NoseCone();
setBasics(stage);
nose.setAftRadius(rnd(0.03));
nose.setClipped(rnd.nextBoolean());
nose.setThickness(rnd(0.002));
nose.setFilled(rnd.nextBoolean());
- nose.setForeRadius(rnd(0.1)); // Unset
+ nose.setForeRadius(rnd(0.1)); // Unset
nose.setLength(rnd(0.15));
nose.setShapeParameter(rnd(0.5));
nose.setType((Shape) randomEnum(Shape.class));
stage.addChild(nose);
-
+
Transition shoulder = new Transition();
setBasics(shoulder);
shoulder.setAftRadius(rnd(0.06));
shoulder.setType((Shape) randomEnum(Shape.class));
stage.addChild(shoulder);
-
+
BodyTube body = new BodyTube();
setBasics(body);
body.setThickness(rnd(0.002));
body.setFilled(rnd.nextBoolean());
- body.setIgnitionDelay(rnd.nextDouble()*3);
+ body.setIgnitionDelay(rnd.nextDouble() * 3);
body.setIgnitionEvent((IgnitionEvent) randomEnum(IgnitionEvent.class));
body.setLength(rnd(0.3));
body.setMotorMount(rnd.nextBoolean());
- body.setMotorOverhang(rnd.nextGaussian()*0.03);
+ body.setMotorOverhang(rnd.nextGaussian() * 0.03);
body.setRadius(rnd(0.06));
body.setRadiusAutomatic(rnd.nextBoolean());
stage.addChild(body);
-
+
Transition boattail = new Transition();
setBasics(boattail);
boattail.setAftRadius(rnd(0.03));
mass.setRadius(rnd(0.05));
nose.addChild(mass);
-
-
-
+
+
+
return rocket;
}
private void setBasics(RocketComponent c) {
c.setComment(c.getComponentName() + " comment " + key);
c.setName(c.getComponentName() + " name " + key);
-
+
c.setCGOverridden(rnd.nextBoolean());
c.setMassOverridden(rnd.nextBoolean());
c.setOverrideCGX(rnd(0.2));
c.setOverrideMass(rnd(0.05));
c.setOverrideSubcomponents(rnd.nextBoolean());
-
+
if (c.isMassive()) {
// Only massive components are drawn
c.setColor(randomColor());
}
if (c instanceof ExternalComponent) {
- ExternalComponent e = (ExternalComponent)c;
+ ExternalComponent e = (ExternalComponent) c;
e.setFinish((Finish) randomEnum(Finish.class));
double d = rnd(100);
- e.setMaterial(Material.newMaterial(Type.BULK, "Testmat "+d, d, rnd.nextBoolean()));
+ e.setMaterial(Material.newMaterial(Type.BULK, "Testmat " + d, d, rnd.nextBoolean()));
}
if (c instanceof InternalComponent) {
- InternalComponent i = (InternalComponent)c;
+ InternalComponent i = (InternalComponent) c;
i.setRelativePosition((Position) randomEnum(Position.class));
i.setPositionValue(rnd(0.3));
}
}
-
+
private double rnd(double scale) {
- return (rnd.nextDouble()*0.2+0.9) * scale;
+ return (rnd.nextDouble() * 0.2 + 0.9) * scale;
}
private Color randomColor() {
return values[rnd.nextInt(values.length)];
}
-
-
-
-
+
+
+
public Rocket makeSmallFlyable() {
- double noseconeLength=0.10,noseconeRadius=0.01;
- double bodytubeLength=0.20,bodytubeRadius=0.01,bodytubeThickness=0.001;
-
- int finCount=3;
- double finRootChord=0.04,finTipChord=0.05,finSweep=0.01,finThickness=0.003, finHeight=0.03;
+ double noseconeLength = 0.10, noseconeRadius = 0.01;
+ double bodytubeLength = 0.20, bodytubeRadius = 0.01, bodytubeThickness = 0.001;
+ int finCount = 3;
+ double finRootChord = 0.04, finTipChord = 0.05, finSweep = 0.01, finThickness = 0.003, finHeight = 0.03;
+
Rocket rocket;
Stage stage;
NoseCone nosecone;
rocket = new Rocket();
stage = new Stage();
stage.setName("Stage1");
-
- nosecone = new NoseCone(Transition.Shape.ELLIPSOID,noseconeLength,noseconeRadius);
- bodytube = new BodyTube(bodytubeLength,bodytubeRadius,bodytubeThickness);
-
- finset = new TrapezoidFinSet(finCount,finRootChord,finTipChord,finSweep,finHeight);
+ nosecone = new NoseCone(Transition.Shape.ELLIPSOID, noseconeLength, noseconeRadius);
+ bodytube = new BodyTube(bodytubeLength, bodytubeRadius, bodytubeThickness);
+ finset = new TrapezoidFinSet(finCount, finRootChord, finTipChord, finSweep, finHeight);
+
+
// Stage construction
rocket.addChild(stage);
-
+
// Component construction
stage.addChild(nosecone);
stage.addChild(bodytube);
-
+
bodytube.addChild(finset);
Material material = Prefs.getDefaultComponentMaterial(null, Material.Type.BULK);
String id = rocket.newMotorConfigurationID();
bodytube.setMotorMount(true);
- for (Motor m: Databases.MOTOR) {
- if (m.getDesignation().equals("B4")) {
- bodytube.setMotor(id, m);
- break;
- }
- }
+ Motor m = Application.getMotorSetDatabase().findMotors(null, null, "B4", Double.NaN, Double.NaN).get(0);
+ bodytube.setMotor(id, m);
bodytube.setMotorOverhang(0.005);
rocket.getDefaultConfiguration().setMotorConfigurationID(id);
rocket.getDefaultConfiguration().setAllStages();
-
+
return rocket;
}
-
-
+
+
public static Rocket makeBigBlue() {
Rocket rocket;
Stage stage;
rocket = new Rocket();
stage = new Stage();
stage.setName("Stage1");
-
- nosecone = new NoseCone(Transition.Shape.ELLIPSOID,0.105,0.033);
+
+ nosecone = new NoseCone(Transition.Shape.ELLIPSOID, 0.105, 0.033);
nosecone.setThickness(0.001);
- bodytube = new BodyTube(0.69,0.033,0.001);
-
+ bodytube = new BodyTube(0.69, 0.033, 0.001);
+
finset = new FreeformFinSet();
try {
finset.setPoints(new Coordinate[] {
finset.setThickness(0.003);
finset.setFinCount(4);
- finset.setCantAngle(0*Math.PI/180);
- System.err.println("Fin cant angle: "+(finset.getCantAngle() * 180/Math.PI));
+ finset.setCantAngle(0 * Math.PI / 180);
+ System.err.println("Fin cant angle: " + (finset.getCantAngle() * 180 / Math.PI));
- mcomp = new MassComponent(0.2,0.03,0.045 + 0.060);
+ mcomp = new MassComponent(0.2, 0.03, 0.045 + 0.060);
mcomp.setRelativePosition(Position.TOP);
mcomp.setPositionValue(0);
// Stage construction
rocket.addChild(stage);
rocket.setPerfectFinish(false);
-
+
// Component construction
stage.addChild(nosecone);
stage.addChild(bodytube);
-
+
bodytube.addChild(finset);
bodytube.addChild(mcomp);
-// Material material = new Material("Test material", 500);
-// nosecone.setMaterial(material);
-// bodytube.setMaterial(material);
-// finset.setMaterial(material);
+ // Material material = new Material("Test material", 500);
+ // nosecone.setMaterial(material);
+ // bodytube.setMaterial(material);
+ // finset.setMaterial(material);
String id = rocket.newMotorConfigurationID();
bodytube.setMotorMount(true);
- for (Motor m: Databases.MOTOR) {
- if (m.getDesignation().equals("F12J")) {
- bodytube.setMotor(id, m);
- break;
- }
- }
+ Motor m = Application.getMotorSetDatabase().findMotors(null, null, "F12J", Double.NaN, Double.NaN).get(0);
+ bodytube.setMotor(id, m);
bodytube.setMotorOverhang(0.005);
rocket.getDefaultConfiguration().setMotorConfigurationID(id);
rocket.getDefaultConfiguration().setAllStages();
-
+
return rocket;
}
rocket = new Rocket();
stage = new Stage();
stage.setName("Stage1");
-
- nosecone = new NoseCone(Transition.Shape.OGIVE,0.53,R);
+
+ nosecone = new NoseCone(Transition.Shape.OGIVE, 0.53, R);
nosecone.setThickness(0.005);
nosecone.setMassOverridden(true);
nosecone.setOverrideMass(0.588);
stage.addChild(nosecone);
- tube1 = new BodyTube(0.505,R,0.005);
+ tube1 = new BodyTube(0.505, R, 0.005);
tube1.setMassOverridden(true);
tube1.setOverrideMass(0.366);
stage.addChild(tube1);
- tube2 = new BodyTube(0.605,R,0.005);
+ tube2 = new BodyTube(0.605, R, 0.005);
tube2.setMassOverridden(true);
tube2.setOverrideMass(0.427);
stage.addChild(tube2);
- tube3 = new BodyTube(1.065,R,0.005);
+ tube3 = new BodyTube(1.065, R, 0.005);
tube3.setMassOverridden(true);
tube3.setOverrideMass(0.730);
stage.addChild(tube3);
-
+
LaunchLug lug = new LaunchLug();
tube1.addChild(lug);
coupler.setPositionValue(-0.14);
tube1.addChild(coupler);
-
+
// Parachute
MassComponent mass = new MassComponent(0.05, 0.05, 0.280);
mass.setRelativePosition(Position.TOP);
mass.setPositionValue(0.25);
tube1.addChild(mass);
-
+
auxfinset = new TrapezoidFinSet();
auxfinset.setName("CONTROL");
auxfinset.setFinCount(2);
auxfinset.setCrossSection(CrossSection.AIRFOIL);
auxfinset.setRelativePosition(Position.TOP);
auxfinset.setPositionValue(0.28);
- auxfinset.setBaseRotation(Math.PI/2);
+ auxfinset.setBaseRotation(Math.PI / 2);
tube1.addChild(auxfinset);
-
-
-
+
+
+
coupler = new TubeCoupler();
coupler.setOuterRadiusAutomatic(true);
coupler.setLength(0.28);
coupler.setOverrideMass(0.360);
tube2.addChild(coupler);
-
-
+
+
// Parachute
mass = new MassComponent(0.1, 0.05, 0.028);
mass.setRelativePosition(Position.TOP);
mass.setPositionValue(0.19);
tube2.addChild(mass);
-
-
+
+
InnerTube inner = new InnerTube();
- inner.setOuterRadius(0.08/2);
- inner.setInnerRadius(0.0762/2);
+ inner.setOuterRadius(0.08 / 2);
+ inner.setInnerRadius(0.0762 / 2);
inner.setLength(0.86);
inner.setMassOverridden(true);
inner.setOverrideMass(0.388);
tube3.addChild(inner);
-
+
CenteringRing center = new CenteringRing();
center.setInnerRadiusAutomatic(true);
center.setOuterRadiusAutomatic(true);
center.setPositionValue(0);
tube3.addChild(center);
-
+
center = new CenteringRing();
center.setInnerRadiusAutomatic(true);
center.setOuterRadiusAutomatic(true);
center.setPositionValue(0.28);
tube3.addChild(center);
-
+
center = new CenteringRing();
center.setInnerRadiusAutomatic(true);
center.setOuterRadiusAutomatic(true);
center.setPositionValue(0.83);
tube3.addChild(center);
-
-
-
-
+
+
+
+
finset = new TrapezoidFinSet();
finset.setRootChord(0.495);
finset.setTipChord(0.1);
finset.setSweep(0.3);
finset.setRelativePosition(Position.BOTTOM);
finset.setPositionValue(-0.03);
- finset.setBaseRotation(Math.PI/2);
+ finset.setBaseRotation(Math.PI / 2);
tube3.addChild(finset);
+
+ finset.setCantAngle(0 * Math.PI / 180);
+ System.err.println("Fin cant angle: " + (finset.getCantAngle() * 180 / Math.PI));
- finset.setCantAngle(0*Math.PI/180);
- System.err.println("Fin cant angle: "+(finset.getCantAngle() * 180/Math.PI));
-
-
+
// Stage construction
rocket.addChild(stage);
rocket.setPerfectFinish(false);
-
-
+
+
String id = rocket.newMotorConfigurationID();
tube3.setMotorMount(true);
- for (Motor m: Databases.MOTOR) {
- if (m.getDesignation().equals("L540")) {
- tube3.setMotor(id, m);
- break;
- }
- }
+ Motor m = Application.getMotorSetDatabase().findMotors(null, null, "L540", Double.NaN, Double.NaN).get(0);
+ tube3.setMotor(id, m);
tube3.setMotorOverhang(0.02);
rocket.getDefaultConfiguration().setMotorConfigurationID(id);
-
-// tube3.setIgnitionEvent(MotorMount.IgnitionEvent.NEVER);
- rocket.getDefaultConfiguration().setAllStages();
+ // tube3.setIgnitionEvent(MotorMount.IgnitionEvent.NEVER);
+ rocket.getDefaultConfiguration().setAllStages();
+
return rocket;
}
-
-
+
+
}
@Test
public void testMotorLoading() {
- MotorSetDatabase db = new MotorSetDatabase(true) {
+ ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
@Override
protected void loadMotors() {
try {