enhanced motor selection dialog
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 22 Jul 2010 07:21:37 +0000 (07:21 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 22 Jul 2010 07:21:37 +0000 (07:21 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@60 180e2498-e6e9-4542-8430-84ac67f01cd8

35 files changed:
.classpath
ChangeLog
doc/properties.txt
run.sh [new file with mode: 0755]
src/net/sf/openrocket/database/Databases.java
src/net/sf/openrocket/database/MotorSetDatabase.java [deleted file]
src/net/sf/openrocket/database/ThrustCurveMotorSet.java
src/net/sf/openrocket/database/ThrustCurveMotorSetDatabase.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java
src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java
src/net/sf/openrocket/gui/configdialog/MotorConfig.java
src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java
src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java [deleted file]
src/net/sf/openrocket/gui/dialogs/MotorDatabaseLoadingDialog.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/motor/CloseableDialog.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/motor/MotorChooserDialog.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/motor/MotorSelector.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorHolder.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorColumns.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorDatabaseModel.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java [new file with mode: 0644]
src/net/sf/openrocket/gui/main/BasicFrame.java
src/net/sf/openrocket/gui/plot/PlotDialog.java
src/net/sf/openrocket/motor/ThrustCurveMotor.java
src/net/sf/openrocket/simulation/RK4SimulationStepper.java
src/net/sf/openrocket/startup/Application.java
src/net/sf/openrocket/startup/Startup.java
src/net/sf/openrocket/startup/VersionHelper.java
src/net/sf/openrocket/util/BugException.java
src/net/sf/openrocket/util/GUIUtil.java
src/net/sf/openrocket/util/Icons.java
src/net/sf/openrocket/util/Prefs.java
src/net/sf/openrocket/util/TestRockets.java
test/net/sf/openrocket/database/MotorSetDatabaseTest.java

index 9df09db586dd1f2e90ec9ddda15a7365181f6736..c71fbb9c8c6e4b56d985972e2a1b8e4240cb0df6 100644 (file)
@@ -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"/>
index 58bb84c166d18d65704ce1f75137326ba9940ccb..f958fe54bfdaddc68175bee9b828901c13c4313c 100644 (file)
--- 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
 
index 202bc87eaadb47dc38d67958129ae7e6d83d54bc..cf2982564494104da43e2021e724243ece9d3385 100644 (file)
@@ -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 (executable)
index 0000000..abf2f1c
--- /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 "$@"
+
index 6031f40b1dbe7522f17a91429fe1d64b3e98cb7d..fe6abcbf2f29606dfa762f5e4c580cea44aa7f57 100644 (file)
@@ -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/MotorSetDatabase.java b/src/net/sf/openrocket/database/MotorSetDatabase.java
deleted file mode 100644 (file)
index 4cd48f5..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-package net.sf.openrocket.database;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.motor.ThrustCurveMotor;
-import net.sf.openrocket.startup.Application;
-
-/**
- * A database containing ThrustCurveMotorSet objects and allowing adding a motor
- * to the database.
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public abstract class MotorSetDatabase {
-       
-       private static final LogHelper logger = Application.getLogger();
-
-       private List<ThrustCurveMotorSet> motorSets;
-       
-       private volatile boolean startedLoading = false;
-       private volatile boolean endedLoading = false;
-       private final boolean asynchronous;
-       
-       
-       /**
-        * Sole constructor.
-        * 
-        * @param asynchronous  whether to load motors asynchronously in a background thread.
-        */
-       public MotorSetDatabase(boolean asynchronous) {
-               this.asynchronous = asynchronous;
-       }
-       
-
-       /**
-        * Return a list of the ThrustCurveMotorSet objects.  The list is in sorted order and
-        * is unmodifiable.
-        * 
-        * @return              the list of all ThrustCurveMotorSets.
-        */
-       public List<ThrustCurveMotorSet> getMotorSets() {
-               blockUntilLoaded();
-               return motorSets;
-       }
-       
-
-       
-
-       
-       /**
-        * Add a motor to the database.  If a matching ThrustCurveMototSet is found, 
-        * the motor is added to that set, otherwise a new set is created and added to the
-        * database.
-        * 
-        * @param motor         the motor to add
-        */
-       protected void addMotor(ThrustCurveMotor motor) {
-               // Iterate from last to first, as this is most likely to hit early when loading files
-               for (int i = motorSets.size()-1; i>= 0; i--) {
-                       ThrustCurveMotorSet set = motorSets.get(i);
-                       if (set.matches(motor)) {
-                               set.addMotor(motor);
-                               return;
-                       }
-               }
-               
-               ThrustCurveMotorSet newSet = new ThrustCurveMotorSet();
-               newSet.addMotor(motor);
-               motorSets.add(newSet);
-       }
-       
-       
-       
-       
-       
-       /**
-        * Start loading the motors.  If asynchronous 
-        * 
-        * @throws  IllegalStateException       if this method has already been called.
-        */
-       public void startLoading() {
-               if (startedLoading) {
-                       throw new IllegalStateException("Already called startLoading");
-               }
-               startedLoading = true;
-               if (asynchronous) {
-                       new LoadingThread().start();
-               } else {
-                       performMotorLoading();
-               }
-       }
-       
-       
-       /**
-        * Return whether loading the database has ended.
-        * 
-        * @return      whether background loading has ended.
-        */
-       public boolean isLoaded() {
-               return endedLoading;
-       }
-
-       
-       /**
-        * Block the current thread until loading of the motors has been completed.
-        * 
-        * @throws IllegalStateException        if startLoading() has not been called.
-        */
-       public void blockUntilLoaded() {
-               if (!startedLoading) {
-                       throw new IllegalStateException("startLoading() has not been called");
-               }
-               if (!endedLoading) {
-                       synchronized (this) {
-                               while (!endedLoading) {
-                                       try {
-                                               this.wait();
-                                       } catch (InterruptedException e) {
-                                               logger.warn("InterruptedException occurred, ignoring", e);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       
-       /**
-        * Used for loading the motor database.  This method will be called in a background
-        * thread to load the motors asynchronously.  This method should call 
-        * {@link #addMotor(ThrustCurveMotor)} to add the motors to the database.
-        */
-       protected abstract void loadMotors();
-       
-       
-       
-       /**
-        * Creates the motor list, calls {@link #loadMotors()}, sorts the list and marks
-        * the motors as loaded.  This method is called either synchronously or from the
-        * background thread.
-        */
-       private void performMotorLoading() {
-               motorSets = new ArrayList<ThrustCurveMotorSet>();
-               try {
-                       loadMotors();
-               } catch (Exception e) {
-                       logger.error("Loading motors failed", e);
-               }
-               Collections.sort(motorSets);
-               motorSets = Collections.unmodifiableList(motorSets);
-               synchronized (MotorSetDatabase.this) {
-                       endedLoading = true;
-                       MotorSetDatabase.this.notifyAll();
-               }
-       }
-       
-       
-       /**
-        * Background thread for loading the motors.  This creates the motor list,
-        * calls loadMotors(), sorts the database, makes it unmodifiable, and finally
-        * marks the database as loaded and notifies any blocked threads.
-        */
-       private class LoadingThread extends Thread {
-               @Override
-               public void run() {
-                       performMotorLoading();
-               }
-       }
-
-}
index afffd30bbe961a850a04437fd8aaefcc9afc4bae..dfd7cb55cb497d26f6271cd6e7c8c1c2db9b0bdf 100644 (file)
@@ -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/ThrustCurveMotorSetDatabase.java b/src/net/sf/openrocket/database/ThrustCurveMotorSetDatabase.java
new file mode 100644 (file)
index 0000000..d151159
--- /dev/null
@@ -0,0 +1,223 @@
+package net.sf.openrocket.database;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A database containing ThrustCurveMotorSet objects and allowing adding a motor
+ * to the database.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class ThrustCurveMotorSetDatabase {
+       
+       private static final LogHelper logger = Application.getLogger();
+       
+       protected List<ThrustCurveMotorSet> motorSets;
+       
+       private volatile boolean startedLoading = false;
+       private volatile boolean endedLoading = false;
+       private final boolean asynchronous;
+       
+       /** Set to true the first time {@link #blockUntilLoaded()} is called. */
+       protected volatile boolean inUse = false;
+       
+       /**
+        * Sole constructor.
+        * 
+        * @param asynchronous  whether to load motors asynchronously in a background thread.
+        */
+       public ThrustCurveMotorSetDatabase(boolean asynchronous) {
+               this.asynchronous = asynchronous;
+       }
+       
+       
+       /**
+        * Return a list of the ThrustCurveMotorSet objects.  The list is in sorted order and
+        * is unmodifiable.
+        * 
+        * @return              the list of all ThrustCurveMotorSets.
+        */
+       public List<ThrustCurveMotorSet> getMotorSets() {
+               blockUntilLoaded();
+               return motorSets;
+       }
+       
+       
+
+       /**
+        * Return all motors in the database matching a search criteria.  Any search criteria that
+        * is null or NaN is ignored.
+        * 
+        * @param type                  the motor type, or null.
+        * @param manufacturer  the manufacturer, or null.
+        * @param designation   the designation, or null.
+        * @param diameter              the diameter, or NaN.
+        * @param length                the length, or NaN.
+        * @return                              a list of all the matching motors.
+        */
+       public List<ThrustCurveMotor> findMotors(Motor.Type type, String manufacturer, String designation,
+                       double diameter, double length) {
+               blockUntilLoaded();
+               ArrayList<ThrustCurveMotor> results = new ArrayList<ThrustCurveMotor>();
+               
+               for (ThrustCurveMotorSet set : motorSets) {
+                       for (ThrustCurveMotor m : set.getMotors()) {
+                               boolean match = true;
+                               if (type != null && type != set.getType())
+                                       match = false;
+                               else if (manufacturer != null && !m.getManufacturer().matches(manufacturer))
+                                       match = false;
+                               else if (designation != null && !designation.equalsIgnoreCase(m.getDesignation()))
+                                       match = false;
+                               else if (!Double.isNaN(diameter) && (Math.abs(diameter - m.getDiameter()) > 0.0015))
+                                       match = false;
+                               else if (!Double.isNaN(length) && (Math.abs(length - m.getLength()) > 0.0015))
+                                       match = false;
+                               
+                               if (match)
+                                       results.add(m);
+                       }
+               }
+               
+               return results;
+       }
+       
+       
+       /**
+        * Add a motor to the database.  If a matching ThrustCurveMototSet is found, 
+        * the motor is added to that set, otherwise a new set is created and added to the
+        * database.
+        * 
+        * @param motor         the motor to add
+        */
+       protected void addMotor(ThrustCurveMotor motor) {
+               // Iterate from last to first, as this is most likely to hit early when loading files
+               for (int i = motorSets.size() - 1; i >= 0; i--) {
+                       ThrustCurveMotorSet set = motorSets.get(i);
+                       if (set.matches(motor)) {
+                               set.addMotor(motor);
+                               return;
+                       }
+               }
+               
+               ThrustCurveMotorSet newSet = new ThrustCurveMotorSet();
+               newSet.addMotor(motor);
+               motorSets.add(newSet);
+       }
+       
+       
+
+
+
+       /**
+        * Start loading the motors.  If asynchronous 
+        * 
+        * @throws  IllegalStateException       if this method has already been called.
+        */
+       public void startLoading() {
+               if (startedLoading) {
+                       throw new IllegalStateException("Already called startLoading");
+               }
+               startedLoading = true;
+               if (asynchronous) {
+                       new LoadingThread().start();
+               } else {
+                       performMotorLoading();
+               }
+       }
+       
+       
+       /**
+        * Return whether loading the database has ended.
+        * 
+        * @return      whether background loading has ended.
+        */
+       public boolean isLoaded() {
+               return endedLoading;
+       }
+       
+       
+       /**
+        * Mark that this database is in use or a place is waiting for the database to 
+        * become loaded.  This can be used in conjunction with {@link #isLoaded()} to load
+        * the database without blocking.
+        */
+       public void setInUse() {
+               inUse = true;
+       }
+       
+       
+       /**
+        * Block the current thread until loading of the motors has been completed.
+        * 
+        * @throws IllegalStateException        if startLoading() has not been called.
+        */
+       public void blockUntilLoaded() {
+               inUse = true;
+               if (!startedLoading) {
+                       throw new IllegalStateException("startLoading() has not been called");
+               }
+               if (!endedLoading) {
+                       synchronized (this) {
+                               while (!endedLoading) {
+                                       try {
+                                               this.wait();
+                                       } catch (InterruptedException e) {
+                                               logger.warn("InterruptedException occurred, ignoring", e);
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       
+       /**
+        * Used for loading the motor database.  This method will be called in a background
+        * thread to load the motors asynchronously.  This method should call 
+        * {@link #addMotor(ThrustCurveMotor)} to add the motors to the database.
+        */
+       protected abstract void loadMotors();
+       
+       
+
+       /**
+        * Creates the motor list, calls {@link #loadMotors()}, sorts the list and marks
+        * the motors as loaded.  This method is called either synchronously or from the
+        * background thread.
+        */
+       private void performMotorLoading() {
+               motorSets = new ArrayList<ThrustCurveMotorSet>();
+               try {
+                       loadMotors();
+               } catch (Exception e) {
+                       logger.error("Loading motors failed", e);
+               }
+               Collections.sort(motorSets);
+               motorSets = Collections.unmodifiableList(motorSets);
+               synchronized (ThrustCurveMotorSetDatabase.this) {
+                       endedLoading = true;
+                       ThrustCurveMotorSetDatabase.this.notifyAll();
+               }
+       }
+       
+       
+       /**
+        * Background thread for loading the motors.  This creates the motor list,
+        * calls loadMotors(), sorts the database, makes it unmodifiable, and finally
+        * marks the database as loaded and notifies any blocked threads.
+        */
+       private class LoadingThread extends Thread {
+               @Override
+               public void run() {
+                       performMotorLoading();
+               }
+       }
+       
+}
index d9b57a8333e237274dfbca8ca01d2bfcda5ba7c7..db0ca84e5236812c2a2bd369bf0b45f6f49e1955 100644 (file)
@@ -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);
        }
        
        
index b2ddc16f17edf32dfe5ed2895f9af3869990fe0b..e37d80ee93b4f9d612f6b0876dab8cae8c722bb7 100644 (file)
@@ -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)
index 1e93a60855da5cb90a77ce125730ce9d0a52fb02..45a134a2bc2df66212bed7ce8bb0fcedc552b9ea 100644 (file)
@@ -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);
                }
index 3fa7db8c01adce79a2bb457c57f68cfc6420af69..a4390cf468dbbed59c65dc64d7f9300071e8cfe8 100644 (file)
@@ -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 (file)
index 4e4db29..0000000
+++ /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 (file)
index 0000000..c53a63b
--- /dev/null
@@ -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 (file)
index 0000000..48e58a0
--- /dev/null
@@ -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 (file)
index 0000000..2258251
--- /dev/null
@@ -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 (file)
index 0000000..23bd998
--- /dev/null
@@ -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 (file)
index 0000000..bb81133
--- /dev/null
@@ -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 (file)
index 0000000..145a995
--- /dev/null
@@ -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 (file)
index 0000000..2dc92bd
--- /dev/null
@@ -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 (file)
index 0000000..da6fb51
--- /dev/null
@@ -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 (file)
index 0000000..038359d
--- /dev/null
@@ -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);
+                       }
+               }
+       }
+}
index 42aebf2d2e9427a30ca3c44ce9324c2b34c8c3a3..aba5398635fdee8f248706c9b3f7c866a4f2e185 100644 (file)
@@ -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;
-           }
-
-
-           SaveFileWorker worker = new SaveFileWorker(document, file, ROCKET_SAVER);
-
-           if (!SwingWorkerDialog.runWorker(this, "Saving file", 
-                       "Writing " + file.getName() + "...", worker)) {
-               
-               // User cancelled the save
-               file.delete();
-               return false;
-           }
-           
-           try {
+               System.out.println("Saving to file: " + file.getName());
+               boolean saved = false;
+               
+               if (!StorageOptionChooser.verifyStorageOptions(document, this)) {
+                       // User cancelled the dialog
+                       return false;
+               }
+               
+
+               SaveFileWorker worker = new SaveFileWorker(document, file, ROCKET_SAVER);
+               
+               if (!SwingWorkerDialog.runWorker(this, "Saving file",
+                               "Writing " + file.getName() + "...", worker)) {
+                       
+                       // User cancelled the save
+                       file.delete();
+                       return false;
+               }
+               
+               try {
                        worker.get();
                        document.setFile(file);
                        document.setSaved(true);
                        saved = true;
-                   setTitle();
+                       setTitle();
                } catch (ExecutionException e) {
-
+                       
                        Throwable cause = e.getCause();
                        
                        if (cause instanceof IOException) {
-                       JOptionPane.showMessageDialog(this, new String[] { 
-                                       "An I/O error occurred while saving:",
-                                       e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
-                       return false;
+                               JOptionPane.showMessageDialog(this, new String[] {
+                                               "An I/O error occurred while saving:",
+                                               e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
+                               return false;
                        } else {
                                Reflection.handleWrappedException(e);
                        }
@@ -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;
        }
-
+       
 }
index ad6e34a5802bd8f6fa9a84a3330257da256d9312..77d5338e35f585207e20f6965924145ccb1deaee 100644 (file)
@@ -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);
index 10a97deb1e9e2b3de51db5ad7c665c134719d0c0..ce3108dd048f35cc8e032f207eb3cd95179d97b1 100644 (file)
@@ -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
index 85add4865bacc7b9896810c4ae388c75993548b5..45be122dbdf215f7db4594b1f5b816ff5eaaaa58 100644 (file)
@@ -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)));
index ec51721458c19a60c40c601da72f64d1a91dbb97..a00c9535dfa27b70d4e9c290e83dcd70f431f112 100644 (file)
@@ -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;
        }
        
