+2009-09-26 Sampo Niskanen
+
+ * Implemented custom material editing
+
+2009-09-20 Sampo Niskanen
+
+ * Implemented more unit tests, fixed bugs
+
+2009-09-19 Sampo Niskanen
+
+ * [BUG] Ignore Sun JRE bug 6828938 in ExceptionHandler
+ * Implemented non-exception throwing bug handling
+ * [BUG] Fixed unnecessary cropping for component tree names
+
+2009-09-10 Sampo Niskanen
+
+ * [BUG] Freeform fin set shape undo not working
+ * [BUG] Conversion to freeform fin set not working
+
+2009-09-08 Sampo Niskanen
+
+ * Allow components to be attached to tube coupler
+
+2009-09-07 Sampo Niskanen
+
+ * Implemented fin tab save/load
+
+2009-09-04 Sampo Niskanen
+
+ * Implemented through-the-wall fin tabs (excluding save/load)
+
2009-09-01 Sampo Niskanen
* Released version 0.9.3
<property name="main-dir" value="net/sf/openrocket/startup"/>
- <!-- Classpath definition -->
+ <!-- Classpath definitions -->
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar"/>
</path>
+ <path id="test-classpath">
+ <path refid="classpath"/>
+ <pathelement location="${build-test.dir}"/>
+ <pathelement location="${classes.dir}"/>
+ <pathelement location="${ant.library.dir}/junit4.jar"/>
+ </path>
+
<!-- CLEAN -->
<target name="clean">
<delete dir="${build.dir}"/>
+ <delete dir="tmp/"/>
</target>
<javac srcdir="${src.dir}" destdir="${classes.dir}" excludes="${main-dir}/*" classpathref="classpath"/>
<echo>Compiling startup classes</echo>
<javac srcdir="${src.dir}/${main-dir}" destdir="${classes.dir}" source="1.4" classpathref="classpath"/>
+ <copy file="build.properties" todir="${dist.dir}"/>
</target>
<!-- JAR -->
<target name="jar" depends="build">
<copy todir="${dist.dir}/">
- <fileset dir="." includes="LICENSE.TXT README.TXT ChangeLog ReleaseNotes build.properties" />
+ <fileset dir="." includes="LICENSE.TXT README.TXT ChangeLog ReleaseNotes build.properties fileformat.txt" />
<fileset dir="." includes="datafiles/ pix/" />
</copy>
<mkdir dir="${jar.dir}"/>
<!-- DIST-SRC -->
- <target name="dist-src" depends="checktodo,clean,unittest">
+ <target name="dist-src">
<echo>
Building source distribution
</echo>
<target name="unittest" description="Execute unit tests" depends="build">
<echo>Building unit tests</echo>
<mkdir dir="${build-test.dir}"/>
- <javac srcdir="${src-test.dir}" destdir="${build-test.dir}" classpathref="classpath" classpath="${classes.dir}:${ant.library.dir}/junit4.jar"/>
+ <javac srcdir="${src-test.dir}" destdir="${build-test.dir}" classpathref="test-classpath"/>
<echo>Running unit tests</echo>
<mkdir dir="tmp/rawtestoutput"/>
<junit printsummary="true" failureproperty="junit.failure">
- <classpath path="${build-test.dir}"/>
- <classpath path="${classes.dir}"/>
- <classpath path="${ant.library.dir}/junit4.jar"/>
-
+ <classpath>
+ <path refid="test-classpath"/>
+ <path location="${basedir}"/>
+ </classpath>
<batchtest todir="tmp/rawtestoutput">
- <fileset dir="${src-test.dir}">
- <include name="**/*Test*.java" />
- <exclude name="Test.java" />
+ <fileset dir="${build-test.dir}">
+ <include name="**/*Test*.class" />
+ <exclude name="Test.class" />
</fileset>
<formatter type="xml"/>
</batchtest>
--- /dev/null
+
+The current OpenRocket file format is "documented" only as the
+reference implementation. This will hopefully change in the future.
+
+
+The "version" attribute of the <openrocket> tag describes the file
+format version used, while the "creator" attribute MAY describe the
+software version used to write the document.
+
+The file format version is increased every time the format is
+changed. The minor number is increased when changes are made that are
+mostly backward-compatible, meaning that older software versions
+should be able to read the design sans the new features. The major
+number is increased when changes are made that render the design
+problematic or impossible to read for older software. For maximum
+compatibility software should save a file in the oldest file format
+version that supports all the necessary design features.
+
+
+The following file format versions exist:
+
+
+0.9: Used before the first public release of Openrocket; effectively
+ equivalent to 1.0. Should not be used when writing documents.
+ OpenRocket accepts this version when reading files, but other
+ software need not.
+
+1.0: File format version of the first public release (OpenRocket 0.9.0)
+
+1.1: Introduced with OpenRocket 0.9.4. Adds support for saving fin
+ tabs (<tabheight>, <tablength> and <tabposition> elements) and
+ allows attaching subcomponents to a tube coupler (previously
+ forbidden).
+
+
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.ExternalComponent;
import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.PolyInterpolator;
import net.sf.openrocket.util.Reflection;
-import net.sf.openrocket.util.Test;
/**
* An aerodynamic calculator that uses the extended Barrowman method to
- public static void main(String[] arg) {
-
- PolyInterpolator interpolator;
-
- interpolator = new PolyInterpolator(
- new double[] { 0, 17*Math.PI/180 },
- new double[] { 0, 17*Math.PI/180 }
- );
- double[] poly1 = interpolator.interpolator(1, 1.3, 0, 0);
-
- interpolator = new PolyInterpolator(
- new double[] { 17*Math.PI/180, Math.PI/2 },
- new double[] { 17*Math.PI/180, Math.PI/2 },
- new double[] { Math.PI/2 }
- );
- double[] poly2 = interpolator.interpolator(1.3, 0, 0, 0, 0);
-
-
- for (double a=0; a<=180.1; a++) {
- double r = a*Math.PI/180;
- if (r > Math.PI/2)
- r = Math.PI - r;
-
- double value;
- if (r < 18*Math.PI/180)
- value = PolyInterpolator.eval(r, poly1);
- else
- value = PolyInterpolator.eval(r, poly2);
-
- System.out.println(""+a+" "+value);
- }
-
- System.exit(0);
-
-
- Rocket normal = Test.makeRocket();
- Rocket perfect = Test.makeRocket();
- normal.setPerfectFinish(false);
- perfect.setPerfectFinish(true);
-
- Configuration confNormal = new Configuration(normal);
- Configuration confPerfect = new Configuration(perfect);
-
- for (RocketComponent c: confNormal) {
- if (c instanceof ExternalComponent) {
- ((ExternalComponent)c).setFinish(Finish.NORMAL);
- }
- }
- for (RocketComponent c: confPerfect) {
- if (c instanceof ExternalComponent) {
- ((ExternalComponent)c).setFinish(Finish.NORMAL);
- }
- }
-
-
- confNormal.setToStage(0);
- confPerfect.setToStage(0);
-
-
-
- BarrowmanCalculator calcNormal = new BarrowmanCalculator(confNormal);
- BarrowmanCalculator calcPerfect = new BarrowmanCalculator(confPerfect);
-
- FlightConditions conditions = new FlightConditions(confNormal);
-
- for (double mach=0; mach < 3; mach += 0.1) {
- conditions.setMach(mach);
-
- Map<RocketComponent, AerodynamicForces> data =
- calcNormal.getForceAnalysis(conditions, null);
- AerodynamicForces forcesNormal = data.get(normal);
-
- data = calcPerfect.getForceAnalysis(conditions, null);
- AerodynamicForces forcesPerfect = data.get(perfect);
-
- System.out.printf("%f %f %f %f %f %f %f\n",mach,
- forcesNormal.pressureCD, forcesPerfect.pressureCD,
- forcesNormal.frictionCD, forcesPerfect.frictionCD,
- forcesNormal.CD, forcesPerfect.CD);
- }
-
-
-
- }
+// public static void main(String[] arg) {
+//
+// PolyInterpolator interpolator;
+//
+// interpolator = new PolyInterpolator(
+// new double[] { 0, 17*Math.PI/180 },
+// new double[] { 0, 17*Math.PI/180 }
+// );
+// double[] poly1 = interpolator.interpolator(1, 1.3, 0, 0);
+//
+// interpolator = new PolyInterpolator(
+// new double[] { 17*Math.PI/180, Math.PI/2 },
+// new double[] { 17*Math.PI/180, Math.PI/2 },
+// new double[] { Math.PI/2 }
+// );
+// double[] poly2 = interpolator.interpolator(1.3, 0, 0, 0, 0);
+//
+//
+// for (double a=0; a<=180.1; a++) {
+// double r = a*Math.PI/180;
+// if (r > Math.PI/2)
+// r = Math.PI - r;
+//
+// double value;
+// if (r < 18*Math.PI/180)
+// value = PolyInterpolator.eval(r, poly1);
+// else
+// value = PolyInterpolator.eval(r, poly2);
+//
+// System.out.println(""+a+" "+value);
+// }
+//
+// System.exit(0);
+//
+//
+// Rocket normal = TestRocket.makeRocket();
+// Rocket perfect = TestRocket.makeRocket();
+// normal.setPerfectFinish(false);
+// perfect.setPerfectFinish(true);
+//
+// Configuration confNormal = new Configuration(normal);
+// Configuration confPerfect = new Configuration(perfect);
+//
+// for (RocketComponent c: confNormal) {
+// if (c instanceof ExternalComponent) {
+// ((ExternalComponent)c).setFinish(Finish.NORMAL);
+// }
+// }
+// for (RocketComponent c: confPerfect) {
+// if (c instanceof ExternalComponent) {
+// ((ExternalComponent)c).setFinish(Finish.NORMAL);
+// }
+// }
+//
+//
+// confNormal.setToStage(0);
+// confPerfect.setToStage(0);
+//
+//
+//
+// BarrowmanCalculator calcNormal = new BarrowmanCalculator(confNormal);
+// BarrowmanCalculator calcPerfect = new BarrowmanCalculator(confPerfect);
+//
+// FlightConditions conditions = new FlightConditions(confNormal);
+//
+// for (double mach=0; mach < 3; mach += 0.1) {
+// conditions.setMach(mach);
+//
+// Map<RocketComponent, AerodynamicForces> data =
+// calcNormal.getForceAnalysis(conditions, null);
+// AerodynamicForces forcesNormal = data.get(normal);
+//
+// data = calcPerfect.getForceAnalysis(conditions, null);
+// AerodynamicForces forcesPerfect = data.get(perfect);
+//
+// System.out.printf("%f %f %f %f %f %f %f\n",mach,
+// forcesNormal.pressureCD, forcesPerfect.pressureCD,
+// forcesNormal.frictionCD, forcesPerfect.frictionCD,
+// forcesNormal.CD, forcesPerfect.CD);
+// }
+//
+//
+//
+// }
}
package net.sf.openrocket.aerodynamics.barrowman;
-import static java.lang.Math.pow;
-import static java.lang.Math.sqrt;
+import static java.lang.Math.*;
import static net.sf.openrocket.util.MathUtil.pow2;
import java.util.Arrays;
-import java.util.Iterator;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.aerodynamics.WarningSet;
-import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.LinearInterpolator;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.PolyInterpolator;
-import net.sf.openrocket.util.Test;
public class FinSetCalc extends RocketComponentCalc {
}
- @SuppressWarnings("null")
- public static void main(String arg[]) {
- Rocket rocket = Test.makeRocket();
- FinSet finset = null;
-
- Iterator<RocketComponent> iter = rocket.deepIterator();
- while (iter.hasNext()) {
- RocketComponent c = iter.next();
- if (c instanceof FinSet) {
- finset = (FinSet)c;
- break;
- }
- }
-
- ((TrapezoidFinSet)finset).setHeight(0.10);
- ((TrapezoidFinSet)finset).setRootChord(0.10);
- ((TrapezoidFinSet)finset).setTipChord(0.10);
- ((TrapezoidFinSet)finset).setSweep(0.0);
-
-
- FinSetCalc calc = new FinSetCalc(finset);
-
- calc.calculateFinGeometry();
- FlightConditions cond = new FlightConditions(new Configuration(rocket));
- for (double m=0; m < 3; m+=0.05) {
- cond.setMach(m);
- cond.setAOA(0.0*Math.PI/180);
- double cna = calc.calculateFinCNa1(cond);
- System.out.printf("%5.2f "+cna+"\n", m);
- }
-
- }
+// @SuppressWarnings("null")
+// public static void main(String arg[]) {
+// Rocket rocket = TestRocket.makeRocket();
+// FinSet finset = null;
+//
+// Iterator<RocketComponent> iter = rocket.deepIterator();
+// while (iter.hasNext()) {
+// RocketComponent c = iter.next();
+// if (c instanceof FinSet) {
+// finset = (FinSet)c;
+// break;
+// }
+// }
+//
+// ((TrapezoidFinSet)finset).setHeight(0.10);
+// ((TrapezoidFinSet)finset).setRootChord(0.10);
+// ((TrapezoidFinSet)finset).setTipChord(0.10);
+// ((TrapezoidFinSet)finset).setSweep(0.0);
+//
+//
+// FinSetCalc calc = new FinSetCalc(finset);
+//
+// calc.calculateFinGeometry();
+// FlightConditions cond = new FlightConditions(new Configuration(rocket));
+// for (double m=0; m < 3; m+=0.05) {
+// cond.setMach(m);
+// cond.setAOA(0.0*Math.PI/180);
+// double cna = calc.calculateFinCNa1(cond);
+// System.out.printf("%5.2f "+cna+"\n", m);
+// }
+//
+// }
@Override
--- /dev/null
+package net.sf.openrocket.communication;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+
+import net.sf.openrocket.util.ComparablePair;
+import net.sf.openrocket.util.Prefs;
+
+public class Communication {
+
+ private static final String BUG_REPORT_URL =
+ "http://openrocket.sourceforge.net/actions/reportbug";
+ private static final String UPDATE_INFO_URL =
+ "http://openrocket.sourceforge.net/actions/updates";
+
+ private static final String VERSION_PARAM = "version";
+
+
+ private static final String BUG_REPORT_PARAM = "content";
+ private static final int BUG_REPORT_RESPONSE_CODE = HttpURLConnection.HTTP_ACCEPTED;
+ private static final int CONNECTION_TIMEOUT = 10000; // in milliseconds
+
+ private static final int UPDATE_INFO_UPDATE_AVAILABLE = HttpURLConnection.HTTP_OK;
+ private static final int UPDATE_INFO_NO_UPDATE_CODE = HttpURLConnection.HTTP_NO_CONTENT;
+ private static final String UPDATE_INFO_CONTENT_TYPE = "text/plain";
+
+
+ private static UpdateInfoFetcher fetcher = null;
+
+
+ /**
+ * Send the provided report to the OpenRocket bug report URL. If the connection
+ * fails or the server does not respond with the correct response code, an
+ * exception is thrown.
+ *
+ * @param report the report to send.
+ * @throws IOException if an error occurs while connecting to the server or
+ * the server responds with a wrong response code.
+ */
+ public static void sendBugReport(String report) throws IOException {
+ URL url = new URL(BUG_REPORT_URL);
+
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+ connection.setConnectTimeout(CONNECTION_TIMEOUT);
+ connection.setInstanceFollowRedirects(true);
+ connection.setRequestMethod("POST");
+ connection.setUseCaches(false);
+ connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
+
+ String post;
+ post = (VERSION_PARAM + "=" + encode(Prefs.getVersion())
+ + "&" + BUG_REPORT_PARAM + "=" + encode(report));
+
+ OutputStreamWriter wr = null;
+ try {
+ // Send post information
+ connection.setDoOutput(true);
+ wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
+ wr.write(post);
+ wr.flush();
+
+ if (connection.getResponseCode() != BUG_REPORT_RESPONSE_CODE) {
+ throw new IOException("Server responded with code " +
+ connection.getResponseCode() + ", expecting " + BUG_REPORT_RESPONSE_CODE);
+ }
+ } finally {
+ if (wr != null)
+ wr.close();
+ connection.disconnect();
+ }
+ }
+
+
+
+ /**
+ * Start an asynchronous task that will fetch information about the latest
+ * OpenRocket version. This will overwrite any previous fetching operation.
+ */
+ public static void startFetchUpdateInfo() {
+ fetcher = new UpdateInfoFetcher();
+ fetcher.start();
+ }
+
+
+ /**
+ * Check whether the update info fetching is still in progress.
+ *
+ * @return <code>true</code> if the communication is still in progress.
+ */
+ public static boolean isFetchUpdateInfoRunning() {
+ if (fetcher == null) {
+ throw new IllegalStateException("startFetchUpdateInfo() has not been called");
+ }
+ return fetcher.isAlive();
+ }
+
+
+ /**
+ * Retrieve the result of the background update info fetcher. This method returns
+ * the result of the previous call to {@link #startFetchUpdateInfo()}. It must be
+ * called before calling this method.
+ * <p>
+ * This method will return <code>null</code> if the info fetcher is still running or
+ * if it encountered a problem in communicating with the server. The difference can
+ * be checked using {@link #isFetchUpdateInfoRunning()}.
+ *
+ * @return the update result, or <code>null</code> if the fetching is still in progress
+ * or an error occurred while communicating with the server.
+ * @throws IllegalStateException if {@link #startFetchUpdateInfo()} has not been called.
+ */
+ public static UpdateInfo getUpdateInfo() {
+ if (fetcher == null) {
+ throw new IllegalStateException("startFetchUpdateInfo() has not been called");
+ }
+ return fetcher.info;
+ }
+
+
+
+ /**
+ * Parse the data received from the server.
+ *
+ * @param r the Reader from which to read.
+ * @return an UpdateInfo construct, or <code>null</code> if the data was invalid.
+ * @throws IOException if an I/O exception occurs.
+ */
+ /* package-private */
+ static UpdateInfo parseUpdateInput(Reader r) throws IOException {
+ BufferedReader reader;
+ if (r instanceof BufferedReader) {
+ reader = (BufferedReader)r;
+ } else {
+ reader = new BufferedReader(r);
+ }
+
+
+ String version = null;
+ ArrayList<ComparablePair<Integer,String>> updates =
+ new ArrayList<ComparablePair<Integer,String>>();
+
+ String str = reader.readLine();
+ while (str != null) {
+ if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
+ version = str.substring(8).trim();
+ } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
+ int index = str.indexOf(':');
+ int value = Integer.parseInt(str.substring(0, index));
+ String desc = str.substring(index+1).trim();
+ if (!desc.equals("")) {
+ updates.add(new ComparablePair<Integer,String>(value, desc));
+ }
+ }
+ // Ignore anything else
+ str = reader.readLine();
+ }
+
+ if (version != null) {
+ return new UpdateInfo(version, updates);
+ } else {
+ return null;
+ }
+ }
+
+
+
+
+ private static class UpdateInfoFetcher extends Thread {
+
+ private volatile UpdateInfo info = null;
+
+ @Override
+ public void run() {
+ try {
+ doConnection();
+ } catch (IOException e) {
+ return;
+ }
+ }
+
+
+ private void doConnection() throws IOException {
+ URL url;
+ url = new URL(UPDATE_INFO_URL + "?" + VERSION_PARAM + "=" +
+ encode(Prefs.getVersion()));
+
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+ connection.setConnectTimeout(CONNECTION_TIMEOUT);
+ connection.setInstanceFollowRedirects(true);
+ connection.setRequestMethod("GET");
+ connection.setUseCaches(false);
+ connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
+ connection.setRequestProperty("X-OpenRocket-ID", encode(Prefs.getUniqueID()));
+ connection.setRequestProperty("X-OpenRocket-OS", encode(
+ System.getProperty("os.name") + " " + System.getProperty("os.arch")));
+ connection.setRequestProperty("X-OpenRocket-Java", encode(
+ System.getProperty("java.vendor") + " " + System.getProperty("java.version")));
+ connection.setRequestProperty("X-OpenRocket-Country", encode(
+ System.getProperty("user.country")));
+
+ InputStream is = null;
+ try {
+ connection.connect();
+
+ if (connection.getResponseCode() == UPDATE_INFO_NO_UPDATE_CODE) {
+ // No updates are available
+ info = new UpdateInfo();
+ return;
+ }
+
+ if (connection.getResponseCode() != UPDATE_INFO_UPDATE_AVAILABLE) {
+ // Error communicating with server
+ return;
+ }
+
+ if (!UPDATE_INFO_CONTENT_TYPE.equalsIgnoreCase(connection.getContentType())) {
+ // Unknown response type
+ return;
+ }
+
+ // Update is available, parse input
+ is = connection.getInputStream();
+ String encoding = connection.getContentEncoding();
+ if (encoding == null)
+ encoding = "UTF-8";
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
+
+
+
+ } finally {
+ if (is != null)
+ is.close();
+ connection.disconnect();
+ }
+
+
+ }
+
+ }
+
+
+ private static String encode(String str) {
+ if (str == null)
+ return "null";
+ try {
+ return URLEncoder.encode(str, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Unsupported encoding UTF-8", e);
+ }
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.communication;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.util.ComparablePair;
+import net.sf.openrocket.util.Prefs;
+
+public class UpdateInfo {
+
+ private final String latestVersion;
+
+ private final ArrayList<ComparablePair<Integer, String>> updates;
+
+
+ public UpdateInfo() {
+ this.latestVersion = Prefs.getVersion();
+ this.updates = new ArrayList<ComparablePair<Integer, String>>();
+ }
+
+ public UpdateInfo(String version, List<ComparablePair<Integer, String>> updates) {
+ this.latestVersion = version;
+ this.updates = new ArrayList<ComparablePair<Integer, String>>(updates);
+ }
+
+
+
+ /**
+ * Get the latest OpenRocket version. If it is the current version, then the value
+ * of {@link Prefs#getVersion()} is returned.
+ *
+ * @return the latest OpenRocket version.
+ */
+ public String getLatestVersion() {
+ return latestVersion;
+ }
+
+
+ /**
+ * Return a list of the new features/updates that are available. The list has a
+ * priority for each update and a message text. The returned list may be modified.
+ *
+ * @return a modifiable list of the updates.
+ */
+ @SuppressWarnings("unchecked")
+ public List<ComparablePair<Integer, String>> getUpdates() {
+ return (List<ComparablePair<Integer, String>>) updates.clone();
+ }
+
+}
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.rocketcomponent.TubeCoupler;
import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
+import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
class DocumentConfig {
/* Remember to update OpenRocketSaver as well! */
- public static final String[] SUPPORTED_VERSIONS = { "0.9", "1.0" };
+ public static final String[] SUPPORTED_VERSIONS = { "0.9", "1.0", "1.1" };
//////// Component constructors
FinSet.CrossSection.class));
setters.put("FinSet:cant", new DoubleSetter(
Reflection.findMethodStatic(FinSet.class, "setCantAngle", double.class), Math.PI/180.0));
+ setters.put("FinSet:tabheight", new DoubleSetter(
+ Reflection.findMethodStatic(FinSet.class, "setTabHeight", double.class)));
+ setters.put("FinSet:tablength", new DoubleSetter(
+ Reflection.findMethodStatic(FinSet.class, "setTabLength", double.class)));
+ setters.put("FinSet:tabposition", new FinTabPositionSetter());
// TrapezoidFinSet
setters.put("TrapezoidFinSet:rootchord", new DoubleSetter(
* @param enumClass the class of the enum.
* @return the found enum value, or <code>null</code>.
*/
- public static <T extends Enum<T>> Enum<T> findEnum(String name, Class<? extends Enum<T>> enumClass) {
+ public static <T extends Enum<T>> Enum<T> findEnum(String name,
+ Class<? extends Enum<T>> enumClass) {
if (name == null)
return null;
}
+class FinTabPositionSetter extends DoubleSetter {
+
+ public FinTabPositionSetter() {
+ super(Reflection.findMethodStatic(FinSet.class, "setTabShift", double.class));
+ }
+
+ @Override
+ public void set(RocketComponent c, String s, HashMap<String, String> attributes,
+ WarningSet warnings) {
+
+ if (!(c instanceof FinSet)) {
+ throw new IllegalStateException("FinTabPositionSetter called for component " + c);
+ }
+
+ String relative = attributes.get("relativeto");
+ FinSet.TabRelativePosition position =
+ (TabRelativePosition) DocumentConfig.findEnum(relative,
+ FinSet.TabRelativePosition.class);
+
+ if (position != null) {
+
+ ((FinSet)c).setTabRelativePosition(position);
+
+ } else {
+ if (relative == null) {
+ warnings.add("Required attribute 'relativeto' not found for fin tab position.");
+ } else {
+ warnings.add("Illegal attribute value '" + relative + "' encountered.");
+ }
+ }
+
+ super.set(c, s, attributes, warnings);
+ }
+
+
+}
+
class ClusterConfigurationSetter implements Setter {
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.document.StorageOptions;
+import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.TubeCoupler;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationConditions;
+import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Pair;
import net.sf.openrocket.util.Prefs;
import net.sf.openrocket.util.Reflection;
public class OpenRocketSaver extends RocketSaver {
- /* Remember to update OpenRocketLoader as well! */
- public static final String FILE_VERSION = "1.0";
+ /**
+ * Divisor used in converting an integer version to the point-represented version.
+ * The integer version divided by this value is the major version and the remainder is
+ * the minor version. For example 101 corresponds to file version "1.1".
+ */
+ public static final int FILE_VERSION_DIVISOR = 100;
+
private static final String OPENROCKET_CHARSET = "UTF-8";
dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET));
+ final int fileVersion = calculateNecessaryFileVersion(document, options);
+ final String fileVersionString =
+ (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR);
+
this.indent = 0;
System.out.println("Writing...");
writeln("<?xml version='1.0' encoding='utf-8'?>");
- writeln("<openrocket version=\""+FILE_VERSION+"\" creator=\"OpenRocket "
- +Prefs.getVersion()+ "\">");
+ writeln("<openrocket version=\"" + fileVersionString + "\" creator=\"OpenRocket "
+ + Prefs.getVersion() + "\">");
indent++;
// Recursively save the rocket structure
writeln("</openrocket>");
dest.flush();
- if (output instanceof GZIPOutputStream)
+ if (options.isCompressionEnabled()) {
((GZIPOutputStream)output).finish();
+ }
}
}
+ /**
+ * Determine which file version is required in order to store all the features of the
+ * current design. By default the oldest version that supports all the necessary features
+ * will be used.
+ *
+ * @param document the document to output.
+ * @param opts the storage options.
+ * @return the integer file version to use.
+ */
+ private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
+ /*
+ * File version 1.1 is required for:
+ * - fin tabs
+ * - components attached to tube coupler
+ *
+ * Otherwise use version 1.0.
+ */
+
+ // Check for fin tabs (version 1.1)
+ Iterator<RocketComponent> iterator = document.getRocket().deepIterator();
+ while (iterator.hasNext()) {
+ RocketComponent c = iterator.next();
+
+ // Check for fin tabs
+ if (c instanceof FinSet) {
+ FinSet fin = (FinSet)c;
+ if (!MathUtil.equals(fin.getTabHeight(),0) &&
+ !MathUtil.equals(fin.getTabLength(), 0)) {
+ return FILE_VERSION_DIVISOR + 1;
+ }
+ }
+
+ // Check for components attached to tube coupler
+ if (c instanceof TubeCoupler) {
+ if (c.getChildCount() > 0) {
+ return FILE_VERSION_DIVISOR + 1;
+ }
+ }
+ }
+
+ // Default (version 1.0)
+ return FILE_VERSION_DIVISOR + 0;
+ }
+
+
@SuppressWarnings("unchecked")
private void saveComponent(RocketComponent component) throws IOException {
import java.util.List;
+import net.sf.openrocket.util.MathUtil;
+
public class FinSetSaver extends ExternalComponentSaver {
@Override
elements.add("<crosssection>" + fins.getCrossSection().name().toLowerCase()
+ "</crosssection>");
elements.add("<cant>" + (fins.getCantAngle() * 180.0 / Math.PI) + "</cant>");
+
+ // Save fin tabs only if they exist (compatibility with file version < 1.1)
+ if (!MathUtil.equals(fins.getTabHeight(),0) &&
+ !MathUtil.equals(fins.getTabLength(), 0)) {
+
+ elements.add("<tabheight>" + fins.getTabHeight() + "</tabheight>");
+ elements.add("<tablength>" + fins.getTabLength() + "</tablength>");
+ elements.add("<tabposition relativeto=\"" +
+ fins.getTabRelativePosition().name().toLowerCase() + "\">" +
+ fins.getTabShift() + "</tabposition>");
+
+ }
}
}
}
+ /**
+ * Return the column type class. This is necessary for example for numerical
+ * sorting of Value objects, showing booleans as checkboxes etc.
+ *
+ * @return the object class of this column, by default <code>Object.class</code>.
+ */
public Class<?> getColumnClass() {
return Object.class;
}
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
+import net.sf.openrocket.gui.main.ExceptionHandler;
+
public abstract class ColumnTableModel extends AbstractTableModel {
private final Column[] columns;
public Object getValueAt(int row, int col) {
if ((row < 0) || (row >= getRowCount()) ||
(col < 0) || (col >= columns.length)) {
- System.err.println("Error: Requested illegal column/row = "+col+"/"+row+".");
+ ExceptionHandler.handleErrorCondition("Error: Requested illegal column/row, " +
+ "col="+col+" row="+row);
assert(false);
return null;
}
public class DoubleModel implements ChangeListener, ChangeSource {
private static final boolean DEBUG_LISTENERS = false;
+
+ public static final DoubleModel ZERO = new DoubleModel(0);
//////////// JSpinner Model ////////////
/**
- * Add a listener to the model. Adds the model as a listener to the Component if this
+ * Add a listener to the model. Adds the model as a listener to the value source if this
* is the first listener.
* @param l Listener to add.
*/
package net.sf.openrocket.gui.adaptors;
-import java.awt.Dialog;
-import java.awt.Window;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
+import java.awt.Component;
import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JSpinner;
-import javax.swing.JTextField;
import javax.swing.SwingUtilities;
-import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.database.Database;
import net.sf.openrocket.database.DatabaseListener;
import net.sf.openrocket.database.Databases;
-import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.dialogs.CustomMaterialDialog;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Reflection;
public class MaterialModel extends AbstractListModel implements
private static final String CUSTOM = "Custom";
+ private final Component parentComponent;
+
private final RocketComponent component;
private final Material.Type type;
private final Database<Material> database;
private final Reflection.Method setMethod;
- public MaterialModel(RocketComponent component, Material.Type type) {
- this(component, type, "Material");
+ public MaterialModel(Component parent, RocketComponent component, Material.Type type) {
+ this(parent, component, type, "Material");
}
- public MaterialModel(RocketComponent component, Material.Type type, String name) {
+ public MaterialModel(Component parent, RocketComponent component, Material.Type type,
+ String name) {
+ this.parentComponent = parent;
this.component = component;
this.type = type;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
- AddMaterialDialog dialog = new AddMaterialDialog();
+ CustomMaterialDialog dialog = new CustomMaterialDialog(
+ SwingUtilities.getWindowAncestor(parentComponent),
+ (Material) getSelectedItem(), true,
+ "Define custom material");
+
dialog.setVisible(true);
- if (!dialog.okClicked)
+ if (!dialog.getOkClicked())
return;
- Material material = Material.newMaterial(type,
- dialog.nameField.getText().trim(),
- dialog.density.getValue(), true);
+ Material material = dialog.getMaterial();
setMethod.invoke(component, material);
- if (dialog.addBox.isSelected()) {
+ if (dialog.isAddSelected()) {
database.add(material);
}
}
public void elementRemoved(Material element, Database<Material> source) {
this.fireContentsChanged(this, 0, database.size());
}
-
-
-
-
-
- private class AddMaterialDialog extends JDialog {
-
- private boolean okClicked = false;
- private JTextField nameField;
- private DoubleModel density;
- private JCheckBox addBox;
-
- public AddMaterialDialog() {
- super((Window)null, "Custom material", Dialog.ModalityType.APPLICATION_MODAL);
-
- Material material = (Material) getSelectedItem();
-
- JPanel panel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]"));
-
- panel.add(new JLabel("Material name:"));
- nameField = new JTextField(15);
- nameField.setText(material.getName());
- panel.add(nameField,"span 2, growx, wrap");
-
- panel.add(new JLabel("Material density:"));
- density = new DoubleModel(material.getDensity(),UnitGroup.UNITS_DENSITY_BULK,0);
- JSpinner spinner = new JSpinner(density.getSpinnerModel());
- panel.add(spinner, "growx");
- panel.add(new UnitSelector(density),"wrap");
-
- addBox = new JCheckBox("Add material to database");
- panel.add(addBox,"span, wrap");
-
- JButton button = new JButton("OK");
- button.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- okClicked = true;
- AddMaterialDialog.this.setVisible(false);
- }
- });
- panel.add(button,"span, split, tag ok");
-
- button = new JButton("Cancel");
- button.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- AddMaterialDialog.this.setVisible(false);
- }
- });
- panel.add(button,"tag cancel");
-
- this.setContentPane(panel);
- this.pack();
- this.setAlwaysOnTop(true);
- this.setLocationRelativeTo(null);
- }
- }
-
-
}
--- /dev/null
+package net.sf.openrocket.gui.components;
+
+import javax.swing.JTable;
+import javax.swing.table.AbstractTableModel;
+
+
+/*
+ * TODO: LOW: This is currently unused.
+ */
+public abstract class CollectionTable<T> extends JTable {
+
+ private final String[] columnNames;
+ private CollectionTableModel model;
+
+
+ protected CollectionTable(String[] columnNames) {
+ this.columnNames = columnNames.clone();
+ }
+
+
+ protected void initializeTable() {
+ model = new CollectionTableModel();
+ this.setModel(model);
+ }
+
+
+ /**
+ * Retrieve the object for the specified row number.
+ *
+ * @param row the row number being queried.
+ * @return the object at that row.
+ */
+ protected abstract T getModelObjectAt(int row);
+
+ protected abstract int getModelRowCount();
+
+
+
+ protected abstract Object getViewForModelObject(T object, int column);
+
+ protected Class<?> getViewColumnClass(int column) {
+ return Object.class;
+ }
+
+
+
+ private class CollectionTableModel extends AbstractTableModel {
+ @Override
+ public int getColumnCount() {
+ return columnNames.length;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return columnNames[column];
+ }
+
+ @Override
+ public Class<?> getColumnClass(int column) {
+ return getViewColumnClass(column);
+ }
+
+
+ @Override
+ public int getRowCount() {
+ return getModelRowCount();
+ }
+
+ @Override
+ public Object getValueAt(int row, int column) {
+ T value = getModelObjectAt(row);
+ return getViewForModelObject(value, column);
+ }
+ }
+}
public class UnitSelector extends ResizeLabel implements ChangeListener, MouseListener,
ItemSelectable {
- private final DoubleModel model;
+ private DoubleModel model;
private final Action[] extraActions;
private UnitGroup unitGroup;
if (model != null) {
this.unitGroup = model.getUnitGroup();
this.currentUnit = model.getCurrentUnit();
- } else {
+ } else if (group != null) {
this.unitGroup = group;
this.currentUnit = group.getDefaultUnit();
+ } else {
+ this.unitGroup = UnitGroup.UNITS_NONE;
+ this.currentUnit = UnitGroup.UNITS_NONE.getDefaultUnit();
}
this.extraActions = actions;
withinBorder = new CompoundBorder(new LineBorder(new Color(0f, 0f, 0f, 0.6f)),
new EmptyBorder(1, 1, 1, 1));
+ // Add model listener if showing value
+ if (showValue)
+ this.model.addChangeListener(this);
+
setBorder(normalBorder);
updateText();
}
public UnitSelector(DoubleModel model, boolean showValue, Action... actions) {
this(model, showValue, null, actions);
-
- // Add model listener
- this.model.addChangeListener(this);
}
return model;
}
+
+ /**
+ * Set the current double model.
+ *
+ * @param model the model to set, <code>null</code> is NOT allowed.
+ */
+ public void setModel(DoubleModel model) {
+ if (this.model != null && showValue) {
+ this.model.removeChangeListener(this);
+ }
+ this.model = model;
+ this.unitGroup = model.getUnitGroup();
+ this.currentUnit = model.getCurrentUnit();
+ if (showValue) {
+ this.model.addChangeListener(this);
+ }
+ updateText();
+ }
+
+
/**
* Return the unit group that is being shown, or <code>null</code>. Either this method
package net.sf.openrocket.gui.configdialog;
-import java.awt.Component;
-import java.awt.Container;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
-import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JDialog;
-import javax.swing.JSlider;
-import javax.swing.JSpinner;
-import javax.swing.SpinnerNumberModel;
import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.gui.Resettable;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Prefs;
/**
- * A JFrame dialog that contains the configuration elements of one component.
+ * A dialog that contains the configuration elements of one component.
* The contents of the dialog are instantiated from CONFIGDIALOGPACKAGE according
* to the current component.
*
}
});
-
- // Install ESC listener
- GUIUtil.installEscapeCloseOperation(this);
+ GUIUtil.setDisposableDialogOptions(this, null);
}
if (configurator != null) {
// Remove listeners by setting all applicable models to null
- setNullModels(configurator); // null-safe
-
-// mainPanel.remove(configurator);
+ GUIUtil.setNullModels(configurator); // null-safe
}
this.document = document;
configurator = getDialogContents();
this.setContentPane(configurator);
configurator.updateFields();
-// mainPanel.add(configurator,"cell 0 0, growx, growy");
setTitle(component.getComponentName()+" configuration");
// Dimension pref = getPreferredSize();
// Dimension real = getSize();
// if (pref.width > real.width || pref.height > real.height)
- pack();
+ this.pack();
}
- /**
- * Traverses recursively the component tree, and sets all applicable component
- * models to null, so as to remove the listener connections.
- *
- * NOTE: All components in the configuration dialogs that use custom models must be added
- * to this method.
- */
- private void setNullModels(Component c) {
- if (c==null)
- return;
-
- // Remove models for known components
- // Why the FSCK must this be so hard?!?!?
-
- if (c instanceof JSpinner) {
- ((JSpinner)c).setModel(new SpinnerNumberModel());
- } else if (c instanceof JSlider) {
- ((JSlider)c).setModel(new DefaultBoundedRangeModel());
- } else if (c instanceof Resettable) {
- ((Resettable)c).resetModel();
- }
-
-
- if (c instanceof Container) {
- Component[] cs = ((Container)c).getComponents();
- for (Component sub: cs)
- setNullModels(sub);
- }
-
- }
-
-
/**
* Return the configurator panel of the current component.
*/
import java.awt.event.ActionListener;
import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
import javax.swing.SwingUtilities;
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.SpinnerEditor;
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+import net.sf.openrocket.gui.adaptors.EnumModel;
+import net.sf.openrocket.gui.components.BasicSlider;
+import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.unit.UnitGroup;
public abstract class FinSetConfig extends RocketComponentConfig {
public FinSetConfig(RocketComponent component) {
super(component);
+
+ tabbedPane.insertTab("Fin tabs", null, finTabPanel(), "Through-the-wall fin tabs", 0);
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
- FreeformFinSet freeform = new FreeformFinSet((FinSet)component);
- String name = component.getComponentName();
-
- if (freeform.getName().startsWith(name)) {
- freeform.setName(freeform.getComponentName() +
- freeform.getName().substring(name.length()));
- }
-
- RocketComponent parent = component.getParent();
- int index = parent.getChildPosition(component);
-
ComponentConfigDialog.addUndoPosition("Convert fin set");
- parent.removeChild(index);
- parent.addChild(freeform, index);
+ RocketComponent freeform =
+ FreeformFinSet.convertFinSet((FinSet)component);
ComponentConfigDialog.showDialog(freeform);
}
});
}
+ public JPanel finTabPanel() {
+ JPanel panel = new JPanel(
+ new MigLayout("align 50% 20%, fillx, gap rel unrel, ins 20lp 10% 20lp 10%",
+ "[150lp::][65lp::][30lp::][200lp::]",""));
+// JPanel panel = new JPanel(new MigLayout("fillx, align 20% 20%, gap rel unrel",
+// "[40lp][80lp::][30lp::][100lp::]",""));
+
+ panel.add(new JLabel("<html><b>Through-the-wall fin tabs:</b>"), "spanx, wrap 30lp");
+
+ JLabel label;
+ DoubleModel m;
+ DoubleModel length;
+ DoubleModel length2;
+ DoubleModel length_2;
+ JSpinner spin;
+
+ length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
+ length2 = new DoubleModel(component, "Length", 0.5, UnitGroup.UNITS_LENGTH, 0);
+ length_2 = new DoubleModel(component, "Length", -0.5, UnitGroup.UNITS_LENGTH, 0);
+
+ //// Tab length
+ label = new JLabel("Tab length:");
+ label.setToolTipText("The length of the fin tab.");
+ panel.add(label, "gapleft para, gapright 40lp, growx 1");
+
+ m = new DoubleModel(component, "TabLength", UnitGroup.UNITS_LENGTH, 0);
+
+ spin = new JSpinner(m.getSpinnerModel());
+ spin.setEditor(new SpinnerEditor(spin));
+ panel.add(spin,"growx 1");
+
+ panel.add(new UnitSelector(m),"growx 1");
+ panel.add(new BasicSlider(m.getSliderModel(DoubleModel.ZERO, length)),
+ "w 100lp, growx 5, wrap");
+
+
+ //// Tab length
+ label = new JLabel("Tab height:");
+ label.setToolTipText("The spanwise height of the fin tab.");
+ panel.add(label, "gapleft para");
+
+ m = new DoubleModel(component, "TabHeight", UnitGroup.UNITS_LENGTH, 0);
+
+ spin = new JSpinner(m.getSpinnerModel());
+ spin.setEditor(new SpinnerEditor(spin));
+ panel.add(spin,"growx");
+
+ panel.add(new UnitSelector(m),"growx");
+ panel.add(new BasicSlider(m.getSliderModel(DoubleModel.ZERO, length2)),
+ "w 100lp, growx 5, wrap para");
+
+
+ //// Tab position
+ label = new JLabel("Tab position:");
+ label.setToolTipText("The position of the fin tab.");
+ panel.add(label, "gapleft para");
+
+ m = new DoubleModel(component, "TabShift", UnitGroup.UNITS_LENGTH);
+
+ spin = new JSpinner(m.getSpinnerModel());
+ spin.setEditor(new SpinnerEditor(spin));
+ panel.add(spin,"growx");
+
+ panel.add(new UnitSelector(m),"growx");
+ panel.add(new BasicSlider(m.getSliderModel(length_2, length2)),"w 100lp, growx 5, wrap");
+
+
+
+ label = new JLabel("relative to");
+ panel.add(label, "right, gapright unrel");
+
+ EnumModel<FinSet.TabRelativePosition> em =
+ new EnumModel<FinSet.TabRelativePosition>(component, "TabRelativePosition");
+
+ panel.add(new JComboBox(em), "spanx 3, growx");
+
+ return panel;
+ }
@Override
public void updateFields() {
updateFields();
}
});
- panel.add(button,"span, split, grow");
+ panel.add(button,"span, split, growx");
button = new JButton("Remove motor");
button.addActionListener(new ActionListener() {
updateFields();
}
});
- panel.add(button,"grow, wrap");
+ panel.add(button,"growx, wrap");
panel.add(new JLabel("Material:"));
- JComboBox combo = new JComboBox(new MaterialModel(component, Material.Type.SURFACE));
+ JComboBox combo = new JComboBox(new MaterialModel(panel, component,
+ Material.Type.SURFACE));
combo.setToolTipText("The component material affects the weight of the component.");
panel.add(combo,"spanx 3, growx, wrap paragraph");
panel.add(new JLabel("Material:"));
- combo = new JComboBox(new MaterialModel(component, Material.Type.LINE,
+ combo = new JComboBox(new MaterialModel(panel, component, Material.Type.LINE,
"LineMaterial"));
panel.add(combo,"spanx 3, growx, wrap");
label.setToolTipText("The component material affects the weight of the component.");
panel.add(label,"spanx 4, wrap rel");
- JComboBox combo = new JComboBox(new MaterialModel(component,type));
+ JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
combo.setToolTipText("The component material affects the weight of the component.");
panel.add(combo,"spanx 4, growx, wrap paragraph");
JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
"[][65lp::][30lp::][]",""));
- panel.add(new JLabel("Override the mass or center of gravity of the " +
- component.getComponentName() + ":"),"spanx, wrap 20lp");
+ panel.add(new JLabel("<html><b>Override the mass or center of gravity of the " +
+ component.getComponentName() + ":</b>"),"spanx, wrap 20lp");
JCheckBox check;
BooleanModel bm;
private JPanel commentTab() {
JPanel panel = new JPanel(new MigLayout("fill"));
- panel.add(new JLabel("Comments on the "+component.getComponentName()+":"), "wrap");
+ panel.add(new JLabel("<html><b>Comments on the "+component.getComponentName()+":</b>"),
+ "wrap");
// TODO: LOW: Changes in comment from other sources not reflected in component
commentTextArea = new JTextArea(component.getComment());
GUIUtil.setTabToFocusing(commentTextArea);
commentTextArea.addFocusListener(textFieldListener);
- panel.add(new JScrollPane(commentTextArea), "growx, growy");
+ panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
return panel;
}
private JPanel figureTab() {
JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
- panel.add(new JLabel("Figure style:"), "wrap para");
+ panel.add(new JLabel("<html><b>Figure style:</b>"), "wrap para");
panel.add(new JLabel("Component color:"), "gapleft para, gapright 10lp");
panel.add(new JLabel("Material:"));
- JComboBox combo = new JComboBox(new MaterialModel(component, Material.Type.SURFACE));
+ JComboBox combo = new JComboBox(new MaterialModel(panel, component,
+ Material.Type.SURFACE));
combo.setToolTipText("The component material affects the weight of the component.");
panel.add(combo,"spanx 3, growx, wrap 20lp");
this.pack();
this.setResizable(false);
this.setLocationRelativeTo(parent);
- GUIUtil.setDefaultButton(close);
- GUIUtil.installEscapeCloseOperation(this);
+
+ GUIUtil.setDisposableDialogOptions(this, close);
}
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
-import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
-import java.net.URL;
import java.net.URLEncoder;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.JTextArea;
import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.communication.Communication;
import net.sf.openrocket.gui.components.ResizeLabel;
import net.sf.openrocket.gui.components.SelectableLabel;
import net.sf.openrocket.util.GUIUtil;
private static final String REPORT_EMAIL = "openrocket-bugs@lists.sourceforge.net";
- private static final String REPORT_URL =
- "http://openrocket.sourceforge.net/actions/reportbug";
-
- private static final String REPORT_VERSION_PARAM = "version";
- private static final String REPORT_PARAM = "content";
- private static final int REPORT_RESPONSE_CODE = HttpURLConnection.HTTP_ACCEPTED;
- private static final int REPORT_TIMEOUT = 10000; // in milliseconds
-
public BugReportDialog(Window parent, String labelText, String message) {
super(parent, "Bug report", Dialog.ModalityType.APPLICATION_MODAL);
String text = textArea.getText();
try {
- sendReport(text);
+ Communication.sendBugReport(text);
+
// Success if we came here
JOptionPane.showMessageDialog(BugReportDialog.this,
new Object[] { "Bug report successfully sent.",
this.pack();
this.pack();
this.setLocationRelativeTo(parent);
- GUIUtil.installEscapeCloseOperation(this);
- GUIUtil.setDefaultButton(send);
+
+ GUIUtil.setDisposableDialogOptions(this, send);
}
- /**
- * Send the provided report to the OpenRocket bug report URL. If the connection
- * fails or the server does not respond with the correct response code, an
- * exception is thrown.
- *
- * @param report the report to send.
- * @throws IOException if an error occurs while connecting to the server or
- * the server responds with a wrong response code.
- */
- private void sendReport(String report) throws IOException {
- URL url = new URL(REPORT_URL);
-
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-
- connection.setConnectTimeout(REPORT_TIMEOUT);
- connection.setInstanceFollowRedirects(true);
- connection.setRequestMethod("POST");
- connection.setUseCaches(false);
- connection.setRequestProperty("X-OpenRocket-Version", Prefs.getVersion());
-
- String post;
- post = (REPORT_VERSION_PARAM + "=" + URLEncoder.encode(Prefs.getVersion(), "UTF-8")
- + "&" + REPORT_PARAM + "=" + URLEncoder.encode(report, "UTF-8"));
-
- OutputStreamWriter wr = null;
- try {
- // Send post information
- connection.setDoOutput(true);
- wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
- wr.write(post);
- wr.flush();
-
- if (connection.getResponseCode() != REPORT_RESPONSE_CODE) {
- throw new IOException("Server responded with code " +
- connection.getResponseCode() + ", expecting " + REPORT_RESPONSE_CODE);
- }
- } finally {
- if (wr != null)
- wr.close();
- connection.disconnect();
- }
- }
-
-
/**
* Open the default email client with the suitable bug report.
* Note that this does not work on some systems even if Desktop.isSupported()
this.setLocationByPlatform(true);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
- GUIUtil.installEscapeCloseOperation(this);
pack();
+
+ GUIUtil.setDisposableDialogOptions(this, null);
}
--- /dev/null
+package net.sf.openrocket.gui.dialogs;
+
+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.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.JTextField;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.util.GUIUtil;
+
+public class CustomMaterialDialog extends JDialog {
+
+ private final Material originalMaterial;
+
+ private boolean okClicked = false;
+ private JComboBox typeBox;
+ private JTextField nameField;
+ private DoubleModel density;
+ private JSpinner densitySpinner;
+ private UnitSelector densityUnit;
+ private JCheckBox addBox;
+
+ public CustomMaterialDialog(Window parent, Material material, boolean saveOption,
+ String title) {
+ this(parent, material, saveOption, title, null);
+ }
+
+
+ public CustomMaterialDialog(Window parent, Material material, boolean saveOption,
+ String title, String note) {
+ super(parent, "Custom material", Dialog.ModalityType.APPLICATION_MODAL);
+
+ this.originalMaterial = material;
+
+ JPanel panel = new JPanel(new MigLayout("fill, gap rel unrel"));
+
+
+ // Add title and note
+ if (title != null) {
+ panel.add(new JLabel("<html><b>" + title + ":"),
+ "gapleft para, span, wrap" + (note == null ? " para":""));
+ }
+ if (note != null) {
+ panel.add(new ResizeLabel(note, -1), "span, wrap para");
+ }
+
+
+ // Material name
+ panel.add(new JLabel("Material name:"));
+ nameField = new JTextField(15);
+ if (material != null) {
+ nameField.setText(material.getName());
+ }
+ panel.add(nameField, "span, growx, wrap");
+
+
+ // Material type (if not known)
+ panel.add(new JLabel("Material type:"));
+ if (material == null) {
+ typeBox = new JComboBox(Material.Type.values());
+ typeBox.setSelectedItem(Material.Type.BULK);
+ typeBox.setEditable(false);
+ typeBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateDensityModel();
+ }
+ });
+ panel.add(typeBox, "span, growx, wrap");
+ } else {
+ panel.add(new JLabel(material.getType().toString()), "span, growx, wrap");
+ }
+
+
+ // Material density
+ panel.add(new JLabel("Material density:"));
+ densitySpinner = new JSpinner();
+ panel.add(densitySpinner, "w 70lp");
+ densityUnit = new UnitSelector((DoubleModel)null);
+ panel.add(densityUnit, "w 30lp");
+ panel.add(new JPanel(), "growx, wrap");
+ updateDensityModel();
+
+
+ // Save option
+ if (saveOption) {
+ addBox = new JCheckBox("Add material to database");
+ panel.add(addBox,"span, wrap");
+ }
+
+ JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ okClicked = true;
+ CustomMaterialDialog.this.setVisible(false);
+ }
+ });
+ panel.add(okButton,"span, split, tag ok");
+
+ JButton closeButton = new JButton("Cancel");
+ closeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ okClicked = false;
+ CustomMaterialDialog.this.setVisible(false);
+ }
+ });
+ panel.add(closeButton,"tag cancel");
+
+ this.setContentPane(panel);
+ this.pack();
+ this.setLocationByPlatform(true);
+ GUIUtil.setDisposableDialogOptions(this, okButton);
+ }
+
+
+ public boolean getOkClicked() {
+ return okClicked;
+ }
+
+
+ public boolean isAddSelected() {
+ return addBox.isSelected();
+ }
+
+
+ public Material getMaterial() {
+ Material.Type type;
+ String name;
+ double density;
+
+ if (typeBox != null) {
+ type = (Material.Type) typeBox.getSelectedItem();
+ } else {
+ type = originalMaterial.getType();
+ }
+
+ name = nameField.getText().trim();
+
+ density = this.density.getValue();
+
+ return Material.newMaterial(type, name, density, true);
+ }
+
+
+ private void updateDensityModel() {
+ if (originalMaterial != null) {
+ if (density == null) {
+ density = new DoubleModel(originalMaterial.getDensity(),
+ originalMaterial.getType().getUnitGroup(), 0);
+ densitySpinner.setModel(density.getSpinnerModel());
+ densityUnit.setModel(density);
+ }
+ } else {
+ Material.Type type = (Material.Type) typeBox.getSelectedItem();
+ density = new DoubleModel(0, type.getUnitGroup(), 0);
+ densitySpinner.setModel(density.getSpinnerModel());
+ densityUnit.setModel(density);
+ }
+ }
+}
updateEnabled();
- GUIUtil.installEscapeCloseOperation(this);
- GUIUtil.setDefaultButton(close);
this.setLocationByPlatform(true);
+ GUIUtil.setDisposableDialogOptions(this, close);
// Undo description
final OpenRocketDocument document = BasicFrame.findDocument(rocket);
this.add(panel);
this.pack();
this.setLocationByPlatform(true);
- GUIUtil.installEscapeCloseOperation(this);
- GUIUtil.setDefaultButton(openButton);
+
+ GUIUtil.setDisposableDialogOptions(this, openButton);
}
this.setTitle("OpenRocket license");
this.pack();
this.setLocationRelativeTo(parent);
- GUIUtil.setDefaultButton(close);
- GUIUtil.installEscapeCloseOperation(this);
+
+ GUIUtil.setDisposableDialogOptions(this, close);
}
}
this.pack();
// this.setAlwaysOnTop(true);
- GUIUtil.setDefaultButton(okButton);
- GUIUtil.installEscapeCloseOperation(this);
this.setLocationByPlatform(true);
+ GUIUtil.setDisposableDialogOptions(this, okButton);
// Table can be scrolled only after pack() has been called
setSelectionVisible();
+++ /dev/null
-package net.sf.openrocket.gui.dialogs;
-
-import java.awt.Dialog;
-import java.awt.Window;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.AbstractListModel;
-import javax.swing.ComboBoxModel;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JTabbedPane;
-
-import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.gui.components.ResizeLabel;
-import net.sf.openrocket.unit.Unit;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.GUIUtil;
-import net.sf.openrocket.util.Prefs;
-
-public class PreferencesDialog extends JDialog {
-
- private final List<DefaultUnitSelector> unitSelectors = new ArrayList<DefaultUnitSelector>();
-
- private PreferencesDialog() {
- super((Window)null, "Preferences", Dialog.ModalityType.APPLICATION_MODAL);
-
- JPanel panel = new JPanel(new MigLayout("fill, gap unrel","[grow]","[grow][]"));
-
- JTabbedPane tabbedPane = new JTabbedPane();
- panel.add(tabbedPane,"grow, wrap");
-
-
- tabbedPane.addTab("Units", null, unitsPane(), "Default units");
- tabbedPane.addTab("Confirmation", null, confirmationPane(), "Confirmation dialog settings");
-
-
-
- JButton close = new JButton("Close");
- close.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent arg0) {
- PreferencesDialog.this.setVisible(false);
- PreferencesDialog.this.dispose();
- }
- });
- panel.add(close,"span, right, tag close");
-
- this.setContentPane(panel);
- pack();
- setAlwaysOnTop(true);
- this.setLocationRelativeTo(null);
-
- this.addWindowListener(new WindowAdapter() {
- @Override
- public void windowClosed(WindowEvent e) {
- Prefs.storeDefaultUnits();
- }
- });
-
- GUIUtil.setDefaultButton(close);
- GUIUtil.installEscapeCloseOperation(this);
- }
-
-
- private JPanel confirmationPane() {
- JPanel panel = new JPanel(new MigLayout("fill"));
-
- panel.add(new JLabel("Position to insert new body components:"));
- panel.add(new JComboBox(new PrefChoiseSelector(Prefs.BODY_COMPONENT_INSERT_POSITION_KEY,
- "Always ask", "Insert in middle", "Add to end")), "wrap para, sg combos");
-
- panel.add(new JLabel("Confirm deletion of simulations:"));
- panel.add(new JComboBox(new PrefBooleanSelector(Prefs.CONFIRM_DELETE_SIMULATION,
- "Delete", "Confirm", true)), "wrap para, sg combos");
-
- return panel;
- }
-
- private JPanel unitsPane() {
- JPanel panel = new JPanel(new MigLayout("", "[][]40lp[][]"));
- JComboBox combo;
-
- panel.add(new JLabel("Select your preferred units:"), "span, wrap paragraph");
-
-/*
- public static final UnitGroup UNITS_LENGTH;
- public static final UnitGroup UNITS_MOTOR_DIMENSIONS;
- public static final UnitGroup UNITS_DISTANCE;
-
- public static final UnitGroup UNITS_VELOCITY;
- public static final UnitGroup UNITS_ACCELERATION;
- public static final UnitGroup UNITS_MASS;
- public static final UnitGroup UNITS_FORCE;
- public static final UnitGroup UNITS_IMPULSE;
-
- public static final UnitGroup UNITS_STABILITY;
- public static final UnitGroup UNITS_FLIGHT_TIME;
- public static final UnitGroup UNITS_ROLL;
-
- public static final UnitGroup UNITS_AREA;
- public static final UnitGroup UNITS_DENSITY_LINE;
- public static final UnitGroup UNITS_DENSITY_SURFACE;
- public static final UnitGroup UNITS_DENSITY_BULK;
- public static final UnitGroup UNITS_ROUGHNESS;
-
- public static final UnitGroup UNITS_TEMPERATURE;
- public static final UnitGroup UNITS_PRESSURE;
- public static final UnitGroup UNITS_ANGLE;
-*/
-
- panel.add(new JLabel("Rocket dimensions:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_LENGTH));
- panel.add(combo, "sizegroup boxes");
-
- panel.add(new JLabel("Line density:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_LINE));
- panel.add(combo, "sizegroup boxes, wrap");
-
-
-
- panel.add(new JLabel("Motor dimensions:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MOTOR_DIMENSIONS));
- panel.add(combo, "sizegroup boxes");
-
- panel.add(new JLabel("Surface density:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_SURFACE));
- panel.add(combo, "sizegroup boxes, wrap");
-
-
-
- panel.add(new JLabel("Distance:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DISTANCE));
- panel.add(combo, "sizegroup boxes");
-
- panel.add(new JLabel("Bulk density::"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_BULK));
- panel.add(combo, "sizegroup boxes, wrap");
-
-
-
- panel.add(new JLabel("Velocity:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_VELOCITY));
- panel.add(combo, "sizegroup boxes");
-
- panel.add(new JLabel("Surface roughness:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROUGHNESS));
- panel.add(combo, "sizegroup boxes, wrap");
-
-
-
- panel.add(new JLabel("Acceleration:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ACCELERATION));
- panel.add(combo, "sizegroup boxes");
-
- panel.add(new JLabel("Area:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_AREA));
- panel.add(combo, "sizegroup boxes, wrap");
-
-
-
- panel.add(new JLabel("Mass:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MASS));
- panel.add(combo, "sizegroup boxes");
-
- panel.add(new JLabel("Angle:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ANGLE));
- panel.add(combo, "sizegroup boxes, wrap");
-
-
-
- panel.add(new JLabel("Force:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_FORCE));
- panel.add(combo, "sizegroup boxes");
-
- panel.add(new JLabel("Roll rate:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROLL));
- panel.add(combo, "sizegroup boxes, wrap");
-
-
-
- panel.add(new JLabel("Total impulse:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_IMPULSE));
- panel.add(combo, "sizegroup boxes");
-
- panel.add(new JLabel("Temperature:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_TEMPERATURE));
- panel.add(combo, "sizegroup boxes, wrap");
-
-
-
- panel.add(new JLabel("Stability:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
- panel.add(combo, "sizegroup boxes");
-
- panel.add(new JLabel("Pressure:"));
- combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_PRESSURE));
- panel.add(combo, "sizegroup boxes, wrap para");
-
-
-
- JButton button = new JButton("Default metric");
- button.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- UnitGroup.setDefaultMetricUnits();
- for (DefaultUnitSelector s: unitSelectors)
- s.fireChange();
- }
- });
- panel.add(button, "spanx, split 2, grow");
-
- button = new JButton("Default imperial");
- button.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- UnitGroup.setDefaultImperialUnits();
- for (DefaultUnitSelector s: unitSelectors)
- s.fireChange();
- }
- });
- panel.add(button, "grow, wrap para");
-
-
- panel.add(new ResizeLabel("The effects will take place the next time you open a window.",-2),
- "spanx, wrap");
-
-
- return panel;
- }
-
-
-
-
- private class DefaultUnitSelector extends AbstractListModel implements ComboBoxModel {
-
- private final UnitGroup group;
- public DefaultUnitSelector(UnitGroup group) {
- this.group = group;
- unitSelectors.add(this);
- }
-
- @Override
- public Object getSelectedItem() {
- return group.getDefaultUnit();
- }
- @Override
- public void setSelectedItem(Object item) {
- if (!(item instanceof Unit)) {
- throw new IllegalArgumentException("Illegal argument "+item);
- }
- group.setDefaultUnit(group.getUnitIndex((Unit)item));
- }
- @Override
- public Object getElementAt(int index) {
- return group.getUnit(index);
- }
- @Override
- public int getSize() {
- return group.getUnitCount();
- }
-
-
- public void fireChange() {
- this.fireContentsChanged(this, 0, this.getSize());
- }
- }
-
-
-
- private class PrefChoiseSelector extends AbstractListModel implements ComboBoxModel {
- private final String preference;
- private final String[] descriptions;
-
- public PrefChoiseSelector(String preference, String ... descriptions) {
- this.preference = preference;
- this.descriptions = descriptions;
- }
-
- @Override
- public Object getSelectedItem() {
- return descriptions[Prefs.getChoise(preference, descriptions.length, 0)];
- }
-
- @Override
- public void setSelectedItem(Object item) {
- if (!(item instanceof String)) {
- throw new IllegalArgumentException("Illegal argument "+item);
- }
- int index;
- for (index = 0; index < descriptions.length; index++) {
- if (((String)item).equalsIgnoreCase(descriptions[index]))
- break;
- }
- if (index >= descriptions.length) {
- throw new IllegalArgumentException("Illegal argument "+item);
- }
-
- Prefs.putChoise(preference, index);
- }
-
- @Override
- public Object getElementAt(int index) {
- return descriptions[index];
- }
- @Override
- public int getSize() {
- return descriptions.length;
- }
- }
-
-
- private class PrefBooleanSelector extends AbstractListModel implements ComboBoxModel {
- private final String preference;
- private final String trueDesc, falseDesc;
- private final boolean def;
-
- public PrefBooleanSelector(String preference, String falseDescription,
- String trueDescription, boolean defaultState) {
- this.preference = preference;
- this.trueDesc = trueDescription;
- this.falseDesc = falseDescription;
- this.def = defaultState;
- }
-
- @Override
- public Object getSelectedItem() {
- if (Prefs.NODE.getBoolean(preference, def)) {
- return trueDesc;
- } else {
- return falseDesc;
- }
- }
-
- @Override
- public void setSelectedItem(Object item) {
- if (!(item instanceof String)) {
- throw new IllegalArgumentException("Illegal argument "+item);
- }
-
- if (trueDesc.equals(item)) {
- Prefs.NODE.putBoolean(preference, true);
- } else if (falseDesc.equals(item)) {
- Prefs.NODE.putBoolean(preference, false);
- } else {
- throw new IllegalArgumentException("Illegal argument "+item);
- }
- }
-
- @Override
- public Object getElementAt(int index) {
- switch (index) {
- case 0:
- return def ? trueDesc : falseDesc;
-
- case 1:
- return def ? falseDesc: trueDesc;
-
- default:
- throw new IndexOutOfBoundsException("Boolean asked for index="+index);
- }
- }
- @Override
- public int getSize() {
- return 2;
- }
- }
-
-
-
- //////// Singleton implementation ////////
-
- private static PreferencesDialog dialog = null;
-
- public static void showPreferences() {
- if (dialog != null) {
- dialog.dispose();
- }
- dialog = new PreferencesDialog();
- dialog.setVisible(true);
- }
-
-
-}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.preferences;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Iterator;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.DefaultTableCellRenderer;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.database.Database;
+import net.sf.openrocket.database.Databases;
+import net.sf.openrocket.gui.adaptors.Column;
+import net.sf.openrocket.gui.adaptors.ColumnTableModel;
+import net.sf.openrocket.gui.dialogs.CustomMaterialDialog;
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.unit.Value;
+
+public class MaterialEditPanel extends JPanel {
+
+ private final JTable table;
+
+ private final JButton addButton;
+ private final JButton editButton;
+ private final JButton deleteButton;
+ private final JButton revertButton;
+
+
+ public MaterialEditPanel() {
+ super(new MigLayout("fill"));
+
+
+ // TODO: LOW: Create sorter that keeps material types always in order
+ final ColumnTableModel model = new ColumnTableModel(
+ new Column("Material") {
+ @Override
+ public Object getValueAt(int row) {
+ return getMaterial(row).getName();
+ }
+ },
+
+ new Column("Type") {
+ @Override
+ public Object getValueAt(int row) {
+ return getMaterial(row).getType().toString();
+ }
+ @Override
+ public int getDefaultWidth() {
+ return 15;
+ }
+ },
+
+ new Column("Density") {
+ @Override
+ public Object getValueAt(int row) {
+ Material m = getMaterial(row);
+ double d = m.getDensity();
+ switch (m.getType()) {
+ case LINE:
+ return UnitGroup.UNITS_DENSITY_LINE.toValue(d);
+
+ case SURFACE:
+ return UnitGroup.UNITS_DENSITY_SURFACE.toValue(d);
+
+ case BULK:
+ return UnitGroup.UNITS_DENSITY_BULK.toValue(d);
+
+ default:
+ throw new IllegalStateException("Material type " + m.getType());
+ }
+ }
+ @Override
+ public int getDefaultWidth() {
+ return 15;
+ }
+ @Override
+ public Class<?> getColumnClass() {
+ return Value.class;
+ }
+ }
+ ) {
+ @Override
+ public int getRowCount() {
+ return Databases.BULK_MATERIAL.size() + Databases.SURFACE_MATERIAL.size() +
+ Databases.LINE_MATERIAL.size();
+ }
+ };
+
+ table = new JTable(model);
+ model.setColumnWidths(table.getColumnModel());
+ table.setAutoCreateRowSorter(true);
+ table.setDefaultRenderer(Object.class, new MaterialCellRenderer());
+ this.add(new JScrollPane(table), "w 200px, h 100px, grow 100");
+
+
+
+ addButton = new JButton("New");
+ addButton.setToolTipText("Add a new material");
+ addButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ CustomMaterialDialog dialog = new CustomMaterialDialog(
+ SwingUtilities.getWindowAncestor(MaterialEditPanel.this),
+ null, false, "Add a custom material");
+ dialog.setVisible(true);
+ if (dialog.getOkClicked()) {
+ Material mat = dialog.getMaterial();
+ getDatabase(mat).add(mat);
+ model.fireTableDataChanged();
+ setButtonStates();
+ }
+ }
+ });
+ this.add(addButton, "gap rel rel para para, w 70lp, split 5, flowy, growx 1, top");
+
+
+ editButton = new JButton("Edit");
+ editButton.setToolTipText("Edit an existing material");
+ editButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int sel = table.getSelectedRow();
+ if (sel < 0)
+ return;
+ sel = table.convertRowIndexToModel(sel);
+ Material m = getMaterial(sel);
+
+ CustomMaterialDialog dialog;
+ if (m.isUserDefined()) {
+ dialog = new CustomMaterialDialog(
+ SwingUtilities.getWindowAncestor(MaterialEditPanel.this),
+ m, false, "Edit material");
+ } else {
+ dialog = new CustomMaterialDialog(
+ SwingUtilities.getWindowAncestor(MaterialEditPanel.this),
+ m, false, "Add a custom material",
+ "The built-in materials cannot be modified.");
+ }
+
+ dialog.setVisible(true);
+
+ if (dialog.getOkClicked()) {
+ if (m.isUserDefined()) {
+ getDatabase(m).remove(m);
+ }
+ Material mat = dialog.getMaterial();
+ getDatabase(mat).add(mat);
+ model.fireTableDataChanged();
+ setButtonStates();
+ }
+ }
+ });
+ this.add(editButton, "gap rel rel para para, growx 1, top");
+
+
+ deleteButton = new JButton("Delete");
+ deleteButton.setToolTipText("Delete a user-defined material");
+ deleteButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int sel = table.getSelectedRow();
+ if (sel < 0)
+ return;
+ sel = table.convertRowIndexToModel(sel);
+ Material m = getMaterial(sel);
+ if (!m.isUserDefined())
+ return;
+ getDatabase(m).remove(m);
+ model.fireTableDataChanged();
+ setButtonStates();
+ }
+ });
+ this.add(deleteButton, "gap rel rel para para, growx 1, top");
+
+
+ this.add(new JPanel(), "grow 1");
+
+ revertButton = new JButton("Revert all");
+ revertButton.setToolTipText("Delete all user-defined materials");
+ revertButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int sel = JOptionPane.showConfirmDialog(MaterialEditPanel.this,
+ "Delete all user-defined materials?", "Revert all?",
+ JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
+ if (sel == JOptionPane.YES_OPTION) {
+ Iterator<Material> iterator;
+
+ iterator = Databases.LINE_MATERIAL.iterator();
+ while (iterator.hasNext()) {
+ if (iterator.next().isUserDefined())
+ iterator.remove();
+ }
+
+ iterator = Databases.SURFACE_MATERIAL.iterator();
+ while (iterator.hasNext()) {
+ if (iterator.next().isUserDefined())
+ iterator.remove();
+ }
+
+ iterator = Databases.BULK_MATERIAL.iterator();
+ while (iterator.hasNext()) {
+ if (iterator.next().isUserDefined())
+ iterator.remove();
+ }
+ model.fireTableDataChanged();
+ setButtonStates();
+ }
+ }
+ });
+ this.add(revertButton, "gap rel rel para para, growx 1, bottom, wrap");
+
+ setButtonStates();
+ table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ setButtonStates();
+ }
+ });
+ table.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ editButton.doClick();
+ }
+ }
+ });
+
+
+ this.add(new JLabel("<html><i>Editing materials will not affect existing " +
+ "rocket designs.</i>"), "span");
+
+
+ }
+
+
+ private Database<Material> getDatabase(Material m) {
+ switch (m.getType()) {
+ case BULK:
+ return Databases.BULK_MATERIAL;
+
+ case SURFACE:
+ return Databases.SURFACE_MATERIAL;
+
+ case LINE:
+ return Databases.LINE_MATERIAL;
+
+ default:
+ throw new IllegalArgumentException("Material type invalid, m="+m);
+ }
+ }
+
+
+ private void setButtonStates() {
+ int sel = table.getSelectedRow();
+
+ // Add button always enabled
+ addButton.setEnabled(true);
+
+ // Edit button enabled if a material is selected
+ editButton.setEnabled(sel >= 0);
+
+ // Delete button enabled if a user-defined material is selected
+ if (sel >= 0) {
+ int modelRow = table.convertRowIndexToModel(sel);
+ deleteButton.setEnabled(getMaterial(modelRow).isUserDefined());
+ } else {
+ deleteButton.setEnabled(false);
+ }
+
+ // Revert button enabled if any user-defined material exists
+ boolean found = false;
+
+ for (Material m: Databases.BULK_MATERIAL) {
+ if (m.isUserDefined()) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ for (Material m: Databases.SURFACE_MATERIAL) {
+ if (m.isUserDefined()) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ for (Material m: Databases.LINE_MATERIAL) {
+ if (m.isUserDefined()) {
+ found = true;
+ break;
+ }
+ }
+ }
+ revertButton.setEnabled(found);
+
+ }
+
+ private Material getMaterial(int origRow) {
+ int row = origRow;
+ int n;
+
+ n = Databases.BULK_MATERIAL.size();
+ if (row < n) {
+ return Databases.BULK_MATERIAL.get(row);
+ }
+ row -= n;
+
+ n = Databases.SURFACE_MATERIAL.size();
+ if (row < n) {
+ return Databases.SURFACE_MATERIAL.get(row);
+ }
+ row -= n;
+
+ n = Databases.LINE_MATERIAL.size();
+ if (row < n) {
+ return Databases.LINE_MATERIAL.get(row);
+ }
+ throw new IndexOutOfBoundsException("row="+origRow+" while material count" +
+ " bulk:" + Databases.BULK_MATERIAL.size() +
+ " surface:" + Databases.SURFACE_MATERIAL.size() +
+ " line:" + Databases.LINE_MATERIAL.size());
+ }
+
+
+ private class MaterialCellRenderer extends DefaultTableCellRenderer {
+
+ /* (non-Javadoc)
+ * @see javax.swing.table.DefaultTableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
+ */
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value,
+ boolean isSelected, boolean hasFocus, int row, int column) {
+ Component c = super.getTableCellRendererComponent(table, value, isSelected,
+ hasFocus, row, column);
+ if (c instanceof JLabel) {
+ JLabel label = (JLabel)c;
+ Material m = getMaterial(row);
+
+ if (isSelected) {
+ if (m.isUserDefined())
+ label.setForeground(table.getSelectionForeground());
+ else
+ label.setForeground(Color.GRAY);
+ } else {
+ if (m.isUserDefined())
+ label.setForeground(table.getForeground());
+ else
+ label.setForeground(Color.GRAY);
+ }
+ }
+ return c;
+ }
+
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.dialogs.preferences;
+
+import java.awt.Dialog;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractListModel;
+import javax.swing.ComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Prefs;
+
+public class PreferencesDialog extends JDialog {
+
+ private final List<DefaultUnitSelector> unitSelectors = new ArrayList<DefaultUnitSelector>();
+
+ private PreferencesDialog() {
+ super((Window)null, "Preferences", Dialog.ModalityType.APPLICATION_MODAL);
+
+ JPanel panel = new JPanel(new MigLayout("fill, gap unrel","[grow]","[grow][]"));
+
+ JTabbedPane tabbedPane = new JTabbedPane();
+ panel.add(tabbedPane,"grow, wrap");
+
+
+ tabbedPane.addTab("Units", null, unitsPane(), "Default units");
+ tabbedPane.addTab("Materials", null, new MaterialEditPanel(), "Custom materials");
+ tabbedPane.addTab("Confirmation", null, confirmationPane(), "Confirmation dialog settings");
+
+
+ JButton close = new JButton("Close");
+ close.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent arg0) {
+ PreferencesDialog.this.setVisible(false);
+ PreferencesDialog.this.dispose();
+ }
+ });
+ panel.add(close,"span, right, tag close");
+
+ this.setContentPane(panel);
+ pack();
+ this.setLocationRelativeTo(null);
+
+ this.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent e) {
+ Prefs.storeDefaultUnits();
+ }
+ });
+
+ GUIUtil.setDisposableDialogOptions(this, close);
+ }
+
+
+ private JPanel confirmationPane() {
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+ panel.add(new JLabel("Position to insert new body components:"));
+ panel.add(new JComboBox(new PrefChoiseSelector(Prefs.BODY_COMPONENT_INSERT_POSITION_KEY,
+ "Always ask", "Insert in middle", "Add to end")), "wrap para, sg combos");
+
+ panel.add(new JLabel("Confirm deletion of simulations:"));
+ panel.add(new JComboBox(new PrefBooleanSelector(Prefs.CONFIRM_DELETE_SIMULATION,
+ "Delete", "Confirm", true)), "wrap para, sg combos");
+
+ return panel;
+ }
+
+
+
+ private JPanel unitsPane() {
+ JPanel panel = new JPanel(new MigLayout("", "[][]40lp[][]"));
+ JComboBox combo;
+
+ panel.add(new JLabel("Select your preferred units:"), "span, wrap paragraph");
+
+
+ panel.add(new JLabel("Rocket dimensions:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_LENGTH));
+ panel.add(combo, "sizegroup boxes");
+
+ panel.add(new JLabel("Line density:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_LINE));
+ panel.add(combo, "sizegroup boxes, wrap");
+
+
+
+ panel.add(new JLabel("Motor dimensions:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MOTOR_DIMENSIONS));
+ panel.add(combo, "sizegroup boxes");
+
+ panel.add(new JLabel("Surface density:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_SURFACE));
+ panel.add(combo, "sizegroup boxes, wrap");
+
+
+
+ panel.add(new JLabel("Distance:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DISTANCE));
+ panel.add(combo, "sizegroup boxes");
+
+ panel.add(new JLabel("Bulk density::"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_BULK));
+ panel.add(combo, "sizegroup boxes, wrap");
+
+
+
+ panel.add(new JLabel("Velocity:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_VELOCITY));
+ panel.add(combo, "sizegroup boxes");
+
+ panel.add(new JLabel("Surface roughness:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROUGHNESS));
+ panel.add(combo, "sizegroup boxes, wrap");
+
+
+
+ panel.add(new JLabel("Acceleration:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ACCELERATION));
+ panel.add(combo, "sizegroup boxes");
+
+ panel.add(new JLabel("Area:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_AREA));
+ panel.add(combo, "sizegroup boxes, wrap");
+
+
+
+ panel.add(new JLabel("Mass:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MASS));
+ panel.add(combo, "sizegroup boxes");
+
+ panel.add(new JLabel("Angle:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ANGLE));
+ panel.add(combo, "sizegroup boxes, wrap");
+
+
+
+ panel.add(new JLabel("Force:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_FORCE));
+ panel.add(combo, "sizegroup boxes");
+
+ panel.add(new JLabel("Roll rate:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROLL));
+ panel.add(combo, "sizegroup boxes, wrap");
+
+
+
+ panel.add(new JLabel("Total impulse:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_IMPULSE));
+ panel.add(combo, "sizegroup boxes");
+
+ panel.add(new JLabel("Temperature:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_TEMPERATURE));
+ panel.add(combo, "sizegroup boxes, wrap");
+
+
+
+ panel.add(new JLabel("Stability:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
+ panel.add(combo, "sizegroup boxes");
+
+ panel.add(new JLabel("Pressure:"));
+ combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_PRESSURE));
+ panel.add(combo, "sizegroup boxes, wrap para");
+
+
+
+ JButton button = new JButton("Default metric");
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ UnitGroup.setDefaultMetricUnits();
+ for (DefaultUnitSelector s: unitSelectors)
+ s.fireChange();
+ }
+ });
+ panel.add(button, "spanx, split 2, grow");
+
+ button = new JButton("Default imperial");
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ UnitGroup.setDefaultImperialUnits();
+ for (DefaultUnitSelector s: unitSelectors)
+ s.fireChange();
+ }
+ });
+ panel.add(button, "grow, wrap para");
+
+
+ panel.add(new ResizeLabel("The effects will take place the next time you open a window.",-2),
+ "spanx, wrap");
+
+
+ return panel;
+ }
+
+
+
+
+
+ private class DefaultUnitSelector extends AbstractListModel implements ComboBoxModel {
+
+ private final UnitGroup group;
+ public DefaultUnitSelector(UnitGroup group) {
+ this.group = group;
+ unitSelectors.add(this);
+ }
+
+ @Override
+ public Object getSelectedItem() {
+ return group.getDefaultUnit();
+ }
+ @Override
+ public void setSelectedItem(Object item) {
+ if (!(item instanceof Unit)) {
+ throw new IllegalArgumentException("Illegal argument "+item);
+ }
+ group.setDefaultUnit(group.getUnitIndex((Unit)item));
+ }
+ @Override
+ public Object getElementAt(int index) {
+ return group.getUnit(index);
+ }
+ @Override
+ public int getSize() {
+ return group.getUnitCount();
+ }
+
+
+ public void fireChange() {
+ this.fireContentsChanged(this, 0, this.getSize());
+ }
+ }
+
+
+
+ private class PrefChoiseSelector extends AbstractListModel implements ComboBoxModel {
+ private final String preference;
+ private final String[] descriptions;
+
+ public PrefChoiseSelector(String preference, String ... descriptions) {
+ this.preference = preference;
+ this.descriptions = descriptions;
+ }
+
+ @Override
+ public Object getSelectedItem() {
+ return descriptions[Prefs.getChoise(preference, descriptions.length, 0)];
+ }
+
+ @Override
+ public void setSelectedItem(Object item) {
+ if (!(item instanceof String)) {
+ throw new IllegalArgumentException("Illegal argument "+item);
+ }
+ int index;
+ for (index = 0; index < descriptions.length; index++) {
+ if (((String)item).equalsIgnoreCase(descriptions[index]))
+ break;
+ }
+ if (index >= descriptions.length) {
+ throw new IllegalArgumentException("Illegal argument "+item);
+ }
+
+ Prefs.putChoise(preference, index);
+ }
+
+ @Override
+ public Object getElementAt(int index) {
+ return descriptions[index];
+ }
+ @Override
+ public int getSize() {
+ return descriptions.length;
+ }
+ }
+
+
+ private class PrefBooleanSelector extends AbstractListModel implements ComboBoxModel {
+ private final String preference;
+ private final String trueDesc, falseDesc;
+ private final boolean def;
+
+ public PrefBooleanSelector(String preference, String falseDescription,
+ String trueDescription, boolean defaultState) {
+ this.preference = preference;
+ this.trueDesc = trueDescription;
+ this.falseDesc = falseDescription;
+ this.def = defaultState;
+ }
+
+ @Override
+ public Object getSelectedItem() {
+ if (Prefs.NODE.getBoolean(preference, def)) {
+ return trueDesc;
+ } else {
+ return falseDesc;
+ }
+ }
+
+ @Override
+ public void setSelectedItem(Object item) {
+ if (!(item instanceof String)) {
+ throw new IllegalArgumentException("Illegal argument "+item);
+ }
+
+ if (trueDesc.equals(item)) {
+ Prefs.NODE.putBoolean(preference, true);
+ } else if (falseDesc.equals(item)) {
+ Prefs.NODE.putBoolean(preference, false);
+ } else {
+ throw new IllegalArgumentException("Illegal argument "+item);
+ }
+ }
+
+ @Override
+ public Object getElementAt(int index) {
+ switch (index) {
+ case 0:
+ return def ? trueDesc : falseDesc;
+
+ case 1:
+ return def ? falseDesc: trueDesc;
+
+ default:
+ throw new IndexOutOfBoundsException("Boolean asked for index="+index);
+ }
+ }
+ @Override
+ public int getSize() {
+ return 2;
+ }
+ }
+
+
+
+ //////// Singleton implementation ////////
+
+ private static PreferencesDialog dialog = null;
+
+ public static void showPreferences() {
+ if (dialog != null) {
+ dialog.dispose();
+ }
+ dialog = new PreferencesDialog();
+ dialog.setVisible(true);
+ }
+
+
+}
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
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.PreferencesDialog;
import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
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.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.util.OpenFileWorker;
import net.sf.openrocket.util.Prefs;
import net.sf.openrocket.util.SaveFileWorker;
+import net.sf.openrocket.util.TestRockets;
public class BasicFrame extends JFrame {
private static final long serialVersionUID = 1L;
int selRow = tree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
if(selRow != -1) {
- if(e.getClickCount() == 2) {
+ if((e.getClickCount() == 2) && !ComponentConfigDialog.isDialogVisible()) {
// Double-click
RocketComponent c = (RocketComponent)selPath.getLastPathComponent();
ComponentConfigDialog.showDialog(BasicFrame.this,
menu.addSeparator();
+ item = new JMenuItem("Create test rocket");
+ item.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JTextField field = new JTextField();
+ 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"
+ }, "OK");
+
+ Rocket r;
+ if (sel == 0) {
+ r = new TestRockets(null).makeTestRocket();
+ } else if (sel == 1) {
+ r = new TestRockets(field.getText()).makeTestRocket();
+ } else {
+ return;
+ }
+
+ OpenRocketDocument doc = new OpenRocketDocument(r);
+ doc.setSaved(true);
+ BasicFrame frame = new BasicFrame(doc);
+ frame.setVisible(true);
+ }
+ });
+ menu.add(item);
+
+ menu.addSeparator();
+
item = new JMenuItem("Exception here");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (c == null) {
// Should not occur
- System.err.println("ERROR: Could not place new component.");
- Thread.dumpStack();
+ ExceptionHandler.handleErrorCondition("ERROR: Could not place new component.");
updateEnabled();
return;
}
// Insert at the end of the parent
return new Pair<RocketComponent,Integer>(parent, null);
default:
- System.err.println("ERROR: Bad position type: "+pos);
- Thread.dumpStack();
+ ExceptionHandler.handleErrorCondition("ERROR: Bad position type: "+pos);
return null;
}
}
sel = 2;
break;
default:
- System.err.println("ERROR: JOptionPane returned "+sel);
- Thread.dumpStack();
+ ExceptionHandler.handleErrorCondition("ERROR: JOptionPane returned "+sel);
return 0;
}
private static ImageIcon loadSmall(String file, String desc) {
URL url = ClassLoader.getSystemResource(file);
if (url==null) {
- System.err.println("ERROR: Couldn't find file: " + file);
+ ExceptionHandler.handleErrorCondition("ERROR: Couldn't find file: " + file);
return null;
}
return new ImageIcon(url, desc);
bi = ImageIO.read(url);
bi2 = ImageIO.read(url); // How the fsck can one duplicate a BufferedImage???
} catch (IOException e) {
- System.err.println("ERROR: Couldn't read file: "+file);
- e.printStackTrace();
+ ExceptionHandler.handleErrorCondition("ERROR: Couldn't read file: "+file, e);
return new ImageIcon[]{null,null};
}
return icons;
} else {
- System.err.println("ERROR: Couldn't find file: " + file);
+ ExceptionHandler.handleErrorCondition("ERROR: Couldn't find file: " + file);
return new ImageIcon[]{null,null};
}
}
listeners.remove(l);
}
+ private void fireTreeNodeChanged(RocketComponent node) {
+ TreeModelEvent e = new TreeModelEvent(this, makeTreePath(node), null, null);
+ Object[] l = listeners.toArray();
+ for (int i=0; i<l.length; i++)
+ ((TreeModelListener)l[i]).treeNodesChanged(e);
+ }
+
private void fireTreeNodesChanged() {
Object[] path = { root };
TreeModelEvent e = new TreeModelEvent(this,path);
expandAll();
}
} else if (e.isOtherChange()) {
- fireTreeNodesChanged();
+ fireTreeNodeChanged(e.getSource());
}
}
handling = false;
}
+ e.printStackTrace();
+
try {
if (handling) {
- System.err.println("Exception is currently being handled, " +
- "dumping exception instead:");
+ System.err.println("Exception is currently being handled, ignoring:");
e.printStackTrace();
return;
}
}
+ /**
+ * Handle an error condition programmatically without throwing an exception.
+ * This can be used in cases where recovery of the error is desirable.
+ *
+ * @param message the error message.
+ */
+ public static void handleErrorCondition(String message) {
+ handleErrorCondition(new InternalException(message));
+ }
+
+
+ /**
+ * Handle an error condition programmatically without throwing an exception.
+ * This can be used in cases where recovery of the error is desirable.
+ *
+ * @param message the error message.
+ * @param exception the exception that occurred.
+ */
+ public static void handleErrorCondition(String message, Exception exception) {
+ handleErrorCondition(new InternalException(message, exception));
+ }
+
+
+ /**
+ * Handle an error condition programmatically without throwing an exception.
+ * This can be used in cases where recovery of the error is desirable.
+ *
+ * @param exception the exception that occurred.
+ */
+ public static void handleErrorCondition(final Exception exception) {
+ final ExceptionHandler handler;
+
+ try {
+
+ if (instance == null) {
+ handler = new ExceptionHandler();
+ } else {
+ handler = instance;
+ }
+
+ final Thread thread = Thread.currentThread();
+
+ if (SwingUtilities.isEventDispatchThread()) {
+ handler.showDialog(thread, exception);
+ } else {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ handler.showDialog(thread, exception);
+ }
+ });
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
/**
* The actual handling routine.
*
// Normal exception, show question dialog
+ String msg = e.getClass().getSimpleName() + ": " + e.getMessage();
+ if (msg.length() > 90) {
+ msg = msg.substring(0, 90) + "...";
+ }
+
int selection = JOptionPane.showOptionDialog(null, new Object[] {
"OpenRocket encountered an uncaught exception. This typically signifies " +
- "a bug in the software.", " ",
+ "a bug in the software.",
+ "<html><em> " + msg + "</em>",
+ " ",
"Please take a moment to report this bug to the developers.",
"This can be done automatically if you have an Internet connection."
}, "Uncaught exception", JOptionPane.DEFAULT_OPTION,
*/
public static class AwtHandler {
public void handle(Throwable t) {
+
+ /*
+ * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
+ */
+ if (t instanceof ArrayIndexOutOfBoundsException) {
+ final String buggyClass = "sun.font.FontDesignMetrics";
+ StackTraceElement[] elements = t.getStackTrace();
+ if (elements.length >= 3 &&
+ (buggyClass.equals(elements[0].getClassName()) ||
+ buggyClass.equals(elements[1].getClassName()) ||
+ buggyClass.equals(elements[2].getClassName()))) {
+ System.err.println("Ignoring Sun JRE bug 6828938: " + t);
+ return;
+ }
+ }
+
+
if (instance != null) {
instance.uncaughtException(Thread.currentThread(), t);
}
}
}
+
+
+ private static class InternalException extends Exception {
+ public InternalException() {
+ super();
+ }
+
+ public InternalException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public InternalException(String message) {
+ super(message);
+ }
+
+ public InternalException(Throwable cause) {
+ super(cause);
+ }
+ }
}
this.validate();
this.pack();
this.setLocationByPlatform(true);
- GUIUtil.setDefaultButton(button);
- GUIUtil.installEscapeCloseOperation(this);
+
+ GUIUtil.setDisposableDialogOptions(this, button);
}
dialog.setLocationByPlatform(true);
dialog.pack();
- GUIUtil.installEscapeCloseOperation(dialog);
- GUIUtil.setDefaultButton(button);
+
+ GUIUtil.setDisposableDialogOptions(dialog, button);
dialog.setVisible(true);
}
this.setLocationByPlatform(true);
this.validate();
this.pack();
- GUIUtil.installEscapeCloseOperation(this);
+
+ GUIUtil.setDisposableDialogOptions(this, null);
updateProgress();
}
panel.add(chartPanel, "grow, wrap 20lp");
- final JCheckBox check = new JCheckBox("Show points");
+ final JCheckBox check = new JCheckBox("Show data points");
check.setSelected(initialShowPoints);
check.addActionListener(new ActionListener() {
@Override
this.setLocationByPlatform(true);
this.pack();
- GUIUtil.installEscapeCloseOperation(this);
- GUIUtil.setDefaultButton(button);
+
+ GUIUtil.setDisposableDialogOptions(this, button);
}
Transformation baseRotation = finset.getBaseRotationTransformation();
Transformation finRotation = finset.getFinRotationTransformation();
- Coordinate c[] = finset.getFinPoints();
-
+ Coordinate finPoints[] = finset.getFinPointsWithTab();
+
// TODO: MEDIUM: sloping radius
double radius = finset.getBodyRadius();
// Translate & rotate the coordinates
- for (int i=0; i<c.length; i++) {
- c[i] = cantRotation.transform(c[i]);
- c[i] = baseRotation.transform(c[i].add(0,radius,0));
+ for (int i=0; i<finPoints.length; i++) {
+ finPoints[i] = cantRotation.transform(finPoints[i]);
+ finPoints[i] = baseRotation.transform(finPoints[i].add(0,radius,0));
}
// Make polygon
p = new Path2D.Float();
- for (int i=0; i<c.length; i++) {
- a = transformation.transform(finset.toAbsolute(c[i])[0]);
+ for (int i=0; i<finPoints.length; i++) {
+ a = transformation.transform(finset.toAbsolute(finPoints[i])[0]);
if (i==0)
p.moveTo(a.x*S, a.y*S);
else
p.lineTo(a.x*S, a.y*S);
}
+
p.closePath();
s[fin] = p;
// Rotate fin coordinates
- for (int i=0; i<c.length; i++)
- c[i] = finRotation.transform(c[i]);
+ for (int i=0; i<finPoints.length; i++)
+ finPoints[i] = finRotation.transform(finPoints[i]);
}
return s;
import java.awt.Shape;
+import net.sf.openrocket.gui.main.ExceptionHandler;
import net.sf.openrocket.gui.scalefigure.RocketFigure;
import net.sf.openrocket.util.Transformation;
public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component,
Transformation t) {
// no-op
- System.err.println("ERROR: RocketComponent.getShapesSide called with "+component);
+ ExceptionHandler.handleErrorCondition("ERROR: RocketComponent.getShapesSide called with "
+ + component);
return new Shape[0];
}
public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component,
Transformation t) {
// no-op
- System.err.println("ERROR: RocketComponent.getShapesBack called with "+component);
+ ExceptionHandler.handleErrorCondition("ERROR: RocketComponent.getShapesBack called with "
+ +component);
return new Shape[0];
}
import java.util.LinkedHashSet;
import net.sf.openrocket.gui.figureelements.FigureElement;
+import net.sf.openrocket.gui.main.ExceptionHandler;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.MotorMount;
}
if (m == null) {
- System.err.println("ERROR: Rocket figure paint method not found for " + component);
+ ExceptionHandler.handleErrorCondition("ERROR: Rocket figure paint method not found for "
+ + component);
return new Shape[0];
}
public abstract class Material implements Comparable<Material> {
public enum Type {
- LINE,
- SURFACE,
- BULK
+ LINE("Line", UnitGroup.UNITS_DENSITY_LINE),
+ SURFACE("Surface", UnitGroup.UNITS_DENSITY_SURFACE),
+ BULK("Bulk", UnitGroup.UNITS_DENSITY_BULK);
+
+ private final String name;
+ private final UnitGroup units;
+ private Type(String name, UnitGroup units) {
+ this.name = name;
+ this.units = units;
+ }
+ public UnitGroup getUnitGroup() {
+ return units;
+ }
+ @Override
+ public String toString() {
+ return name;
+ }
}
+
+ ///// Definitions of different material types /////
+
public static class Line extends Material {
public Line(String name, double density, boolean userDefined) {
super(name, density, userDefined);
}
- @Override
- public UnitGroup getUnitGroup() {
- return UnitGroup.UNITS_DENSITY_LINE;
- }
-
@Override
public Type getType() {
return Type.LINE;
super(name, density, userDefined);
}
- @Override
- public UnitGroup getUnitGroup() {
- return UnitGroup.UNITS_DENSITY_SURFACE;
- }
-
@Override
public Type getType() {
return Type.SURFACE;
super(name, density, userDefined);
}
- @Override
- public UnitGroup getUnitGroup() {
- return UnitGroup.UNITS_DENSITY_BULK;
- }
-
@Override
public Type getType() {
return Type.BULK;
return userDefined;
}
- public abstract UnitGroup getUnitGroup();
public abstract Type getType();
@Override
public String toString() {
- return getName(getUnitGroup().getDefaultUnit());
+ return this.getName(this.getType().getUnitGroup().getDefaultUnit());
}
}
+ /**
+ * Return the source component of this event as specified in the constructor.
+ */
+ @Override
+ public RocketComponent getSource() {
+ return (RocketComponent) super.getSource();
+ }
+
+
public boolean isAerodynamicChange() {
return (type & AERODYNAMIC_CHANGE) != 0;
}
this.finish = finish;
fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
+
+
+
+ @Override
+ protected void copyFrom(RocketComponent c) {
+ super.copyFrom(c);
+
+ ExternalComponent src = (ExternalComponent)c;
+ this.finish = src.finish;
+ this.material = src.material;
+ }
}
package net.sf.openrocket.rocketcomponent;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
}
}
+ public enum TabRelativePosition {
+ FRONT("Root chord leading edge"),
+ CENTER("Root chord midpoint"),
+ END("Root chord trailing edge");
+
+ private final String name;
+ TabRelativePosition(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
/**
* Number of fins.
*/
protected CrossSection crossSection = CrossSection.SQUARE;
+ /*
+ * Fin tab properties.
+ */
+ private double tabHeight = 0;
+ private double tabLength = 0.05;
+ private double tabShift = 0;
+ private TabRelativePosition tabRelativePosition = TabRelativePosition.CENTER;
+
+
// Cached fin area & CG. Validity of both must be checked using finArea!
+ // Fin area does not include fin tabs, CG does.
private double finArea = -1;
private double finCGx = -1;
private double finCGy = -1;
super.setPositionValue(value);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
+
+
+ public double getTabHeight() {
+ return tabHeight;
+ }
+
+ public void setTabHeight(double height) {
+ height = MathUtil.max(height, 0);
+ if (MathUtil.equals(this.tabHeight, height))
+ return;
+ this.tabHeight = height;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ }
+
+ public double getTabLength() {
+ return tabLength;
+ }
+
+ public void setTabLength(double length) {
+ length = MathUtil.max(length, 0);
+ if (MathUtil.equals(this.tabLength, length))
+ return;
+ this.tabLength = length;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ }
+
+
+ public double getTabShift() {
+ return tabShift;
+ }
+
+ public void setTabShift(double shift) {
+ this.tabShift = shift;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ }
+ public TabRelativePosition getTabRelativePosition() {
+ return tabRelativePosition;
+ }
+
+ public void setTabRelativePosition(TabRelativePosition position) {
+ if (this.tabRelativePosition == position)
+ return;
+
+
+ double front = getTabFrontEdge();
+ switch (position) {
+ case FRONT:
+ this.tabShift = front;
+ break;
+
+ case CENTER:
+ this.tabShift = front + tabLength/2 - getLength()/2;
+ break;
+
+ case END:
+ this.tabShift = front + tabLength - getLength();
+ break;
+
+ default:
+ throw new IllegalArgumentException("position="+position);
+ }
+ this.tabRelativePosition = position;
+
+ fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
+ }
+
+
+ /**
+ * Return the tab front edge position from the front of the fin.
+ */
+ public double getTabFrontEdge() {
+ switch (this.tabRelativePosition) {
+ case FRONT:
+ return tabShift;
+
+ case CENTER:
+ return getLength()/2 - tabLength/2 + tabShift;
+
+ case END:
+ return getLength() - tabLength + tabShift;
+
+ default:
+ throw new IllegalStateException("tabRelativePosition="+tabRelativePosition);
+ }
+ }
+
+ /**
+ * Return the tab trailing edge position *from the front of the fin*.
+ */
+ public double getTabTrailingEdge() {
+ switch (this.tabRelativePosition) {
+ case FRONT:
+ return tabLength + tabShift;
+ case CENTER:
+ return getLength()/2 + tabLength/2 + tabShift;
+
+ case END:
+ return getLength() + tabShift;
+
+ default:
+ throw new IllegalStateException("tabRelativePosition="+tabRelativePosition);
+ }
+ }
+
+
- /////////// Calculation methods ///////////
+ /////////// Calculation methods ///////////
+
/**
- * Return the area of one side of one fin.
+ * Return the area of one side of one fin. This does NOT include the area of
+ * the fin tab.
*
* @return the area of one side of one fin.
*/
return finArea;
}
+
/**
* Return the unweighted CG of a single fin. The X-coordinate is relative to
* the root chord trailing edge and the Y-coordinate to the fin root chord.
@Override
public double getComponentVolume() {
- return fins * getFinArea() * thickness * crossSection.getRelativeVolume();
+ return fins * (getFinArea() + tabHeight*tabLength) * thickness *
+ crossSection.getRelativeVolume();
}
if (finArea < 0)
finArea = 0;
- if (finArea > 0) {
- finCGx /= finArea;
- finCGy /= finArea;
+ // Add effect of fin tabs to CG
+ double tabArea = tabLength * tabHeight;
+ if (!MathUtil.equals(tabArea, 0)) {
+
+ double x = (getTabFrontEdge() + getTabTrailingEdge())/2;
+ double y = -this.tabHeight/2;
+
+ finCGx += x*tabArea;
+ finCGy += y*tabArea;
+
+ }
+
+ if ((finArea + tabArea) > 0) {
+ finCGx /= (finArea + tabArea);
+ finCGy /= (finArea + tabArea);
} else {
finCGx = (points[0].x + points[points.length-1].x)/2;
finCGy = 0;
*/
public abstract Coordinate[] getFinPoints();
+
+ /**
+ * Return a list of coordinates defining the geometry of a single fin, including a
+ * possible fin tab. The coordinates are the XY-coordinates of points defining the
+ * shape of a single fin, where the origin is the leading root edge. This implementation
+ * calls {@link #getFinPoints()} and adds the necessary points for the fin tab.
+ * The tab coordinates will have a negative y value.
+ *
+ * @return List of XY-coordinates.
+ */
+ public Coordinate[] getFinPointsWithTab() {
+ Coordinate[] points = getFinPoints();
+
+ if (MathUtil.equals(getTabHeight(), 0) ||
+ MathUtil.equals(getTabLength(), 0))
+ return points;
+
+ double x1 = getTabFrontEdge();
+ double x2 = getTabTrailingEdge();
+ double y = -getTabHeight();
+
+ int n = points.length;
+ points = Arrays.copyOf(points, points.length+4);
+ points[n] = new Coordinate(x2, 0);
+ points[n+1] = new Coordinate(x2, y);
+ points[n+2] = new Coordinate(x1, y);
+ points[n+3] = new Coordinate(x1, 0);
+ return points;
+ }
+
+
+
/**
* Get the span of a single fin. That is, the length from the root to the tip of the fin.
* @return Span of a single fin.
this.cantRotation = src.cantRotation;
this.thickness = src.thickness;
this.crossSection = src.crossSection;
+ this.tabHeight = src.tabHeight;
+ this.tabLength = src.tabLength;
+ this.tabRelativePosition = src.tabRelativePosition;
+ this.tabShift = src.tabShift;
}
}
package net.sf.openrocket.rocketcomponent;
import java.util.ArrayList;
-import java.util.List;
import net.sf.openrocket.util.Coordinate;
public class FreeformFinSet extends FinSet {
- private final List<Coordinate> points = new ArrayList<Coordinate>();
+ private ArrayList<Coordinate> points = new ArrayList<Coordinate>();
public FreeformFinSet() {
points.add(Coordinate.NUL);
this.length = 0.05;
}
-
+
+ public FreeformFinSet(Coordinate[] finpoints) {
+ points.clear();
+ for (Coordinate c: finpoints) {
+ points.add(c);
+ }
+ this.length = points.get(points.size()-1).x - points.get(0).x;
+ }
+
+ /*
public FreeformFinSet(FinSet finset) {
Coordinate[] finpoints = finset.getFinPoints();
this.copyFrom(finset);
}
this.length = points.get(points.size()-1).x - points.get(0).x;
}
+ */
+ /**
+ * Convert an existing fin set into a freeform fin set. The specified
+ * fin set is taken out of the rocket tree (if any) and the new component
+ * inserted in its stead.
+ * <p>
+ * The specified fin set should not be used after the call!
+ *
+ * @param finset the fin set to convert.
+ * @return the new freeform fin set.
+ */
+ public static FreeformFinSet convertFinSet(FinSet finset) {
+ final RocketComponent root = finset.getRoot();
+ FreeformFinSet freeform;
+
+ try {
+ if (root instanceof Rocket) {
+ ((Rocket)root).freeze();
+ }
+
+ // Get fin set position and remove fin set
+ final RocketComponent parent = finset.getParent();
+ final int position;
+ if (parent != null) {
+ position = parent.getChildPosition(finset);
+ parent.removeChild(position);
+ } else {
+ position = -1;
+ }
+
+
+ // Create the freeform fin set
+ Coordinate[] finpoints = finset.getFinPoints();
+ freeform = new FreeformFinSet(finpoints);
+
+ // Copy component attributes
+ freeform.copyFrom(finset);
+
+ // Set name
+ final String componentTypeName = finset.getComponentName();
+ final String name = freeform.getName();
+
+ if (name.startsWith(componentTypeName)) {
+ freeform.setName(freeform.getComponentName() +
+ name.substring(componentTypeName.length()));
+ }
+
+ // Add freeform fin set to parent
+ if (parent != null) {
+ parent.addChild(freeform, position);
+ }
+
+ } finally {
+ if (root instanceof Rocket) {
+ ((Rocket)root).thaw();
+ }
+ }
+ return freeform;
+ }
+
+
/**
* Add a fin point between indices <code>index-1</code> and <code>index</code>.
return "Freeform fin set";
}
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public RocketComponent copy() {
+ RocketComponent c = super.copy();
+ ((FreeformFinSet)c).points = (ArrayList<Coordinate>) this.points.clone();
+ return c;
+ }
+
}
/**
- * Loads the RocketComponent fields from the given component. This method may
- * be called only for a Rocket component and is meant for use with the
- * undo/redo mechanism.
- *
+ * Loads the RocketComponent fields from the given component. This method is meant
+ * for in-place replacement of a component. It is used with the undo/redo
+ * mechanism and when converting a finset into a freeform fin set.
+ * This component must not have a parent, otherwise this method will fail.
+ * <p>
* The fields are copied by reference, and the supplied component must not be used
* after the call, as it is in an undefined state.
*
*/
protected void copyFrom(RocketComponent src) {
- if (!(this instanceof Rocket)) {
- throw new UnsupportedOperationException("copyFrom called for component " + this);
- }
if (this.parent != null) {
throw new UnsupportedOperationException("copyFrom called for non-root component "
+ this);
package net.sf.openrocket.rocketcomponent;
-public class TubeCoupler extends ThicknessRingComponent {
+public class TubeCoupler extends ThicknessRingComponent implements RadialParent {
public TubeCoupler() {
setOuterRadiusAutomatic(true);
return "Tube coupler";
}
+ /**
+ * Allow all InternalComponents to be added to this component.
+ */
@Override
public boolean isCompatible(Class<? extends RocketComponent> type) {
- return false;
+ return InternalComponent.class.isAssignableFrom(type);
+ }
+
+
+ @Override
+ public double getInnerRadius(double x) {
+ return getInnerRadius();
+ }
+
+
+ @Override
+ public double getOuterRadius(double x) {
+ return getOuterRadius();
}
}
+ /**
+ * Creates a new Value object with the specified value and this unit.
+ *
+ * @param value the value to set.
+ * @return a new Value object.
+ */
+ public Value toValue(double value) {
+ return new Value(value, this);
+ }
+
+
/**
* Round the value (in the current units) to a precision suitable for rough valuing
defaultUnit = n;
}
+
+
+ /**
+ * Find a unit by approximate unit name. Only letters and (ordinary) numbers are
+ * considered in the matching. This method is mainly means for testing, allowing
+ * a simple means to obtain a particular unit.
+ *
+ * @param str the unit name.
+ * @return the corresponding unit, or <code>null</code> if not found.
+ */
+ public Unit findApproximate(String str) {
+ str = str.replaceAll("\\W", "").trim();
+ for (Unit u: units) {
+ String name = u.getUnit().replaceAll("\\W", "").trim();
+ if (str.equalsIgnoreCase(name))
+ return u;
+ }
+ return null;
+ }
+
/**
* Set the default unit based on the unit name. Does nothing if the name
* does not match any of the units.
}
+
+
+
+ /**
+ * Creates a new Value object with the specified value and the default unit of this group.
+ *
+ * @param value the value to set.
+ * @return a new Value object.
+ */
+ public Value toValue(double value) {
+ return this.getDefaultUnit().toValue(value);
+ }
+
+
+
+
private static final Pattern STRING_PATTERN = Pattern.compile("^\\s*([0-9.,-]+)(.*?)$");
/**
* Converts a string into an SI value. If the string has one of the units in this
--- /dev/null
+package net.sf.openrocket.unit;
+
+/**
+ * A class representing an SI value and a unit. The toString() method yields the
+ * current value in the current units. This class may be used to encapsulate
+ * a sortable value for example for tables. The sorting is performed by the
+ * value in the current units, ignoring the unit.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class Value implements Comparable<Value> {
+
+ private double value;
+ private Unit unit;
+
+
+ /**
+ * Create a new Value object.
+ *
+ * @param value the value to set.
+ * @param unit the unit to set (<code>null</code> not allowed)
+ */
+ public Value(double value, Unit unit) {
+ if (unit == null) {
+ throw new IllegalArgumentException("unit is null");
+ }
+ this.value = value;
+ this.unit = unit;
+ }
+
+
+ /**
+ * Get the value of this object.
+ *
+ * @return the value
+ */
+ public double getValue() {
+ return value;
+ }
+
+ /**
+ * Set the value of this object.
+ *
+ * @param value the value to set
+ */
+ public void setValue(double value) {
+ this.value = value;
+ }
+
+
+ /**
+ * Get the value of this object in the current units.
+ *
+ * @return the value in the current units.
+ */
+ public double getUnitValue() {
+ return unit.toUnit(value);
+ }
+
+
+ /**
+ * Set the value of this object in the current units.
+ *
+ * @param value the value in current units.
+ */
+ public void setUnitValue(double value) {
+ this.value = unit.fromUnit(value);
+ }
+
+
+ /**
+ * Get the unit of this object.
+ *
+ * @return the unit
+ */
+ public Unit getUnit() {
+ return unit;
+ }
+
+ /**
+ * Set the value of this object.
+ *
+ * @param unit the unit to set (<code>null</code> not allowed)
+ */
+ public void setUnit(Unit unit) {
+ if (unit == null) {
+ throw new IllegalArgumentException("unit is null");
+ }
+ this.unit = unit;
+ }
+
+
+ /**
+ * Return a string formatted using the {@link Unit#toStringUnit(double)} method
+ * of the current unit. If the unit is <code>null</code> then the UNITS_NONE
+ * group is used.
+ */
+ @Override
+ public String toString() {
+ return unit.toStringUnit(value);
+ }
+
+
+
+ /**
+ * Compare this value to another value. The comparison is performed primarily by
+ * the unit text, secondarily the value in the unit values.
+ */
+ @Override
+ public int compareTo(Value o) {
+ int n = this.getUnit().getUnit().compareTo(o.getUnit().getUnit());
+ if (n != 0)
+ return n;
+
+ double us = this.getUnitValue();
+ double them = o.getUnitValue();
+
+ if (Double.isNaN(us)) {
+ if (Double.isNaN(them))
+ return 0;
+ else
+ return 1;
+ }
+ if (Double.isNaN(them))
+ return -1;
+
+ if (us < them)
+ return -1;
+ else if (us > them)
+ return 1;
+ else
+ return 0;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.util;
+
+/**
+ * Sortable storage of a pair of objects. A list of these objects can be sorted according
+ * to the first object.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ * @param <U> the first object type, according to which comparisons are performed.
+ * @param <V> the second object type.
+ */
+public class ComparablePair<U extends Comparable<U>, V> extends Pair<U, V>
+ implements Comparable<ComparablePair<U, V>>{
+
+ public ComparablePair(U u, V v) {
+ super(u, v);
+ }
+
+
+ /**
+ * Compares the first objects. If either of the objects is <code>null</code> this
+ * method throws <code>NullPointerException</code>.
+ */
+ @Override
+ public int compareTo(ComparablePair<U, V> other) {
+ return this.getU().compareTo(other.getU());
+ }
+
+}
package net.sf.openrocket.util;
import java.awt.Component;
+import java.awt.Container;
import java.awt.Image;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentListener;
+import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
+import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
import javax.swing.Action;
+import javax.swing.DefaultBoundedRangeModel;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
+import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JRootPane;
+import javax.swing.JSlider;
+import javax.swing.JSpinner;
import javax.swing.JTable;
+import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.RootPaneContainer;
+import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeListener;
import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.DefaultTreeSelectionModel;
+import javax.swing.tree.TreeNode;
+
+import net.sf.openrocket.gui.Resettable;
public class GUIUtil {
loadImage("pix/icon/icon-032.png");
loadImage("pix/icon/icon-016.png");
}
-
private static void loadImage(String file) {
InputStream is;
}
+
+ /**
+ * 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.
+ * <p>
+ * The default button must be already attached to the dialog.
+ *
+ * @param dialog the dialog.
+ * @param defaultButton the default button of the dialog, or <code>null</code>.
+ */
+ public static void setDisposableDialogOptions(JDialog dialog, JButton defaultButton) {
+ installEscapeCloseOperation(dialog);
+ setWindowIcons(dialog);
+ addModelNullingListener(dialog);
+ if (defaultButton != null) {
+ setDefaultButton(defaultButton);
+ }
+ }
+
/**
}
+
+ /**
+ * Set the OpenRocket icons to the window icons.
+ *
+ * @param window the window to set.
+ */
public static void setWindowIcons(Window window) {
window.setIconImages(images);
}
+ /**
+ * Add a listener to the provided window that will call {@link #setNullModels(Component)}
+ * on the window once it is disposed. This method may only be used on single-use
+ * windows and dialogs, that will never be shown again once closed!
+ *
+ * @param window the window to add the listener to.
+ */
+ public static void addModelNullingListener(final Window window) {
+ window.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent e) {
+ setNullModels(window);
+ }
+ });
+ }
+
+
+ /**
+ * Traverses recursively the component tree, and sets all applicable component
+ * models to null, so as to remove the listener connections. After calling this
+ * method the component hierarchy should no longed be used.
+ * <p>
+ * All components that use custom models should be added to this method, as
+ * there exists no standard way of removing the model from a component.
+ *
+ * @param c the component (<code>null</code> is ok)
+ */
+ public static void setNullModels(Component c) {
+ if (c==null)
+ return;
+
+ // Remove various listeners
+ for (ComponentListener l: c.getComponentListeners()) {
+ c.removeComponentListener(l);
+ }
+ for (FocusListener l: c.getFocusListeners()) {
+ c.removeFocusListener(l);
+ }
+ for (MouseListener l: c.getMouseListeners()) {
+ c.removeMouseListener(l);
+ }
+ for (PropertyChangeListener l: c.getPropertyChangeListeners()) {
+ c.removePropertyChangeListener(l);
+ }
+ for (PropertyChangeListener l: c.getPropertyChangeListeners("model")) {
+ c.removePropertyChangeListener("model", l);
+ }
+ for (PropertyChangeListener l: c.getPropertyChangeListeners("action")) {
+ c.removePropertyChangeListener("action", l);
+ }
+
+ // Remove models for known components
+ // Why the FSCK must this be so hard?!?!?
+
+ if (c instanceof JSpinner) {
+
+ JSpinner spinner = (JSpinner)c;
+ for (ChangeListener l: spinner.getChangeListeners()) {
+ spinner.removeChangeListener(l);
+ }
+ spinner.setModel(new SpinnerNumberModel());
+
+ } else if (c instanceof JSlider) {
+
+ JSlider slider = (JSlider)c;
+ for (ChangeListener l: slider.getChangeListeners()) {
+ slider.removeChangeListener(l);
+ }
+ slider.setModel(new DefaultBoundedRangeModel());
+
+ } else if (c instanceof JComboBox) {
+
+ JComboBox combo = (JComboBox)c;
+ for (ActionListener l: combo.getActionListeners()) {
+ combo.removeActionListener(l);
+ }
+ combo.setModel(new DefaultComboBoxModel());
+
+ } else if (c instanceof AbstractButton) {
+
+ AbstractButton button = (AbstractButton)c;
+ for (ActionListener l: button.getActionListeners()) {
+ button.removeActionListener(l);
+ }
+ button.setAction(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) { }
+ });
+
+ } else if (c instanceof JTable) {
+
+ JTable table = (JTable)c;
+ table.setModel(new DefaultTableModel());
+ table.setColumnModel(new DefaultTableColumnModel());
+ table.setSelectionModel(new DefaultListSelectionModel());
+
+ } else if (c instanceof JTree) {
+
+ JTree tree = (JTree)c;
+ tree.setModel(new DefaultTreeModel(new TreeNode() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public Enumeration children() {
+ return new Vector().elements();
+ }
+ @Override
+ public boolean getAllowsChildren() {
+ return false;
+ }
+ @Override
+ public TreeNode getChildAt(int childIndex) {
+ return null;
+ }
+ @Override
+ public int getChildCount() {
+ return 0;
+ }
+ @Override
+ public int getIndex(TreeNode node) {
+ return 0;
+ }
+ @Override
+ public TreeNode getParent() {
+ return null;
+ }
+ @Override
+ public boolean isLeaf() {
+ return true;
+ }
+ }));
+ tree.setSelectionModel(new DefaultTreeSelectionModel());
+
+ } else if (c instanceof Resettable) {
+
+ ((Resettable)c).resetModel();
+
+ }
+
+ // Recurse the component
+ if (c instanceof Container) {
+ Component[] cs = ((Container)c).getComponents();
+ for (Component sub: cs)
+ setNullModels(sub);
+ }
+
+ }
+
+
+
/**
* A mouse listener that toggles the state of a boolean value in a table model
}
}
-
+
}
import javax.swing.ImageIcon;
import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.main.ExceptionHandler;
public class Icons {
+ /**
+ * Load an ImageIcon from the specified file. The file is obtained as a system
+ * resource from the normal classpath. If the file cannot be loaded a bug dialog
+ * is opened and <code>null</code> is returned.
+ *
+ * @param file the file to load.
+ * @param name the description of the icon.
+ * @return the ImageIcon, or null if could not be loaded (after the user closes the dialog)
+ */
public static ImageIcon loadImageIcon(String file, String name) {
URL url = ClassLoader.getSystemResource(file);
if (url == null) {
- System.err.println("Resource "+file+" not found! Ignoring...");
+ ExceptionHandler.handleErrorCondition("Image file " + file + " not found, ignoring.");
return null;
}
return new ImageIcon(url, name);
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.file.RocketLoader;
+import net.sf.openrocket.gui.main.ExceptionHandler;
/**
try {
is.close();
} catch (Exception e) {
- System.err.println("Error closing file: ");
- e.printStackTrace();
+ ExceptionHandler.handleErrorCondition("Error closing file", e);
}
}
}
package net.sf.openrocket.util;
+/**
+ * Storage for a pair of objects.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ * @param <U> the first object type.
+ * @param <V> the second object type.
+ */
public class Pair<U,V> {
private final U u;
import net.sf.openrocket.database.Databases;
import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.main.ExceptionHandler;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.rocketcomponent.BodyComponent;
import net.sf.openrocket.rocketcomponent.FinSet;
InputStream is = ClassLoader.getSystemResourceAsStream("build.properties");
if (is == null) {
throw new MissingResourceException(
- "build.properties not found, distribution built wrong",
+ "build.properties not found, distribution built wrong" +
+ " path:"+System.getProperty("java.class.path"),
"build.properties", "build.version");
}
}
+ public static String getUniqueID() {
+ String id = PREFNODE.get("id", null);
+ if (id == null) {
+ id = UniqueID.generateHashedID();
+ PREFNODE.put("id", id);
+ }
+ return id;
+ }
+
+
public static void storeVersion() {
PREFNODE.put("OpenRocketVersion", getVersion());
}
} catch (BackingStoreException e) {
- System.err.println("BackingStoreException:");
- e.printStackTrace();
+ ExceptionHandler.handleErrorCondition(e);
}
}
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.file.CSVExport;
import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
+import net.sf.openrocket.gui.main.ExceptionHandler;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataBranch.Type;
import net.sf.openrocket.unit.Unit;
try {
os.close();
} catch (Exception e) {
- System.err.println("Error closing file: ");
- e.printStackTrace();
+ ExceptionHandler.handleErrorCondition("Error closing file", e);
}
}
return null;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.file.RocketSaver;
+import net.sf.openrocket.gui.main.ExceptionHandler;
public class SaveFileWorker extends SwingWorker<Void, Void> {
try {
os.close();
} catch (Exception e) {
- System.err.println("Error closing file: ");
- e.printStackTrace();
+ ExceptionHandler.handleErrorCondition("Error closing file", e);
}
}
return null;
+++ /dev/null
-package net.sf.openrocket.util;
-
-import net.sf.openrocket.database.Databases;
-import net.sf.openrocket.material.Material;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.Bulkhead;
-import net.sf.openrocket.rocketcomponent.CenteringRing;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.MassComponent;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.Stage;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
-import net.sf.openrocket.rocketcomponent.TubeCoupler;
-import net.sf.openrocket.rocketcomponent.FinSet.CrossSection;
-import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
-
-public class Test {
-
- public static double noseconeLength=0.10,noseconeRadius=0.01;
- public static double bodytubeLength=0.20,bodytubeRadius=0.01,bodytubeThickness=0.001;
-
- public static int finCount=3;
- public static double finRootChord=0.04,finTipChord=0.05,finSweep=0.01,finThickness=0.003, finHeight=0.03;
-
- public static double materialDensity=1000; // kg/m3
-
-
- public static Rocket makeRocket() {
- Rocket rocket;
- Stage stage,stage2;
- NoseCone nosecone;
- BodyTube bodytube, bt2;
- Transition transition;
- TrapezoidFinSet finset;
-
- rocket = new Rocket();
- stage = new Stage();
- stage.setName("Stage1");
- stage2 = new Stage();
- stage2.setName("Stage2");
- nosecone = new NoseCone(Transition.Shape.ELLIPSOID,noseconeLength,noseconeRadius);
- bodytube = new BodyTube(bodytubeLength,bodytubeRadius,bodytubeThickness);
- transition = new Transition();
- bt2 = new BodyTube(bodytubeLength,bodytubeRadius*2,bodytubeThickness);
- bt2.setMotorMount(true);
-
- finset = new TrapezoidFinSet(finCount,finRootChord,finTipChord,finSweep,finHeight);
-
-
- // Stage construction
- rocket.addChild(stage);
- rocket.addChild(stage2);
-
-
- // Component construction
- stage.addChild(nosecone);
-
- stage.addChild(bodytube);
-
-
- stage2.addChild(transition);
-
- stage2.addChild(bt2);
-
- bodytube.addChild(finset);
-
-
- rocket.getDefaultConfiguration().setAllStages();
-
- return rocket;
- }
-
-
- public static Rocket makeSmallFlyable() {
- Rocket rocket;
- Stage stage;
- NoseCone nosecone;
- BodyTube bodytube;
- TrapezoidFinSet finset;
-
- 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);
-
-
- // Stage construction
- rocket.addChild(stage);
-
-
- // Component construction
- stage.addChild(nosecone);
- stage.addChild(bodytube);
-
- bodytube.addChild(finset);
-
- Material material = Prefs.getDefaultComponentMaterial(null, Material.Type.BULK);
- 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("B4")) {
- bodytube.setMotor(id, m);
- break;
- }
- }
- bodytube.setMotorOverhang(0.005);
- rocket.getDefaultConfiguration().setMotorConfigurationID(id);
-
- rocket.getDefaultConfiguration().setAllStages();
-
-
- return rocket;
- }
-
-
- public static Rocket makeBigBlue() {
- Rocket rocket;
- Stage stage;
- NoseCone nosecone;
- BodyTube bodytube;
- FreeformFinSet finset;
- MassComponent mcomp;
-
- rocket = new Rocket();
- stage = new Stage();
- stage.setName("Stage1");
-
- nosecone = new NoseCone(Transition.Shape.ELLIPSOID,0.105,0.033);
- nosecone.setThickness(0.001);
- bodytube = new BodyTube(0.69,0.033,0.001);
-
- finset = new FreeformFinSet();
- try {
- finset.setPoints(new Coordinate[] {
- new Coordinate(0, 0),
- new Coordinate(0.115, 0.072),
- new Coordinate(0.255, 0.072),
- new Coordinate(0.255, 0.037),
- new Coordinate(0.150, 0)
- });
- } catch (IllegalFinPointException e) {
- e.printStackTrace();
- }
- finset.setThickness(0.003);
- finset.setFinCount(4);
-
- 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.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);
-
- String id = rocket.newMotorConfigurationID();
- bodytube.setMotorMount(true);
-
- for (Motor m: Databases.MOTOR) {
- if (m.getDesignation().equals("F12J")) {
- bodytube.setMotor(id, m);
- break;
- }
- }
- bodytube.setMotorOverhang(0.005);
- rocket.getDefaultConfiguration().setMotorConfigurationID(id);
-
- rocket.getDefaultConfiguration().setAllStages();
-
-
- return rocket;
- }
-
-
-
- public static Rocket makeIsoHaisu() {
- Rocket rocket;
- Stage stage;
- NoseCone nosecone;
- BodyTube tube1, tube2, tube3;
- TrapezoidFinSet finset;
- TrapezoidFinSet auxfinset;
- MassComponent mcomp;
-
- final double R = 0.07;
-
- rocket = new Rocket();
- stage = new Stage();
- stage.setName("Stage1");
-
- 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.setMassOverridden(true);
- tube1.setOverrideMass(0.366);
- stage.addChild(tube1);
-
- 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.setMassOverridden(true);
- tube3.setOverrideMass(0.730);
- stage.addChild(tube3);
-
-
- LaunchLug lug = new LaunchLug();
- tube1.addChild(lug);
-
- TubeCoupler coupler = new TubeCoupler();
- coupler.setOuterRadiusAutomatic(true);
- coupler.setThickness(0.005);
- coupler.setLength(0.28);
- coupler.setMassOverridden(true);
- coupler.setOverrideMass(0.360);
- coupler.setRelativePosition(Position.BOTTOM);
- coupler.setPositionValue(-0.14);
- tube1.addChild(coupler);
-
-
- // Parachute
- MassComponent mass = new MassComponent(0.05, 0.05, 0.280);
- mass.setRelativePosition(Position.TOP);
- mass.setPositionValue(0.2);
- tube1.addChild(mass);
-
- // Cord
- mass = new MassComponent(0.05, 0.05, 0.125);
- mass.setRelativePosition(Position.TOP);
- mass.setPositionValue(0.2);
- tube1.addChild(mass);
-
- // Payload
- mass = new MassComponent(0.40, R, 1.500);
- mass.setRelativePosition(Position.TOP);
- mass.setPositionValue(0.25);
- tube1.addChild(mass);
-
-
- auxfinset = new TrapezoidFinSet();
- auxfinset.setName("CONTROL");
- auxfinset.setFinCount(2);
- auxfinset.setRootChord(0.05);
- auxfinset.setTipChord(0.05);
- auxfinset.setHeight(0.10);
- auxfinset.setSweep(0);
- auxfinset.setThickness(0.008);
- auxfinset.setCrossSection(CrossSection.AIRFOIL);
- auxfinset.setRelativePosition(Position.TOP);
- auxfinset.setPositionValue(0.28);
- auxfinset.setBaseRotation(Math.PI/2);
- tube1.addChild(auxfinset);
-
-
-
-
- coupler = new TubeCoupler();
- coupler.setOuterRadiusAutomatic(true);
- coupler.setLength(0.28);
- coupler.setRelativePosition(Position.TOP);
- coupler.setPositionValue(0.47);
- coupler.setMassOverridden(true);
- coupler.setOverrideMass(0.360);
- tube2.addChild(coupler);
-
-
-
- // Parachute
- mass = new MassComponent(0.1, 0.05, 0.028);
- mass.setRelativePosition(Position.TOP);
- mass.setPositionValue(0.14);
- tube2.addChild(mass);
-
- Bulkhead bulk = new Bulkhead();
- bulk.setOuterRadiusAutomatic(true);
- bulk.setMassOverridden(true);
- bulk.setOverrideMass(0.050);
- bulk.setRelativePosition(Position.TOP);
- bulk.setPositionValue(0.27);
- tube2.addChild(bulk);
-
- // Chord
- mass = new MassComponent(0.1, 0.05, 0.125);
- mass.setRelativePosition(Position.TOP);
- mass.setPositionValue(0.19);
- tube2.addChild(mass);
-
-
-
- InnerTube inner = new InnerTube();
- inner.setOuterRadius(0.08/2);
- inner.setInnerRadius(0.0762/2);
- inner.setLength(0.86);
- inner.setMassOverridden(true);
- inner.setOverrideMass(0.388);
- tube3.addChild(inner);
-
-
- CenteringRing center = new CenteringRing();
- center.setInnerRadiusAutomatic(true);
- center.setOuterRadiusAutomatic(true);
- center.setLength(0.005);
- center.setMassOverridden(true);
- center.setOverrideMass(0.038);
- center.setRelativePosition(Position.BOTTOM);
- center.setPositionValue(0);
- tube3.addChild(center);
-
-
- center = new CenteringRing();
- center.setInnerRadiusAutomatic(true);
- center.setOuterRadiusAutomatic(true);
- center.setLength(0.005);
- center.setMassOverridden(true);
- center.setOverrideMass(0.038);
- center.setRelativePosition(Position.TOP);
- center.setPositionValue(0.28);
- tube3.addChild(center);
-
-
- center = new CenteringRing();
- center.setInnerRadiusAutomatic(true);
- center.setOuterRadiusAutomatic(true);
- center.setLength(0.005);
- center.setMassOverridden(true);
- center.setOverrideMass(0.038);
- center.setRelativePosition(Position.TOP);
- center.setPositionValue(0.83);
- tube3.addChild(center);
-
-
-
-
-
- finset = new TrapezoidFinSet();
- finset.setRootChord(0.495);
- finset.setTipChord(0.1);
- finset.setHeight(0.185);
- finset.setThickness(0.005);
- finset.setSweep(0.3);
- finset.setRelativePosition(Position.BOTTOM);
- finset.setPositionValue(-0.03);
- 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));
-
-
- // 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;
- }
- }
- tube3.setMotorOverhang(0.02);
- rocket.getDefaultConfiguration().setMotorConfigurationID(id);
-
-// tube3.setIgnitionEvent(MotorMount.IgnitionEvent.NEVER);
-
- rocket.getDefaultConfiguration().setAllStages();
-
-
- return rocket;
- }
-
-
-
-}
--- /dev/null
+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;
+import net.sf.openrocket.rocketcomponent.BodyTube;
+import net.sf.openrocket.rocketcomponent.Bulkhead;
+import net.sf.openrocket.rocketcomponent.CenteringRing;
+import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.FreeformFinSet;
+import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
+import net.sf.openrocket.rocketcomponent.InnerTube;
+import net.sf.openrocket.rocketcomponent.InternalComponent;
+import net.sf.openrocket.rocketcomponent.LaunchLug;
+import net.sf.openrocket.rocketcomponent.MassComponent;
+import net.sf.openrocket.rocketcomponent.NoseCone;
+import net.sf.openrocket.rocketcomponent.ReferenceType;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Stage;
+import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
+import net.sf.openrocket.rocketcomponent.TubeCoupler;
+import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
+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;
+
+public class TestRockets {
+
+ private final String key;
+ private final Random rnd;
+
+
+ public TestRockets(String key) {
+
+ if (key == null) {
+ Random rnd = new Random();
+ StringBuilder sb = new StringBuilder();
+ for (int i=0; i<6; i++) {
+ int n = rnd.nextInt(62);
+ if (n < 10) {
+ sb.append((char)('0'+n));
+ } else if (n < 36) {
+ sb.append((char)('A'+n-10));
+ } else {
+ sb.append((char)('a'+n-36));
+ }
+ }
+ key = sb.toString();
+ }
+
+ this.key = key;
+ this.rnd = new Random(key.hashCode());
+
+ }
+
+
+ /**
+ * Create a new test rocket based on the value 'key'. The rocket utilizes most of the
+ * properties and features available. The same key always returns the same rocket,
+ * but different key values produce slightly different rockets. A key value of
+ * <code>null</code> generates a rocket using a random key.
+ * <p>
+ * The rocket created by this method is not fly-worthy. It is also NOT guaranteed
+ * that later versions would produce exactly the same rocket!
+ *
+ * @return a rocket design.
+ */
+ public Rocket makeTestRocket() {
+
+ Rocket rocket = new Rocket();
+ setBasics(rocket);
+ rocket.setCustomReferenceLength(rnd(0.05));
+ rocket.setDesigner("Designer " + key);
+ rocket.setReferenceType((ReferenceType) randomEnum(ReferenceType.class));
+ rocket.setRevision("Rocket revision " + key);
+ rocket.setName(key);
+
+
+ Stage stage = new Stage();
+ setBasics(stage);
+ rocket.addChild(stage);
+
+
+ NoseCone nose = new NoseCone();
+ setBasics(stage);
+ nose.setAftRadius(rnd(0.03));
+ nose.setAftRadiusAutomatic(rnd.nextBoolean());
+ nose.setAftShoulderCapped(rnd.nextBoolean());
+ nose.setAftShoulderLength(rnd(0.02));
+ nose.setAftShoulderRadius(rnd(0.02));
+ nose.setAftShoulderThickness(rnd(0.002));
+ nose.setClipped(rnd.nextBoolean());
+ nose.setThickness(rnd(0.002));
+ nose.setFilled(rnd.nextBoolean());
+ nose.setForeRadius(rnd(0.1)); // Unset
+ nose.setLength(rnd(0.15));
+ nose.setShapeParameter(rnd(0.5));
+ nose.setType((Shape) randomEnum(Shape.class));
+ stage.addChild(nose);
+
+
+ Transition shoulder = new Transition();
+ setBasics(shoulder);
+ shoulder.setAftRadius(rnd(0.06));
+ shoulder.setAftRadiusAutomatic(rnd.nextBoolean());
+ shoulder.setAftShoulderCapped(rnd.nextBoolean());
+ shoulder.setAftShoulderLength(rnd(0.02));
+ shoulder.setAftShoulderRadius(rnd(0.05));
+ shoulder.setAftShoulderThickness(rnd(0.002));
+ shoulder.setClipped(rnd.nextBoolean());
+ shoulder.setThickness(rnd(0.002));
+ shoulder.setFilled(rnd.nextBoolean());
+ shoulder.setForeRadius(rnd(0.03));
+ shoulder.setForeRadiusAutomatic(rnd.nextBoolean());
+ shoulder.setForeShoulderCapped(rnd.nextBoolean());
+ shoulder.setForeShoulderLength(rnd(0.02));
+ shoulder.setForeShoulderRadius(rnd(0.02));
+ shoulder.setForeShoulderThickness(rnd(0.002));
+ shoulder.setLength(rnd(0.15));
+ shoulder.setShapeParameter(rnd(0.5));
+ shoulder.setThickness(rnd(0.003));
+ 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.setIgnitionEvent((IgnitionEvent) randomEnum(IgnitionEvent.class));
+ body.setLength(rnd(0.3));
+ body.setMotorMount(rnd.nextBoolean());
+ 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));
+ boattail.setAftRadiusAutomatic(rnd.nextBoolean());
+ boattail.setAftShoulderCapped(rnd.nextBoolean());
+ boattail.setAftShoulderLength(rnd(0.02));
+ boattail.setAftShoulderRadius(rnd(0.02));
+ boattail.setAftShoulderThickness(rnd(0.002));
+ boattail.setClipped(rnd.nextBoolean());
+ boattail.setThickness(rnd(0.002));
+ boattail.setFilled(rnd.nextBoolean());
+ boattail.setForeRadius(rnd(0.06));
+ boattail.setForeRadiusAutomatic(rnd.nextBoolean());
+ boattail.setForeShoulderCapped(rnd.nextBoolean());
+ boattail.setForeShoulderLength(rnd(0.02));
+ boattail.setForeShoulderRadius(rnd(0.05));
+ boattail.setForeShoulderThickness(rnd(0.002));
+ boattail.setLength(rnd(0.15));
+ boattail.setShapeParameter(rnd(0.5));
+ boattail.setThickness(rnd(0.003));
+ boattail.setType((Shape) randomEnum(Shape.class));
+ stage.addChild(boattail);
+
+
+ MassComponent mass = new MassComponent();
+ setBasics(mass);
+ mass.setComponentMass(rnd(0.05));
+ mass.setLength(rnd(0.05));
+ mass.setRadialDirection(rnd(100));
+ mass.setRadialPosition(rnd(0.02));
+ mass.setRadius(rnd(0.05));
+ nose.addChild(mass);
+
+
+
+
+ return rocket;
+ }
+
+
+ private void setBasics(RocketComponent c) {
+ c.setComment(c.getComponentName() + " comment " + key);
+ c.setName(c.getComponentName() + " name " + key);
+
+ c.setCGOverridden(rnd.nextBoolean());
+ c.setMassOverridden(rnd.nextBoolean());
+ c.setOverrideCGX(rnd(0.2));
+ c.setOverrideMass(rnd(0.05));
+ c.setOverrideSubcomponents(rnd.nextBoolean());
+
+ if (c.isMassive()) {
+ // Only massive components are drawn
+ c.setColor(randomColor());
+ c.setLineStyle((LineStyle) randomEnum(LineStyle.class));
+ }
+
+ if (c instanceof ExternalComponent) {
+ 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()));
+ }
+
+ if (c instanceof InternalComponent) {
+ 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;
+ }
+
+ private Color randomColor() {
+ return new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
+ }
+
+ private <T extends Enum<T>> Enum<T> randomEnum(Class<T> c) {
+ Enum<T>[] values = c.getEnumConstants();
+ if (values.length == 0)
+ return null;
+
+ 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;
+
+
+ Rocket rocket;
+ Stage stage;
+ NoseCone nosecone;
+ BodyTube bodytube;
+ TrapezoidFinSet finset;
+
+ 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);
+
+
+ // Stage construction
+ rocket.addChild(stage);
+
+
+ // Component construction
+ stage.addChild(nosecone);
+ stage.addChild(bodytube);
+
+ bodytube.addChild(finset);
+
+ Material material = Prefs.getDefaultComponentMaterial(null, Material.Type.BULK);
+ 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("B4")) {
+ bodytube.setMotor(id, m);
+ break;
+ }
+ }
+ bodytube.setMotorOverhang(0.005);
+ rocket.getDefaultConfiguration().setMotorConfigurationID(id);
+
+ rocket.getDefaultConfiguration().setAllStages();
+
+
+ return rocket;
+ }
+
+
+ public static Rocket makeBigBlue() {
+ Rocket rocket;
+ Stage stage;
+ NoseCone nosecone;
+ BodyTube bodytube;
+ FreeformFinSet finset;
+ MassComponent mcomp;
+
+ rocket = new Rocket();
+ stage = new Stage();
+ stage.setName("Stage1");
+
+ nosecone = new NoseCone(Transition.Shape.ELLIPSOID,0.105,0.033);
+ nosecone.setThickness(0.001);
+ bodytube = new BodyTube(0.69,0.033,0.001);
+
+ finset = new FreeformFinSet();
+ try {
+ finset.setPoints(new Coordinate[] {
+ new Coordinate(0, 0),
+ new Coordinate(0.115, 0.072),
+ new Coordinate(0.255, 0.072),
+ new Coordinate(0.255, 0.037),
+ new Coordinate(0.150, 0)
+ });
+ } catch (IllegalFinPointException e) {
+ e.printStackTrace();
+ }
+ finset.setThickness(0.003);
+ finset.setFinCount(4);
+
+ 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.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);
+
+ String id = rocket.newMotorConfigurationID();
+ bodytube.setMotorMount(true);
+
+ for (Motor m: Databases.MOTOR) {
+ if (m.getDesignation().equals("F12J")) {
+ bodytube.setMotor(id, m);
+ break;
+ }
+ }
+ bodytube.setMotorOverhang(0.005);
+ rocket.getDefaultConfiguration().setMotorConfigurationID(id);
+
+ rocket.getDefaultConfiguration().setAllStages();
+
+
+ return rocket;
+ }
+
+
+
+ public static Rocket makeIsoHaisu() {
+ Rocket rocket;
+ Stage stage;
+ NoseCone nosecone;
+ BodyTube tube1, tube2, tube3;
+ TrapezoidFinSet finset;
+ TrapezoidFinSet auxfinset;
+ MassComponent mcomp;
+
+ final double R = 0.07;
+
+ rocket = new Rocket();
+ stage = new Stage();
+ stage.setName("Stage1");
+
+ 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.setMassOverridden(true);
+ tube1.setOverrideMass(0.366);
+ stage.addChild(tube1);
+
+ 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.setMassOverridden(true);
+ tube3.setOverrideMass(0.730);
+ stage.addChild(tube3);
+
+
+ LaunchLug lug = new LaunchLug();
+ tube1.addChild(lug);
+
+ TubeCoupler coupler = new TubeCoupler();
+ coupler.setOuterRadiusAutomatic(true);
+ coupler.setThickness(0.005);
+ coupler.setLength(0.28);
+ coupler.setMassOverridden(true);
+ coupler.setOverrideMass(0.360);
+ coupler.setRelativePosition(Position.BOTTOM);
+ coupler.setPositionValue(-0.14);
+ tube1.addChild(coupler);
+
+
+ // Parachute
+ MassComponent mass = new MassComponent(0.05, 0.05, 0.280);
+ mass.setRelativePosition(Position.TOP);
+ mass.setPositionValue(0.2);
+ tube1.addChild(mass);
+
+ // Cord
+ mass = new MassComponent(0.05, 0.05, 0.125);
+ mass.setRelativePosition(Position.TOP);
+ mass.setPositionValue(0.2);
+ tube1.addChild(mass);
+
+ // Payload
+ mass = new MassComponent(0.40, R, 1.500);
+ mass.setRelativePosition(Position.TOP);
+ mass.setPositionValue(0.25);
+ tube1.addChild(mass);
+
+
+ auxfinset = new TrapezoidFinSet();
+ auxfinset.setName("CONTROL");
+ auxfinset.setFinCount(2);
+ auxfinset.setRootChord(0.05);
+ auxfinset.setTipChord(0.05);
+ auxfinset.setHeight(0.10);
+ auxfinset.setSweep(0);
+ auxfinset.setThickness(0.008);
+ auxfinset.setCrossSection(CrossSection.AIRFOIL);
+ auxfinset.setRelativePosition(Position.TOP);
+ auxfinset.setPositionValue(0.28);
+ auxfinset.setBaseRotation(Math.PI/2);
+ tube1.addChild(auxfinset);
+
+
+
+
+ coupler = new TubeCoupler();
+ coupler.setOuterRadiusAutomatic(true);
+ coupler.setLength(0.28);
+ coupler.setRelativePosition(Position.TOP);
+ coupler.setPositionValue(0.47);
+ coupler.setMassOverridden(true);
+ coupler.setOverrideMass(0.360);
+ tube2.addChild(coupler);
+
+
+
+ // Parachute
+ mass = new MassComponent(0.1, 0.05, 0.028);
+ mass.setRelativePosition(Position.TOP);
+ mass.setPositionValue(0.14);
+ tube2.addChild(mass);
+
+ Bulkhead bulk = new Bulkhead();
+ bulk.setOuterRadiusAutomatic(true);
+ bulk.setMassOverridden(true);
+ bulk.setOverrideMass(0.050);
+ bulk.setRelativePosition(Position.TOP);
+ bulk.setPositionValue(0.27);
+ tube2.addChild(bulk);
+
+ // Chord
+ mass = new MassComponent(0.1, 0.05, 0.125);
+ mass.setRelativePosition(Position.TOP);
+ mass.setPositionValue(0.19);
+ tube2.addChild(mass);
+
+
+
+ InnerTube inner = new InnerTube();
+ inner.setOuterRadius(0.08/2);
+ inner.setInnerRadius(0.0762/2);
+ inner.setLength(0.86);
+ inner.setMassOverridden(true);
+ inner.setOverrideMass(0.388);
+ tube3.addChild(inner);
+
+
+ CenteringRing center = new CenteringRing();
+ center.setInnerRadiusAutomatic(true);
+ center.setOuterRadiusAutomatic(true);
+ center.setLength(0.005);
+ center.setMassOverridden(true);
+ center.setOverrideMass(0.038);
+ center.setRelativePosition(Position.BOTTOM);
+ center.setPositionValue(0);
+ tube3.addChild(center);
+
+
+ center = new CenteringRing();
+ center.setInnerRadiusAutomatic(true);
+ center.setOuterRadiusAutomatic(true);
+ center.setLength(0.005);
+ center.setMassOverridden(true);
+ center.setOverrideMass(0.038);
+ center.setRelativePosition(Position.TOP);
+ center.setPositionValue(0.28);
+ tube3.addChild(center);
+
+
+ center = new CenteringRing();
+ center.setInnerRadiusAutomatic(true);
+ center.setOuterRadiusAutomatic(true);
+ center.setLength(0.005);
+ center.setMassOverridden(true);
+ center.setOverrideMass(0.038);
+ center.setRelativePosition(Position.TOP);
+ center.setPositionValue(0.83);
+ tube3.addChild(center);
+
+
+
+
+
+ finset = new TrapezoidFinSet();
+ finset.setRootChord(0.495);
+ finset.setTipChord(0.1);
+ finset.setHeight(0.185);
+ finset.setThickness(0.005);
+ finset.setSweep(0.3);
+ finset.setRelativePosition(Position.BOTTOM);
+ finset.setPositionValue(-0.03);
+ 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));
+
+
+ // 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;
+ }
+ }
+ tube3.setMotorOverhang(0.02);
+ rocket.getDefaultConfiguration().setMotorConfigurationID(id);
+
+// tube3.setIgnitionEvent(MotorMount.IgnitionEvent.NEVER);
+
+ rocket.getDefaultConfiguration().setAllStages();
+
+
+ return rocket;
+ }
+
+
+
+}
package net.sf.openrocket.util;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
/**
* Defines an affine transformation of the form A*x+c, where x and c are Coordinates and
}
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Transformation))
+ return false;
+ Transformation o = (Transformation)other;
+ for (int i=0; i<3; i++) {
+ for (int j=0; j<3; j++) {
+ if (!MathUtil.equals(this.rotation[i][j], o.rotation[i][j]))
+ return false;
+ }
+ }
+ return this.translate.equals(o.translate);
+ }
+
public static void main(String[] arg) {
Transformation t;
package net.sf.openrocket.util;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
+import net.sf.openrocket.gui.main.ExceptionHandler;
+
public class UniqueID {
private static AtomicInteger nextId = new AtomicInteger(1);
return UUID.randomUUID().toString();
}
+
+ /**
+ * Return a hashed unique ID that contains no information whatsoever of the
+ * originating computer.
+ *
+ * @return a unique identifier string that contains no information about the computer.
+ */
+ public static String generateHashedID() {
+ String id = UUID.randomUUID().toString();
+
+ try {
+ MessageDigest algorithm = MessageDigest.getInstance("MD5");
+ algorithm.reset();
+ algorithm.update(id.getBytes());
+ byte[] digest = algorithm.digest();
+
+ StringBuilder sb = new StringBuilder();
+ for (byte b: digest) {
+ sb.append(String.format("%02X", 0xFF & b));
+ }
+ id = sb.toString();
+
+ } catch (NoSuchAlgorithmException e) {
+ ExceptionHandler.handleErrorCondition(e);
+ id = "" + id.hashCode();
+ }
+
+ return id;
+ }
+
}
--- /dev/null
+package net.sf.openrocket.communication;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Random;
+
+import org.junit.Test;
+
+public class CommunicationTest {
+
+ @Test
+ public void testIllegalInputUpdateParsing() throws IOException {
+
+ UpdateInfo info;
+
+ info = Communication.parseUpdateInput(new StringReader(""));
+ assertNull(info);
+
+ info = Communication.parseUpdateInput(new StringReader("vj\u00e4avdsads"));
+ assertNull(info);
+
+ info = Communication.parseUpdateInput(new StringReader("\u0000\u0001\u0002"));
+ assertNull(info);
+
+ info = Communication.parseUpdateInput(new StringReader("Version: 1.2"));
+ assertNull(info);
+
+ info = Communication.parseUpdateInput(new StringReader("Version: 1.2pre"));
+ assertNull(info);
+
+ info = Communication.parseUpdateInput(new StringReader("Version: 1.2.x"));
+ assertNull(info);
+
+ info = Communication.parseUpdateInput(new StringReader("\u0000\u0001\u0002"));
+ assertNull(info);
+
+ // Feed random bad input
+ Random rnd = new Random();
+ StringBuilder sb = new StringBuilder(10000);
+ for (int i=0; i<100; i++) {
+ int length = rnd.nextInt(10000);
+ sb.delete(0, sb.length());
+ for (int j=0; j<length; j++) {
+ sb.append((char)rnd.nextInt());
+ }
+ info = Communication.parseUpdateInput(new StringReader(sb.toString()));
+ assertNull(info);
+ }
+
+ }
+
+
+
+ @Test
+ public void testValidInputUpdateParsing() throws IOException {
+
+ UpdateInfo info;
+
+ info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3"));
+ assertNotNull(info);
+ assertEquals("1.2.3", info.getLatestVersion());
+ assertEquals(0, info.getUpdates().size());
+
+ info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3pre"));
+ assertNotNull(info);
+ assertEquals("1.2.3pre", info.getLatestVersion());
+ assertEquals(0, info.getUpdates().size());
+
+ info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3-build-3"));
+ assertNotNull(info);
+ assertEquals("1.2.3-build-3", info.getLatestVersion());
+ assertEquals(0, info.getUpdates().size());
+
+ info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3x\n\n"));
+ assertNotNull(info);
+ assertEquals("1.2.3x", info.getLatestVersion());
+ assertEquals(0, info.getUpdates().size());
+
+ info = Communication.parseUpdateInput(new StringReader("Version:1.2.3\nfdsacd\u00e4fdsa"));
+ assertNotNull(info);
+ assertEquals("1.2.3", info.getLatestVersion());
+ assertEquals(0, info.getUpdates().size());
+
+ info = Communication.parseUpdateInput(new StringReader(
+ "Version: 1.2.3 \n" +
+ "15: Fifteen\n" +
+ "3: Three1 \r\n" +
+ "3: Three2\r" +
+ "1:One"));
+ assertNotNull(info);
+ assertEquals("1.2.3", info.getLatestVersion());
+ assertEquals(4, info.getUpdates().size());
+ assertEquals(15, info.getUpdates().get(0).getU());
+ assertEquals(3, info.getUpdates().get(1).getU());
+ assertEquals(3, info.getUpdates().get(2).getU());
+ assertEquals(1, info.getUpdates().get(3).getU());
+ assertEquals("Fifteen", info.getUpdates().get(0).getV());
+ assertEquals("Three1", info.getUpdates().get(1).getV());
+ assertEquals("Three2", info.getUpdates().get(2).getV());
+ assertEquals("One", info.getUpdates().get(3).getV());
+
+
+ info = Communication.parseUpdateInput(new StringReader(
+ "Version: 1.2.3\n" +
+ "15: (C) 1234 A&B %23 \\o/ \r\r\n" +
+ "5: m\u00e4c\n" +
+ "3: Invalid\u0000value\n" +
+ "1: One\u0019two"));
+ assertNotNull(info);
+ assertEquals("1.2.3", info.getLatestVersion());
+ assertEquals(1, info.getUpdates().size());
+ assertEquals(15, info.getUpdates().get(0).getU());
+ assertEquals("(C) 1234 A&B %23 \\o/", info.getUpdates().get(0).getV());
+
+
+
+ }
+}
--- /dev/null
+package net.sf.openrocket.rocketcomponent;
+
+import static org.junit.Assert.*;
+
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.regex.Pattern;
+
+public class ComponentCompare {
+
+ private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*+");
+
+ private static final String[] IGNORED_METHODS = {
+ "getClass", "getChildCount", "getChildren", "getNextComponent", "getID",
+ "getPreviousComponent", "getParent", "getRocket", "getRoot", "getStage",
+ "getStageNumber", "getComponentName",
+ // Rocket specific methods:
+ "getModID", "getMassModID", "getAerodynamicModID", "getTreeModID", "getFunctionalModID",
+ "getMotorConfigurationIDs", "getDefaultConfiguration"
+ };
+
+
+ /**
+ * Check whether the two components are <em>equal</em>. Two components are considered
+ * equal if they are of the same type and all of their getXXX() and isXXX() methods
+ * return equal values.
+ *
+ * @param c1 the first component to compare.
+ * @param c2 the second component to compare.
+ */
+ public static void assertEquality(RocketComponent c1, RocketComponent c2) {
+ assertEquals(c1.getClass(), c2.getClass());
+
+ // Same class + similar == equal
+ assertSimilarity(c1, c2);
+ }
+
+
+
+ public static void assertDeepEquality(RocketComponent c1, RocketComponent c2) {
+ assertEquality(c1, c2);
+
+ Iterator<RocketComponent> i1 = c1.iterator();
+ Iterator<RocketComponent> i2 = c2.iterator();
+ while (i1.hasNext()) {
+ assertTrue("iterator continues", i2.hasNext());
+ RocketComponent comp1 = i1.next();
+ RocketComponent comp2 = i2.next();
+ assertDeepEquality(comp1, comp2);
+ }
+ assertFalse("iterator end", i2.hasNext());
+ }
+
+
+
+ public static void assertDeepSimilarity(RocketComponent c1, RocketComponent c2,
+ boolean allowNameDifference) {
+ assertSimilarity(c1, c2, allowNameDifference);
+
+ Iterator<RocketComponent> i1 = c1.iterator();
+ Iterator<RocketComponent> i2 = c2.iterator();
+ while (i1.hasNext()) {
+ assertTrue("iterator continues", i2.hasNext());
+ RocketComponent comp1 = i1.next();
+ RocketComponent comp2 = i2.next();
+ assertDeepSimilarity(comp1, comp2, allowNameDifference);
+ }
+ assertFalse("iterator end", i2.hasNext());
+ }
+
+
+
+ /**
+ * Check whether the two components are <em>similar</em>. Two components are similar
+ * if each of the getXXX and isXXX methods that both object types have return
+ * equal values. This does not check whether the two components are of the same type.
+ *
+ * @param c1 the first component.
+ * @param c2 the second component.
+ */
+ public static void assertSimilarity(RocketComponent c1, RocketComponent c2) {
+ assertSimilarity(c1, c2, false);
+ }
+
+ /**
+ * Check whether the two components are <em>similar</em>, allowing a name difference.
+ *
+ * @param c1 the first component.
+ * @param c2 the second component.
+ * @param allowNameDifference whether to allow the components to have different names.
+ */
+ public static void assertSimilarity(RocketComponent c1, RocketComponent c2,
+ boolean allowNameDifference) {
+ Class<? extends RocketComponent> class1 = c1.getClass();
+ Class<? extends RocketComponent> class2 = c2.getClass();
+
+ mainloop:
+ for (Method m1: class1.getMethods()) {
+ // Check for getter method
+ String name = m1.getName();
+ if (!GETTER_PATTERN.matcher(name).matches())
+ continue;
+
+ // Ignore methods that take parameters
+ if (m1.getParameterTypes().length != 0)
+ continue;
+
+ // Ignore specific getters
+ for (String ignore: IGNORED_METHODS) {
+ if (name.equals(ignore))
+ continue mainloop;
+ }
+ if (allowNameDifference && name.equals("getName"))
+ continue;
+
+
+ // Check for method in other class
+ Method m2;
+ try {
+ m2 = class2.getMethod(name);
+ } catch (NoSuchMethodException e) {
+ continue;
+ }
+
+// System.out.println("Testing results of method " + name);
+
+ // Run the methods
+ Object result1, result2;
+ try {
+ result1 = m1.invoke(c1);
+ result2 = m2.invoke(c2);
+ } catch (Exception e) {
+ throw new RuntimeException("Error executing method " + name, e);
+ }
+
+ if (result1 != null && result2 != null &&
+ result1.getClass().isArray() && result2.getClass().isArray()) {
+ assertArrayEquals("Comparing result of method " + name,
+ (Object[])result1, (Object[])result2);
+ } else {
+ assertEquals("Comparing result of method " + name, result1, result2);
+ }
+ }
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.rocketcomponent;
+
+import static org.junit.Assert.*;
+
+import java.awt.Color;
+import java.util.Iterator;
+
+import net.sf.openrocket.util.Coordinate;
+
+import org.junit.Test;
+
+public class ComponentCompareTest {
+
+ @Test
+ public void testComponentEquality() {
+ Rocket r1 = net.sf.openrocket.util.TestRockets.makeBigBlue();
+ Rocket r2 = net.sf.openrocket.util.TestRockets.makeBigBlue();
+
+ Iterator<RocketComponent> i1 = r1.deepIterator(true);
+ Iterator<RocketComponent> i2 = r2.deepIterator(true);
+ while (i1.hasNext()) {
+ assertTrue(i2.hasNext());
+
+ RocketComponent c1 = i1.next();
+ RocketComponent c2 = i2.next();
+
+ ComponentCompare.assertEquality(c1, c2);
+ ComponentCompare.assertSimilarity(c1, c2);
+ }
+ assertFalse(i2.hasNext());
+
+
+ ComponentCompare.assertDeepEquality(r1, r2);
+ ComponentCompare.assertDeepSimilarity(r1, r2, false);
+
+
+ r1.setColor(Color.YELLOW);
+ try {
+ ComponentCompare.assertEquality(r1, r2);
+ fail();
+ } catch (AssertionError e) {
+ // Correct behavior
+ }
+
+
+ i1 = r1.deepIterator(true);
+ i2 = r2.deepIterator(true);
+ boolean finsetfound = false;
+ while (i1.hasNext()) {
+ RocketComponent c1 = i1.next();
+ RocketComponent c2 = i2.next();
+
+ if (c1 instanceof FinSet) {
+ finsetfound = true;
+ FinSet f1 = (FinSet)c1;
+ f1.setTabHeight(0.001);
+
+ try {
+ ComponentCompare.assertEquality(c1, c2);
+ fail();
+ } catch (AssertionError e) {
+ // Correct behavior
+ }
+ }
+ }
+ assertTrue(finsetfound);
+ }
+
+
+ @Test
+ public void testComponentSimilarity() {
+ FinSet trap = new TrapezoidFinSet(
+ 5, // fins
+ 5.0, // root
+ 3.0, // tip
+ 0.0, // sweep
+ 2.0); // height
+ FinSet free = new FreeformFinSet(new Coordinate[] {
+ new Coordinate(0,0),
+ new Coordinate(0,2),
+ new Coordinate(3,2),
+ new Coordinate(5,0)
+ });
+ free.setFinCount(5);
+
+ ComponentCompare.assertSimilarity(trap, free, true);
+
+ try {
+ ComponentCompare.assertSimilarity(trap, free);
+ fail();
+ } catch (AssertionError e) {
+ // Correct behavior
+ }
+
+ free.setName(trap.getName());
+ ComponentCompare.assertSimilarity(trap, free);
+
+ try {
+ ComponentCompare.assertEquality(trap, free);
+ fail();
+ } catch (AssertionError e) {
+ // Correct behavior
+ }
+
+
+ BodyTube t1 = new BodyTube();
+ BodyTube t2 = new BodyTube();
+ t1.addChild(free);
+ t2.addChild(trap);
+
+ ComponentCompare.assertDeepSimilarity(t1, t2, false);
+
+ try {
+ ComponentCompare.assertDeepEquality(t1, t2);
+ fail();
+ } catch (AssertionError e) {
+ // Correct behavior
+ }
+
+ t1.addChild(new TrapezoidFinSet());
+
+ try {
+ ComponentCompare.assertDeepSimilarity(t1, t2, true);
+ fail();
+ } catch (AssertionError e) {
+ // Correct behavior
+ }
+
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.rocketcomponent;
+
+import static org.junit.Assert.*;
+
+import java.awt.Color;
+
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.material.Material.Type;
+import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
+import net.sf.openrocket.rocketcomponent.FinSet.CrossSection;
+import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
+import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
+import net.sf.openrocket.util.LineStyle;
+
+import org.junit.Test;
+
+public class FinSetTest {
+
+
+ @Test
+ public void testFreeformConvert() {
+ testFreeformConvert(new TrapezoidFinSet());
+ testFreeformConvert(new EllipticalFinSet());
+ testFreeformConvert(new FreeformFinSet());
+ }
+
+
+ private void testFreeformConvert(FinSet fin) {
+ FreeformFinSet converted;
+ Material mat = Material.newMaterial(Type.BULK, "foo", 0.1, true);
+
+ fin.setBaseRotation(1.1);
+ fin.setCantAngle(0.001);
+ fin.setCGOverridden(true);
+ fin.setColor(Color.YELLOW);
+ fin.setComment("cmt");
+ fin.setCrossSection(CrossSection.ROUNDED);
+ fin.setFinCount(5);
+ fin.setFinish(Finish.ROUGH);
+ fin.setLineStyle(LineStyle.DASHDOT);
+ fin.setMassOverridden(true);
+ fin.setMaterial(mat);
+ fin.setOverrideCGX(0.012);
+ fin.setOverrideMass(0.0123);
+ fin.setOverrideSubcomponents(true);
+ fin.setPositionValue(0.1);
+ fin.setRelativePosition(Position.ABSOLUTE);
+ fin.setTabHeight(0.01);
+ fin.setTabLength(0.02);
+ fin.setTabRelativePosition(TabRelativePosition.END);
+ fin.setTabShift(0.015);
+ fin.setThickness(0.005);
+
+ converted = FreeformFinSet.convertFinSet(fin);
+
+ ComponentCompare.assertSimilarity(fin, converted, true);
+
+ assertEquals(converted.getComponentName(), converted.getName());
+
+
+ // Create test rocket
+ Rocket rocket = new Rocket();
+ Stage stage = new Stage();
+ BodyTube body = new BodyTube();
+
+ rocket.addChild(stage);
+ stage.addChild(body);
+ body.addChild(fin);
+
+ Listener l1 = new Listener("l1");
+ rocket.addComponentChangeListener(l1);
+
+ fin.setName("Custom name");
+ assertTrue(l1.changed);
+ assertEquals(ComponentChangeEvent.NONFUNCTIONAL_CHANGE, l1.changetype);
+
+
+ // Create copy
+ RocketComponent rocketcopy = rocket.copy();
+
+ Listener l2 = new Listener("l2");
+ rocketcopy.addComponentChangeListener(l2);
+
+ FinSet fincopy = (FinSet) rocketcopy.getChild(0).getChild(0).getChild(0);
+ FreeformFinSet.convertFinSet(fincopy);
+
+ assertTrue(l2.changed);
+ assertEquals(ComponentChangeEvent.TREE_CHANGE,
+ l2.changetype & ComponentChangeEvent.TREE_CHANGE);
+
+ }
+
+
+ private static class Listener implements ComponentChangeListener {
+ private boolean changed = false;
+ private int changetype = 0;
+ private final String name;
+
+ public Listener(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public void componentChanged(ComponentChangeEvent e) {
+ assertFalse("Ensuring listener "+name+" has not been called.", changed);
+ changed = true;
+ changetype = e.getType();
+ }
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.rocketcomponent;
+
+import org.junit.Test;
+
+public class RocketTest {
+
+ @Test
+ public void testCopyFrom() {
+ Rocket r1 = net.sf.openrocket.util.TestRockets.makeIsoHaisu();
+ Rocket r2 = net.sf.openrocket.util.TestRockets.makeBigBlue();
+
+ Rocket copy = r2.copy();
+
+ ComponentCompare.assertDeepEquality(r2, copy);
+
+ r1.copyFrom(copy);
+
+ ComponentCompare.assertDeepEquality(r1, r2);
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.unit;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class ValueTest {
+
+ @Test
+ public void testValues() {
+ Value v1, v2;
+
+ v1 = new Value(273.15, UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
+ v2 = new Value(283.15, UnitGroup.UNITS_TEMPERATURE.findApproximate("C"));
+
+ assertTrue(v1.compareTo(v2) > 0);
+ assertTrue(v2.compareTo(v1) < 0);
+ assertTrue(v1.compareTo(v1) == 0);
+ assertTrue(v2.compareTo(v2) == 0);
+
+ v2.setUnit(UnitGroup.UNITS_TEMPERATURE.findApproximate("K"));
+ assertTrue(v1.compareTo(v2) > 0);
+ assertTrue(v2.compareTo(v1) < 0);
+ assertEquals("283 K", v2.toString());
+
+ v2.setUnit(UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
+ assertTrue(v1.compareTo(v2) < 0);
+ assertTrue(v2.compareTo(v1) > 0);
+
+
+ v1.setValue(Double.NaN);
+ assertTrue(v1.compareTo(v2) > 0);
+ assertTrue(v2.compareTo(v1) < 0);
+
+ v2.setValue(Double.NaN);
+ assertTrue(v1.compareTo(v2) == 0);
+ assertTrue(v1.compareTo(v2) == 0);
+ assertEquals("N/A", v1.toString());
+ assertEquals("N/A", v2.toString());
+
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class UniqueIDTest {
+
+ @Test
+ public void integerTest() {
+
+ int n = UniqueID.next();
+ assertTrue(n > 0);
+ assertEquals(n+1, UniqueID.next());
+ assertEquals(n+2, UniqueID.next());
+ assertEquals(n+3, UniqueID.next());
+
+ }
+
+
+ @Test
+ public void stringTest() {
+ String id = UniqueID.uuid();
+ assertNotNull(id);
+ assertNotSame(id, UniqueID.uuid());
+ assertNotSame(id, UniqueID.uuid());
+ }
+
+ @Test
+ public void hashedTest() {
+ String id = UniqueID.generateHashedID();
+ assertNotNull(id);
+
+ boolean matchhigh = false;
+ boolean matchlow = false;
+ for (int i=0; i<100; i++) {
+ String newid = UniqueID.generateHashedID();
+ assertNotNull(newid);
+ assertNotSame(id, newid);
+ assertTrue(newid.matches("^[0-9a-fA-F]{32}$"));
+
+ // Check that both high and low values occur
+ matchhigh = matchhigh || newid.matches("^([0-9a-fA-F][0-9a-fA-F])*[A-F].*");
+ matchlow = matchlow || newid.matches("^([0-9a-fA-F][0-9a-fA-F])*[0-4].*");
+ }
+ assertTrue(matchhigh);
+ assertTrue(matchlow);
+ }
+
+}