diff --git a/.classpath b/.classpath index 9df09db58..c71fbb9c8 100644 --- a/.classpath +++ b/.classpath @@ -11,7 +11,7 @@ </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"/> diff --git a/ChangeLog b/ChangeLog index 58bb84c16..f958fe54b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,15 @@ +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 diff --git a/doc/properties.txt b/doc/properties.txt index 202bc87ea..cf2982564 100644 --- a/doc/properties.txt +++ b/doc/properties.txt @@ -28,6 +28,9 @@ Debugging options 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. diff --git a/run.sh b/run.sh new file mode 100755 index 000000000..abf2f1c8c --- /dev/null +++ b/run.sh @@ -0,0 +1,18 @@ +#!/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 "$@" + diff --git a/src/net/sf/openrocket/database/Databases.java b/src/net/sf/openrocket/database/Databases.java index 6031f40b1..fe6abcbf2 100644 --- a/src/net/sf/openrocket/database/Databases.java +++ b/src/net/sf/openrocket/database/Databases.java @@ -1,20 +1,9 @@ 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; @@ -28,12 +17,7 @@ public class Databases { 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). */ @@ -49,34 +33,6 @@ public class Databases { - // 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 @@ -228,40 +184,5 @@ public class Databases { 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]); - } - } diff --git a/src/net/sf/openrocket/database/ThrustCurveMotorSet.java b/src/net/sf/openrocket/database/ThrustCurveMotorSet.java index afffd30bb..dfd7cb55c 100644 --- a/src/net/sf/openrocket/database/ThrustCurveMotorSet.java +++ b/src/net/sf/openrocket/database/ThrustCurveMotorSet.java @@ -13,14 +13,14 @@ import java.util.regex.Pattern; 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 { @@ -28,12 +28,12 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { } 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>(); @@ -45,7 +45,7 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { private Motor.Type type = Motor.Type.UNKNOWN; - + public void addMotor(ThrustCurveMotor motor) { // Check for first insertion @@ -60,8 +60,8 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { // 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() + @@ -71,6 +71,12 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { // 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 @@ -79,23 +85,25 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { } // 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)) { @@ -123,7 +131,7 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { public boolean matches(ThrustCurveMotor m) { if (motors.isEmpty()) return true; - + if (manufacturer != m.getManufacturer()) return false; @@ -133,7 +141,7 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { 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; } @@ -143,7 +151,7 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { return true; } - + @SuppressWarnings("unchecked") public List<ThrustCurveMotor> getMotors() { @@ -151,6 +159,10 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { } + public int getMotorCount() { + return motors.size(); + } + /** * Return the standard delays applicable to this motor type. This is a union of @@ -160,8 +172,8 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { public List<Double> getDelays() { return Collections.unmodifiableList(delays); } - - + + /** * Return the manufacturer of this motor type. * @return the manufacturer @@ -169,8 +181,8 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { 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. @@ -179,8 +191,8 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { public String getDesignation() { return designation; } - - + + /** * Return the diameter of this motor type. * @return the diameter @@ -188,8 +200,8 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { public double getDiameter() { return diameter; } - - + + /** * Return the length of this motor type. * @return the length @@ -197,8 +209,8 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { 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. @@ -207,12 +219,21 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { 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. @@ -235,7 +256,7 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { * 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 @@ -253,15 +274,15 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { } } - - + + @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; @@ -272,12 +293,12 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { 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; } diff --git a/src/net/sf/openrocket/database/MotorSetDatabase.java b/src/net/sf/openrocket/database/ThrustCurveMotorSetDatabase.java similarity index 65% rename from src/net/sf/openrocket/database/MotorSetDatabase.java rename to src/net/sf/openrocket/database/ThrustCurveMotorSetDatabase.java index 4cd48f500..d1511595d 100644 --- a/src/net/sf/openrocket/database/MotorSetDatabase.java +++ b/src/net/sf/openrocket/database/ThrustCurveMotorSetDatabase.java @@ -5,6 +5,7 @@ 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; @@ -14,27 +15,29 @@ import net.sf.openrocket.startup.Application; * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ -public abstract class MotorSetDatabase { +public abstract class ThrustCurveMotorSetDatabase { private static final LogHelper logger = Application.getLogger(); - - private List<ThrustCurveMotorSet> motorSets; + + 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 MotorSetDatabase(boolean asynchronous) { + public ThrustCurveMotorSetDatabase(boolean asynchronous) { this.asynchronous = asynchronous; } - + /** * Return a list of the ThrustCurveMotorSet objects. The list is in sorted order and * is unmodifiable. @@ -46,9 +49,46 @@ public abstract class MotorSetDatabase { 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, @@ -59,7 +99,7 @@ public abstract class MotorSetDatabase { */ 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--) { + for (int i = motorSets.size() - 1; i >= 0; i--) { ThrustCurveMotorSet set = motorSets.get(i); if (set.matches(motor)) { set.addMotor(motor); @@ -73,9 +113,9 @@ public abstract class MotorSetDatabase { } - - - + + + /** * Start loading the motors. If asynchronous * @@ -102,7 +142,17 @@ public abstract class MotorSetDatabase { 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. @@ -110,6 +160,7 @@ public abstract class MotorSetDatabase { * @throws IllegalStateException if startLoading() has not been called. */ public void blockUntilLoaded() { + inUse = true; if (!startedLoading) { throw new IllegalStateException("startLoading() has not been called"); } @@ -125,7 +176,7 @@ public abstract class MotorSetDatabase { } } } - + /** * Used for loading the motor database. This method will be called in a background @@ -135,7 +186,7 @@ public abstract class MotorSetDatabase { 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 @@ -150,9 +201,9 @@ public abstract class MotorSetDatabase { } Collections.sort(motorSets); motorSets = Collections.unmodifiableList(motorSets); - synchronized (MotorSetDatabase.this) { + synchronized (ThrustCurveMotorSetDatabase.this) { endedLoading = true; - MotorSetDatabase.this.notifyAll(); + ThrustCurveMotorSetDatabase.this.notifyAll(); } } @@ -168,5 +219,5 @@ public abstract class MotorSetDatabase { performMotorLoading(); } } - + } diff --git a/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java b/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java index d9b57a833..db0ca84e5 100644 --- a/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java +++ b/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java @@ -23,6 +23,7 @@ import net.sf.openrocket.file.simplesax.PlainTextHandler; 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; @@ -67,6 +68,7 @@ import net.sf.openrocket.simulation.FlightDataType; 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; @@ -988,21 +990,22 @@ class MotorHandler extends ElementHandler { 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); } diff --git a/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java index b2ddc16f1..e37d80ee9 100644 --- a/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java +++ b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java @@ -32,42 +32,43 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis 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. @@ -78,10 +79,10 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis 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; @@ -92,11 +93,11 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis 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(); } @@ -104,15 +105,15 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis * 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); } @@ -120,16 +121,15 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis // 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; @@ -143,23 +143,24 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis 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 ///////// @@ -170,7 +171,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis * @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(); @@ -178,11 +179,11 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis 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); } @@ -194,7 +195,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis if (dialog != null) dialog.setVisible(false); } - + /** * Add an undo position for the current document. This is intended for use only @@ -202,7 +203,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis * * @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!"); } @@ -221,16 +222,16 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis * 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) diff --git a/src/net/sf/openrocket/gui/configdialog/MotorConfig.java b/src/net/sf/openrocket/gui/configdialog/MotorConfig.java index 1e93a6085..45a134a2b 100644 --- a/src/net/sf/openrocket/gui/configdialog/MotorConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/MotorConfig.java @@ -13,6 +13,7 @@ import javax.swing.JComboBox; 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; @@ -25,7 +26,7 @@ import net.sf.openrocket.gui.adaptors.MotorConfigurationModel; 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; @@ -150,9 +151,9 @@ public class MotorConfig extends JPanel { 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(); @@ -204,7 +205,7 @@ public class MotorConfig extends JPanel { } else { String str = ""; if (m instanceof ThrustCurveMotor) - str = ((ThrustCurveMotor) m).getManufacturer() + ""; + str = ((ThrustCurveMotor) m).getManufacturer() + " "; str += m.getDesignation(mount.getMotorDelay(id)); motorLabel.setText(str); } diff --git a/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java index 3fa7db8c0..a4390cf46 100644 --- a/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java @@ -26,9 +26,9 @@ import javax.swing.table.TableColumnModel; 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; @@ -317,10 +317,8 @@ public class EditMotorConfigurationDialog extends JDialog { 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(); diff --git a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java deleted file mode 100644 index 4e4db29fb..000000000 --- a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java +++ /dev/null @@ -1,665 +0,0 @@ -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)); - } - } - -} diff --git a/src/net/sf/openrocket/gui/dialogs/MotorDatabaseLoadingDialog.java b/src/net/sf/openrocket/gui/dialogs/MotorDatabaseLoadingDialog.java new file mode 100644 index 000000000..c53a63b3d --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/MotorDatabaseLoadingDialog.java @@ -0,0 +1,79 @@ +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"); + } + +} diff --git a/src/net/sf/openrocket/gui/dialogs/motor/CloseableDialog.java b/src/net/sf/openrocket/gui/dialogs/motor/CloseableDialog.java new file mode 100644 index 000000000..48e58a0fe --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/motor/CloseableDialog.java @@ -0,0 +1,12 @@ +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); + +} diff --git a/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java b/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java new file mode 100644 index 000000000..2258251f4 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java @@ -0,0 +1,112 @@ +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); + } + } + +} diff --git a/src/net/sf/openrocket/gui/dialogs/motor/MotorSelector.java b/src/net/sf/openrocket/gui/dialogs/motor/MotorSelector.java new file mode 100644 index 000000000..23bd9984c --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/motor/MotorSelector.java @@ -0,0 +1,38 @@ +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); + +} diff --git a/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorHolder.java b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorHolder.java new file mode 100644 index 000000000..bb8113331 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorHolder.java @@ -0,0 +1,40 @@ +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(); + } +} diff --git a/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorColumns.java b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorColumns.java new file mode 100644 index 000000000..145a9956e --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorColumns.java @@ -0,0 +1,136 @@ +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 diff --git a/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorDatabaseModel.java b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorDatabaseModel.java new file mode 100644 index 000000000..2dc92bd31 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorDatabaseModel.java @@ -0,0 +1,51 @@ +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]; + } + +} diff --git a/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java new file mode 100644 index 000000000..da6fb5165 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java @@ -0,0 +1,134 @@ +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; + } +} diff --git a/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java new file mode 100644 index 000000000..038359d78 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -0,0 +1,892 @@ +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); + } + } + } +} diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index 42aebf2d2..aba539863 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -75,16 +75,19 @@ import net.sf.openrocket.gui.dialogs.BugReportDialog; 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; @@ -95,33 +98,33 @@ import net.sf.openrocket.util.SaveFileWorker; 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 @@ -129,18 +132,18 @@ public class BasicFrame extends JFrame { */ 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; @@ -156,7 +159,7 @@ public class BasicFrame extends JFrame { private final RocketActions actions; - + /** * Sole constructor. Creates a new frame based on the supplied document * and adds it to the current frames list. @@ -164,12 +167,13 @@ public class BasicFrame extends JFrame { * @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) { @@ -178,7 +182,7 @@ public class BasicFrame extends JFrame { } }); - + // Create the component tree selection model that will be used componentSelectionModel = new DefaultTreeSelectionModel(); componentSelectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); @@ -192,16 +196,18 @@ public class BasicFrame extends JFrame { 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()); @@ -215,13 +221,13 @@ public class BasicFrame extends JFrame { rocketpanel = new RocketPanel(document); vertical.setBottomComponent(rocketpanel); - + rocketpanel.setSelectionModel(tree.getSelectionModel()); + - createMenu(); - + rocket.addComponentChangeListener(new ComponentChangeListener() { public void componentChanged(ComponentChangeEvent e) { setTitle(); @@ -230,12 +236,12 @@ public class BasicFrame extends JFrame { 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() { @@ -245,7 +251,7 @@ public class BasicFrame extends JFrame { } }); this.setLocationByPlatform(true); - + GUIUtil.setWindowIcons(this); this.validate(); @@ -258,6 +264,8 @@ public class BasicFrame extends JFrame { } }); frames.add(this); + + log.debug("BasicFrame instantiation complete"); } @@ -267,17 +275,17 @@ public class BasicFrame extends JFrame { * 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); @@ -287,27 +295,27 @@ public class BasicFrame extends JFrame { 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) { @@ -319,62 +327,62 @@ public class BasicFrame extends JFrame { 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. */ @@ -389,7 +397,7 @@ public class BasicFrame extends JFrame { 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"); @@ -403,7 +411,7 @@ public class BasicFrame extends JFrame { }); 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); @@ -416,14 +424,14 @@ public class BasicFrame extends JFrame { 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); } } @@ -433,7 +441,7 @@ public class BasicFrame extends JFrame { 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); @@ -444,10 +452,10 @@ public class BasicFrame extends JFrame { }); 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() { @@ -457,10 +465,10 @@ public class BasicFrame extends JFrame { }); 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); @@ -472,8 +480,8 @@ public class BasicFrame extends JFrame { 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); @@ -484,7 +492,7 @@ public class BasicFrame extends JFrame { }); menu.add(item); - + //// Edit menu = new JMenu("Edit"); @@ -492,15 +500,15 @@ public class BasicFrame extends JFrame { 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)); @@ -511,13 +519,13 @@ public class BasicFrame extends JFrame { 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); @@ -528,7 +536,7 @@ public class BasicFrame extends JFrame { 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) { @@ -536,17 +544,17 @@ public class BasicFrame extends JFrame { } }); 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() { @@ -556,15 +564,15 @@ public class BasicFrame extends JFrame { }); 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"); @@ -572,9 +580,9 @@ public class BasicFrame extends JFrame { 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) { @@ -583,18 +591,18 @@ public class BasicFrame extends JFrame { }); 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) { @@ -603,7 +611,7 @@ public class BasicFrame extends JFrame { }); menu.add(item); - + this.setJMenuBar(menubar); } @@ -621,11 +629,11 @@ public class BasicFrame extends JFrame { 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); } }); @@ -641,9 +649,9 @@ public class BasicFrame extends JFrame { 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; @@ -663,7 +671,7 @@ public class BasicFrame extends JFrame { }); menu.add(item); - + item = new JMenuItem("Create 'Iso-Haisu'"); item.addActionListener(new ActionListener() { @@ -692,8 +700,8 @@ public class BasicFrame extends JFrame { }); menu.add(item); - - + + menu.addSeparator(); item = new JMenuItem("Exception here"); @@ -732,13 +740,13 @@ public class BasicFrame extends JFrame { }); menu.add(item); - - + + return menu; } - + /** * Select the tab on the main pane. * @@ -747,42 +755,42 @@ public class BasicFrame extends JFrame { 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; @@ -790,13 +798,15 @@ public class BasicFrame extends JFrame { 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 @@ -806,20 +816,20 @@ public class BasicFrame extends JFrame { // 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); } @@ -842,7 +852,7 @@ public class BasicFrame extends JFrame { 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. @@ -856,7 +866,7 @@ public class BasicFrame extends JFrame { return open(worker, file.getName(), file, parent); } - + /** * Open the specified file using the provided worker. * @@ -866,49 +876,51 @@ public class BasicFrame extends JFrame { * @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); } @@ -917,59 +929,55 @@ public class BasicFrame extends JFrame { 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); } @@ -978,8 +986,8 @@ public class BasicFrame extends JFrame { 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()); @@ -993,7 +1001,7 @@ public class BasicFrame extends JFrame { file = chooser.getSelectedFile(); if (file == null) return false; - + Prefs.setDefaultDirectory(chooser.getCurrentDirectory()); storageChooser.storeOptions(document.getDefaultStorageOptions()); @@ -1004,53 +1012,53 @@ public class BasicFrame extends JFrame { } 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; - } + 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 { + 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); } @@ -1058,23 +1066,23 @@ public class BasicFrame extends JFrame { } 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 { @@ -1085,7 +1093,7 @@ public class BasicFrame extends JFrame { // Rocket has been saved or discarded this.dispose(); - + // TODO: LOW: Close only dialogs that have this frame as their parent ComponentConfigDialog.hideDialog(); ComponentAnalysisDialog.hideDialog(); @@ -1110,6 +1118,7 @@ public class BasicFrame extends JFrame { * 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"); @@ -1127,7 +1136,7 @@ public class BasicFrame extends JFrame { * 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; @@ -1148,8 +1157,8 @@ public class BasicFrame extends JFrame { String title; title = rocket.getName(); - if (file!=null) { - title = title + " ("+file.getName()+")"; + if (file != null) { + title = title + " (" + file.getName() + ")"; } if (!saved) title = "*" + title; @@ -1158,7 +1167,7 @@ public class BasicFrame extends JFrame { } - + /** * Find a currently open BasicFrame containing the specified rocket. This method * can be used to map a Rocket to a BasicFrame from GUI methods. @@ -1167,7 +1176,7 @@ public class BasicFrame extends JFrame { * @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; } @@ -1182,7 +1191,7 @@ public class BasicFrame extends JFrame { * @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; } @@ -1214,7 +1223,7 @@ public class BasicFrame extends JFrame { // Initialize the splash screen with version info Splash.init(); - + // Start update info fetching final UpdateInfoRetriever updateInfo; if (Prefs.getCheckUpdates()) { @@ -1224,18 +1233,18 @@ public class BasicFrame extends JFrame { 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(); @@ -1248,7 +1257,7 @@ public class BasicFrame extends JFrame { newAction(); } - + // Check whether update info has been fetched or whether it needs more time checkUpdateStatus(updateInfo); } @@ -1257,15 +1266,16 @@ public class BasicFrame extends JFrame { 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()) { @@ -1273,7 +1283,7 @@ public class BasicFrame extends JFrame { 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()) && @@ -1311,12 +1321,12 @@ public class BasicFrame extends JFrame { // 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; } - + } diff --git a/src/net/sf/openrocket/gui/plot/PlotDialog.java b/src/net/sf/openrocket/gui/plot/PlotDialog.java index ad6e34a58..77d5338e3 100644 --- a/src/net/sf/openrocket/gui/plot/PlotDialog.java +++ b/src/net/sf/openrocket/gui/plot/PlotDialog.java @@ -1,6 +1,7 @@ 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; @@ -62,6 +63,8 @@ import org.jfree.ui.TextAnchor; 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>(); @@ -221,6 +224,9 @@ public class PlotDialog extends JDialog { 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); diff --git a/src/net/sf/openrocket/motor/ThrustCurveMotor.java b/src/net/sf/openrocket/motor/ThrustCurveMotor.java index 10a97deb1..ce3108dd0 100644 --- a/src/net/sf/openrocket/motor/ThrustCurveMotor.java +++ b/src/net/sf/openrocket/motor/ThrustCurveMotor.java @@ -4,7 +4,9 @@ import java.text.Collator; 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; @@ -12,6 +14,7 @@ import net.sf.openrocket.util.MathUtil; public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> { + private static final LogHelper log = Application.getLogger(); public static final double MAX_THRUST = 10e6; @@ -171,7 +174,12 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> { } - + /** + * {@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; @@ -391,6 +399,7 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> { private int modID = 0; public ThrustCurveMotorInstance() { + log.debug("ThrustCurveMotor: Creating motor instance of " + ThrustCurveMotor.this); position = 0; prevTime = 0; instThrust = 0; @@ -434,22 +443,18 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> { @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; @@ -501,19 +506,10 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> { 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 diff --git a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java index 85add4865..45be122db 100644 --- a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java +++ b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java @@ -412,14 +412,12 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { 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))); diff --git a/src/net/sf/openrocket/startup/Application.java b/src/net/sf/openrocket/startup/Application.java index ec5172145..a00c9535d 100644 --- a/src/net/sf/openrocket/startup/Application.java +++ b/src/net/sf/openrocket/startup/Application.java @@ -1,6 +1,6 @@ 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; @@ -16,7 +16,7 @@ public final class Application { 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 { @@ -68,14 +68,14 @@ public final class Application { /** * 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; } diff --git a/src/net/sf/openrocket/startup/Startup.java b/src/net/sf/openrocket/startup/Startup.java index d8ff127f1..971a5e94c 100644 --- a/src/net/sf/openrocket/startup/Startup.java +++ b/src/net/sf/openrocket/startup/Startup.java @@ -8,6 +8,7 @@ import java.io.IOException; 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; @@ -17,7 +18,8 @@ import javax.swing.ToolTipManager; 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; @@ -55,9 +57,11 @@ public class Startup { 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 @@ -67,10 +71,12 @@ public class Startup { 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() { @@ -78,6 +84,10 @@ public class Startup { } }); + log.info("Startup complete"); + + // Block motor loading for 2 seconds to allow window painting + blockLoading.set(2000); } @@ -86,21 +96,26 @@ public class Startup { 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. @@ -111,15 +126,18 @@ public class Startup { // 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); } @@ -129,23 +147,48 @@ public class Startup { 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) { @@ -157,7 +200,19 @@ public class Startup { 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."); } }; @@ -240,6 +295,8 @@ public class Startup { */ private static void checkHead() { + log.info("Checking for graphics head"); + if (GraphicsEnvironment.isHeadless()) { log.error("Application is headless."); System.err.println(); diff --git a/src/net/sf/openrocket/startup/VersionHelper.java b/src/net/sf/openrocket/startup/VersionHelper.java index 085638689..3d90d3e9d 100644 --- a/src/net/sf/openrocket/startup/VersionHelper.java +++ b/src/net/sf/openrocket/startup/VersionHelper.java @@ -4,73 +4,74 @@ import net.sf.openrocket.logging.LogHelper; 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?" }); } } } - + } diff --git a/src/net/sf/openrocket/util/BugException.java b/src/net/sf/openrocket/util/BugException.java index 94a283495..140862d0d 100644 --- a/src/net/sf/openrocket/util/BugException.java +++ b/src/net/sf/openrocket/util/BugException.java @@ -6,20 +6,17 @@ package net.sf.openrocket.util; * @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); } - + } diff --git a/src/net/sf/openrocket/util/GUIUtil.java b/src/net/sf/openrocket/util/GUIUtil.java index 4f552b819..46b789310 100644 --- a/src/net/sf/openrocket/util/GUIUtil.java +++ b/src/net/sf/openrocket/util/GUIUtil.java @@ -2,6 +2,7 @@ package net.sf.openrocket.util; import java.awt.Component; import java.awt.Container; +import java.awt.Font; import java.awt.Image; import java.awt.KeyboardFocusManager; import java.awt.Point; @@ -96,8 +97,9 @@ public class GUIUtil { /** * 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. * @@ -108,6 +110,7 @@ public class GUIUtil { installEscapeCloseOperation(dialog); setWindowIcons(dialog); addModelNullingListener(dialog); + dialog.setLocationByPlatform(true); if (defaultButton != null) { setDefaultButton(defaultButton); } @@ -238,6 +241,19 @@ public class GUIUtil { } + /** + * 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 diff --git a/src/net/sf/openrocket/util/Icons.java b/src/net/sf/openrocket/util/Icons.java index a447009f1..52153e148 100644 --- a/src/net/sf/openrocket/util/Icons.java +++ b/src/net/sf/openrocket/util/Icons.java @@ -10,10 +10,17 @@ import javax.swing.ImageIcon; 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). */ @@ -34,7 +41,7 @@ public class Icons { 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"); @@ -50,15 +57,17 @@ public class Icons { 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"); - + static { + log.debug("Icons loaded"); + } /** * Load an ImageIcon from the specified file. The file is obtained as a system diff --git a/src/net/sf/openrocket/util/Prefs.java b/src/net/sf/openrocket/util/Prefs.java index c8ed58b92..8f5a75ec5 100644 --- a/src/net/sf/openrocket/util/Prefs.java +++ b/src/net/sf/openrocket/util/Prefs.java @@ -42,21 +42,21 @@ public class Prefs { /** * 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"); /* @@ -128,6 +128,11 @@ public class Prefs { 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. @@ -137,6 +142,7 @@ public class Prefs { private static final Preferences PREFNODE; + // Clear the preferences if debug mode and clearprefs is defined static { Preferences root = Preferences.userRoot(); if (DEBUG && CLEARPREFS) { @@ -193,16 +199,27 @@ public class Prefs { ////////////////////// + /** + * 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) { @@ -214,7 +231,10 @@ public class Prefs { - 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()); } @@ -249,27 +269,66 @@ public class Prefs { } - + /** + * 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); @@ -280,9 +339,6 @@ public class Prefs { storeVersion(); } - - ////////////////// - public static File getDefaultDirectory() { String file = PREFNODE.get("defaultDirectory", null); if (file == null) diff --git a/src/net/sf/openrocket/util/TestRockets.java b/src/net/sf/openrocket/util/TestRockets.java index 6b5b83f70..cf6b2787a 100644 --- a/src/net/sf/openrocket/util/TestRockets.java +++ b/src/net/sf/openrocket/util/TestRockets.java @@ -3,7 +3,6 @@ package net.sf.openrocket.util; 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; @@ -30,6 +29,7 @@ import net.sf.openrocket.rocketcomponent.FinSet.CrossSection; 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 { @@ -38,18 +38,18 @@ 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(); @@ -57,10 +57,10 @@ public class TestRockets { 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, @@ -82,12 +82,12 @@ public class TestRockets { 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)); @@ -99,13 +99,13 @@ public class TestRockets { 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)); @@ -129,21 +129,21 @@ public class TestRockets { 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)); @@ -177,9 +177,9 @@ public class TestRockets { mass.setRadius(rnd(0.05)); nose.addChild(mass); - - - + + + return rocket; } @@ -187,13 +187,13 @@ public class TestRockets { 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()); @@ -201,23 +201,23 @@ public class TestRockets { } 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() { @@ -231,20 +231,19 @@ public class TestRockets { 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; @@ -254,21 +253,21 @@ public class TestRockets { 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); @@ -279,22 +278,18 @@ public class TestRockets { 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; @@ -306,11 +301,11 @@ public class TestRockets { 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[] { @@ -326,46 +321,42 @@ public class TestRockets { 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; } @@ -385,29 +376,29 @@ public class TestRockets { 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); @@ -421,7 +412,7 @@ public class TestRockets { coupler.setPositionValue(-0.14); tube1.addChild(coupler); - + // Parachute MassComponent mass = new MassComponent(0.05, 0.05, 0.280); mass.setRelativePosition(Position.TOP); @@ -440,7 +431,7 @@ public class TestRockets { mass.setPositionValue(0.25); tube1.addChild(mass); - + auxfinset = new TrapezoidFinSet(); auxfinset.setName("CONTROL"); auxfinset.setFinCount(2); @@ -452,12 +443,12 @@ public class TestRockets { 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); @@ -467,8 +458,8 @@ public class TestRockets { coupler.setOverrideMass(0.360); tube2.addChild(coupler); - - + + // Parachute mass = new MassComponent(0.1, 0.05, 0.028); mass.setRelativePosition(Position.TOP); @@ -489,17 +480,17 @@ public class TestRockets { 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); @@ -510,7 +501,7 @@ public class TestRockets { center.setPositionValue(0); tube3.addChild(center); - + center = new CenteringRing(); center.setInnerRadiusAutomatic(true); center.setOuterRadiusAutomatic(true); @@ -521,7 +512,7 @@ public class TestRockets { center.setPositionValue(0.28); tube3.addChild(center); - + center = new CenteringRing(); center.setInnerRadiusAutomatic(true); center.setOuterRadiusAutomatic(true); @@ -532,10 +523,10 @@ public class TestRockets { center.setPositionValue(0.83); tube3.addChild(center); - - - - + + + + finset = new TrapezoidFinSet(); finset.setRootChord(0.495); finset.setTipChord(0.1); @@ -544,40 +535,36 @@ public class TestRockets { 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); + + // tube3.setIgnitionEvent(MotorMount.IgnitionEvent.NEVER); rocket.getDefaultConfiguration().setAllStages(); - + return rocket; } - - + + } diff --git a/test/net/sf/openrocket/database/MotorSetDatabaseTest.java b/test/net/sf/openrocket/database/MotorSetDatabaseTest.java index 663340f8c..33ced0c8e 100644 --- a/test/net/sf/openrocket/database/MotorSetDatabaseTest.java +++ b/test/net/sf/openrocket/database/MotorSetDatabaseTest.java @@ -17,7 +17,7 @@ public class MotorSetDatabaseTest { @Test public void testMotorLoading() { - MotorSetDatabase db = new MotorSetDatabase(true) { + ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) { @Override protected void loadMotors() { try {