index d8ff127f14d06876419b56dabbbde96e6ceb1abb..971a5e94c6bc79cda85f4388fae4fb4c6e0dcea7 100644 (file)
@@ -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();
index 085638689a9e16a234c71ed72b70d953e647632c..3d90d3e9d67d34ac4231ebb277e52568ee5ababc 100644 (file)
@@ -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?" });
                        }
                }
        }
-
+       
 }
index 94a2834955a9c592fb26bd7685f91abfb9ac62ab..140862d0d48366ca9c2f1cc7dfb5746067ca7050 100644 (file)
@@ -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);
        }
-
+       
 }
index 4f552b819310b7ae7ced7f82518320612c5fec84..46b7893106985d9ebb07059844b596a21f802cc7 100644 (file)
@@ -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
index a447009f1f3ee886375bffeab8262cd05ab89df5..52153e1488093b899c742e186c6691f738f7df26 100644 (file)
@@ -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");
        
+       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
index c8ed58b9273e64e06434b5dd59d51ccdfd278212..8f5a75ec50dc8e0addc6d49475e19fc1ec986ad9 100644 (file)
@@ -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)
index 6b5b83f70e768912b2f1708f8c0e39ca0a500293..cf6b2787a959d401375cc2b56372194c80e6c62c 100644 (file)
@@ -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);
                
-               rocket.getDefaultConfiguration().setAllStages();
+               //              tube3.setIgnitionEvent(MotorMount.IgnitionEvent.NEVER);
                
+               rocket.getDefaultConfiguration().setAllStages();
                
+
                return rocket;
        }
        
-       
-       
+
+
 }
index 663340f8c32513a040c4a44b8ea9907325a14f9d..33ced0c8e826bf9f9776c319380c2b92aa3fcaed 100644 (file)
@@ -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 {