diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index e7f8ae5e9..5a590191d 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -1464,6 +1464,8 @@ main.menu.help = Help
main.menu.help.desc = Information about OpenRocket
main.menu.help.tours = Guided tours
main.menu.help.tours.desc = Take guided tours on OpenRocket
+main.menu.help.wiki = Wiki (Online Help)
+main.menu.help.wiki.desc = Open the OpenRocket Wiki site, containing documentation, in your default webbrowser
main.menu.help.license = License
main.menu.help.license.desc = OpenRocket license information
main.menu.help.bugReport = Bug report
@@ -1579,7 +1581,7 @@ Shape.Haackseries.desc2 = The Haack series nose cones are designed to min
! RocketComponent
-RocketComponent.Position.Method.Axial.ABSOLUTE = Tip of the nose cone
+RocketComponent.Position.Method.Axial.ABSOLUTE = Tip of the rocket
RocketComponent.Position.Method.Axial.AFTER = After the sibling component
RocketComponent.Position.Method.Axial.BOTTOM = Bottom of the parent component
RocketComponent.Position.Method.Axial.MIDDLE = Middle of the parent component
diff --git a/core/resources/l10n/messages_nl.properties b/core/resources/l10n/messages_nl.properties
index a8a009530..00f17c0db 100644
--- a/core/resources/l10n/messages_nl.properties
+++ b/core/resources/l10n/messages_nl.properties
@@ -1465,7 +1465,7 @@ Shape.Haackseries.desc2 = De Haack-serie neuskegels zijn ontworpen om de
! RocketComponent
RocketComponent.Position.Method.Axial.Label = Straal positioneermethode
-RocketComponent.Position.Method.Axial.ABSOLUTE = Tip van neuskegel
+RocketComponent.Position.Method.Axial.ABSOLUTE = Tip van raket
RocketComponent.Position.Method.Axial.AFTER = Na de sibling component
RocketComponent.Position.Method.Axial.BOTTOM = Onderkant van de oudercomponent
RocketComponent.Position.Method.Axial.MIDDLE = Midden van de oudercomponent
diff --git a/core/resources/l10n/messages_uk_UA.properties b/core/resources/l10n/messages_uk_UA.properties
index 6373b525c..6acabbe07 100644
--- a/core/resources/l10n/messages_uk_UA.properties
+++ b/core/resources/l10n/messages_uk_UA.properties
@@ -1295,11 +1295,6 @@ Shape.Haackseries.desc2 = The Haack series nose cones are designed to min
! RocketComponent
-RocketComponent.Position.TOP = Top of the parent component
-RocketComponent.Position.MIDDLE = Middle of the parent component
-RocketComponent.Position.BOTTOM = Bottom of the parent component
-RocketComponent.Position.AFTER = After the parent component
-RocketComponent.Position.ABSOLUTE = Tip of the nose cone
! LaunchLug
LaunchLug.Launchlug = Launch Lug
diff --git a/core/resources/pix/icons/wiki.png b/core/resources/pix/icons/wiki.png
new file mode 100644
index 000000000..7d863f949
Binary files /dev/null and b/core/resources/pix/icons/wiki.png differ
diff --git a/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java b/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java
index dac0541a8..ca18384fc 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java
@@ -181,10 +181,8 @@ public abstract class ComponentAssembly extends RocketComponent implements Axial
public void updateBounds() {
// currently only updates the length
this.length = 0;
- Iterator childIterator = this.getChildren().iterator();
- while (childIterator.hasNext()) {
- RocketComponent curChild = childIterator.next();
- if(curChild.isAfter()){
+ for (RocketComponent curChild : this.getChildren()) {
+ if (curChild.isAfter()) {
this.length += curChild.getLength();
}
}
diff --git a/core/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java b/core/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java
index b81ee55b3..acac70c8c 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java
@@ -102,7 +102,7 @@ public abstract class ExternalComponent extends RocketComponent {
*/
@Override
public double getComponentMass() {
- return material.getDensity() * getComponentVolume();
+ return material.getDensity() * getComponentVolume() * getInstanceCount();
}
/**
diff --git a/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java b/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java
index de2285dc4..4b0c18912 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java
@@ -20,7 +20,7 @@ public class LaunchLug extends Tube implements AnglePositionable, BoxBounded, Li
private double radius;
private double thickness;
- private double angleOffsetRadians = Math.PI;
+ private double angleOffsetRad = Math.PI;
private double radialOffset = 0;
private int instanceCount = 1;
@@ -97,7 +97,7 @@ public class LaunchLug extends Tube implements AnglePositionable, BoxBounded, Li
@Override
public double getAngleOffset() {
- return this.angleOffsetRadians;
+ return this.angleOffsetRad;
}
@Override
@@ -109,9 +109,9 @@ public class LaunchLug extends Tube implements AnglePositionable, BoxBounded, Li
}
double clamped_rad = MathUtil.clamp( newAngleRadians, -Math.PI, Math.PI);
- if (MathUtil.equals(this.angleOffsetRadians, clamped_rad))
+ if (MathUtil.equals(this.angleOffsetRad, clamped_rad))
return;
- this.angleOffsetRadians = clamped_rad;
+ this.angleOffsetRad = clamped_rad;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
@@ -159,8 +159,8 @@ public class LaunchLug extends Tube implements AnglePositionable, BoxBounded, Li
public Coordinate[] getInstanceOffsets(){
Coordinate[] toReturn = new Coordinate[this.getInstanceCount()];
- final double yOffset = Math.cos(angleOffsetRadians) * (radialOffset);
- final double zOffset = Math.sin(angleOffsetRadians) * (radialOffset);
+ final double yOffset = Math.cos(angleOffsetRad) * (radialOffset);
+ final double zOffset = Math.sin(angleOffsetRad) * (radialOffset);
for ( int index=0; index < this.getInstanceCount(); index++){
toReturn[index] = new Coordinate(index*this.instanceSeparation, yOffset, zOffset);
@@ -228,7 +228,10 @@ public class LaunchLug extends Tube implements AnglePositionable, BoxBounded, Li
@Override
public Coordinate getComponentCG() {
- return new Coordinate(length / 2, 0, 0, getComponentMass());
+ final double CMx = length / 2 + (instanceSeparation * (instanceCount-1)) / 2;
+ final double CMy = Math.cos(this.angleOffsetRad)*getOuterRadius();
+ final double CMz = Math.sin(this.angleOffsetRad)*getOuterRadius();
+ return new Coordinate(CMx, CMy, CMz, getComponentMass());
}
@Override
diff --git a/core/src/net/sf/openrocket/rocketcomponent/RailButton.java b/core/src/net/sf/openrocket/rocketcomponent/RailButton.java
index 499795fc0..734966339 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/RailButton.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/RailButton.java
@@ -51,7 +51,7 @@ public class RailButton extends ExternalComponent implements AnglePositionable,
private double radialDistance_m=0;
protected static final AngleMethod angleMethod = AngleMethod.RELATIVE;
- private double angle_rad = Math.PI;
+ private double angleOffsetRad = Math.PI;
private int instanceCount = 1;
private double instanceSeparation = 0; // front-front along the positive rocket axis. i.e. [1,0,0];
@@ -210,7 +210,7 @@ public class RailButton extends ExternalComponent implements AnglePositionable,
@Override
public double getAngleOffset(){
- return angle_rad;
+ return angleOffsetRad;
}
@Override
@@ -234,9 +234,9 @@ public class RailButton extends ExternalComponent implements AnglePositionable,
double clamped_rad = MathUtil.clamp(angle_rad, -Math.PI, Math.PI);
- if (MathUtil.equals(this.angle_rad, clamped_rad))
+ if (MathUtil.equals(this.angleOffsetRad, clamped_rad))
return;
- this.angle_rad = clamped_rad;
+ this.angleOffsetRad = clamped_rad;
fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
@@ -265,8 +265,8 @@ public class RailButton extends ExternalComponent implements AnglePositionable,
public Coordinate[] getInstanceOffsets(){
Coordinate[] toReturn = new Coordinate[this.getInstanceCount()];
- final double yOffset = Math.cos(this.angle_rad) * ( this.radialDistance_m );
- final double zOffset = Math.sin(this.angle_rad) * ( this.radialDistance_m );
+ final double yOffset = Math.cos(this.angleOffsetRad) * ( this.radialDistance_m );
+ final double zOffset = Math.sin(this.angleOffsetRad) * ( this.radialDistance_m );
for ( int index=0; index < this.getInstanceCount(); index++){
toReturn[index] = new Coordinate(index*this.instanceSeparation, yOffset, zOffset);
@@ -384,13 +384,14 @@ public class RailButton extends ExternalComponent implements AnglePositionable,
if (heightCM > this.totalHeight_m + this.screwHeight_m) {
throw new BugException(" bug found while computing the CG of a RailButton: "+this.getName()+"\n height of CG: "+heightCM);
}
+
+ final double CMx = (instanceSeparation * (instanceCount-1)) / 2;
+ final double CMy = Math.cos(this.angleOffsetRad)*heightCM;
+ final double CMz = Math.sin(this.angleOffsetRad)*heightCM;
- final double CMy = Math.cos(this.angle_rad)*heightCM;
- final double CMz = Math.sin(this.angle_rad)*heightCM;
-
- return new Coordinate( 0, CMy, CMz, getComponentMass());
+ return new Coordinate( CMx, CMy, CMz, getComponentMass());
}
-
+
@Override
public String getComponentName() {
return trans.get("RailButton.RailButton");
diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java
index 977feb3c6..dd27bcc7c 100644
--- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java
+++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java
@@ -65,7 +65,8 @@ public class MassCalculatorTest extends BaseTestCase {
assertEquals(" Alpha III Empty Mass is incorrect: ", expRocketDryMass, actualRocketDryMass, EPSILON);
double expCMx = 0.1917685523;
- Coordinate expCM = new Coordinate(expCMx, 0, 0, expRocketDryMass);
+ double expCMy = -0.00006340812673; // Slight offset due to launch lug
+ Coordinate expCM = new Coordinate(expCMx, expCMy, 0, expRocketDryMass);
assertEquals("Simple Rocket CM.x is incorrect: ", expCM.x, actualRocketDryCM.x, EPSILON);
assertEquals("Simple Rocket CM.y is incorrect: ", expCM.y, actualRocketDryCM.y, EPSILON);
assertEquals("Simple Rocket CM.z is incorrect: ", expCM.z, actualRocketDryCM.z, EPSILON);
@@ -117,7 +118,8 @@ public class MassCalculatorTest extends BaseTestCase {
assertEquals(" Alpha III Total Mass (with motor: " + desig + ") is incorrect: ", expRocketLaunchMass, actualRocketLaunchMass, EPSILON);
double expCMx = 0.20996455968266833;
- Coordinate expCM = new Coordinate(expCMx, 0, 0, expRocketLaunchMass);
+ double expCMy = -0.00003845163503; // Slight offset due to launch lug
+ Coordinate expCM = new Coordinate(expCMx, expCMy, 0, expRocketLaunchMass);
assertEquals("Simple Rocket CM.x is incorrect: ", expCM.x, actualRocketLaunchCM.x, EPSILON);
assertEquals("Simple Rocket CM.y is incorrect: ", expCM.y, actualRocketLaunchCM.y, EPSILON);
assertEquals("Simple Rocket CM.z is incorrect: ", expCM.z, actualRocketLaunchCM.z, EPSILON);
diff --git a/core/test/net/sf/openrocket/rocketcomponent/LaunchLugTest.java b/core/test/net/sf/openrocket/rocketcomponent/LaunchLugTest.java
index 1df1864f3..4d2e7f594 100644
--- a/core/test/net/sf/openrocket/rocketcomponent/LaunchLugTest.java
+++ b/core/test/net/sf/openrocket/rocketcomponent/LaunchLugTest.java
@@ -63,5 +63,255 @@ public class LaunchLugTest extends BaseTestCase {
assertEquals(" LaunchLug #2 is in the wrong place: ", expPos, actPos[1]);
}
}
+
+ @Test
+ public void testCMSingleInstance() {
+ BodyTube bodyTube = new BodyTube();
+ LaunchLug lug = new LaunchLug();
+ lug.setLength(0.1);
+ lug.setOuterRadius(0.02);
+ bodyTube.addChild(lug);
+
+ // Test normal CG
+ Coordinate CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.05, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", -0.02, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
+
+ // Test rotated CG
+ lug.setAngleOffset(Math.PI / 2);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.05, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0.02, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
+
+ lug.setAngleOffset(-Math.PI / 3);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.05, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.01, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", -0.0173205, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
+
+
+ // Change dimensions
+ lug.setLength(0.05);
+ lug.setOuterRadius(0.015);
+ lug.setAngleOffset(0);
+
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.025, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.015, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.00309761, CG.weight, EPSILON);
+
+ // Test rotated CG
+ lug.setAngleOffset(Math.PI / 2);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.025, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0.015, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.00309761, CG.weight, EPSILON);
+
+ lug.setAngleOffset(-Math.PI / 3);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.025, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.0075, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", -0.01299038, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.00309761, CG.weight, EPSILON);
+ }
+
+ @Test
+ public void testCMSingleInstanceOverride() {
+ BodyTube bodyTube = new BodyTube();
+ LaunchLug lug = new LaunchLug();
+ lug.setLength(0.1);
+ lug.setOuterRadius(0.02);
+ lug.setCGOverridden(true);
+ lug.setOverrideCGX(0.0123);
+ bodyTube.addChild(lug);
+
+ // Test normal CG
+ Coordinate CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", -0.02, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
+
+ // Test rotated CG
+ lug.setAngleOffset(Math.PI / 2);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0.02, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
+
+ lug.setAngleOffset(-Math.PI / 3);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.01, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", -0.0173205, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.008331504, CG.weight, EPSILON);
+
+
+ // Change dimensions
+ lug.setLength(0.05);
+ lug.setOuterRadius(0.015);
+ lug.setAngleOffset(0);
+ lug.setOverrideCGX(0.0321);
+ lug.setMassOverridden(true);
+ lug.setOverrideMass(0.1);
+
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.015, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.1, CG.weight, EPSILON);
+
+ // Test rotated CG
+ lug.setAngleOffset(Math.PI / 2);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0.015, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.1, CG.weight, EPSILON);
+
+ lug.setAngleOffset(-Math.PI / 3);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.0075, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", -0.01299038, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.1, CG.weight, EPSILON);
+ }
+
+ @Test
+ public void testCMMultipleInstances() {
+ BodyTube bodyTube = new BodyTube();
+ LaunchLug lug = new LaunchLug();
+ lug.setLength(0.1);
+ lug.setOuterRadius(0.02);
+ lug.setInstanceCount(3);
+ lug.setInstanceSeparation(0.2);
+ bodyTube.addChild(lug);
+
+ // Test normal CG
+ Coordinate CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.25, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", -0.02, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
+
+ // Test rotated CG
+ lug.setAngleOffset(Math.PI / 2);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.25, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0.02, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
+
+ lug.setAngleOffset(-Math.PI / 3);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.25, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.01, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", -0.0173205, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
+
+
+ // Change dimensions
+ lug.setLength(0.05);
+ lug.setOuterRadius(0.015);
+ lug.setAngleOffset(0);
+ lug.setInstanceCount(2);
+ lug.setInstanceSeparation(0.15);
+
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.1, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.015, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.00619522, CG.weight, EPSILON);
+
+ // Test rotated CG
+ lug.setAngleOffset(Math.PI / 2);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.1, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0.015, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.00619522, CG.weight, EPSILON);
+
+ lug.setAngleOffset(-Math.PI / 3);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.1, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.0075, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", -0.01299038, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.00619522, CG.weight, EPSILON);
+ }
+
+ @Test
+ public void testCMMultipleInstancesOverride() {
+ BodyTube bodyTube = new BodyTube();
+ LaunchLug lug = new LaunchLug();
+ lug.setLength(0.1);
+ lug.setOuterRadius(0.02);
+ lug.setInstanceCount(3);
+ lug.setInstanceSeparation(0.2);
+ lug.setCGOverridden(true);
+ lug.setOverrideCGX(0.0123);
+ bodyTube.addChild(lug);
+
+ // Test normal CG
+ Coordinate CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", -0.02, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
+
+ // Test rotated CG
+ lug.setAngleOffset(Math.PI / 2);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0.02, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
+
+ lug.setAngleOffset(-Math.PI / 3);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.01, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", -0.0173205, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.024994512, CG.weight, EPSILON);
+
+
+ // Change dimensions
+ lug.setLength(0.05);
+ lug.setOuterRadius(0.015);
+ lug.setAngleOffset(0);
+ lug.setInstanceCount(2);
+ lug.setInstanceSeparation(0.15);
+ lug.setOverrideCGX(0.0321);
+ lug.setMassOverridden(true);
+ lug.setOverrideMass(0.2);
+
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.015, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.2, CG.weight, EPSILON);
+
+ // Test rotated CG
+ lug.setAngleOffset(Math.PI / 2);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", 0.015, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.2, CG.weight, EPSILON);
+
+ lug.setAngleOffset(-Math.PI / 3);
+ CG = lug.getCG();
+ assertEquals(" LaunchLug CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong y value: ", 0.0075, CG.y, EPSILON);
+ assertEquals(" LaunchLug CG has the wrong z value: ", -0.01299038, CG.z, EPSILON);
+ assertEquals(" LaunchLug CM has the wrong value: ", 0.2, CG.weight, EPSILON);
+ }
}
diff --git a/core/test/net/sf/openrocket/rocketcomponent/RailButtonTest.java b/core/test/net/sf/openrocket/rocketcomponent/RailButtonTest.java
new file mode 100644
index 000000000..985299e38
--- /dev/null
+++ b/core/test/net/sf/openrocket/rocketcomponent/RailButtonTest.java
@@ -0,0 +1,263 @@
+package net.sf.openrocket.rocketcomponent;
+
+import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class RailButtonTest extends BaseTestCase {
+ protected final double EPSILON = MathUtil.EPSILON;
+
+ @Test
+ public void testCMSingleInstance() {
+ BodyTube bodyTube = new BodyTube();
+ RailButton button = new RailButton();
+ button.setOuterDiameter(0.05);
+ button.setTotalHeight(0.05);
+ bodyTube.addChild(button);
+
+ // Test normal CG
+ Coordinate CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", -0.025, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
+
+ // Test rotated CG
+ button.setAngleOffset(Math.PI / 2);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0.025, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
+
+ button.setAngleOffset(-Math.PI / 3);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.0125, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", -0.02165064, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
+
+
+ // Change dimensions
+ button.setOuterDiameter(0.025);
+ button.setTotalHeight(0.02);
+ button.setAngleOffset(0);
+
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.01, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.003930195, CG.weight, EPSILON);
+
+ // Test rotated CG
+ button.setAngleOffset(Math.PI / 2);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0.01, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.003930195, CG.weight, EPSILON);
+
+ button.setAngleOffset(-Math.PI / 3);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.005, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", -0.00866025, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.003930195, CG.weight, EPSILON);
+ }
+
+ @Test
+ public void testCMSingleInstanceOverride() {
+ BodyTube bodyTube = new BodyTube();
+ RailButton button = new RailButton();
+ button.setOuterDiameter(0.05);
+ button.setTotalHeight(0.05);
+ button.setCGOverridden(true);
+ button.setOverrideCGX(0.0123);
+ bodyTube.addChild(button);
+
+ // Test normal CG
+ Coordinate CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", -0.025, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
+
+ // Test rotated CG
+ button.setAngleOffset(Math.PI / 2);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0.025, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
+
+ button.setAngleOffset(-Math.PI / 3);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.0125, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", -0.02165064, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.014435995, CG.weight, EPSILON);
+
+
+ // Change dimensions
+ button.setOuterDiameter(0.025);
+ button.setTotalHeight(0.02);
+ button.setAngleOffset(0);
+ button.setOverrideCGX(0.0321);
+ button.setMassOverridden(true);
+ button.setOverrideMass(0.1);
+
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.01, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.1, CG.weight, EPSILON);
+
+ // Test rotated CG
+ button.setAngleOffset(Math.PI / 2);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0.01, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.1, CG.weight, EPSILON);
+
+ button.setAngleOffset(-Math.PI / 3);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.005, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", -0.00866025, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.1, CG.weight, EPSILON);
+ }
+
+ @Test
+ public void testCMMultipleInstances() {
+ BodyTube bodyTube = new BodyTube();
+ RailButton button = new RailButton();
+ button.setOuterDiameter(0.05);
+ button.setTotalHeight(0.05);
+ button.setInstanceCount(3);
+ button.setInstanceSeparation(0.2);
+ bodyTube.addChild(button);
+
+ // Test normal CG
+ Coordinate CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.2, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", -0.025, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
+
+ // Test rotated CG
+ button.setAngleOffset(Math.PI / 2);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.2, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0.025, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
+
+ button.setAngleOffset(-Math.PI / 3);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.2, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.0125, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", -0.02165064, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
+
+
+ // Change dimensions
+ button.setOuterDiameter(0.025);
+ button.setTotalHeight(0.02);
+ button.setAngleOffset(0);
+ button.setInstanceCount(2);
+ button.setInstanceSeparation(0.15);
+
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.075, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.01, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.00786039, CG.weight, EPSILON);
+
+ // Test rotated CG
+ button.setAngleOffset(Math.PI / 2);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.075, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0.01, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.00786039, CG.weight, EPSILON);
+
+ button.setAngleOffset(-Math.PI / 3);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.075, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.005, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", -0.00866025, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.00786039, CG.weight, EPSILON);
+ }
+
+ @Test
+ public void testCMMultipleInstancesOverride() {
+ BodyTube bodyTube = new BodyTube();
+ RailButton button = new RailButton();
+ button.setOuterDiameter(0.05);
+ button.setTotalHeight(0.05);
+ button.setInstanceCount(3);
+ button.setInstanceSeparation(0.2);
+ button.setCGOverridden(true);
+ button.setOverrideCGX(0.0123);
+ bodyTube.addChild(button);
+
+ // Test normal CG
+ Coordinate CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", -0.025, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
+
+ // Test rotated CG
+ button.setAngleOffset(Math.PI / 2);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0.025, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
+
+ button.setAngleOffset(-Math.PI / 3);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0123, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.0125, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", -0.02165064, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.043307985, CG.weight, EPSILON);
+
+
+ // Change dimensions
+ button.setOuterDiameter(0.025);
+ button.setTotalHeight(0.02);
+ button.setAngleOffset(0);
+ button.setInstanceCount(2);
+ button.setInstanceSeparation(0.15);
+ button.setOverrideCGX(0.0321);
+ button.setMassOverridden(true);
+ button.setOverrideMass(0.2);
+
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.01, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.2, CG.weight, EPSILON);
+
+ // Test rotated CG
+ button.setAngleOffset(Math.PI / 2);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", 0.01, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.2, CG.weight, EPSILON);
+
+ button.setAngleOffset(-Math.PI / 3);
+ CG = button.getCG();
+ assertEquals(" RailButton CG has the wrong x value: ", 0.0321, CG.x, EPSILON);
+ assertEquals(" RailButton CG has the wrong y value: ", 0.005, CG.y, EPSILON);
+ assertEquals(" RailButton CG has the wrong z value: ", -0.00866025, CG.z, EPSILON);
+ assertEquals(" RailButton CM has the wrong value: ", 0.2, CG.weight, EPSILON);
+ }
+
+}
diff --git a/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java b/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java
index 4061e2ba1..b60d4f6ad 100644
--- a/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java
+++ b/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java
@@ -1,5 +1,7 @@
package net.sf.openrocket.gui.components;
+import net.sf.openrocket.gui.util.URLUtil;
+
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Dimension;
@@ -143,7 +145,7 @@ public class DescriptionArea extends JScrollPane {
}
try {
- Desktop.getDesktop().browse(uri);
+ URLUtil.openWebpage(uri);
}
catch (Exception ex) {
throw new RuntimeException(ex);
diff --git a/swing/src/net/sf/openrocket/gui/components/URLLabel.java b/swing/src/net/sf/openrocket/gui/components/URLLabel.java
index 182f3b127..5656eaa96 100644
--- a/swing/src/net/sf/openrocket/gui/components/URLLabel.java
+++ b/swing/src/net/sf/openrocket/gui/components/URLLabel.java
@@ -12,6 +12,7 @@ import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
+import net.sf.openrocket.gui.util.URLUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,12 +61,9 @@ public class URLLabel extends SelectableLabel {
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
- Desktop d = Desktop.getDesktop();
try {
- d.browse(new URI(url));
- } catch (URISyntaxException e1) {
- throw new BugException("Illegal URL: " + url, e1);
- } catch (IOException e1) {
+ URLUtil.openWebpage(url);
+ } catch (Exception e1) {
log.error("Unable to launch browser: " + e1.getMessage(), e1);
}
}
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java
index c2122f19c..006222aed 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java
@@ -33,6 +33,7 @@ import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.gui.util.SwingPreferences;
+import net.sf.openrocket.gui.util.URLUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.gui.widgets.SelectColorButton;
@@ -94,9 +95,8 @@ public class UpdateInfoDialog extends JDialog {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
- Desktop desktop = Desktop.getDesktop();
try {
- desktop.browse(e.getURL().toURI());
+ URLUtil.openWebpage(e.getURL().toURI());
} catch (Exception ex) {
log.warn("Exception hyperlink: " + ex.getMessage());
}
@@ -180,9 +180,8 @@ public class UpdateInfoDialog extends JDialog {
String url = AssetHandler.getInstallerURLForPlatform((UpdatePlatform) comboBox.getSelectedItem(),
release.getReleaseName());
if (url == null) return;
- Desktop desktop = Desktop.getDesktop();
try {
- desktop.browse(new URI(url));
+ URLUtil.openWebpage(url);
} catch (Exception ex) {
log.warn("Exception install link: " + ex.getMessage());
}
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/WelcomeDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/WelcomeDialog.java
index 4e4119188..4cc65eabf 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/WelcomeDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/WelcomeDialog.java
@@ -4,6 +4,7 @@ import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
+import net.sf.openrocket.gui.util.URLUtil;
import net.sf.openrocket.gui.widgets.SelectColorButton;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
@@ -68,9 +69,8 @@ public class WelcomeDialog extends JDialog {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
- Desktop desktop = Desktop.getDesktop();
try {
- desktop.browse(e.getURL().toURI());
+ URLUtil.openWebpage(e.getURL().toURI());
} catch (Exception ex) {
log.warn("Exception hyperlink: " + ex.getMessage());
}
diff --git a/swing/src/net/sf/openrocket/gui/help/tours/SlideShowLinkListener.java b/swing/src/net/sf/openrocket/gui/help/tours/SlideShowLinkListener.java
index 0ac1620b8..58675e21c 100644
--- a/swing/src/net/sf/openrocket/gui/help/tours/SlideShowLinkListener.java
+++ b/swing/src/net/sf/openrocket/gui/help/tours/SlideShowLinkListener.java
@@ -9,6 +9,7 @@ import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkEvent.EventType;
import javax.swing.event.HyperlinkListener;
+import net.sf.openrocket.gui.util.URLUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,13 +36,9 @@ public class SlideShowLinkListener implements HyperlinkListener {
URL url = event.getURL();
if (url != null && (url.getProtocol().equalsIgnoreCase("http") || url.getProtocol().equals("https"))) {
-
- if (Desktop.isDesktopSupported()) {
- try {
- Desktop.getDesktop().browse(url.toURI());
- } catch (Exception e) {
- // Ignore
- }
+ try {
+ URLUtil.openWebpage(url.toURI());
+ } catch (Exception ignore) {
}
} else {
diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
index d505e00f1..82a6fb014 100644
--- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
+++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
@@ -84,6 +84,7 @@ import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.gui.util.OpenFileWorker;
import net.sf.openrocket.gui.util.SaveFileWorker;
import net.sf.openrocket.gui.util.SwingPreferences;
+import net.sf.openrocket.gui.util.URLUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.Markers;
import net.sf.openrocket.rocketcomponent.AxialStage;
@@ -751,6 +752,15 @@ public class BasicFrame extends JFrame {
}
//// Help
+ generateHelpMenu(menubar, this);
+
+ this.setJMenuBar(menubar);
+ }
+
+ public static void generateHelpMenu(JMenuBar menubar, JFrame parent) {
+ JMenu menu;
+ JMenuItem item;
+
menu = new JMenu(trans.get("main.menu.help"));
menu.setMnemonic(KeyEvent.VK_H);
menu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.desc"));
@@ -764,7 +774,20 @@ public class BasicFrame extends JFrame {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Guided tours selected");
- GuidedTourSelectionDialog.showDialog(BasicFrame.this);
+ GuidedTourSelectionDialog.showDialog(parent);
+ }
+ });
+ menu.add(item);
+
+ //// Wiki (Online Help)
+ item = new JMenuItem(trans.get("main.menu.help.wiki"));
+ item.setIcon(Icons.WIKI);
+ item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.wiki.desc"));
+ item.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.info(Markers.USER_MARKER, "Wiki selected");
+ URLUtil.openWebpage(URLUtil.WIKI_URL);
}
});
menu.add(item);
@@ -779,7 +802,7 @@ public class BasicFrame extends JFrame {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Bug report selected");
- BugReportDialog.showBugReportDialog(BasicFrame.this);
+ BugReportDialog.showBugReportDialog(parent);
}
});
menu.add(item);
@@ -793,7 +816,7 @@ public class BasicFrame extends JFrame {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Debug log selected");
- new DebugLogDialog(BasicFrame.this).setVisible(true);
+ new DebugLogDialog(parent).setVisible(true);
}
});
menu.add(item);
@@ -808,7 +831,7 @@ public class BasicFrame extends JFrame {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "License selected");
- new LicenseDialog(BasicFrame.this).setVisible(true);
+ new LicenseDialog(parent).setVisible(true);
}
});
menu.add(item);
@@ -821,12 +844,10 @@ public class BasicFrame extends JFrame {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "About selected");
- new AboutDialog(BasicFrame.this).setVisible(true);
+ new AboutDialog(parent).setVisible(true);
}
});
menu.add(item);
-
- this.setJMenuBar(menubar);
}
public RocketActions getRocketActions() {
diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/ComponentAssemblyShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/ComponentAssemblyShapes.java
index 5dd94d4d0..f6b2a5bcf 100644
--- a/swing/src/net/sf/openrocket/gui/rocketfigure/ComponentAssemblyShapes.java
+++ b/swing/src/net/sf/openrocket/gui/rocketfigure/ComponentAssemblyShapes.java
@@ -6,6 +6,7 @@ import net.sf.openrocket.rocketcomponent.ParallelStage;
import net.sf.openrocket.rocketcomponent.PodSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.position.AxialMethod;
+import net.sf.openrocket.rocketcomponent.position.RadiusMethod;
import net.sf.openrocket.util.Color;
import net.sf.openrocket.util.Transformation;
@@ -30,8 +31,10 @@ public class ComponentAssemblyShapes extends RocketComponentShape {
}
// Correct the radius to be at the "reference point" dictated by the component's radius offset.
- double boundingRadius = assembly.getBoundingRadius();
- correctedTransform = correctedTransform.applyTransformation(new Transformation(0, -boundingRadius, 0));
+ if (assembly.getRadiusMethod() == RadiusMethod.RELATIVE) {
+ double boundingRadius = assembly.getBoundingRadius();
+ correctedTransform = correctedTransform.applyTransformation(new Transformation(0, -boundingRadius, 0));
+ }
double markerRadius = getDisplayRadius(component);
Shape[] s = EmptyShapes.getShapesSideWithSelectionSquare(correctedTransform, markerRadius);
@@ -55,8 +58,11 @@ public class ComponentAssemblyShapes extends RocketComponentShape {
ComponentAssembly assembly = (ComponentAssembly) component;
// Correct the radius to be at the "reference point" dictated by the component's radius offset.
- double boundingRadius = assembly.getBoundingRadius();
- Transformation correctedTransform = transformation.applyTransformation(new Transformation(0, -boundingRadius, 0));
+ Transformation correctedTransform = transformation;
+ if (assembly.getRadiusMethod() == RadiusMethod.RELATIVE) {
+ double boundingRadius = assembly.getBoundingRadius();
+ correctedTransform = correctedTransform.applyTransformation(new Transformation(0, -boundingRadius, 0));
+ }
double markerRadius = getDisplayRadius(component);
Shape[] s = EmptyShapes.getShapesBackWithSelectionSquare(correctedTransform, markerRadius);
diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/ParallelStageShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/ParallelStageShapes.java
new file mode 100644
index 000000000..8c99ee9d7
--- /dev/null
+++ b/swing/src/net/sf/openrocket/gui/rocketfigure/ParallelStageShapes.java
@@ -0,0 +1,5 @@
+package net.sf.openrocket.gui.rocketfigure;
+
+public class ParallelStageShapes extends ComponentAssemblyShapes {
+ // Everything is handled by the ComponentAssemblyShapes class.
+}
diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/PodSetShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/PodSetShapes.java
new file mode 100644
index 000000000..d1e391132
--- /dev/null
+++ b/swing/src/net/sf/openrocket/gui/rocketfigure/PodSetShapes.java
@@ -0,0 +1,5 @@
+package net.sf.openrocket.gui.rocketfigure;
+
+public class PodSetShapes extends ComponentAssemblyShapes {
+ // Everything is handled by the ComponentAssemblyShapes class.
+}
diff --git a/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java b/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java
index 1d0bf444e..a509d172f 100644
--- a/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java
+++ b/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java
@@ -131,54 +131,8 @@ public class DummyFrameMenuOSX extends JFrame {
});
menu.add(item);
-
//// Help
- menu = new JMenu(trans.get("main.menu.help"));
- menu.setMnemonic(KeyEvent.VK_H);
- menu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.desc"));
- menubar.add(menu);
-
- //// Guided tours
- item = new JMenuItem(trans.get("main.menu.help.tours"), KeyEvent.VK_L);
- item.setIcon(Icons.HELP_TOURS);
- item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.tours.desc"));
- item.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- log.info(Markers.USER_MARKER, "Guided tours selected");
- GuidedTourSelectionDialog.showDialog(DummyFrameMenuOSX.this);
- }
- });
- menu.add(item);
-
- menu.addSeparator();
-
- //// License
- item = new JMenuItem(trans.get("main.menu.help.license"), KeyEvent.VK_L);
- item.setIcon(Icons.HELP_LICENSE);
- item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.license.desc"));
- item.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- log.info(Markers.USER_MARKER, "License selected");
- new LicenseDialog(DummyFrameMenuOSX.this).setVisible(true);
- }
- });
- menu.add(item);
-
- //// About
- item = new JMenuItem(trans.get("main.menu.help.about"), KeyEvent.VK_A);
- item.setIcon(Icons.HELP_ABOUT);
- item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.about.desc"));
- item.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- log.info(Markers.USER_MARKER, "About selected");
- new AboutDialog(DummyFrameMenuOSX.this).setVisible(true);
- }
- });
- menu.add(item);
-
+ BasicFrame.generateHelpMenu(menubar, this);
this.setJMenuBar(menubar);
}
diff --git a/swing/src/net/sf/openrocket/gui/util/GUIUtil.java b/swing/src/net/sf/openrocket/gui/util/GUIUtil.java
index cb17d0dd2..af1d3d16d 100644
--- a/swing/src/net/sf/openrocket/gui/util/GUIUtil.java
+++ b/swing/src/net/sf/openrocket/gui/util/GUIUtil.java
@@ -20,7 +20,6 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -54,11 +53,9 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeListener;
-import javax.swing.event.TableColumnModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.DefaultTableModel;
-import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.tree.DefaultMutableTreeNode;
@@ -69,7 +66,6 @@ import javax.swing.tree.TreeSelectionModel;
import net.sf.openrocket.gui.Resettable;
import net.sf.openrocket.logging.Markers;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Invalidatable;
diff --git a/swing/src/net/sf/openrocket/gui/util/Icons.java b/swing/src/net/sf/openrocket/gui/util/Icons.java
index 76190fbc1..0ebd96ab0 100644
--- a/swing/src/net/sf/openrocket/gui/util/Icons.java
+++ b/swing/src/net/sf/openrocket/gui/util/Icons.java
@@ -78,6 +78,7 @@ public class Icons {
public static final Icon HELP_BUG_REPORT = loadImageIcon("pix/icons/help-bug.png", "Bug report");
public static final Icon HELP_DEBUG_LOG = loadImageIcon("pix/icons/help-log.png", "Debug log");
public static final Icon HELP_TOURS = loadImageIcon("pix/icons/help-tours.png", "Guided tours");
+ public static final Icon WIKI = loadImageIcon("pix/icons/wiki.png", "Wiki (Documentation)");
public static final Icon ZOOM_IN = loadImageIcon("pix/icons/zoom-in.png", "Zoom in");
public static final Icon ZOOM_OUT = loadImageIcon("pix/icons/zoom-out.png", "Zoom out");
diff --git a/swing/src/net/sf/openrocket/gui/util/URLUtil.java b/swing/src/net/sf/openrocket/gui/util/URLUtil.java
new file mode 100644
index 000000000..fdace3cdb
--- /dev/null
+++ b/swing/src/net/sf/openrocket/gui/util/URLUtil.java
@@ -0,0 +1,32 @@
+package net.sf.openrocket.gui.util;
+
+import net.sf.openrocket.util.BugException;
+
+import java.awt.Desktop;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public abstract class URLUtil {
+ public static final String WIKI_URL = "http://wiki.openrocket.info/";
+
+ public static boolean openWebpage(URI uri) {
+ Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
+ if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
+ try {
+ desktop.browse(uri);
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return false;
+ }
+
+ public static boolean openWebpage(String url) {
+ try {
+ return openWebpage(new URI(url));
+ } catch (URISyntaxException e) {
+ throw new BugException("Illegal URL: " + url, e);
+ }
+ }
+}