From: plaa Date: Sat, 26 Sep 2009 17:06:37 +0000 (+0000) Subject: updates for 0.9.4 X-Git-Tag: upstream/1.0.0~16 X-Git-Url: https://git.gag.com/?p=debian%2Fopenrocket;a=commitdiff_plain;h=b3c3c1071dfdca4c6b3eb9935dc461201abdaf60 updates for 0.9.4 git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@25 180e2498-e6e9-4542-8430-84ac67f01cd8 --- diff --git a/ChangeLog b/ChangeLog index 2e74313c..45192f7b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,34 @@ +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 diff --git a/build.xml b/build.xml index 386b5825..97f5a4e4 100644 --- a/build.xml +++ b/build.xml @@ -26,16 +26,24 @@ - + + + + + + + + + @@ -46,13 +54,14 @@ Compiling startup classes + - + @@ -69,7 +78,7 @@ - + Building source distribution @@ -141,19 +150,19 @@ ${criticaltodos} Building unit tests - + Running unit tests - - - - + + + + - - - + + + diff --git a/fileformat.txt b/fileformat.txt new file mode 100644 index 00000000..a226d42f --- /dev/null +++ b/fileformat.txt @@ -0,0 +1,35 @@ + +The current OpenRocket file format is "documented" only as the +reference implementation. This will hopefully change in the future. + + +The "version" attribute of the 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 (, and elements) and + allows attaching subcomponents to a tube coupler (previously + forbidden). + + diff --git a/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 8ace04ac..10da9f46 100644 --- a/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -13,7 +13,6 @@ import net.sf.openrocket.aerodynamics.barrowman.RocketComponentCalc; 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; @@ -21,7 +20,6 @@ import net.sf.openrocket.util.Coordinate; 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 @@ -809,89 +807,89 @@ public class BarrowmanCalculator extends AerodynamicCalculator { - 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 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 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); +// } +// +// +// +// } } diff --git a/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java index 05144ef5..13a36b67 100644 --- a/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java +++ b/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java @@ -1,26 +1,20 @@ 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 { @@ -603,38 +597,38 @@ public class FinSetCalc extends RocketComponentCalc { } - @SuppressWarnings("null") - public static void main(String arg[]) { - Rocket rocket = Test.makeRocket(); - FinSet finset = null; - - Iterator 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 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 diff --git a/src/net/sf/openrocket/communication/Communication.java b/src/net/sf/openrocket/communication/Communication.java new file mode 100644 index 00000000..df3b0ac1 --- /dev/null +++ b/src/net/sf/openrocket/communication/Communication.java @@ -0,0 +1,262 @@ +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 true 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. + *

+ * This method will return null 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 null 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 null 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> updates = + new ArrayList>(); + + 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(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); + } + } + +} diff --git a/src/net/sf/openrocket/communication/UpdateInfo.java b/src/net/sf/openrocket/communication/UpdateInfo.java new file mode 100644 index 00000000..73e39633 --- /dev/null +++ b/src/net/sf/openrocket/communication/UpdateInfo.java @@ -0,0 +1,50 @@ +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> updates; + + + public UpdateInfo() { + this.latestVersion = Prefs.getVersion(); + this.updates = new ArrayList>(); + } + + public UpdateInfo(String version, List> updates) { + this.latestVersion = version; + this.updates = new ArrayList>(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> getUpdates() { + return (List>) updates.clone(); + } + +} diff --git a/src/net/sf/openrocket/file/OpenRocketLoader.java b/src/net/sf/openrocket/file/OpenRocketLoader.java index 1d33fd18..c5d5f54c 100644 --- a/src/net/sf/openrocket/file/OpenRocketLoader.java +++ b/src/net/sf/openrocket/file/OpenRocketLoader.java @@ -57,6 +57,7 @@ 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.TabRelativePosition; import net.sf.openrocket.rocketcomponent.RocketComponent.Position; import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.FlightDataBranch; @@ -144,7 +145,7 @@ public class OpenRocketLoader extends RocketLoader { 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 @@ -289,6 +290,11 @@ class DocumentConfig { 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( @@ -462,7 +468,8 @@ class DocumentConfig { * @param enumClass the class of the enum. * @return the found enum value, or null. */ - public static > Enum findEnum(String name, Class> enumClass) { + public static > Enum findEnum(String name, + Class> enumClass) { if (name == null) return null; @@ -1901,6 +1908,43 @@ class PositionSetter implements Setter { } +class FinTabPositionSetter extends DoubleSetter { + + public FinTabPositionSetter() { + super(Reflection.findMethodStatic(FinSet.class, "setTabShift", double.class)); + } + + @Override + public void set(RocketComponent c, String s, HashMap 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 { diff --git a/src/net/sf/openrocket/file/OpenRocketSaver.java b/src/net/sf/openrocket/file/OpenRocketSaver.java index de41cb0c..312aa0bb 100644 --- a/src/net/sf/openrocket/file/OpenRocketSaver.java +++ b/src/net/sf/openrocket/file/OpenRocketSaver.java @@ -14,12 +14,15 @@ import net.sf.openrocket.aerodynamics.Warning; 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; @@ -27,8 +30,13 @@ import net.sf.openrocket.util.TextUtil; 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"; @@ -59,14 +67,18 @@ public class OpenRocketSaver extends RocketSaver { 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(""); - writeln(""); + writeln(""); indent++; // Recursively save the rocket structure @@ -91,8 +103,9 @@ public class OpenRocketSaver extends RocketSaver { writeln(""); dest.flush(); - if (output instanceof GZIPOutputStream) + if (options.isCompressionEnabled()) { ((GZIPOutputStream)output).finish(); + } } @@ -147,6 +160,51 @@ public class OpenRocketSaver extends RocketSaver { } + /** + * 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 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 { diff --git a/src/net/sf/openrocket/file/openrocket/FinSetSaver.java b/src/net/sf/openrocket/file/openrocket/FinSetSaver.java index 42f4d896..756115e4 100644 --- a/src/net/sf/openrocket/file/openrocket/FinSetSaver.java +++ b/src/net/sf/openrocket/file/openrocket/FinSetSaver.java @@ -2,6 +2,8 @@ package net.sf.openrocket.file.openrocket; import java.util.List; +import net.sf.openrocket.util.MathUtil; + public class FinSetSaver extends ExternalComponentSaver { @Override @@ -15,6 +17,18 @@ public class FinSetSaver extends ExternalComponentSaver { elements.add("" + fins.getCrossSection().name().toLowerCase() + ""); elements.add("" + (fins.getCantAngle() * 180.0 / Math.PI) + ""); + + // 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("" + fins.getTabHeight() + ""); + elements.add("" + fins.getTabLength() + ""); + elements.add("" + + fins.getTabShift() + ""); + + } } } diff --git a/src/net/sf/openrocket/gui/adaptors/Column.java b/src/net/sf/openrocket/gui/adaptors/Column.java index 3b22d11b..73341367 100644 --- a/src/net/sf/openrocket/gui/adaptors/Column.java +++ b/src/net/sf/openrocket/gui/adaptors/Column.java @@ -46,6 +46,12 @@ public abstract class Column { } + /** + * 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 Object.class. + */ public Class getColumnClass() { return Object.class; } diff --git a/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java b/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java index 651738c1..53011f37 100644 --- a/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java +++ b/src/net/sf/openrocket/gui/adaptors/ColumnTableModel.java @@ -4,6 +4,8 @@ import javax.swing.table.AbstractTableModel; 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; @@ -45,7 +47,8 @@ public abstract class ColumnTableModel extends AbstractTableModel { 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; } diff --git a/src/net/sf/openrocket/gui/adaptors/DoubleModel.java b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java index f2125b63..b39cb010 100644 --- a/src/net/sf/openrocket/gui/adaptors/DoubleModel.java +++ b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java @@ -37,6 +37,8 @@ import net.sf.openrocket.util.MathUtil; public class DoubleModel implements ChangeListener, ChangeSource { private static final boolean DEBUG_LISTENERS = false; + + public static final DoubleModel ZERO = new DoubleModel(0); //////////// JSpinner Model //////////// @@ -706,7 +708,7 @@ public class DoubleModel implements ChangeListener, ChangeSource { /** - * 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. */ diff --git a/src/net/sf/openrocket/gui/adaptors/MaterialModel.java b/src/net/sf/openrocket/gui/adaptors/MaterialModel.java index 8834e48a..be8df36e 100644 --- a/src/net/sf/openrocket/gui/adaptors/MaterialModel.java +++ b/src/net/sf/openrocket/gui/adaptors/MaterialModel.java @@ -1,32 +1,20 @@ 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 @@ -35,6 +23,8 @@ 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 database; @@ -43,11 +33,13 @@ public class MaterialModel extends AbstractListModel implements 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; @@ -94,18 +86,20 @@ public class MaterialModel extends AbstractListModel implements 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); } } @@ -156,65 +150,5 @@ public class MaterialModel extends AbstractListModel implements public void elementRemoved(Material element, Database 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); - } - } - - } diff --git a/src/net/sf/openrocket/gui/components/CollectionTable.java b/src/net/sf/openrocket/gui/components/CollectionTable.java new file mode 100644 index 00000000..ef14dc61 --- /dev/null +++ b/src/net/sf/openrocket/gui/components/CollectionTable.java @@ -0,0 +1,75 @@ +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 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); + } + } +} diff --git a/src/net/sf/openrocket/gui/components/UnitSelector.java b/src/net/sf/openrocket/gui/components/UnitSelector.java index 962992e2..8a81a9ad 100644 --- a/src/net/sf/openrocket/gui/components/UnitSelector.java +++ b/src/net/sf/openrocket/gui/components/UnitSelector.java @@ -39,7 +39,7 @@ import net.sf.openrocket.unit.UnitGroup; public class UnitSelector extends ResizeLabel implements ChangeListener, MouseListener, ItemSelectable { - private final DoubleModel model; + private DoubleModel model; private final Action[] extraActions; private UnitGroup unitGroup; @@ -73,9 +73,12 @@ public class UnitSelector extends ResizeLabel implements ChangeListener, MouseLi 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; @@ -90,6 +93,10 @@ public class UnitSelector extends ResizeLabel implements ChangeListener, MouseLi 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(); } @@ -102,9 +109,6 @@ public class UnitSelector extends ResizeLabel implements ChangeListener, MouseLi public UnitSelector(DoubleModel model, boolean showValue, Action... actions) { this(model, showValue, null, actions); - - // Add model listener - this.model.addChangeListener(this); } @@ -125,6 +129,26 @@ public class UnitSelector extends ResizeLabel implements ChangeListener, MouseLi return model; } + + /** + * Set the current double model. + * + * @param model the model to set, null 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 null. Either this method diff --git a/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java index e785e45e..0ad6c93a 100644 --- a/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java +++ b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java @@ -1,8 +1,6 @@ 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; @@ -10,14 +8,9 @@ import java.awt.event.ComponentEvent; 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; @@ -25,7 +18,7 @@ import net.sf.openrocket.util.GUIUtil; 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. * @@ -69,9 +62,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis } }); - - // Install ESC listener - GUIUtil.installEscapeCloseOperation(this); + GUIUtil.setDisposableDialogOptions(this, null); } @@ -88,9 +79,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis 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; @@ -100,48 +89,15 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis 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. */ diff --git a/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java index fb8e76cc..efec1267 100644 --- a/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java @@ -4,11 +4,22 @@ import java.awt.event.ActionEvent; 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 { @@ -17,6 +28,8 @@ public abstract class FinSetConfig extends RocketComponentConfig { public FinSetConfig(RocketComponent component) { super(component); + + tabbedPane.insertTab("Fin tabs", null, finTabPanel(), "Through-the-wall fin tabs", 0); } @@ -35,20 +48,9 @@ public abstract class FinSetConfig extends RocketComponentConfig { 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); } }); @@ -98,6 +100,84 @@ public abstract class FinSetConfig extends RocketComponentConfig { } + 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("Through-the-wall fin tabs:"), "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 em = + new EnumModel(component, "TabRelativePosition"); + + panel.add(new JComboBox(em), "spanx 3, growx"); + + return panel; + } @Override public void updateFields() { diff --git a/src/net/sf/openrocket/gui/configdialog/MotorConfig.java b/src/net/sf/openrocket/gui/configdialog/MotorConfig.java index c4210e6c..7cfc1bd6 100644 --- a/src/net/sf/openrocket/gui/configdialog/MotorConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/MotorConfig.java @@ -152,7 +152,7 @@ public class MotorConfig extends JPanel { updateFields(); } }); - panel.add(button,"span, split, grow"); + panel.add(button,"span, split, growx"); button = new JButton("Remove motor"); button.addActionListener(new ActionListener() { @@ -162,7 +162,7 @@ public class MotorConfig extends JPanel { updateFields(); } }); - panel.add(button,"grow, wrap"); + panel.add(button,"growx, wrap"); diff --git a/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java b/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java index c058725d..f41ca5ce 100644 --- a/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java @@ -52,7 +52,8 @@ public class ParachuteConfig extends RecoveryDeviceConfig { 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"); @@ -112,7 +113,7 @@ public class ParachuteConfig extends RecoveryDeviceConfig { 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"); diff --git a/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java index 5e017a4b..0d2461b1 100644 --- a/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java @@ -174,7 +174,7 @@ public class RocketComponentConfig extends JPanel { 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"); @@ -229,8 +229,8 @@ public class RocketComponentConfig extends JPanel { 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("Override the mass or center of gravity of the " + + component.getComponentName() + ":"),"spanx, wrap 20lp"); JCheckBox check; BooleanModel bm; @@ -315,7 +315,8 @@ public class RocketComponentConfig extends JPanel { private JPanel commentTab() { JPanel panel = new JPanel(new MigLayout("fill")); - panel.add(new JLabel("Comments on the "+component.getComponentName()+":"), "wrap"); + panel.add(new JLabel("Comments on the "+component.getComponentName()+":"), + "wrap"); // TODO: LOW: Changes in comment from other sources not reflected in component commentTextArea = new JTextArea(component.getComment()); @@ -325,7 +326,7 @@ public class RocketComponentConfig extends JPanel { 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; } @@ -335,7 +336,7 @@ public class RocketComponentConfig extends JPanel { private JPanel figureTab() { JPanel panel = new JPanel(new MigLayout("align 20% 20%")); - panel.add(new JLabel("Figure style:"), "wrap para"); + panel.add(new JLabel("Figure style:"), "wrap para"); panel.add(new JLabel("Component color:"), "gapleft para, gapright 10lp"); diff --git a/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java b/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java index 132f6a5e..02011b71 100644 --- a/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java @@ -85,7 +85,8 @@ public class StreamerConfig extends RecoveryDeviceConfig { 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"); diff --git a/src/net/sf/openrocket/gui/dialogs/AboutDialog.java b/src/net/sf/openrocket/gui/dialogs/AboutDialog.java index 3b151f70..516be5ec 100644 --- a/src/net/sf/openrocket/gui/dialogs/AboutDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/AboutDialog.java @@ -62,8 +62,8 @@ public class AboutDialog extends JDialog { this.pack(); this.setResizable(false); this.setLocationRelativeTo(parent); - GUIUtil.setDefaultButton(close); - GUIUtil.installEscapeCloseOperation(this); + + GUIUtil.setDisposableDialogOptions(this, close); } diff --git a/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java b/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java index b58d48a3..6df68031 100644 --- a/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java @@ -7,14 +7,11 @@ import java.awt.Window; 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; @@ -28,6 +25,7 @@ import javax.swing.JScrollPane; 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; @@ -38,14 +36,6 @@ public class BugReportDialog extends JDialog { 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); @@ -117,7 +107,8 @@ public class BugReportDialog extends JDialog { 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.", @@ -142,8 +133,8 @@ public class BugReportDialog extends JDialog { this.pack(); this.pack(); this.setLocationRelativeTo(parent); - GUIUtil.installEscapeCloseOperation(this); - GUIUtil.setDefaultButton(send); + + GUIUtil.setDisposableDialogOptions(this, send); } @@ -282,50 +273,6 @@ public class BugReportDialog extends JDialog { - /** - * 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() diff --git a/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java b/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java index a5a184ae..81577443 100644 --- a/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java @@ -414,8 +414,9 @@ public class ComponentAnalysisDialog extends JDialog implements ChangeListener { this.setLocationByPlatform(true); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - GUIUtil.installEscapeCloseOperation(this); pack(); + + GUIUtil.setDisposableDialogOptions(this, null); } diff --git a/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java b/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java new file mode 100644 index 00000000..7fb7ad40 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java @@ -0,0 +1,175 @@ +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("" + 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); + } + } +} diff --git a/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java index a42d8a94..550dee56 100644 --- a/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java @@ -249,9 +249,8 @@ public class EditMotorConfigurationDialog extends JDialog { updateEnabled(); - GUIUtil.installEscapeCloseOperation(this); - GUIUtil.setDefaultButton(close); this.setLocationByPlatform(true); + GUIUtil.setDisposableDialogOptions(this, close); // Undo description final OpenRocketDocument document = BasicFrame.findDocument(rocket); diff --git a/src/net/sf/openrocket/gui/dialogs/ExampleDesignDialog.java b/src/net/sf/openrocket/gui/dialogs/ExampleDesignDialog.java index b6d1ec42..13a80c07 100644 --- a/src/net/sf/openrocket/gui/dialogs/ExampleDesignDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/ExampleDesignDialog.java @@ -88,8 +88,8 @@ public class ExampleDesignDialog extends JDialog { this.add(panel); this.pack(); this.setLocationByPlatform(true); - GUIUtil.installEscapeCloseOperation(this); - GUIUtil.setDefaultButton(openButton); + + GUIUtil.setDisposableDialogOptions(this, openButton); } diff --git a/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java b/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java index 92144db7..b58113da 100644 --- a/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java @@ -72,8 +72,8 @@ public class LicenseDialog extends JDialog { this.setTitle("OpenRocket license"); this.pack(); this.setLocationRelativeTo(parent); - GUIUtil.setDefaultButton(close); - GUIUtil.installEscapeCloseOperation(this); + + GUIUtil.setDisposableDialogOptions(this, close); } } diff --git a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java index 95b2aad6..6893dccf 100644 --- a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java @@ -284,9 +284,8 @@ public class MotorChooserDialog extends JDialog { 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(); diff --git a/src/net/sf/openrocket/gui/dialogs/PreferencesDialog.java b/src/net/sf/openrocket/gui/dialogs/PreferencesDialog.java deleted file mode 100644 index c06cbbc6..00000000 --- a/src/net/sf/openrocket/gui/dialogs/PreferencesDialog.java +++ /dev/null @@ -1,391 +0,0 @@ -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 unitSelectors = new ArrayList(); - - 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); - } - - -} diff --git a/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java b/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java new file mode 100644 index 00000000..e6d66f46 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java @@ -0,0 +1,371 @@ +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 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("Editing materials will not affect existing " + + "rocket designs."), "span"); + + + } + + + private Database 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; + } + + } + +} diff --git a/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java new file mode 100644 index 00000000..2ef8e113 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java @@ -0,0 +1,367 @@ +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 unitSelectors = new ArrayList(); + + 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); + } + + +} diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index 5357b329..8ad7601a 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -42,6 +42,7 @@ import javax.swing.JScrollPane; 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; @@ -72,9 +73,9 @@ import net.sf.openrocket.gui.dialogs.BugReportDialog; import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog; import net.sf.openrocket.gui.dialogs.ExampleDesignDialog; import net.sf.openrocket.gui.dialogs.LicenseDialog; -import net.sf.openrocket.gui.dialogs.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; @@ -86,6 +87,7 @@ import net.sf.openrocket.util.Icons; 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; @@ -295,7 +297,7 @@ public class BasicFrame extends JFrame { 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, @@ -631,6 +633,38 @@ public class BasicFrame extends JFrame { 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) { diff --git a/src/net/sf/openrocket/gui/main/ComponentAddButtons.java b/src/net/sf/openrocket/gui/main/ComponentAddButtons.java index 0b7454e7..f1402256 100644 --- a/src/net/sf/openrocket/gui/main/ComponentAddButtons.java +++ b/src/net/sf/openrocket/gui/main/ComponentAddButtons.java @@ -380,8 +380,7 @@ public class ComponentAddButtons extends JPanel implements Scrollable { 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; } @@ -492,8 +491,7 @@ public class ComponentAddButtons extends JPanel implements Scrollable { // Insert at the end of the parent return new Pair(parent, null); default: - System.err.println("ERROR: Bad position type: "+pos); - Thread.dumpStack(); + ExceptionHandler.handleErrorCondition("ERROR: Bad position type: "+pos); return null; } } @@ -533,8 +531,7 @@ public class ComponentAddButtons extends JPanel implements Scrollable { sel = 2; break; default: - System.err.println("ERROR: JOptionPane returned "+sel); - Thread.dumpStack(); + ExceptionHandler.handleErrorCondition("ERROR: JOptionPane returned "+sel); return 0; } diff --git a/src/net/sf/openrocket/gui/main/ComponentIcons.java b/src/net/sf/openrocket/gui/main/ComponentIcons.java index 0c140b26..d1e47a30 100644 --- a/src/net/sf/openrocket/gui/main/ComponentIcons.java +++ b/src/net/sf/openrocket/gui/main/ComponentIcons.java @@ -86,7 +86,7 @@ public class ComponentIcons { 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); @@ -103,8 +103,7 @@ public class ComponentIcons { 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}; } @@ -138,7 +137,7 @@ public class ComponentIcons { 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}; } } diff --git a/src/net/sf/openrocket/gui/main/ComponentTreeModel.java b/src/net/sf/openrocket/gui/main/ComponentTreeModel.java index f4428bfb..7e7ba155 100644 --- a/src/net/sf/openrocket/gui/main/ComponentTreeModel.java +++ b/src/net/sf/openrocket/gui/main/ComponentTreeModel.java @@ -90,6 +90,13 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener { 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 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.", + "        " + msg + "", + " ", "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, @@ -158,9 +223,45 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler { */ 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); + } + } } diff --git a/src/net/sf/openrocket/gui/main/SimulationEditDialog.java b/src/net/sf/openrocket/gui/main/SimulationEditDialog.java index 4d92c1fb..81234d3e 100644 --- a/src/net/sf/openrocket/gui/main/SimulationEditDialog.java +++ b/src/net/sf/openrocket/gui/main/SimulationEditDialog.java @@ -174,8 +174,8 @@ public class SimulationEditDialog extends JDialog { this.validate(); this.pack(); this.setLocationByPlatform(true); - GUIUtil.setDefaultButton(button); - GUIUtil.installEscapeCloseOperation(this); + + GUIUtil.setDisposableDialogOptions(this, button); } @@ -931,8 +931,8 @@ public class SimulationEditDialog extends JDialog { dialog.setLocationByPlatform(true); dialog.pack(); - GUIUtil.installEscapeCloseOperation(dialog); - GUIUtil.setDefaultButton(button); + + GUIUtil.setDisposableDialogOptions(dialog, button); dialog.setVisible(true); } diff --git a/src/net/sf/openrocket/gui/main/SimulationRunDialog.java b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java index e16486b7..238ca322 100644 --- a/src/net/sf/openrocket/gui/main/SimulationRunDialog.java +++ b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java @@ -142,7 +142,8 @@ public class SimulationRunDialog extends JDialog { this.setLocationByPlatform(true); this.validate(); this.pack(); - GUIUtil.installEscapeCloseOperation(this); + + GUIUtil.setDisposableDialogOptions(this, null); updateProgress(); } diff --git a/src/net/sf/openrocket/gui/plot/PlotDialog.java b/src/net/sf/openrocket/gui/plot/PlotDialog.java index a40c841a..d84fed9b 100644 --- a/src/net/sf/openrocket/gui/plot/PlotDialog.java +++ b/src/net/sf/openrocket/gui/plot/PlotDialog.java @@ -397,7 +397,7 @@ public class PlotDialog extends JDialog { 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 @@ -424,8 +424,8 @@ public class PlotDialog extends JDialog { this.setLocationByPlatform(true); this.pack(); - GUIUtil.installEscapeCloseOperation(this); - GUIUtil.setDefaultButton(button); + + GUIUtil.setDisposableDialogOptions(this, button); } diff --git a/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java b/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java index 3d874a20..0eb856f5 100644 --- a/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java +++ b/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java @@ -22,16 +22,16 @@ public class FinSetShapes extends RocketComponentShapes { 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 { 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; @@ -44,11 +56,6 @@ public abstract class Material implements Comparable { super(name, density, userDefined); } - @Override - public UnitGroup getUnitGroup() { - return UnitGroup.UNITS_DENSITY_SURFACE; - } - @Override public Type getType() { return Type.SURFACE; @@ -65,11 +72,6 @@ public abstract class Material implements Comparable { super(name, density, userDefined); } - @Override - public UnitGroup getUnitGroup() { - return UnitGroup.UNITS_DENSITY_BULK; - } - @Override public Type getType() { return Type.BULK; @@ -107,12 +109,11 @@ public abstract class Material implements Comparable { 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()); } diff --git a/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java b/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java index 5e7fea07..58d3297b 100644 --- a/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java +++ b/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java @@ -39,6 +39,15 @@ public class ComponentChangeEvent extends ChangeEvent { } + /** + * 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; } diff --git a/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java b/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java index 9f96bbdc..8e11c914 100644 --- a/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java @@ -114,5 +114,16 @@ public abstract class ExternalComponent extends RocketComponent { 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; + } } diff --git a/src/net/sf/openrocket/rocketcomponent/FinSet.java b/src/net/sf/openrocket/rocketcomponent/FinSet.java index be68570a..96c85b26 100644 --- a/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -1,6 +1,7 @@ package net.sf.openrocket.rocketcomponent; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -38,6 +39,22 @@ public abstract class FinSet extends ExternalComponent { } } + 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. */ @@ -80,7 +97,17 @@ public abstract class FinSet extends ExternalComponent { 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; @@ -220,16 +247,123 @@ public abstract class FinSet extends ExternalComponent { 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. */ @@ -240,6 +374,7 @@ public abstract class FinSet extends ExternalComponent { 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. @@ -257,7 +392,8 @@ public abstract class FinSet extends ExternalComponent { @Override public double getComponentVolume() { - return fins * getFinArea() * thickness * crossSection.getRelativeVolume(); + return fins * (getFinArea() + tabHeight*tabLength) * thickness * + crossSection.getRelativeVolume(); } @@ -303,9 +439,21 @@ public abstract class FinSet extends ExternalComponent { 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; @@ -488,6 +636,38 @@ public abstract class FinSet extends ExternalComponent { */ 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. @@ -508,5 +688,9 @@ public abstract class FinSet extends ExternalComponent { 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; } } diff --git a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java index 1312799f..d5275db8 100644 --- a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java @@ -1,14 +1,13 @@ 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 points = new ArrayList(); + private ArrayList points = new ArrayList(); public FreeformFinSet() { points.add(Coordinate.NUL); @@ -19,7 +18,16 @@ public class FreeformFinSet extends FinSet { 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); @@ -30,8 +38,69 @@ public class FreeformFinSet extends 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. + *

+ * 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 index-1 and index. @@ -229,4 +298,13 @@ public class FreeformFinSet extends FinSet { return "Freeform fin set"; } + + @SuppressWarnings("unchecked") + @Override + public RocketComponent copy() { + RocketComponent c = super.copy(); + ((FreeformFinSet)c).points = (ArrayList) this.points.clone(); + return c; + } + } diff --git a/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 6cb230c4..29f146be 100644 --- a/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -1411,10 +1411,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, /** - * 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. + *

* The fields are copied by reference, and the supplied component must not be used * after the call, as it is in an undefined state. * @@ -1422,9 +1423,6 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, */ 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); diff --git a/src/net/sf/openrocket/rocketcomponent/TubeCoupler.java b/src/net/sf/openrocket/rocketcomponent/TubeCoupler.java index bb199219..d27d2cca 100644 --- a/src/net/sf/openrocket/rocketcomponent/TubeCoupler.java +++ b/src/net/sf/openrocket/rocketcomponent/TubeCoupler.java @@ -1,7 +1,7 @@ package net.sf.openrocket.rocketcomponent; -public class TubeCoupler extends ThicknessRingComponent { +public class TubeCoupler extends ThicknessRingComponent implements RadialParent { public TubeCoupler() { setOuterRadiusAutomatic(true); @@ -22,9 +22,24 @@ public class TubeCoupler extends ThicknessRingComponent { return "Tube coupler"; } + /** + * Allow all InternalComponents to be added to this component. + */ @Override public boolean isCompatible(Class type) { - return false; + return InternalComponent.class.isAssignableFrom(type); + } + + + @Override + public double getInnerRadius(double x) { + return getInnerRadius(); + } + + + @Override + public double getOuterRadius(double x) { + return getOuterRadius(); } } diff --git a/src/net/sf/openrocket/unit/Unit.java b/src/net/sf/openrocket/unit/Unit.java index 3686bedc..55b4f160 100644 --- a/src/net/sf/openrocket/unit/Unit.java +++ b/src/net/sf/openrocket/unit/Unit.java @@ -167,6 +167,17 @@ public abstract class Unit { + /** + * 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 diff --git a/src/net/sf/openrocket/unit/UnitGroup.java b/src/net/sf/openrocket/unit/UnitGroup.java index 850cb925..6b6d4997 100644 --- a/src/net/sf/openrocket/unit/UnitGroup.java +++ b/src/net/sf/openrocket/unit/UnitGroup.java @@ -316,6 +316,26 @@ public class UnitGroup { 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 null 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. @@ -393,6 +413,22 @@ public class UnitGroup { } + + + + /** + * 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 diff --git a/src/net/sf/openrocket/unit/Value.java b/src/net/sf/openrocket/unit/Value.java new file mode 100644 index 00000000..54e19279 --- /dev/null +++ b/src/net/sf/openrocket/unit/Value.java @@ -0,0 +1,135 @@ +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 + */ +public class Value implements Comparable { + + private double value; + private Unit unit; + + + /** + * Create a new Value object. + * + * @param value the value to set. + * @param unit the unit to set (null 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 (null 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 null 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; + } + +} diff --git a/src/net/sf/openrocket/util/ComparablePair.java b/src/net/sf/openrocket/util/ComparablePair.java new file mode 100644 index 00000000..7b879683 --- /dev/null +++ b/src/net/sf/openrocket/util/ComparablePair.java @@ -0,0 +1,28 @@ +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 + * @param the first object type, according to which comparisons are performed. + * @param the second object type. + */ +public class ComparablePair, V> extends Pair + implements Comparable>{ + + public ComparablePair(U u, V v) { + super(u, v); + } + + + /** + * Compares the first objects. If either of the objects is null this + * method throws NullPointerException. + */ + @Override + public int compareTo(ComparablePair other) { + return this.getU().compareTo(other.getU()); + } + +} diff --git a/src/net/sf/openrocket/util/GUIUtil.java b/src/net/sf/openrocket/util/GUIUtil.java index 27cf121c..2f50a9cf 100644 --- a/src/net/sf/openrocket/util/GUIUtil.java +++ b/src/net/sf/openrocket/util/GUIUtil.java @@ -1,36 +1,62 @@ 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 { @@ -45,7 +71,6 @@ public class GUIUtil { loadImage("pix/icon/icon-032.png"); loadImage("pix/icon/icon-016.png"); } - private static void loadImage(String file) { InputStream is; @@ -62,6 +87,26 @@ public class GUIUtil { } + + /** + * Set suitable options for a single-use disposable dialog. This includes + * setting ESC to close the dialog and adding the appropriate window icons. + * If defaultButton is provided, it is set to the default button action. + *

+ * The default button must be already attached to the dialog. + * + * @param dialog the dialog. + * @param defaultButton the default button of the dialog, or null. + */ + public static void setDisposableDialogOptions(JDialog dialog, JButton defaultButton) { + installEscapeCloseOperation(dialog); + setWindowIcons(dialog); + addModelNullingListener(dialog); + if (defaultButton != null) { + setDefaultButton(defaultButton); + } + } + /** @@ -115,10 +160,165 @@ public class GUIUtil { } + + /** + * 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. + *

+ * 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 (null 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 @@ -184,5 +384,5 @@ public class GUIUtil { } } - + } diff --git a/src/net/sf/openrocket/util/Icons.java b/src/net/sf/openrocket/util/Icons.java index fa2c25e7..a447009f 100644 --- a/src/net/sf/openrocket/util/Icons.java +++ b/src/net/sf/openrocket/util/Icons.java @@ -9,6 +9,7 @@ import javax.swing.Icon; import javax.swing.ImageIcon; import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.gui.main.ExceptionHandler; public class Icons { @@ -59,10 +60,19 @@ 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 null 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); diff --git a/src/net/sf/openrocket/util/OpenFileWorker.java b/src/net/sf/openrocket/util/OpenFileWorker.java index 4b765c3d..0246e91f 100644 --- a/src/net/sf/openrocket/util/OpenFileWorker.java +++ b/src/net/sf/openrocket/util/OpenFileWorker.java @@ -12,6 +12,7 @@ import javax.swing.SwingWorker; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.RocketLoader; +import net.sf.openrocket.gui.main.ExceptionHandler; /** @@ -67,8 +68,7 @@ public class OpenFileWorker extends SwingWorker { try { is.close(); } catch (Exception e) { - System.err.println("Error closing file: "); - e.printStackTrace(); + ExceptionHandler.handleErrorCondition("Error closing file", e); } } } diff --git a/src/net/sf/openrocket/util/Pair.java b/src/net/sf/openrocket/util/Pair.java index 8a4cec28..e13b9b94 100644 --- a/src/net/sf/openrocket/util/Pair.java +++ b/src/net/sf/openrocket/util/Pair.java @@ -1,5 +1,12 @@ package net.sf.openrocket.util; +/** + * Storage for a pair of objects. + * + * @author Sampo Niskanen + * @param the first object type. + * @param the second object type. + */ public class Pair { private final U u; diff --git a/src/net/sf/openrocket/util/Prefs.java b/src/net/sf/openrocket/util/Prefs.java index 049b5468..2b7c4101 100644 --- a/src/net/sf/openrocket/util/Prefs.java +++ b/src/net/sf/openrocket/util/Prefs.java @@ -18,6 +18,7 @@ import java.util.prefs.Preferences; 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; @@ -62,7 +63,8 @@ public class Prefs { 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"); } @@ -172,6 +174,16 @@ public class Prefs { } + 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()); @@ -471,8 +483,7 @@ public class Prefs { } } catch (BackingStoreException e) { - System.err.println("BackingStoreException:"); - e.printStackTrace(); + ExceptionHandler.handleErrorCondition(e); } } diff --git a/src/net/sf/openrocket/util/SaveCSVWorker.java b/src/net/sf/openrocket/util/SaveCSVWorker.java index 444c6926..a4a65c46 100644 --- a/src/net/sf/openrocket/util/SaveCSVWorker.java +++ b/src/net/sf/openrocket/util/SaveCSVWorker.java @@ -13,6 +13,7 @@ import javax.swing.SwingWorker; 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; @@ -75,8 +76,7 @@ public class SaveCSVWorker extends SwingWorker { try { os.close(); } catch (Exception e) { - System.err.println("Error closing file: "); - e.printStackTrace(); + ExceptionHandler.handleErrorCondition("Error closing file", e); } } return null; diff --git a/src/net/sf/openrocket/util/SaveFileWorker.java b/src/net/sf/openrocket/util/SaveFileWorker.java index 9f94d510..fe5f70cf 100644 --- a/src/net/sf/openrocket/util/SaveFileWorker.java +++ b/src/net/sf/openrocket/util/SaveFileWorker.java @@ -8,6 +8,7 @@ import javax.swing.SwingWorker; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.RocketSaver; +import net.sf.openrocket.gui.main.ExceptionHandler; public class SaveFileWorker extends SwingWorker { @@ -46,8 +47,7 @@ public class SaveFileWorker extends SwingWorker { try { os.close(); } catch (Exception e) { - System.err.println("Error closing file: "); - e.printStackTrace(); + ExceptionHandler.handleErrorCondition("Error closing file", e); } } return null; diff --git a/src/net/sf/openrocket/util/Test.java b/src/net/sf/openrocket/util/Test.java deleted file mode 100644 index c0e37c7a..00000000 --- a/src/net/sf/openrocket/util/Test.java +++ /dev/null @@ -1,416 +0,0 @@ -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; - } - - - -} diff --git a/src/net/sf/openrocket/util/TestRockets.java b/src/net/sf/openrocket/util/TestRockets.java new file mode 100644 index 00000000..6b5b83f7 --- /dev/null +++ b/src/net/sf/openrocket/util/TestRockets.java @@ -0,0 +1,583 @@ +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 + * null generates a rocket using a random key. + *

+ * 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 > Enum randomEnum(Class c) { + Enum[] 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; + } + + + +} diff --git a/src/net/sf/openrocket/util/Transformation.java b/src/net/sf/openrocket/util/Transformation.java index d263c42c..2c546974 100644 --- a/src/net/sf/openrocket/util/Transformation.java +++ b/src/net/sf/openrocket/util/Transformation.java @@ -1,6 +1,8 @@ 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 @@ -241,6 +243,20 @@ public class Transformation implements java.io.Serializable { } + @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; diff --git a/src/net/sf/openrocket/util/UniqueID.java b/src/net/sf/openrocket/util/UniqueID.java index 1cc082e8..e7b56d4a 100644 --- a/src/net/sf/openrocket/util/UniqueID.java +++ b/src/net/sf/openrocket/util/UniqueID.java @@ -1,8 +1,12 @@ 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); @@ -30,4 +34,34 @@ public class UniqueID { 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; + } + } diff --git a/test/net/sf/openrocket/communication/CommunicationTest.java b/test/net/sf/openrocket/communication/CommunicationTest.java new file mode 100644 index 00000000..8a2b9beb --- /dev/null +++ b/test/net/sf/openrocket/communication/CommunicationTest.java @@ -0,0 +1,120 @@ +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; jequal. 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 i1 = c1.iterator(); + Iterator 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 i1 = c1.iterator(); + Iterator 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 similar. 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 similar, 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 class1 = c1.getClass(); + Class 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); + } + } + } + +} diff --git a/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java b/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java new file mode 100644 index 00000000..d1f94d94 --- /dev/null +++ b/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java @@ -0,0 +1,131 @@ +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 i1 = r1.deepIterator(true); + Iterator 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 + } + + } + +} diff --git a/test/net/sf/openrocket/rocketcomponent/FinSetTest.java b/test/net/sf/openrocket/rocketcomponent/FinSetTest.java new file mode 100644 index 00000000..ae0e665a --- /dev/null +++ b/test/net/sf/openrocket/rocketcomponent/FinSetTest.java @@ -0,0 +1,111 @@ +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(); + } + } + +} diff --git a/test/net/sf/openrocket/rocketcomponent/RocketTest.java b/test/net/sf/openrocket/rocketcomponent/RocketTest.java new file mode 100644 index 00000000..11c55dca --- /dev/null +++ b/test/net/sf/openrocket/rocketcomponent/RocketTest.java @@ -0,0 +1,21 @@ +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); + } + +} diff --git a/test/net/sf/openrocket/unit/ValueTest.java b/test/net/sf/openrocket/unit/ValueTest.java new file mode 100644 index 00000000..0c8c61ff --- /dev/null +++ b/test/net/sf/openrocket/unit/ValueTest.java @@ -0,0 +1,43 @@ +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()); + + } + +} diff --git a/test/net/sf/openrocket/util/UniqueIDTest.java b/test/net/sf/openrocket/util/UniqueIDTest.java new file mode 100644 index 00000000..eeec6153 --- /dev/null +++ b/test/net/sf/openrocket/util/UniqueIDTest.java @@ -0,0 +1,50 @@ +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); + } + +}