Merge branch 'kruland-integration' of github.com:plaa/openrocket into

kruland-integration-ui

Conflicts:
	core/src/net/sf/openrocket/file/openrocket/importt/MotorHandler.java
This commit is contained in:
Sampo Niskanen 2013-02-23 08:51:22 +02:00
commit 4c48ec48c9
54 changed files with 2124 additions and 2974 deletions

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<openrocket version="1.5" creator="OpenRocket 12.09.1dev">
<openrocket version="1.4" creator="OpenRocket 12.09.1dev">
<rocket>
<name>High Power Airstart</name>
<comment>This file demonstrates the effect of different airstart timings on overall altitude.</comment>
@ -33,7 +33,6 @@
<bodytube>
<name>Body tube</name>
<preset type="BODY_TUBE" manufacturer="Public Missiles" partno="PML PT-7.512" digest="c7a647e1a06db7fa41ac2ec20858ea0d"/>
<finish>normal</finish>
<material type="bulk" density="958.7050344900001">Kraft phenolic</material>
<length>1.2191999999999998</length>
@ -63,7 +62,6 @@
<bodytube>
<name>AV Bay</name>
<preset type="BODY_TUBE" manufacturer="Public Missiles" partno="PML PT-7.512" digest="c7a647e1a06db7fa41ac2ec20858ea0d"/>
<finish>normal</finish>
<material type="bulk" density="958.7050344900001">Kraft phenolic</material>
<length>0.0508</length>
@ -73,7 +71,6 @@
<subcomponents>
<tubecoupler>
<name>Tube coupler</name>
<preset type="TUBE_COUPLER" manufacturer="Always Ready Rocketry" partno="BT20-191CL" digest="0ec0536414f46211787ee3caeb1ac730"/>
<position type="bottom">0.17779999999999996</position>
<material type="bulk" density="1250.0">Vulcanized Fiber</material>
<length>0.4064</length>
@ -119,7 +116,6 @@
<bodytube>
<name>Body tube</name>
<preset type="BODY_TUBE" manufacturer="Public Missiles" partno="PML PT-7.512" digest="c7a647e1a06db7fa41ac2ec20858ea0d"/>
<finish>normal</finish>
<material type="bulk" density="958.7050344900001">Kraft phenolic</material>
<length>1.2191999999999998</length>
@ -148,7 +144,6 @@
<innertube>
<name>54mm center</name>
<preset type="BODY_TUBE" manufacturer="Public Missiles" partno="PML KS-2.152" digest="abd74b1da651e330e12cd06e8f6a7082"/>
<position type="bottom">0.0</position>
<material type="bulk" density="958.7050344900001">Kraft phenolic</material>
<length>0.36829999999999996</length>
@ -208,7 +203,6 @@
<innertube>
<name>38mm airstart</name>
<preset type="BODY_TUBE" manufacturer="Always Ready Rocketry" partno="BT20-38A" digest="21bf520cfb6ea7d2a81ae0fca7adb285"/>
<position type="bottom">0.0</position>
<material type="bulk" density="1250.0">Vulcanized Fiber</material>
<length>0.35559999999999803</length>
@ -237,9 +231,11 @@
<diameter>0.038</diameter>
<length>0.25</length>
<delay>none</delay>
</motor>
<ignitionconfiguration configid="bc7de2d0-b362-463a-9853-0200700175c7">
<ignitionevent>automatic</ignitionevent>
<ignitiondelay>1.0</ignitiondelay>
</motor>
</ignitionconfiguration>
<motor configid="bbf39580-3dab-4987-9c86-4b3932934a28">
<type>reload</type>
<manufacturer>AeroTech</manufacturer>
@ -248,9 +244,11 @@
<diameter>0.038</diameter>
<length>0.25</length>
<delay>none</delay>
</motor>
<ignitionconfiguration configid="bbf39580-3dab-4987-9c86-4b3932934a28">
<ignitionevent>automatic</ignitionevent>
<ignitiondelay>2.0</ignitiondelay>
</motor>
</ignitionconfiguration>
<motor configid="37647ba6-86f6-423d-a08c-4c68a3215fc0">
<type>reload</type>
<manufacturer>AeroTech</manufacturer>
@ -259,9 +257,11 @@
<diameter>0.038</diameter>
<length>0.25</length>
<delay>none</delay>
</motor>
<ignitionconfiguration configid="37647ba6-86f6-423d-a08c-4c68a3215fc0">
<ignitionevent>automatic</ignitionevent>
<ignitiondelay>4.0</ignitiondelay>
</motor>
</ignitionconfiguration>
<motor configid="7c4d659a-a836-434a-acd4-66e27ae5e053">
<type>reload</type>
<manufacturer>AeroTech</manufacturer>
@ -270,9 +270,11 @@
<diameter>0.038</diameter>
<length>0.25</length>
<delay>none</delay>
</motor>
<ignitionconfiguration configid="7c4d659a-a836-434a-acd4-66e27ae5e053">
<ignitionevent>automatic</ignitionevent>
<ignitiondelay>6.0</ignitiondelay>
</motor>
</ignitionconfiguration>
<ignitionevent>automatic</ignitionevent>
<ignitiondelay>0.0</ignitiondelay>
<overhang>0.0</overhang>
@ -376,7 +378,6 @@
<datapoint>0.097966,0.60807,13.048,139.62,13.048,139.62,0,0,0,0,0,0,0.7854,0,9.8062,0.14085,0,0,0,9.9143,1.5946,12.056,0.089201,NaN,2.2543,NaN,0.038718,2873855,1483.2,1.5759,0.43623,0.49671,0.25684,0.059195,0.12019,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,0.0013456,0.19487,0.029825,1.5708,0,NaN,288.15,101318,340.39,0.0076637,0.0073901</datapoint>
<datapoint>0.10563,0.71217,14.119,139.57,14.119,139.57,0,0,0,0,0,0,0.7854,0,9.8062,0.12959,0,0,0,9.9071,1.5874,12.051,0.089184,NaN,2.2537,NaN,0.041831,3104893,1481.7,1.8122,0.43627,0.48936,0.25684,0.0592,0.12023,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,0.0014561,0.19487,0.029825,1.5708,0,NaN,288.15,101317,340.39,0.0070826,0.0078055</datapoint>
<datapoint>0.11271,0.81568,15.108,139.57,15.108,139.57,0,0,0,0,0,0,0.7854,0,9.8062,0.12055,0,0,0,9.9008,1.5811,12.047,0.089169,NaN,2.2532,NaN,0.04471,3318583,1481,2.0457,0.4363,0.48356,0.25683,0.059206,0.12026,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,0.001558,0.19487,0.029825,1.5708,0,NaN,288.14,101315,340.39,0.0066189,0.0082238</datapoint>
<datapoint>0.11933,0.91873,16.032,139.57,16.032,139.57,0,0,0,0,0,0,0.7854,0,9.8062,0.11309,0,0,0,9.8951,1.5754,12.043,0.089155,NaN,2.2527,NaN,0.047404,3518487,1480.4,2.2774,0.43633,0.47888,0.25682,0.059211,0.12029,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,0.0016534,0.19487,0.029825,1.5708,0,NaN,288.14,101314,340.39,0.0062374,0.0086403</datapoint>
<datapoint>0.12557,1.0215,16.903,139.56,16.903,139.57,0,0,0,0,0,0.66645,0.7854,0,9.8062,0.1068,0,0,0,9.8898,1.57,12.039,0.089142,2.3106,2.2523,0.29928,0.049944,3707012,1479.6,2.5077,0.43636,0.47503,0.25682,0.059216,0.12032,1.2453,0.37292,-3.2233e-4,0,0,0,0,0,0.0017432,0.19487,0.029825,1.5708,0,NaN,288.14,101313,340.38,0.009356,0.0092977</datapoint>
<datapoint>0.13492,1.1857,18.21,139.51,18.21,139.51,-2.965e-5,0,2.965e-5,-3.1416,0.0063882,0.69853,0.7854,0,9.8062,0.098128,0,3.3224e-4,1.1827e-7,9.8833,1.5635,12.035,0.089127,2.3198,2.2517,0.3494,0.053756,3989873,1478.4,2.8736,0.43641,0.4699,0.25681,0.059224,0.12038,1.1263,0.3935,3.347e-4,0,0,0,0,1.2876e-6,0.0018779,0.19487,0.029825,1.5708,-4.2474e-4,NaN,288.14,101311,340.38,0.014034,0.00973</datapoint>
<datapoint>0.14896,1.455,20.169,139.43,20.169,139.43,-1.8963e-4,0,1.8963e-4,-3.1416,0.016517,0.74389,0.7854,0,9.8062,0.087175,0,9.5541e-4,1.7249e-7,9.8735,1.5538,12.028,0.089104,2.3319,2.2509,0.41582,0.059478,4414517,1476.6,3.4719,0.43649,0.46376,0.25679,0.059238,0.12046,0.98038,0.40804,3.3336e-5,0,0,0,0,8.6974e-6,0.0020799,0.19487,0.029825,1.5708,-2.0992e-4,NaN,288.14,101308,340.38,0.021051,0.010155</datapoint>

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -47,7 +47,8 @@ RocketActions.MoveDownAct.ttip.Movedown = Move this component downwards.
RocketPanel.FigTypeAct.Sideview = Side view
RocketPanel.FigTypeAct.Backview = Back view
RocketPanel.FigTypeAct.Figure3D = 3D Figure
RocketPanel.FigTypeAct.Realistic3D = 3D Realistic
RocketPanel.FigTypeAct.Finished = 3D Finished
RocketPanel.FigTypeAct.Unfinished = 3D Unfinished
RocketPanel.lbl.Flightcfg = Flight configuration:
@ -1094,7 +1095,7 @@ TCMotorSelPan.noDescription = No description available.
! PlotDialog
PlotDialog.CheckBox.Showdatapoints = Show data points
PlotDialog.lbl.Chart = mouse wheel to zoom. alt-mouse wheel to zoom x axis only. drag to pan.
PlotDialog.lbl.Chart = left click drag to zoom area. mouse wheel to zoom. ctrl-mouse wheel to zoom x axis only. ctrl-left click drag to pan. right click drag to zoom dynamically.
! "main" prefix is used for the main application dialog

View File

@ -10,19 +10,19 @@ import net.sf.openrocket.util.MathUtil;
* @author Bill Kuker <bkuker@billkuker.com>
*/
public class Appearance {
public static final Appearance MISSING = new Appearance(new Color(0, 0, 0), 100, null);
public static final Appearance MISSING = new Appearance(new Color(0, 0, 0), 1, null);
private final Color paint;
private final double shine;
private final Decal texture;
Appearance(final Color paint, final double shine, final Decal texture) {
public Appearance(final Color paint, final double shine, final Decal texture) {
this.paint = paint;
this.shine = MathUtil.clamp(shine, 0, 1);
this.texture = texture;
}
Appearance(final Color paint, final double shine) {
public Appearance(final Color paint, final double shine) {
this.paint = paint;
this.shine = MathUtil.clamp(shine, 0, 1);
this.texture = null;

View File

@ -9,25 +9,27 @@ import net.sf.openrocket.util.Coordinate;
* @author Bill Kuker <bkuker@billkuker.com>
*/
public class Decal {
public static enum EdgeMode {
REPEAT("TextureWrap.Repeat"), MIRROR("TextureWrap.Mirror"), CLAMP("TextureWrap.Clamp"), STICKER("TextureWrap.Sticker");
private final String transName;
EdgeMode(final String name){
EdgeMode(final String name) {
this.transName = name;
}
@Override
public String toString(){
public String toString() {
return Application.getTranslator().get(transName);
}
}
private final Coordinate offset, center, scale;
private final double rotation;
private final DecalImage image;
private final EdgeMode mode;
Decal(final Coordinate offset, final Coordinate center, final Coordinate scale, final double rotation,
public Decal(final Coordinate offset, final Coordinate center, final Coordinate scale, final double rotation,
final DecalImage image, final EdgeMode mode) {
this.offset = offset;
this.center = center;
@ -36,35 +38,35 @@ public class Decal {
this.image = image;
this.mode = mode;
}
public Coordinate getOffset() {
return offset;
}
public Coordinate getCenter() {
return center;
}
public Coordinate getScale() {
return scale;
}
public double getRotation() {
return rotation;
}
public EdgeMode getEdgeMode() {
return mode;
}
public DecalImage getImage() {
return image;
}
@Override
public String toString() {
return "Texture [offset=" + offset + ", center=" + center + ", scale=" + scale + ", rotation=" + rotation
+ ", image=" + image + "]";
}
}

View File

@ -1,12 +1,18 @@
package net.sf.openrocket.appearance;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import net.sf.openrocket.document.Attachment;
import net.sf.openrocket.util.ChangeSource;
public interface DecalImage extends Attachment {
public void exportImage( File file, boolean watchForChanges ) throws IOException;
public interface DecalImage extends ChangeSource {
public String getName();
public InputStream getBytes() throws FileNotFoundException, IOException;
public void exportImage(File file, boolean watchForChanges) throws IOException;
}

View File

@ -0,0 +1,111 @@
package net.sf.openrocket.appearance.defaults;
import java.util.HashMap;
import net.sf.openrocket.appearance.Appearance;
import net.sf.openrocket.appearance.Decal;
import net.sf.openrocket.appearance.Decal.EdgeMode;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.EngineBlock;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.InnerTube;
import net.sf.openrocket.rocketcomponent.LaunchLug;
import net.sf.openrocket.rocketcomponent.MassObject;
import net.sf.openrocket.rocketcomponent.Parachute;
import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.Transition;
import net.sf.openrocket.rocketcomponent.TubeCoupler;
import net.sf.openrocket.util.Color;
import net.sf.openrocket.util.Coordinate;
public class DefaultAppearance {
private static Appearance simple(String resource) {
return new Appearance(
new Color(1, 1, 1),
0,
new Decal(
new Coordinate(0, 0),
new Coordinate(0, 0),
new Coordinate(1, 1),
0,
new ResourceDecalImage(resource), EdgeMode.REPEAT));
};
private static Appearance simpleAlpha(Color base, float shine, String resource) {
return new Appearance(
base,
shine,
new Decal(
new Coordinate(0, 0),
new Coordinate(0, 0),
new Coordinate(1, 1),
0,
new ResourceDecalImage(resource), EdgeMode.REPEAT));
};
private static Appearance BALSA = simple("/datafiles/textures/balsa.png");
private static Appearance WOOD = simple("/datafiles/textures/wood.png");
@SuppressWarnings("unused")
private static Appearance CARDBOARD = simple("/datafiles/textures/cardboard.png");
private static Appearance HARDBOARD = simple("/datafiles/textures/hardboard.png");
private static Appearance WADDING = simple("/datafiles/textures/wadding.png");
private static Appearance CHUTE = simple("/datafiles/textures/chute.png");
private static final Appearance ESTES_BT = simpleAlpha(new Color(212, 185, 145), .3f, "/datafiles/textures/spiral-wound-alpha.png");
private static final Appearance ESTES_IT = simpleAlpha(new Color(168, 146, 116), .1f, "/datafiles/textures/spiral-wound-alpha.png");
private static final Appearance WHITE_BT = simpleAlpha(new Color(240, 240, 240), .3f, "/datafiles/textures/spiral-wound-alpha.png");
private static Appearance ESTES_MOTOR = simple("/datafiles/textures/motors/estes.png");
private static Appearance AEROTECH_MOTOR = simple("/datafiles/textures/motors/aerotech.png");
private static Appearance REUSABLE_MOTOR = simpleAlpha(new Color(195, 60, 50), .6f, "/datafiles/textures/motors/reusable.png");
private static HashMap<Color, Appearance> plastics = new HashMap<Color, Appearance>();
private static Appearance getPlastic(Color c) {
if (!plastics.containsKey(c)) {
plastics.put(c, new Appearance(c, .3));
}
return plastics.get(c);
}
public static Appearance getDefaultAppearance(RocketComponent c) {
if (c instanceof BodyTube)
return ESTES_BT;
if (c instanceof InnerTube || c instanceof TubeCoupler)
return ESTES_IT;
if (c instanceof FinSet)
return BALSA;
if (c instanceof LaunchLug)
return WHITE_BT;
if (c instanceof Transition)
return getPlastic(new Color(255, 255, 255));
if (c instanceof RadiusRingComponent)
return WOOD;
if (c instanceof Parachute)
return CHUTE;
if (c instanceof EngineBlock)
return HARDBOARD;
if (c instanceof MassObject)
return WADDING;
return Appearance.MISSING;
}
public static Appearance getDefaultAppearance(Motor m) {
if (m instanceof ThrustCurveMotor) {
ThrustCurveMotor tcm = (ThrustCurveMotor) m;
if ("Estes".equals(tcm.getManufacturer().getSimpleName())) {
return ESTES_MOTOR;
}
if ("AeroTech".equals(tcm.getManufacturer().getSimpleName())) {
return AEROTECH_MOTOR;
}
}
return REUSABLE_MOTOR;
}
}

View File

@ -0,0 +1,50 @@
package net.sf.openrocket.appearance.defaults;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.EventListener;
import net.sf.openrocket.appearance.DecalImage;
class ResourceDecalImage implements DecalImage {
final String resource;
ResourceDecalImage(final String resource) {
this.resource = resource;
}
@Override
public String toString() {
return getName();
}
@Override
public String getName() {
return resource;
}
@Override
public InputStream getBytes() throws FileNotFoundException, IOException {
return this.getClass().getResourceAsStream(resource);
}
@Override
public void exportImage(File file, boolean watchForChanges) throws IOException {
}
@Override
public void addChangeListener(EventListener listener) {
//Unimplemented, this can not change
}
@Override
public void removeChangeListener(EventListener listener) {
//Unimplemented, this can not change
}
}

View File

@ -4,12 +4,38 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import net.sf.openrocket.util.AbstractChangeSource;
import net.sf.openrocket.util.ChangeSource;
public interface Attachment extends Comparable<Attachment>, ChangeSource {
public abstract class Attachment extends AbstractChangeSource implements Comparable<Attachment>, ChangeSource {
public abstract String getName();
private final String name;
public Attachment(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public abstract InputStream getBytes() throws FileNotFoundException, IOException;
@Override
public int compareTo(Attachment o) {
return this.name.compareTo(o.name);
}
@Override
public String toString() {
return getName();
}
@Override
public void fireChangeEvent() {
super.fireChangeEvent();
}
}

View File

@ -19,7 +19,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.document.attachments.BaseAttachment;
import net.sf.openrocket.document.attachments.FileSystemAttachment;
import net.sf.openrocket.gui.watcher.FileWatcher;
import net.sf.openrocket.gui.watcher.WatchEvent;
@ -27,6 +26,7 @@ import net.sf.openrocket.gui.watcher.WatchService;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.FileUtils;
import net.sf.openrocket.util.StateChangeListener;
@ -99,7 +99,7 @@ public class DecalRegistry {
return decals;
}
public class DecalImageImpl implements DecalImage, Cloneable {
public class DecalImageImpl implements DecalImage, Cloneable, Comparable<DecalImage>, ChangeSource {
private final Attachment delegate;
@ -115,7 +115,6 @@ public class DecalRegistry {
this.delegate = delegate;
}
@Override
public String getName() {
return name != null ? name : delegate.getName();
}
@ -128,7 +127,6 @@ public class DecalRegistry {
* @throws FileNotFoundException
* @throws IOException
*/
@Override
public InputStream getBytes() throws FileNotFoundException, IOException {
// First check if the decal is located on the file system
File exportedFile = getFileSystemLocation();
@ -164,7 +162,7 @@ public class DecalRegistry {
@Override
public void handleEvent(WatchEvent evt) {
((BaseAttachment) DecalImageImpl.this.delegate).fireChangeEvent();
DecalImageImpl.this.delegate.fireChangeEvent();
System.out.println(this.getFile() + " has changed");
}
@ -191,10 +189,7 @@ public class DecalRegistry {
}
@Override
public int compareTo(Attachment o) {
if (!(o instanceof DecalImageImpl)) {
return -1;
}
public int compareTo(DecalImage o) {
return getName().compareTo(o.getName());
}

View File

@ -1,45 +0,0 @@
package net.sf.openrocket.document.attachments;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import net.sf.openrocket.document.Attachment;
import net.sf.openrocket.util.AbstractChangeSource;
public abstract class BaseAttachment extends AbstractChangeSource implements Attachment {
private final String name;
public BaseAttachment(String name) {
super();
this.name = name;
}
@Override
public final String getName() {
return name;
}
@Override
public abstract InputStream getBytes() throws FileNotFoundException, IOException;
@Override
public int compareTo(Attachment o) {
if (!(o instanceof BaseAttachment)) {
return -1;
}
return this.name.compareTo(((BaseAttachment) o).name);
}
@Override
public String toString() {
return getName();
}
@Override
public void fireChangeEvent() {
super.fireChangeEvent();
}
}

View File

@ -8,7 +8,7 @@ import java.io.InputStream;
import net.sf.openrocket.document.Attachment;
public class FileSystemAttachment extends BaseAttachment implements Attachment {
public class FileSystemAttachment extends Attachment {
private final File location;

View File

@ -11,7 +11,7 @@ import java.util.zip.ZipInputStream;
import net.sf.openrocket.document.Attachment;
import net.sf.openrocket.util.FileUtils;
public class ZipFileAttachment extends BaseAttachment implements Attachment {
public class ZipFileAttachment extends Attachment {
private final URL zipFileLocation;

View File

@ -0,0 +1,74 @@
package net.sf.openrocket.file.openrocket.importt;
import java.util.HashMap;
import java.util.Locale;
import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.file.DocumentLoadingContext;
import net.sf.openrocket.file.simplesax.AbstractElementHandler;
import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.file.simplesax.PlainTextHandler;
import net.sf.openrocket.rocketcomponent.MotorConfiguration;
import org.xml.sax.SAXException;
class IgnitionConfigurationHandler extends AbstractElementHandler {
/** File version where latest digest format was introduced */
private static final int MOTOR_DIGEST_VERSION = 104;
private final DocumentLoadingContext context;
private Double ignitionDelay = null;
private MotorConfiguration.IgnitionEvent ignitionEvent = null;
public IgnitionConfigurationHandler(DocumentLoadingContext context) {
this.context = context;
}
@Override
public ElementHandler openElement(String element, HashMap<String, String> attributes,
WarningSet warnings) {
return PlainTextHandler.INSTANCE;
}
public Double getIgnitionDelay() {
return ignitionDelay;
}
public MotorConfiguration.IgnitionEvent getIgnitionEvent() {
return ignitionEvent;
}
@Override
public void closeElement(String element, HashMap<String, String> attributes,
String content, WarningSet warnings) throws SAXException {
content = content.trim();
if (element.equals("ignitionevent")) {
for (MotorConfiguration.IgnitionEvent e : MotorConfiguration.IgnitionEvent.values()) {
if (e.name().toLowerCase(Locale.ENGLISH).replaceAll("_", "").equals(content)) {
ignitionEvent = e;
break;
}
}
if (ignitionEvent == null) {
warnings.add(Warning.fromString("Unknown ignition event type '" + content + "', ignoring."));
}
} else if (element.equals("ignitiondelay")) {
try {
ignitionDelay = Double.parseDouble(content);
} catch (NumberFormatException nfe) {
warnings.add(Warning.fromString("Illegal ignition delay specified, ignoring."));
}
} else {
super.closeElement(element, attributes, content, warnings);
}
}
}

View File

@ -10,7 +10,6 @@ import net.sf.openrocket.file.simplesax.AbstractElementHandler;
import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.file.simplesax.PlainTextHandler;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.IgnitionConfiguration;
import org.xml.sax.SAXException;
@ -27,9 +26,6 @@ class MotorHandler extends AbstractElementHandler {
private double length = Double.NaN;
private double delay = Double.NaN;
private Double ignitionDelay = null;
private IgnitionConfiguration.IgnitionEvent ignitionEvent = null;
public MotorHandler(DocumentLoadingContext context) {
this.context = context;
}
@ -60,14 +56,6 @@ class MotorHandler extends AbstractElementHandler {
return delay;
}
public Double getIgnitionDelay() {
return ignitionDelay;
}
public IgnitionConfiguration.IgnitionEvent getIgnitionEvent() {
return ignitionEvent;
}
@Override
public void closeElement(String element, HashMap<String, String> attributes,
String content, WarningSet warnings) throws SAXException {
@ -149,24 +137,6 @@ class MotorHandler extends AbstractElementHandler {
}
} else if ( element.equals("ignitionevent")) {
for (IgnitionConfiguration.IgnitionEvent e : IgnitionConfiguration.IgnitionEvent.values()) {
if (e.name().toLowerCase(Locale.ENGLISH).replaceAll("_", "").equals(content)) {
ignitionEvent = e;
break;
}
}
if (ignitionEvent == null) {
warnings.add(Warning.fromString("Unknown ignition event type '" + content + "', ignoring."));
}
} else if ( element.equals("ignitiondelay")) {
try {
ignitionDelay = Double.parseDouble(content);
} catch (NumberFormatException nfe) {
warnings.add(Warning.fromString("Illegal ignition delay specified, ignoring."));
}
} else {
super.closeElement(element, attributes, content, warnings);
}

View File

@ -20,6 +20,7 @@ class MotorMountHandler extends AbstractElementHandler {
private final DocumentLoadingContext context;
private final MotorMount mount;
private MotorHandler motorHandler;
private IgnitionConfigurationHandler ignitionConfigHandler;
public MotorMountHandler(MotorMount mount, DocumentLoadingContext context) {
this.mount = mount;
@ -36,6 +37,11 @@ class MotorMountHandler extends AbstractElementHandler {
return motorHandler;
}
if (element.equals("ignitionconfiguration")) {
ignitionConfigHandler = new IgnitionConfigurationHandler(context);
return ignitionConfigHandler;
}
if (element.equals("ignitionevent") ||
element.equals("ignitiondelay") ||
element.equals("overhang")) {
@ -62,9 +68,18 @@ class MotorMountHandler extends AbstractElementHandler {
Motor motor = motorHandler.getMotor(warnings);
mount.setMotor(id, motor);
mount.setMotorDelay(id, motorHandler.getDelay(warnings));
return;
}
if (element.equals("ignitionconfiguration")) {
String id = attributes.get("configid");
if (id == null || id.equals("")) {
warnings.add(Warning.fromString("Illegal motor specification, ignoring."));
return;
}
MotorConfiguration motorConfig = mount.getFlightConfiguration(id);
motorConfig.setIgnitionEvent( motorHandler.getIgnitionEvent());
motorConfig.setIgnitionDelay( motorHandler.getIgnitionDelay());
motorConfig.setIgnitionEvent(ignitionConfigHandler.getIgnitionEvent());
motorConfig.setIgnitionDelay(ignitionConfigHandler.getIgnitionDelay());
return;
}

View File

@ -179,14 +179,21 @@ public class RocketComponentSaver {
elements.add(" <delay>" + motorConfig.getEjectionDelay() + "</delay>");
}
if (motorConfig.getIgnitionEvent() != null) {
elements.add(" <ignitionevent>" + motorConfig.getIgnitionEvent().name().toLowerCase(Locale.ENGLISH).replace("_", "") + "</ignitionevent>");
}
if (motorConfig.getIgnitionDelay() != null) {
elements.add(" <ignitiondelay>" + motorConfig.getIgnitionDelay() + "</ignitiondelay>");
}
elements.add(" </motor>");
if (motorConfig.getIgnitionEvent() != null || motorConfig.getIgnitionDelay() != null) {
elements.add(" <ignitionconfiguration configid=\"" + id + "\">");
if (motorConfig.getIgnitionEvent() != null) {
elements.add(" <ignitionevent>" + motorConfig.getIgnitionEvent().name().toLowerCase(Locale.ENGLISH).replace("_", "") + "</ignitionevent>");
}
if (motorConfig.getIgnitionDelay() != null) {
elements.add(" <ignitiondelay>" + motorConfig.getIgnitionDelay() + "</ignitiondelay>");
}
elements.add(" </ignitionconfiguration>");
}
}
elements.add(" <ignitionevent>"

View File

@ -16,7 +16,6 @@ import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.file.AttachmentUtils;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
@ -79,7 +78,7 @@ public class ExportDecalDialog extends JDialog {
private void export(DecalImage decal, File selectedFile) {
try {
AttachmentUtils.exportAttachment(decal, selectedFile);
decal.exportImage(selectedFile, false);
} catch (IOException iex) {
// FIXME - probably want a simple user dialog here since FileIO is not really a bug.
throw new BugException(iex);

View File

@ -23,6 +23,7 @@ import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.appearance.AppearanceBuilder;
import net.sf.openrocket.appearance.Decal.EdgeMode;
import net.sf.openrocket.appearance.defaults.DefaultAppearance;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.BooleanModel;
@ -116,7 +117,7 @@ public class AppearancePanel extends JPanel {
public AppearancePanel(final OpenRocketDocument document, final RocketComponent c) {
super(new MigLayout("fill", "[150][grow][150][grow]"));
ab = new AppearanceBuilder(c.getAppearance());
ab = new AppearanceBuilder(c.getAppearance() != null ? c.getAppearance() : DefaultAppearance.getDefaultAppearance(c));
net.sf.openrocket.util.Color figureColor = c.getColor();
if (figureColor == null) {
@ -151,6 +152,7 @@ public class AppearancePanel extends JPanel {
figureColorButton.addActionListener(new ColorActionListener(c, "Color"));
colorButton.addActionListener(new ColorActionListener(ab, "Paint"));
BooleanModel mDefault = new BooleanModel(c.getAppearance() == null);
BooleanModel fDefault = new BooleanModel(c.getColor() == null);
@ -215,12 +217,26 @@ public class AppearancePanel extends JPanel {
add(new JSeparator(SwingConstants.HORIZONTAL), "span, wrap, growx");
{// Texture Header Row
add(new StyledLabel(trans.get("AppearanceCfg.lbl.Appearance"), Style.BOLD), "wrap");
add(new StyledLabel(trans.get("AppearanceCfg.lbl.Appearance"), Style.BOLD));
final JCheckBox materialDefault = new JCheckBox(mDefault);
materialDefault.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (materialDefault.isSelected()) {
c.setAppearance(null);
} else {
c.setAppearance(ab.getAppearance());
}
}
});
materialDefault.setText(trans.get("AppearanceCfg.lbl.Usedefault"));
add(materialDefault, "wrap");
}
{// Texture File
add(new JLabel(trans.get("AppearanceCfg.lbl.Texture")));
JPanel p = new JPanel(new MigLayout("fill, ins 0", "[grow][]"));
mDefault.addEnableComponent(textureDropDown, false);
p.add(textureDropDown, "grow");
add(p, "span 3, growx, wrap");
final JButton editBtn = new JButton(trans.get("AppearanceCfg.but.edit"));
@ -243,12 +259,13 @@ public class AppearancePanel extends JPanel {
}
});
mDefault.addEnableComponent(editBtn, false);
p.add(editBtn);
}
{ // Color
add(new JLabel(trans.get("AppearanceCfg.lbl.color.Color")));
//mDefault.addEnableComponent(colorButton, false);
mDefault.addEnableComponent(colorButton, false);
add(colorButton);
}
@ -258,11 +275,13 @@ public class AppearancePanel extends JPanel {
add(new JLabel("x:"), "split 4");
JSpinner scaleU = new JSpinner(new DoubleModel(ab, "ScaleX", TEXTURE_UNIT).getSpinnerModel());
scaleU.setEditor(new SpinnerEditor(scaleU));
mDefault.addEnableComponent(scaleU, false);
add(scaleU, "w 40");
add(new JLabel("y:"));
JSpinner scaleV = new JSpinner(new DoubleModel(ab, "ScaleY", TEXTURE_UNIT).getSpinnerModel());
scaleV.setEditor(new SpinnerEditor(scaleV));
mDefault.addEnableComponent(scaleV, false);
add(scaleV, "wrap, w 40");
}
@ -274,6 +293,10 @@ public class AppearancePanel extends JPanel {
JSlider slide = new JSlider(shineModel.getSliderModel(0, 1));
UnitSelector unit = new UnitSelector(shineModel);
mDefault.addEnableComponent(slide, false);
mDefault.addEnableComponent(spin, false);
mDefault.addEnableComponent(unit, false);
add(spin, "split 3, w 50");
add(unit);
add(slide, "w 50");
@ -286,11 +309,13 @@ public class AppearancePanel extends JPanel {
add(new JLabel("x:"), "split 4");
JSpinner offsetU = new JSpinner(new DoubleModel(ab, "OffsetU", TEXTURE_UNIT).getSpinnerModel());
offsetU.setEditor(new SpinnerEditor(offsetU));
mDefault.addEnableComponent(offsetU, false);
add(offsetU, "w 40");
add(new JLabel("y:"));
JSpinner offsetV = new JSpinner(new DoubleModel(ab, "OffsetV", TEXTURE_UNIT).getSpinnerModel());
offsetV.setEditor(new SpinnerEditor(offsetV));
mDefault.addEnableComponent(offsetV, false);
add(offsetV, "wrap, w 40");
}
@ -299,6 +324,7 @@ public class AppearancePanel extends JPanel {
EdgeMode[] list = new EdgeMode[EdgeMode.values().length + 1];
System.arraycopy(EdgeMode.values(), 0, list, 1, EdgeMode.values().length);
JComboBox combo = new JComboBox(new EnumModel<EdgeMode>(ab, "EdgeMode", list));
mDefault.addEnableComponent(combo, false);
add(combo);
}
@ -308,9 +334,11 @@ public class AppearancePanel extends JPanel {
DoubleModel rotationModel = new DoubleModel(ab, "Rotation", UnitGroup.UNITS_ANGLE);
JSpinner rotation = new JSpinner(rotationModel.getSpinnerModel());
rotation.setEditor(new SpinnerEditor(rotation));
mDefault.addEnableComponent(rotation, false);
add(rotation, "split 3, w 50");
add(new UnitSelector(rotationModel));
BasicSlider bs = new BasicSlider(rotationModel.getSliderModel(-Math.PI, Math.PI));
mDefault.addEnableComponent(bs, false);
add(bs, "w 50, wrap");
}

View File

@ -15,6 +15,7 @@ import javax.media.opengl.glu.GLUtessellatorCallback;
import javax.media.opengl.glu.GLUtessellatorCallbackAdapter;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
import net.sf.openrocket.rocketcomponent.FinSet;
@ -314,10 +315,9 @@ public class ComponentRenderer {
}
public void renderMotor(final GL2 gl, final Coordinate c, double l, double r) {
final float outside[] = { 0.2f, 0.2f, 0.2f, 1.0f };
gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, outside, 0);
gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, outside, 0);
public void renderMotor(final GL2 gl, final Coordinate c, Motor motor) {
double l = motor.getLength();
double r = motor.getDiameter() / 2;
gl.glPushMatrix();
@ -325,15 +325,48 @@ public class ComponentRenderer {
gl.glRotated(90, 0, 1.0, 0);
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPushMatrix();
gl.glTranslated(0, .125, 0);
gl.glScaled(1, .75, 0);
glu.gluCylinder(q, r, r, l, LOD, 1);
glu.gluDisk(q, r, 0, LOD, 2);
gl.glPopMatrix();
gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
{
final double da = (2.0f * Math.PI) / LOD;
final double dt = 1.0 / LOD;
gl.glBegin(GL.GL_TRIANGLE_STRIP);
gl.glNormal3d(0, 0, 1);
for (int i = 0; i < LOD + 1; i++) {
gl.glTexCoord2d(i * dt, .125);
gl.glVertex3d(r * Math.cos(da * i), r * Math.sin(da * i), 0);
gl.glTexCoord2d(i * dt, 0);
gl.glVertex3d(0, 0, 0);
}
gl.glEnd();
}
gl.glTranslated(0, 0, l);
gl.glRotated(180, 0, 1.0, 0);
glu.gluDisk(q, r, 0, LOD, 2);
{
final double da = (2.0f * Math.PI) / LOD;
final double dt = 1.0 / LOD;
gl.glBegin(GL.GL_TRIANGLE_STRIP);
gl.glNormal3d(0, 0, 1);
for (int i = 0; i < LOD + 1; i++) {
gl.glTexCoord2d(i * dt, .875);
gl.glVertex3d(r * Math.cos(da * i), r * Math.sin(da * i), 0);
gl.glTexCoord2d(i * dt, 1);
gl.glVertex3d(0, 0, 0);
}
gl.glEnd();
}
gl.glPopMatrix();
}
}

View File

@ -8,6 +8,7 @@ import javax.media.opengl.GL2ES1;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.fixedfunc.GLLightingFunc;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.ExternalComponent;
import net.sf.openrocket.rocketcomponent.NoseCone;
@ -16,6 +17,7 @@ import net.sf.openrocket.rocketcomponent.SymmetricComponent;
import net.sf.openrocket.rocketcomponent.Transition;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Color;
import net.sf.openrocket.util.Coordinate;
public class FigureRenderer extends RocketRenderer {
private final float[] color = new float[4];
@ -156,4 +158,13 @@ public class FigureRenderer extends RocketRenderer {
out[2] = Math.max(0.2f, (float) color.getBlue() / 255f) * 2;
}
}
@Override
protected void renderMotor(GL2 gl, Coordinate c, Motor motor) {
final float outside[] = { 0.2f, 0.2f, 0.2f, 1.0f };
gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, outside, 0);
gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, outside, 0);
super.renderMotor(gl, c, motor);
}
}

View File

@ -14,9 +14,12 @@ import javax.media.opengl.fixedfunc.GLMatrixFunc;
import net.sf.openrocket.appearance.Appearance;
import net.sf.openrocket.appearance.Decal;
import net.sf.openrocket.appearance.defaults.DefaultAppearance;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Color;
import net.sf.openrocket.util.Coordinate;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureData;
@ -92,8 +95,26 @@ public class RealisticRenderer extends RocketRenderer {
}
@Override
public void renderComponent(GL2 gl, RocketComponent c, float alpha) {
final Appearance a = getAppearance(c);
protected void renderMotor(final GL2 gl, final Coordinate c, final Motor motor) {
render(gl, new Runnable() {
@Override
public void run() {
cr.renderMotor(gl, c, motor);
}
}, DefaultAppearance.getDefaultAppearance(motor), 1);
}
@Override
public void renderComponent(final GL2 gl, final RocketComponent c, final float alpha) {
render(gl, new Runnable() {
@Override
public void run() {
cr.renderGeometry(gl, c);
}
}, getAppearance(c), alpha);
}
private void render(GL2 gl, Runnable g, Appearance a, float alpha) {
final Decal t = a.getTexture();
final Texture tex = getTexture(t);
@ -116,7 +137,7 @@ public class RealisticRenderer extends RocketRenderer {
gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_SPECULAR, colorBlack, 0);
gl.glMateriali(GL.GL_BACK, GLLightingFunc.GL_SHININESS, 0);
cr.renderGeometry(gl, c);
g.run();
if (t != null && tex != null) {
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_LINEAR);
@ -155,7 +176,7 @@ public class RealisticRenderer extends RocketRenderer {
gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotrophy);
}
cr.renderGeometry(gl, c);
g.run();
if (t.getEdgeMode() == Decal.EdgeMode.STICKER) {
gl.glDepthFunc(GL.GL_LESS);
@ -220,10 +241,10 @@ public class RealisticRenderer extends RocketRenderer {
}
private Appearance getAppearance(RocketComponent c) {
protected Appearance getAppearance(RocketComponent c) {
Appearance ret = c.getAppearance();
if (ret == null) {
ret = Appearance.MISSING;
ret = DefaultAppearance.getDefaultAppearance(c);
}
return ret;
}

View File

@ -51,8 +51,9 @@ import com.jogamp.opengl.util.awt.Overlay;
*/
public class RocketFigure3d extends JPanel implements GLEventListener {
public static final int TYPE_REALISTIC = 0;
public static final int TYPE_FIGURE = 1;
public static final int TYPE_FIGURE = 0;
public static final int TYPE_UNFINISHED = 1;
public static final int TYPE_FINISHED = 2;
private static final long serialVersionUID = 1L;
private static final LogHelper log = Application.getLogger();
@ -67,8 +68,8 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
private static double fovX = Double.NaN;
private static final int CARET_SIZE = 20;
private OpenRocketDocument document;
private Configuration configuration;
private final OpenRocketDocument document;
private final Configuration configuration;
private GLCanvas canvas;
@ -90,7 +91,7 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
RocketRenderer rr = new FigureRenderer();
public RocketFigure3d(OpenRocketDocument document, Configuration config) {
public RocketFigure3d(final OpenRocketDocument document, final Configuration config) {
this.document = document;
this.configuration = config;
this.setLayout(new BorderLayout());
@ -131,10 +132,10 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
log.debug("Setting up GL capabilities...");
log.verbose("GL - Getting Default Profile");
GLProfile glp = GLProfile.get(GLProfile.GL2);
final GLProfile glp = GLProfile.get(GLProfile.GL2);
log.verbose("GL - creating GLCapabilities");
GLCapabilities caps = new GLCapabilities(glp);
final GLCapabilities caps = new GLCapabilities(glp);
log.verbose("GL - setSampleBuffers");
caps.setSampleBuffers(true);
@ -217,21 +218,21 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
MouseEvent pressEvent;
@Override
public void mousePressed(MouseEvent e) {
public void mousePressed(final MouseEvent e) {
lastX = e.getX();
lastY = e.getY();
pressEvent = e;
}
@Override
public void mouseClicked(MouseEvent e) {
public void mouseClicked(final MouseEvent e) {
pickPoint = new Point(lastX, canvas.getHeight() - lastY);
pickEvent = e;
internalRepaint();
}
@Override
public void mouseDragged(MouseEvent e) {
public void mouseDragged(final MouseEvent e) {
int dx = lastX - e.getX();
int dy = lastY - e.getY();
lastX = e.getX();
@ -257,13 +258,9 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
canvas.addMouseListener(a);
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
updateFigure();
}
@Override
public void display(GLAutoDrawable drawable) {
public void display(final GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
GLU glu = new GLU();
@ -307,7 +304,7 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
}
private void drawCarets(GL2 gl, GLU glu) {
private void drawCarets(final GL2 gl, final GLU glu) {
final Graphics2D og2d = caretOverlay.createGraphics();
setRenderingHints(og2d);
@ -355,7 +352,7 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
* Re-blits the overlay every frame. Only re-renders the overlay
* when needed.
*/
private void drawExtras(GL2 gl, GLU glu) {
private void drawExtras(final GL2 gl, final GLU glu) {
//Only re-render if needed
// redrawExtras: Some external change (new simulation data) means
// the data is out of date.
@ -391,17 +388,17 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
}
@Override
public void dispose(GLAutoDrawable drawable) {
public void dispose(final GLAutoDrawable drawable) {
log.verbose("GL - dispose() called");
rr.dispose(drawable);
}
@Override
public void init(GLAutoDrawable drawable) {
public void init(final GLAutoDrawable drawable) {
log.verbose("GL - init()");
rr.init(drawable);
GL2 gl = drawable.getGL().getGL2();
final GL2 gl = drawable.getGL().getGL2();
gl.glClearDepth(1.0f); // clear z-buffer to the farthest
gl.glDepthFunc(GL.GL_LESS); // the type of depth test to do
@ -426,12 +423,12 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
}
@Override
public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {
public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int w, final int h) {
log.verbose("GL - reshape()");
GL2 gl = drawable.getGL().getGL2();
GLU glu = new GLU();
final GL2 gl = drawable.getGL().getGL2();
final GLU glu = new GLU();
double ratio = (double) w / (double) h;
final double ratio = (double) w / (double) h;
fovX = fovY * ratio;
gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
@ -461,8 +458,8 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
if (cachedBounds != null) {
return cachedBounds;
} else {
Bounds b = new Bounds();
Collection<Coordinate> bounds = configuration.getBounds();
final Bounds b = new Bounds();
final Collection<Coordinate> bounds = configuration.getBounds();
for (Coordinate c : bounds) {
b.xMax = Math.max(b.xMax, c.x);
b.xMin = Math.min(b.xMin, c.x);
@ -484,21 +481,21 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
}
}
private void setupView(GL2 gl, GLU glu) {
private void setupView(final GL2 gl, final GLU glu) {
gl.glLoadIdentity();
gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_POSITION,
lightPosition, 0);
// Get the bounds
Bounds b = calculateBounds();
final Bounds b = calculateBounds();
// Calculate the distance needed to fit the bounds in both the X and Y
// direction
// Add 10% for space around it.
double dX = (b.xSize * 1.2 / 2.0)
final double dX = (b.xSize * 1.2 / 2.0)
/ Math.tan(Math.toRadians(fovX / 2.0));
double dY = (b.rMax * 2.0 * 1.2 / 2.0)
final double dY = (b.rMax * 2.0 * 1.2 / 2.0)
/ Math.tan(Math.toRadians(fovY / 2.0));
// Move back the greater of the 2 distances
@ -551,7 +548,7 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
private Set<RocketComponent> selection = new HashSet<RocketComponent>();
public void setSelection(RocketComponent[] selection) {
public void setSelection(final RocketComponent[] selection) {
this.selection.clear();
if (selection != null) {
for (RocketComponent c : selection)
@ -560,14 +557,14 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
internalRepaint();
}
private void setRoll(double rot) {
private void setRoll(final double rot) {
if (MathUtil.equals(roll, rot))
return;
this.roll = MathUtil.reduce360(rot);
internalRepaint();
}
private void setYaw(double rot) {
private void setYaw(final double rot) {
if (MathUtil.equals(yaw, rot))
return;
this.yaw = MathUtil.reduce360(rot);
@ -576,16 +573,16 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
// ///////////// Extra methods
private Coordinate project(Coordinate c, GL2 gl, GLU glu) {
double[] mvmatrix = new double[16];
double[] projmatrix = new double[16];
int[] viewport = new int[4];
private Coordinate project(final Coordinate c, final GL2 gl, final GLU glu) {
final double[] mvmatrix = new double[16];
final double[] projmatrix = new double[16];
final int[] viewport = new int[4];
gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
gl.glGetDoublev(GLMatrixFunc.GL_MODELVIEW_MATRIX, mvmatrix, 0);
gl.glGetDoublev(GLMatrixFunc.GL_PROJECTION_MATRIX, projmatrix, 0);
double out[] = new double[4];
final double out[] = new double[4];
glu.gluProject(c.x, c.y, c.z, mvmatrix, 0, projmatrix, 0, viewport, 0,
out, 0);
@ -596,22 +593,22 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
private Coordinate cp = new Coordinate(0, 0, 0);
private Coordinate cg = new Coordinate(0, 0, 0);
public void setCG(Coordinate cg) {
public void setCG(final Coordinate cg) {
this.cg = cg;
redrawExtras = true;
}
public void setCP(Coordinate cp) {
public void setCP(final Coordinate cp) {
this.cp = cp;
redrawExtras = true;
}
public void addRelativeExtra(FigureElement p) {
public void addRelativeExtra(final FigureElement p) {
relativeExtra.add(p);
redrawExtras = true;
}
public void removeRelativeExtra(FigureElement p) {
public void removeRelativeExtra(final FigureElement p) {
relativeExtra.remove(p);
redrawExtras = true;
}
@ -621,12 +618,12 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
redrawExtras = true;
}
public void addAbsoluteExtra(FigureElement p) {
public void addAbsoluteExtra(final FigureElement p) {
absoluteExtra.add(p);
redrawExtras = true;
}
public void removeAbsoluteExtra(FigureElement p) {
public void removeAbsoluteExtra(final FigureElement p) {
absoluteExtra.remove(p);
redrawExtras = true;
}
@ -654,8 +651,10 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
rr.dispose(drawable);
if (t == TYPE_FIGURE) {
rr = new FigureRenderer();
} else {
} else if (t == TYPE_FINISHED) {
rr = new RealisticRenderer(document);
} else if (t == TYPE_UNFINISHED) {
rr = new UnfinishedRenderer(document);
}
rr.init(drawable);
return false;

View File

@ -178,16 +178,19 @@ public abstract class RocketRenderer {
MotorMount mount = iterator.next();
Motor motor = mount.getMotor(motorID);
double length = motor.getLength();
double radius = motor.getDiameter() / 2;
Coordinate[] position = ((RocketComponent) mount).toAbsolute(new Coordinate(((RocketComponent) mount)
.getLength() + mount.getMotorOverhang() - length));
for (int i = 0; i < position.length; i++) {
cr.renderMotor(gl, position[i], length, radius);
renderMotor(gl, position[i], motor);
}
}
}
protected void renderMotor(GL2 gl, Coordinate c, Motor motor) {
cr.renderMotor(gl, c, motor);
}
}

View File

@ -0,0 +1,25 @@
package net.sf.openrocket.gui.figure3d;
import net.sf.openrocket.appearance.Appearance;
import net.sf.openrocket.appearance.defaults.DefaultAppearance;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.InnerTube;
import net.sf.openrocket.rocketcomponent.RocketComponent;
public class UnfinishedRenderer extends RealisticRenderer {
public UnfinishedRenderer(OpenRocketDocument document) {
super(document);
}
@Override
public boolean isDrawnTransparent(RocketComponent c) {
return c instanceof BodyTube || c instanceof InnerTube;
}
@Override
protected Appearance getAppearance(RocketComponent c) {
return DefaultAppearance.getDefaultAppearance(c);
}
}

View File

@ -1,6 +1,5 @@
package net.sf.openrocket.gui.plot;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
@ -128,8 +127,8 @@ public class PlotConfiguration implements Cloneable {
DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]);
}
/** Bonus given for the first type being on the first axis */
private static final double BONUS_FIRST_TYPE_ON_FIRST_AXIS = 1.0;
@ -145,11 +144,11 @@ public class PlotConfiguration implements Cloneable {
/** Bonus given for only using a single axis. */
private static final double BONUS_ONLY_ONE_AXIS = 50.0;
private static final double INCLUDE_ZERO_DISTANCE = 0.3; // 30% of total range
/** The data types to be plotted. */
private ArrayList<FlightDataType> plotDataTypes = new ArrayList<FlightDataType>();
@ -164,14 +163,14 @@ public class PlotConfiguration implements Cloneable {
private FlightDataType domainAxisType = null;
private Unit domainAxisUnit = null;
/** All available axes. */
private final int axesCount;
private ArrayList<Axis> allAxes = new ArrayList<Axis>();
private String name = null;
public PlotConfiguration() {
this(null, FlightDataType.TYPE_TIME);
}
@ -191,7 +190,7 @@ public class PlotConfiguration implements Cloneable {
}
//// Axis
public FlightDataType getDomainAxisType() {
return domainAxisType;
}
@ -221,7 +220,7 @@ public class PlotConfiguration implements Cloneable {
}
//// FlightDataTypes
public void addPlotDataType(FlightDataType type) {
plotDataTypes.add(type);
plotDataUnits.add(type.getUnitGroup().getDefaultUnit());
@ -278,7 +277,7 @@ public class PlotConfiguration implements Cloneable {
}
public FlightDataType getType(int index) {
return plotDataTypes.get(index);
}
@ -315,9 +314,9 @@ public class PlotConfiguration implements Cloneable {
}
public List<Axis> getAllAxes() {
List<Axis> list = new ArrayList<Axis>();
list.addAll(allAxes);
@ -342,7 +341,7 @@ public class PlotConfiguration implements Cloneable {
}
/**
* Find the best combination of the auto-selectable axes.
*
@ -357,8 +356,8 @@ public class PlotConfiguration implements Cloneable {
}
/**
* Recursively search for the best combination of the auto-selectable axes.
* This is a brute-force search method.
@ -377,13 +376,13 @@ public class PlotConfiguration implements Cloneable {
break;
}
if (autoindex >= plotDataAxes.size()) {
// All axes have been assigned, just return since we are already the best
return new Pair<PlotConfiguration, Double>(copy, copy.getGoodnessValue(data));
}
// Set the auto-selected index one at a time and choose the best one
PlotConfiguration best = null;
double bestValue = Double.NEGATIVE_INFINITY;
@ -400,9 +399,9 @@ public class PlotConfiguration implements Cloneable {
}
/**
* Fit the axes to hold the provided data. All of the plotDataAxis elements must
* be non-negative.
@ -452,7 +451,7 @@ public class PlotConfiguration implements Cloneable {
}
}
// Check whether to use a common zero
Axis left = allAxes.get(0);
Axis right = allAxes.get(1);
@ -462,8 +461,8 @@ public class PlotConfiguration implements Cloneable {
Double.isNaN(left.getMinValue()) || Double.isNaN(right.getMinValue()))
return;
//// Compute common zero
// TODO: MEDIUM: This algorithm may require tweaking
@ -474,7 +473,7 @@ public class PlotConfiguration implements Cloneable {
// Calculate and round scaling factor
double scale = Math.max(left.getRangeLength(), right.getRangeLength()) /
Math.min(left.getRangeLength(), right.getRangeLength());
Math.min(left.getRangeLength(), right.getRangeLength());
//System.out.println("Scale: " + scale);
@ -494,54 +493,6 @@ public class PlotConfiguration implements Cloneable {
min2 /= scale;
max2 /= scale;
// Scale to unit length
// double scale1 = left.getRangeLength();
// double scale2 = right.getRangeLength();
//
// double min1 = left.getMinValue() / scale1;
// double max1 = left.getMaxValue() / scale1;
// double min2 = right.getMinValue() / scale2;
// double max2 = right.getMaxValue() / scale2;
//
// // Combine unit ranges
// min1 = MathUtil.min(min1, min2);
// min2 = min1;
// max1 = MathUtil.max(max1, max2);
// max2 = max1;
//
// // Scale up
// min1 *= scale1;
// max1 *= scale1;
// min2 *= scale2;
// max2 *= scale2;
//
// // Compute common scale
// double range1 = max1-min1;
// double range2 = max2-min2;
//
// double scale = MathUtil.max(range1, range2) / MathUtil.min(range1, range2);
// double roundScale = roundScale(scale);
//
// if (range2 < range1) {
// if (roundScale < scale) {
// min2 = min1 / roundScale;
// max2 = max1 / roundScale;
// } else {
// min1 = min2 * roundScale;
// max1 = max2 * roundScale;
// }
// } else {
// if (roundScale > scale) {
// min2 = min1 * roundScale;
// max2 = max1 * roundScale;
// } else {
// min1 = min2 / roundScale;
// max1 = max2 / roundScale;
// }
// }
// Apply scale
left.addBound(min1);
left.addBound(max1);
@ -551,7 +502,7 @@ public class PlotConfiguration implements Cloneable {
}
private double roundScale(double scale) {
double mul = 1;
while (scale >= 10) {
@ -580,7 +531,7 @@ public class PlotConfiguration implements Cloneable {
}
@SuppressWarnings("unused")
private double roundScaleUp(double scale) {
double mul = 1;
@ -633,7 +584,7 @@ public class PlotConfiguration implements Cloneable {
}
/**
* Fits the axis ranges to the data and returns the "goodness value" of this
* selection of axes. All plotDataAxis elements must be non-null.
@ -675,11 +626,11 @@ public class PlotConfiguration implements Cloneable {
goodness += d * 100.0;
}
/*
* Add extra points for specific things.
*/
// A little for the first type being on the first axis
if (plotDataAxes.get(0) == 0)
goodness += BONUS_FIRST_TYPE_ON_FIRST_AXIS;
@ -703,7 +654,7 @@ public class PlotConfiguration implements Cloneable {
}
/**
* Reset the units of this configuration to the default units. Returns this
* PlotConfiguration.
@ -718,8 +669,8 @@ public class PlotConfiguration implements Cloneable {
}
@Override
public PlotConfiguration clone() {
try {
@ -740,7 +691,7 @@ public class PlotConfiguration implements Cloneable {
return copy;
} catch (CloneNotSupportedException e) {
throw new BugException("BUG! Could not clone().");
}

View File

@ -15,7 +15,6 @@ import javax.swing.BorderFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.Pannable;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
@ -35,81 +34,86 @@ import com.jogamp.newt.event.InputEvent;
*
*/
public class SimulationChart extends ChartPanel {
private Point2D panLast;
private Point startPoint;
private double panW;
private double panH;
private enum Interaction {ZOOM, PAN};
private Interaction interaction = Interaction.PAN;
private enum Interaction {
ZOOM
};
private Interaction interaction = null;
private MouseWheelHandler mouseWheelHandler = null;
public SimulationChart(JFreeChart chart) {
super(chart,
/* properties */false,
/* save */ true,
/* print */ false,
/* zoom */ true,
/* tooltips */ true);
/* save */true,
/* print */false,
/* zoom */true,
/* tooltips */true);
this.setMouseWheelEnabled(true);
this.setEnforceFileExtensions(true);
this.setInitialDelay(500);
this.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
}
@Override
public boolean isMouseWheelEnabled() {
return mouseWheelHandler != null;
}
@Override
public void setMouseWheelEnabled(boolean flag) {
if ( flag && mouseWheelHandler == null ) {
if (flag && mouseWheelHandler == null) {
this.mouseWheelHandler = new MouseWheelHandler();
this.addMouseWheelListener(this.mouseWheelHandler);
} else if ( !flag && mouseWheelHandler != null ) {
} else if (!flag && mouseWheelHandler != null) {
this.removeMouseWheelListener(this.mouseWheelHandler);
this.mouseWheelHandler = null;
}
}
@Override
public void mousePressed(MouseEvent e) {
if ( e.getButton() == MouseEvent.BUTTON1 || e.getButton() == MouseEvent.BUTTON3) {
// if no modifiers, use pan
if (e.getButton() == MouseEvent.BUTTON3) {
Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY());
if ( screenDataArea != null && screenDataArea.contains(e.getPoint())) {
this.panW = screenDataArea.getWidth();
if (screenDataArea != null && screenDataArea.contains(e.getPoint())) {
this.panW = screenDataArea.getWidth();
this.panH = screenDataArea.getHeight();
this.panLast = e.getPoint();
this.startPoint = e.getPoint();
}
}
if ( e.getButton() == MouseEvent.BUTTON2 ) {
// middle/scroll button
} else if ( e.getButton() == MouseEvent.BUTTON3 ) { // right button
interaction = Interaction.ZOOM;
setCursor (Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
} else if ( e.getButton() == MouseEvent.BUTTON1 ) { // left button
interaction = Interaction.PAN;
setCursor( Cursor.getPredefinedCursor( Cursor.MOVE_CURSOR));
setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
}
else {
interaction = null;
super.mousePressed(e);
}
}
@Override
public void mouseDragged(MouseEvent e) {
if ( panLast == null ) {
if (interaction == null) {
super.mouseDragged(e);
return;
}
if (panLast == null) {
return;
}
double dx = e.getX() - this.panLast.getX();
double dy = e.getY() - this.panLast.getY();
if ( dx == 0.0 && dy == 0.0 ) {
return ;
if (dx == 0.0 && dy == 0.0) {
return;
}
double wPercent = -dx / this.panW;
double hPercent = dy / this.panH;
@ -117,49 +121,44 @@ public class SimulationChart extends ChartPanel {
this.getChart().getPlot().setNotify(false);
switch (interaction) {
case PAN:
Pannable p = (Pannable) this.getChart().getPlot();
if ( p.getOrientation() == PlotOrientation.VERTICAL){
p.panDomainAxes( wPercent, this.getChartRenderingInfo().getPlotInfo(),panLast);
p.panRangeAxes( hPercent, this.getChartRenderingInfo().getPlotInfo(),panLast);
} else {
p.panDomainAxes( hPercent, this.getChartRenderingInfo().getPlotInfo(),panLast);
p.panRangeAxes( wPercent, this.getChartRenderingInfo().getPlotInfo(),panLast);
}
break;
case ZOOM:
Zoomable pz = (Zoomable) this.getChart().getPlot();
double zoomx = 1 + 2*wPercent;
double zoomy = 1 + 2*hPercent;
Point2D anchor = SimulationChart.this.translateScreenToJava2D(startPoint);
if ( pz.getOrientation() == PlotOrientation.VERTICAL) {
pz.zoomDomainAxes(zoomx, this.getChartRenderingInfo().getPlotInfo(), anchor, true);
pz.zoomRangeAxes(zoomy, this.getChartRenderingInfo().getPlotInfo(), anchor, true);
} else {
pz.zoomRangeAxes(zoomx, this.getChartRenderingInfo().getPlotInfo(), anchor, true);
pz.zoomDomainAxes(zoomy, this.getChartRenderingInfo().getPlotInfo(), anchor, true);
}
break;
case ZOOM:
Zoomable pz = (Zoomable) this.getChart().getPlot();
double zoomx = 1 + 2 * wPercent;
double zoomy = 1 + 2 * hPercent;
Point2D anchor = SimulationChart.this.translateScreenToJava2D(startPoint);
if (pz.getOrientation() == PlotOrientation.VERTICAL) {
pz.zoomDomainAxes(zoomx, this.getChartRenderingInfo().getPlotInfo(), anchor, true);
pz.zoomRangeAxes(zoomy, this.getChartRenderingInfo().getPlotInfo(), anchor, true);
} else {
pz.zoomRangeAxes(zoomx, this.getChartRenderingInfo().getPlotInfo(), anchor, true);
pz.zoomDomainAxes(zoomy, this.getChartRenderingInfo().getPlotInfo(), anchor, true);
}
break;
}
this.panLast = e.getPoint();
this.getChart().getPlot().setNotify(old);
}
@Override
public void mouseReleased(MouseEvent e) {
if ( this.panLast != null ) {
if (interaction == null) {
super.mouseReleased(e);
return;
}
if (this.panLast != null) {
this.panLast = null;
setCursor(Cursor.getDefaultCursor());
}
interaction = null;
}
/**
*
@ -173,97 +172,97 @@ public class SimulationChart extends ChartPanel {
* @since 1.0.13
*/
class MouseWheelHandler implements MouseWheelListener, Serializable {
/** The zoom factor. */
double zoomFactor;
/**
* Creates a new instance for the specified chart panel.
*
* @param chartPanel the chart panel (<code>null</code> not permitted).
*/
public MouseWheelHandler() {
this.zoomFactor = 0.10;
}
/**
* Returns the current zoom factor. The default value is 0.10 (ten
* percent).
*
* @return The zoom factor.
*
* @see #setZoomFactor(double)
*/
public double getZoomFactor() {
return this.zoomFactor;
}
/**
* Sets the zoom factor.
*
* @param zoomFactor the zoom factor.
*
* @see #getZoomFactor()
*/
public void setZoomFactor(double zoomFactor) {
this.zoomFactor = zoomFactor;
}
/**
* Handles a mouse wheel event from the underlying chart panel.
*
* @param e the event.
*/
public void mouseWheelMoved(MouseWheelEvent e) {
JFreeChart chart = SimulationChart.this.getChart();
if (chart == null) {
return;
}
Plot plot = chart.getPlot();
if (plot instanceof Zoomable) {
Zoomable zoomable = (Zoomable) plot;
handleZoomable(zoomable, e);
}
else if (plot instanceof PiePlot) {
PiePlot pp = (PiePlot) plot;
pp.handleMouseWheelRotation(e.getWheelRotation());
}
}
/**
* Handle the case where a plot implements the {@link Zoomable} interface.
*
* @param zoomable the zoomable plot.
* @param e the mouse wheel event.
*/
private void handleZoomable(Zoomable zoomable, MouseWheelEvent e) {
// don't zoom unless the mouse pointer is in the plot's data area
ChartRenderingInfo info = SimulationChart.this.getChartRenderingInfo();
PlotRenderingInfo pinfo = info.getPlotInfo();
Point2D p = SimulationChart.this.translateScreenToJava2D(e.getPoint());
if (!pinfo.getDataArea().contains(p)) {
return;
}
Plot plot = (Plot) zoomable;
// do not notify while zooming each axis
boolean notifyState = plot.isNotify();
plot.setNotify(false);
int clicks = e.getWheelRotation();
double zf = 1.0 + this.zoomFactor;
if (clicks < 0) {
zf = 1.0 / zf;
}
if (SimulationChart.this.isDomainZoomable()) {
zoomable.zoomDomainAxes(zf, pinfo, p, true);
}
boolean domainOnly = ( e.getModifiers() & InputEvent.ALT_MASK ) != 0;
if (SimulationChart.this.isRangeZoomable() && !domainOnly ) {
zoomable.zoomRangeAxes(zf, pinfo, p, true);
}
plot.setNotify(notifyState); // this generates the change event too
}
/** The zoom factor. */
double zoomFactor;
/**
* Creates a new instance for the specified chart panel.
*
* @param chartPanel the chart panel (<code>null</code> not permitted).
*/
public MouseWheelHandler() {
this.zoomFactor = 0.10;
}
/**
* Returns the current zoom factor. The default value is 0.10 (ten
* percent).
*
* @return The zoom factor.
*
* @see #setZoomFactor(double)
*/
public double getZoomFactor() {
return this.zoomFactor;
}
/**
* Sets the zoom factor.
*
* @param zoomFactor the zoom factor.
*
* @see #getZoomFactor()
*/
public void setZoomFactor(double zoomFactor) {
this.zoomFactor = zoomFactor;
}
/**
* Handles a mouse wheel event from the underlying chart panel.
*
* @param e the event.
*/
public void mouseWheelMoved(MouseWheelEvent e) {
JFreeChart chart = SimulationChart.this.getChart();
if (chart == null) {
return;
}
Plot plot = chart.getPlot();
if (plot instanceof Zoomable) {
Zoomable zoomable = (Zoomable) plot;
handleZoomable(zoomable, e);
}
else if (plot instanceof PiePlot) {
PiePlot pp = (PiePlot) plot;
pp.handleMouseWheelRotation(e.getWheelRotation());
}
}
/**
* Handle the case where a plot implements the {@link Zoomable} interface.
*
* @param zoomable the zoomable plot.
* @param e the mouse wheel event.
*/
private void handleZoomable(Zoomable zoomable, MouseWheelEvent e) {
// don't zoom unless the mouse pointer is in the plot's data area
ChartRenderingInfo info = SimulationChart.this.getChartRenderingInfo();
PlotRenderingInfo pinfo = info.getPlotInfo();
Point2D p = SimulationChart.this.translateScreenToJava2D(e.getPoint());
if (!pinfo.getDataArea().contains(p)) {
return;
}
Plot plot = (Plot) zoomable;
// do not notify while zooming each axis
boolean notifyState = plot.isNotify();
plot.setNotify(false);
int clicks = e.getWheelRotation();
double zf = 1.0 + this.zoomFactor;
if (clicks < 0) {
zf = 1.0 / zf;
}
if (SimulationChart.this.isDomainZoomable()) {
zoomable.zoomDomainAxes(zf, pinfo, p, true);
}
boolean domainOnly = (e.getModifiers() & InputEvent.CTRL_MASK) != 0;
if (SimulationChart.this.isRangeZoomable() && !domainOnly) {
zoomable.zoomRangeAxes(zf, pinfo, p, true);
}
plot.setNotify(notifyState); // this generates the change event too
}
}
}

View File

@ -29,9 +29,13 @@ import net.sf.openrocket.util.LinearInterpolator;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.LegendItemSource;
import org.jfree.chart.annotations.XYImageAnnotation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.LineBorder;
import org.jfree.chart.plot.DefaultDrawingSupplier;
import org.jfree.chart.plot.Marker;
import org.jfree.chart.plot.PlotOrientation;
@ -39,6 +43,7 @@ import org.jfree.chart.plot.ValueMarker;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.Range;
import org.jfree.data.xy.XYSeries;
@ -46,83 +51,99 @@ import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.text.TextUtilities;
import org.jfree.ui.LengthAdjustmentType;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.TextAnchor;
/*
* It should be possible to simplify this code quite a bit by using a single Renderer instance for
* both datasets and the legend. But for now, the renderers are queried for the line color information
* and this is held in the Legend.
*/
public class SimulationPlot {
private static final float PLOT_STROKE_WIDTH = 1.5f;
private final JFreeChart chart;
private final PlotConfiguration config;
private final Simulation simulation;
private final PlotConfiguration filled;
private final List<EventDisplayInfo> eventList;
private final List<ModifiedXYItemRenderer> renderers = new ArrayList<ModifiedXYItemRenderer>();
private final List<ModifiedXYItemRenderer> renderers = new ArrayList<ModifiedXYItemRenderer>();
private final LegendItems legendItems;
int branchCount;
void setShowPoints( boolean showPoints ) {
void setShowPoints(boolean showPoints) {
for (ModifiedXYItemRenderer r : renderers) {
r.setBaseShapesVisible(showPoints);
}
}
void setShowBranch( int branch ) {
void setShowBranch(int branch) {
XYPlot plot = (XYPlot) chart.getPlot();
int datasetcount = plot.getDatasetCount();
for( int i =0; i< datasetcount; i++ ) {
int seriescount = ((XYSeriesCollection)plot.getDataset(i)).getSeriesCount();
XYItemRenderer r = ((XYPlot)chart.getPlot()).getRenderer(i);
for( int j=0; j<seriescount; j++) {
boolean show = (branch<0) || (j % branchCount == branch);
for (int i = 0; i < datasetcount; i++) {
int seriescount = ((XYSeriesCollection) plot.getDataset(i)).getSeriesCount();
XYItemRenderer r = ((XYPlot) chart.getPlot()).getRenderer(i);
for (int j = 0; j < seriescount; j++) {
boolean show = (branch < 0) || (j % branchCount == branch);
r.setSeriesVisible(j, show);
}
}
drawDomainMarkers(branch);
}
SimulationPlot( Simulation simulation, PlotConfiguration config, boolean initialShowPoints ) {
SimulationPlot(Simulation simulation, PlotConfiguration config, boolean initialShowPoints) {
this.simulation = simulation;
this.config = config;
this.branchCount = simulation.getSimulatedData().getBranchCount();
this.chart = ChartFactory.createXYLineChart(
//// Simulated flight
simulation.getName(),
null,
null,
null,
PlotOrientation.VERTICAL,
true,
true,
false
/*title*/simulation.getName(),
/*xAxisLabel*/null,
/*yAxisLabel*/null,
/*dataset*/null,
/*orientation*/PlotOrientation.VERTICAL,
/*legend*/false,
/*tooltips*/true,
/*urls*/false
);
this.legendItems = new LegendItems();
LegendTitle legend = new LegendTitle(legendItems);
legend.setMargin(new RectangleInsets(1.0, 1.0, 1.0, 1.0));
legend.setFrame(new LineBorder());
legend.setBackgroundPaint(Color.white);
legend.setPosition(RectangleEdge.BOTTOM);
chart.addSubtitle(legend);
chart.addSubtitle(new TextTitle(config.getName()));
this.branchCount = simulation.getSimulatedData().getBranchCount();
// Fill the auto-selections based on first branch selected.
FlightDataBranch mainBranch = simulation.getSimulatedData().getBranch( 0 );
FlightDataBranch mainBranch = simulation.getSimulatedData().getBranch(0);
this.filled = config.fillAutoAxes(mainBranch);
List<Axis> axes = filled.getAllAxes();
// Create the data series for both axes
XYSeriesCollection[] data = new XYSeriesCollection[2];
data[0] = new XYSeriesCollection();
data[1] = new XYSeriesCollection();
// Get the domain axis type
final FlightDataType domainType = filled.getDomainAxisType();
final Unit domainUnit = filled.getDomainAxisUnit();
if (domainType == null) {
throw new IllegalArgumentException("Domain axis type not specified.");
}
// Get plot length (ignore trailing NaN's)
int typeCount = filled.getTypeCount();
// Create the XYSeries objects from the flight data and store into the collections
String[] axisLabel = new String[2];
for (int i = 0; i < typeCount; i++) {
@ -131,34 +152,73 @@ public class SimulationPlot {
Unit unit = filled.getUnit(i);
int axis = filled.getAxis(i);
String name = getLabel(type, unit);
this.legendItems.lineLabels.add(name);
List<String> seriesNames = Util.generateSeriesLabels(simulation);
for( int branchIndex=0; branchIndex<branchCount; branchIndex++ ) {
// Populate data for each branch.
// The primary branch (branchIndex = 0) is easy since all the data is copied
{
int branchIndex = 0;
FlightDataBranch thisBranch = simulation.getSimulatedData().getBranch(branchIndex);
// Store data in provided units
List<Double> plotx = thisBranch.get(domainType);
List<Double> ploty = thisBranch.get(type);
XYSeries series = new XYSeries(seriesNames.get(branchIndex) + ": " + name, false, true);
series.setDescription(thisBranch.getBranchName()+": " + name);
series.setDescription(thisBranch.getBranchName() + ": " + name);
int pointCount = plotx.size();
for (int j = 0; j < pointCount; j++) {
series.add(domainUnit.toUnit(plotx.get(j)), unit.toUnit(ploty.get(j)));
}
data[axis].addSeries(series);
}
// For each of the secondary branches, we use data from branch 0 for the earlier times
for (int branchIndex = 1; branchIndex < branchCount; branchIndex++) {
FlightDataBranch primaryBranch = simulation.getSimulatedData().getBranch(0);
FlightDataBranch thisBranch = simulation.getSimulatedData().getBranch(branchIndex);
// Get first time index used in secondary branch;
double firstSampleTime = thisBranch.get(FlightDataType.TYPE_TIME).get(0);
XYSeries series = new XYSeries(seriesNames.get(branchIndex) + ": " + name, false, true);
series.setDescription(thisBranch.getBranchName() + ": " + name);
// Copy the first points from the primaryBranch.
List<Double> primaryT = primaryBranch.get(FlightDataType.TYPE_TIME);
List<Double> primaryx = primaryBranch.get(domainType);
List<Double> primaryy = primaryBranch.get(type);
for (int j = 0; j < primaryT.size(); j++) {
if (primaryT.get(j) >= firstSampleTime) {
break;
}
series.add(domainUnit.toUnit(primaryx.get(j)), unit.toUnit(primaryy.get(j)));
}
// Now copy all the data from the secondary branch
List<Double> plotx = thisBranch.get(domainType);
List<Double> ploty = thisBranch.get(type);
int pointCount = plotx.size();
for (int j = 0; j < pointCount; j++) {
series.add(domainUnit.toUnit(plotx.get(j)), unit.toUnit(ploty.get(j)));
}
data[axis].addSeries(series);
}
// Update axis label
if (axisLabel[axis] == null)
axisLabel[axis] = type.getName();
else
axisLabel[axis] += "; " + type.getName();
}
// Add the data and formatting to the plot
XYPlot plot = chart.getXYPlot();
plot.setDomainPannable(true);
plot.setRangePannable(true);
int axisno = 0;
for (int i = 0; i < 2; i++) {
// Check whether axis has any data
@ -170,40 +230,56 @@ public class SimulationPlot {
axis.setLabel(axisLabel[i]);
// axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
plot.setRangeAxis(axisno, axis);
double domainMin = data[i].getDomainLowerBound(true);
double domainMax = data[i].getDomainUpperBound(true);
plot.setDomainAxis(new PresetNumberAxis(domainMin, domainMax));
// Add data and map to the axis
plot.setDataset(axisno, data[i]);
ModifiedXYItemRenderer r = new ModifiedXYItemRenderer(branchCount);
renderers.add(r);
plot.setRenderer(axisno, r);
r.setBaseShapesVisible(initialShowPoints);
r.setBaseShapesFilled(true);
for (int j = 0; j < data[i].getSeriesCount(); j++) {
r.setSeriesStroke(j, new BasicStroke(PLOT_STROKE_WIDTH));
Stroke lineStroke = new BasicStroke(PLOT_STROKE_WIDTH);
r.setSeriesStroke(j, lineStroke);
}
renderers.add(r);
plot.setRenderer(axisno, r);
// Now we pull the colors for the legend.
for (int j = 0; j < data[i].getSeriesCount(); j += branchCount) {
Paint linePaint = r.lookupSeriesPaint(j);
this.legendItems.linePaints.add(linePaint);
Shape itemShape = r.lookupSeriesShape(j);
this.legendItems.pointShapes.add(itemShape);
Stroke lineStroke = r.getSeriesStroke(j);
this.legendItems.lineStrokes.add(lineStroke);
}
plot.mapDatasetToRangeAxis(axisno, axisno);
axisno++;
}
}
plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
plot.addDomainMarker(new ValueMarker(0));
plot.addRangeMarker(new ValueMarker(0));
// Create list of events to show (combine event too close to each other)
this.eventList = buildEventInfo();
// Create the event markers
drawDomainMarkers(-1);
}
JFreeChart getJFreeChart() {
return chart;
}
private String getLabel(FlightDataType type, Unit unit) {
String name = type.getName();
if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
@ -211,16 +287,16 @@ public class SimulationPlot {
name += " (" + unit.getUnit() + ")";
return name;
}
private void drawDomainMarkers( int stage ) {
private void drawDomainMarkers(int stage) {
XYPlot plot = chart.getXYPlot();
FlightDataBranch mainBranch = simulation.getSimulatedData().getBranch( 0 );
FlightDataBranch mainBranch = simulation.getSimulatedData().getBranch(0);
// Clear existing domain markers
plot.clearDomainMarkers();
// Construct domain marker lists collapsing based on time.
List<Double> eventTimes = new ArrayList<Double>();
List<String> eventLabels = new ArrayList<String>();
List<Color> eventColors = new ArrayList<Color>();
@ -231,25 +307,25 @@ public class SimulationPlot {
String text = null;
Color color = null;
Image image = null;
for( EventDisplayInfo info : eventList ) {
if ( stage >=0 && stage != info.stage ) {
for (EventDisplayInfo info : eventList) {
if (stage >= 0 && stage != info.stage) {
continue;
}
double t = info.time;
FlightEvent.Type type = info.event.getType();
if (Math.abs(t - prevTime) <= 0.05) {
if (!typeSet.contains(type)) {
text = text + ", " + type.toString();
color = EventGraphics.getEventColor(type);
image = EventGraphics.getEventImage(type);
typeSet.add(type);
}
} else {
if (text != null) {
eventTimes.add(prevTime);
eventLabels.add(text);
@ -263,7 +339,7 @@ public class SimulationPlot {
typeSet.clear();
typeSet.add(type);
}
}
if (text != null) {
eventTimes.add(prevTime);
@ -275,13 +351,13 @@ public class SimulationPlot {
// Plot the markers
if (config.getDomainAxisType() == FlightDataType.TYPE_TIME) {
// Domain time is plotted as vertical markers
for ( int i=0; i<eventTimes.size(); i++ ) {
for (int i = 0; i < eventTimes.size(); i++) {
double t = eventTimes.get(i);
String event = eventLabels.get(i);
Color color = eventColors.get(i);
ValueMarker m = new ValueMarker(t);
m.setLabel(event);
m.setPaint(color);
@ -289,41 +365,41 @@ public class SimulationPlot {
m.setAlpha(0.7f);
plot.addDomainMarker(m);
}
} else {
// Other domains are plotted as image annotations
List<Double> time = mainBranch.get(FlightDataType.TYPE_TIME);
List<Double> domain = mainBranch.get(config.getDomainAxisType());
LinearInterpolator domainInterpolator = new LinearInterpolator( time, domain );
for ( int i=0; i<eventTimes.size(); i++ ) {
LinearInterpolator domainInterpolator = new LinearInterpolator(time, domain);
for (int i = 0; i < eventTimes.size(); i++) {
double t = eventTimes.get(i);
String event = eventLabels.get(i);
Image image = eventImages.get(i);
if (image == null)
continue;
double xcoord = domainInterpolator.getValue(t);
for (int index = 0; index < config.getTypeCount(); index++) {
FlightDataType type = config.getType(index);
List<Double> range = mainBranch.get(type);
LinearInterpolator rangeInterpolator = new LinearInterpolator(time, range);
// Image annotations are not supported on the right-side axis
// TODO: LOW: Can this be achieved by JFreeChart?
if (filled.getAxis(index) != SimulationPlotPanel.LEFT) {
continue;
}
double ycoord = rangeInterpolator.getValue(t);
// Convert units
xcoord = config.getDomainAxisUnit().toUnit(xcoord);
ycoord = config.getUnit(index).toUnit(ycoord);
XYImageAnnotation annotation =
new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
annotation.setToolTipText(event);
@ -335,12 +411,12 @@ public class SimulationPlot {
private List<EventDisplayInfo> buildEventInfo() {
ArrayList<EventDisplayInfo> eventList = new ArrayList<EventDisplayInfo>();
for( int branch=0; branch<branchCount; branch++ ) {
for (int branch = 0; branch < branchCount; branch++) {
List<FlightEvent> events = simulation.getSimulatedData().getBranch(branch).getEvents();
for( FlightEvent event : events ) {
for (FlightEvent event : events) {
FlightEvent.Type type = event.getType();
if ( type != FlightEvent.Type.ALTITUDE && config.isEventActive(type)) {
if (type != FlightEvent.Type.ALTITUDE && config.isEventActive(type)) {
EventDisplayInfo info = new EventDisplayInfo();
info.stage = branch;
info.time = event.getTime();
@ -351,11 +427,13 @@ public class SimulationPlot {
}
Collections.sort(eventList, new Comparator<EventDisplayInfo>() {
@Override
public int compare(EventDisplayInfo o1, EventDisplayInfo o2) {
if ( o1.time< o2.time) return -1;
if ( o1.time == o2.time)return 0;
if (o1.time < o2.time)
return -1;
if (o1.time == o2.time)
return 0;
return 1;
}
@ -363,61 +441,49 @@ public class SimulationPlot {
return eventList;
/*
double prevTime = -100;
String text = null;
Color color = null;
Image image = null;
for ( int branch=0; branch<branchCount; branch++ ) {
List<FlightEvent> events = simulation.getSimulatedData().getBranch(branch).getEvents();
for (int i = 0; i < events.size(); i++) {
FlightEvent event = events.get(i);
double t = event.getTime();
FlightEvent.Type type = event.getType();
if (type != FlightEvent.Type.ALTITUDE && config.isEventActive(type)) {
if (Math.abs(t - prevTime) <= 0.05) {
if (!typeSet.contains(type)) {
text = text + ", " + type.toString();
color = EventGraphics.getEventColor(type);
image = EventGraphics.getEventImage(type);
typeSet.add(type);
}
} else {
if (text != null) {
EventDisplayInfo info = new EventDisplayInfo();
info.time = prevTime;
info.event = text;
info.color = color;
info.image = image;
eventList.add(info);
}
prevTime = t;
text = type.toString();
color = EventGraphics.getEventColor(type);
image = EventGraphics.getEventImage(type);
typeSet.clear();
typeSet.add(type);
}
}
}
}
if (text != null) {
EventDisplayInfo info = new EventDisplayInfo();
info.time = prevTime;
info.event = text;
info.color = color;
info.image = image;
eventList.add(info);
}
return eventList;
*/
}
private static class LegendItems implements LegendItemSource {
private final List<String> lineLabels = new ArrayList<String>();
private final List<Paint> linePaints = new ArrayList<Paint>();
private final List<Stroke> lineStrokes = new ArrayList<Stroke>();
private final List<Shape> pointShapes = new ArrayList<Shape>();
@Override
public LegendItemCollection getLegendItems() {
LegendItemCollection c = new LegendItemCollection();
int i = 0;
for (String s : lineLabels) {
String label = s;
String description = s;
String toolTipText = null;
String urlText = null;
boolean shapeIsVisible = false;
Shape shape = pointShapes.get(i);
boolean shapeIsFilled = false;
Paint fillPaint = linePaints.get(i);
boolean shapeOutlineVisible = false;
Paint outlinePaint = linePaints.get(i);
Stroke outlineStroke = lineStrokes.get(i);
boolean lineVisible = true;
Stroke lineStroke = lineStrokes.get(i);
Paint linePaint = linePaints.get(i);
Shape legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
LegendItem result = new LegendItem(label, description, toolTipText,
urlText, shapeIsVisible, shape, shapeIsFilled, fillPaint,
shapeOutlineVisible, outlinePaint, outlineStroke, lineVisible,
legendLine, lineStroke, linePaint);
c.add(result);
i++;
}
return c;
}
}
/**
* A modification to the standard renderer that renders the domain marker
* labels vertically instead of horizontally.
@ -435,68 +501,68 @@ public class SimulationPlot {
* series c stage 1
*/
private static class ModifiedXYItemRenderer extends XYLineAndShapeRenderer {
private final int branchCount;
private ModifiedXYItemRenderer( int branchCount ) {
private ModifiedXYItemRenderer(int branchCount) {
this.branchCount = branchCount;
}
@Override
public Paint lookupSeriesPaint(int series) {
return super.lookupSeriesPaint(series/branchCount);
return super.lookupSeriesPaint(series / branchCount);
}
@Override
public Paint lookupSeriesFillPaint(int series) {
return super.lookupSeriesFillPaint(series/branchCount);
return super.lookupSeriesFillPaint(series / branchCount);
}
@Override
public Paint lookupSeriesOutlinePaint(int series) {
return super.lookupSeriesOutlinePaint(series/branchCount);
return super.lookupSeriesOutlinePaint(series / branchCount);
}
@Override
public Stroke lookupSeriesStroke(int series) {
return super.lookupSeriesStroke(series/branchCount);
return super.lookupSeriesStroke(series / branchCount);
}
@Override
public Stroke lookupSeriesOutlineStroke(int series) {
return super.lookupSeriesOutlineStroke(series/branchCount);
return super.lookupSeriesOutlineStroke(series / branchCount);
}
@Override
public Shape lookupSeriesShape(int series) {
return DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE[series%branchCount%DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE.length];
return DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE[series % branchCount % DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE.length];
}
@Override
public Shape lookupLegendShape(int series) {
return DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE[series%branchCount%DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE.length];
return DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE[series % branchCount % DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE.length];
}
@Override
public Font lookupLegendTextFont(int series) {
return super.lookupLegendTextFont(series/branchCount);
return super.lookupLegendTextFont(series / branchCount);
}
@Override
public Paint lookupLegendTextPaint(int series) {
return super.lookupLegendTextPaint(series/branchCount);
return super.lookupLegendTextPaint(series / branchCount);
}
@Override
public void drawDomainMarker(Graphics2D g2, XYPlot plot, ValueAxis domainAxis,
Marker marker, Rectangle2D dataArea) {
if (!(marker instanceof ValueMarker)) {
// Use parent for all others
super.drawDomainMarker(g2, plot, domainAxis, marker, dataArea);
return;
}
/*
* Draw the normal marker, but with rotated text.
* Copied from the overridden method.
@ -507,9 +573,9 @@ public class SimulationPlot {
if (!range.contains(value)) {
return;
}
double v = domainAxis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
PlotOrientation orientation = plot.getOrientation();
Line2D line = null;
if (orientation == PlotOrientation.HORIZONTAL) {
@ -517,14 +583,14 @@ public class SimulationPlot {
} else {
line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
}
final Composite originalComposite = g2.getComposite();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, marker
.getAlpha()));
g2.setPaint(marker.getPaint());
g2.setStroke(marker.getStroke());
g2.draw(line);
String label = marker.getLabel();
RectangleAnchor anchor = marker.getLabelAnchor();
if (label != null) {
@ -533,8 +599,8 @@ public class SimulationPlot {
g2.setPaint(marker.getLabelPaint());
Point2D coordinates = calculateDomainMarkerTextAnchorPoint(g2,
orientation, dataArea, line.getBounds2D(), marker
.getLabelOffset(), LengthAdjustmentType.EXPAND, anchor);
.getLabelOffset(), LengthAdjustmentType.EXPAND, anchor);
// Changed:
TextAnchor textAnchor = TextAnchor.TOP_RIGHT;
TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX() + 2,
@ -543,29 +609,42 @@ public class SimulationPlot {
}
g2.setComposite(originalComposite);
}
}
private static class PresetNumberAxis extends NumberAxis {
private final double min;
private final double max;
public PresetNumberAxis(double min, double max) {
this.min = min;
this.max = max;
autoAdjustRange();
}
@Override
protected void autoAdjustRange() {
this.setRange(min, max);
}
@Override
public void setRange(Range range) {
double lowerValue = range.getLowerBound();
double upperValue = range.getUpperBound();
if (lowerValue < min || upperValue > max) {
// Don't blow past the min & max of the range this is important to keep
// panning constrained within the current bounds.
return;
}
super.setRange(new Range(lowerValue, upperValue));
}
}
private static class EventDisplayInfo {
int stage;
double time;
FlightEvent event;
}
}

View File

@ -91,16 +91,21 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
private static final Translator trans = Application.getTranslator();
/*RocketPanel.FigTypeAct.Sideview = Side view
RocketPanel.FigTypeAct.Backview = Back view
RocketPanel.FigViewAct.3DFigure = 3D Figure
RocketPanel.FigViewAct.3DRealistic = 3D Realistic*/
private static enum VIEW_TYPE {
Sideview,
Backview,
Figure3D,
Realistic3D;
public static enum VIEW_TYPE {
Sideview(false, RocketFigure.TYPE_SIDE),
Backview(false, RocketFigure.TYPE_BACK),
Figure3D(true, RocketFigure3d.TYPE_FIGURE),
Unfinished(true, RocketFigure3d.TYPE_UNFINISHED),
Finished(true, RocketFigure3d.TYPE_FINISHED);
public final boolean is3d;
private final int type;
private VIEW_TYPE(final boolean is3d, final int type) {
this.is3d = is3d;
this.type = type;
};
@Override
public String toString() {
return trans.get("RocketPanel.FigTypeAct." + super.toString());
@ -280,23 +285,12 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
public void setSelectedItem(Object o) {
super.setSelectedItem(o);
VIEW_TYPE v = (VIEW_TYPE) o;
switch (v) {
case Sideview:
figure.setType(RocketFigure.TYPE_SIDE);
go2D();
break;
case Backview:
figure.setType(RocketFigure.TYPE_BACK);
go2D();
break;
case Realistic3D:
figure3d.setType(RocketFigure3d.TYPE_REALISTIC);
if (v.is3d) {
figure3d.setType(v.type);
go3D();
break;
case Figure3D:
figure3d.setType(RocketFigure3d.TYPE_FIGURE);
go3D();
break;
} else {
figure.setType(v.type);
go2D();
}
}
};

View File

@ -4,8 +4,6 @@ import net.sf.openrocket.gui.watcher.WatchService;
import net.sf.openrocket.gui.watcher.WatchServiceImpl;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.util.watcher.DirectoryChangeReactor;
import net.sf.openrocket.util.watcher.DirectoryChangeReactorImpl;
import com.google.inject.AbstractModule;
@ -16,7 +14,6 @@ public class ApplicationModule extends AbstractModule {
bind(LogHelper.class).toInstance(Application.getLogger());
bind(Preferences.class).toInstance(Application.getPreferences());
bind(Translator.class).toInstance(Application.getTranslator());
bind(DirectoryChangeReactor.class).to(DirectoryChangeReactorImpl.class);
bind(WatchService.class).to(WatchServiceImpl.class);
}

View File

@ -1,87 +0,0 @@
package net.sf.openrocket.util.watcher;
import java.io.File;
import java.util.HashMap;
/**
* A kind of watched file that is a directory.
*/
public class Directory extends WatchedFile {
/**
* The contents.
*/
private final HashMap<String, WatchedFile> contents = new HashMap<String, WatchedFile>();
/**
* Internal lock object.
*/
private final Object lock = new Object();
/**
* Construct a directory to be watched.
*
* @param dir the directory to be watched
*
* @throws IllegalArgumentException if dir is null, does not exist, or is not a directory
*/
public Directory(File dir) throws IllegalArgumentException {
super(dir);
if (dir == null || !dir.isDirectory() || !dir.exists()) {
throw new IllegalArgumentException("Invalid directory.");
}
init();
}
/**
* Initialize the directory handling.
*/
private void init() {
String[] result = list();
for (String s : result) {
File t = new File(getTarget(), s);
if (t.exists()) {
contents.put(s, new WatchedFile(t));
}
}
}
/**
* Get the size of the directory's immediate contents (the number of files or subdirectories).
*
* @return the number of files and directories in this directory (not deep)
*/
public int size() {
return list().length;
}
/**
* Get the list of filenames within this directory.
*
* @return an array of filenames
*/
String[] list() {
synchronized (lock) {
return getTarget().list();
}
}
/**
* Get the watched file contents.
*
* @return a map of watched files
*/
protected final HashMap<String, WatchedFile> getContents() {
return contents;
}
/**
* Shared lock.
*
* @return a lock
*/
protected final Object getLock() {
return lock;
}
}

View File

@ -1,26 +0,0 @@
package net.sf.openrocket.util.watcher;
/**
* This interface abstracts the public API for a directory change reactor. In order to use the watcher subsystem, clients may use the default
* change reactor (that implements this interface), or use the WatchService directly. This interface is more of a convenience abstraction.
* <p/>
* This only monitors directories. If you want to monitor an individual file, it is recommended that you monitor the directory that the file resides
* within, then filter the WatchEvents accordingly.
*/
public interface DirectoryChangeReactor {
/**
* Register an event handler with the reactor. The event handler will be called when either a creation, modification, or deletion event is detected
* in the watched directory. Or a modification or a deletion event detected upon a watched file.
*
* @param theEventHandler the handler to be called when an event is detected
*/
void registerHandler(WatchedEventHandler<Directory> theEventHandler);
/**
* Unregister an event handler with the reactor.
*
* @param theEventHandler the handler
*/
void unregisterHandler(WatchedEventHandler<Directory> theEventHandler);
}

View File

@ -1,229 +0,0 @@
package net.sf.openrocket.util.watcher;
import com.google.inject.Inject;
import net.sf.openrocket.logging.LogHelper;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* This class is responsible for monitoring changes to files and directories and dispatching handle events appropriately. In order to use the watcher
* subsystem, clients may use the default change reactor (this class), or use the WatchService directly. This class is more of a convenience
* abstraction and doesn't necessarily represent the most efficient mechanism in all situations, but is sufficient for general purpose file watching
* (short of using JDK 7).
* <p/>
* This reactor creates a new WatchService for each handler. A handler owns the business logic to be performed whenever a state change is detected in a
* monitored directory. Each handler can monitor one directory (and optionally that directory's subdirectories). It's possible to implement a
* different reactor that allows one handler to monitor many different directories or files.
* <p/>
* The default polling interval is 5 seconds. To override the polling interval a system property (openrocket.watcher.poll) can be specified. The time
* value must be specified in milliseconds.
* <p/>
* For example, to change the interval to 10 seconds: -Dopenrocket.watcher.poll=10000
* <p/>
* <p/>
* Example Usage of this class:
* <pre>
* <code>
*
* class MyHandler extends WatchedEventHandler<Directory> {
*
* private Directory dirToWatch = new Directory(new File("/tmp"));
*
* public Directory watchTarget() {
* return dirToWatch;
* }
*
* public boolean watchRecursively() {
* return true;
* }
*
* public void handleEvents(List<WatchEvent<?>> theEvents) {
* for (int i = 0; i < events.size(); i++) {
* WatchEvent<?> watchEvent = events.get(i);
* // process the event
* }
* }
* }
* </code>
*
* Guice (assuming that the binding is performed in an appropriate module):
* {@literal @}Inject
* DirectoryChangeReactor reactor;
* reactor.registerHandler(new MyHandler());
*
* Programmatically:
* DirectoryChangeReactor reactor = new DirectoryChangeReactorImpl();
* reactor.registerHandler(new MyHandler());
* </pre>
*/
public class DirectoryChangeReactorImpl implements DirectoryChangeReactor {
/**
* Property for polling frequency.
*/
public static final String WATCHER_POLLING_INTERVAL_PROPERTY = "openrocket.watcher.poll";
/**
* The polling delay. Defaults to 5 seconds.
*/
public static final long DEFAULT_POLLING_DELAY = 5000;
/**
* The polling delay. Defaults to 5 seconds.
*/
private static long pollingDelay = DEFAULT_POLLING_DELAY;
@Inject
private LogHelper log;
/**
* Scheduler.
*/
private ScheduledExecutorService scheduler;
/**
* The ScheduledFuture.
*/
private ScheduledFuture directoryPollerFuture;
/**
* The runnable that does most of the work.
*/
private final DirectoryPoller directoryPoller = new DirectoryPoller();
/**
* The collection of registered handlers. A new watch service is created for each handler.
*/
private final Map<WatchedEventHandler, WatchService> activeWatchers = new ConcurrentHashMap<WatchedEventHandler, WatchService>();
/**
* Synchronization object.
*/
private final Object lock = new Object();
static {
try {
if (System.getProperty(WATCHER_POLLING_INTERVAL_PROPERTY) != null) {
pollingDelay = Long.parseLong(System.getProperty(WATCHER_POLLING_INTERVAL_PROPERTY));
}
}
catch (Exception e) {
pollingDelay = DEFAULT_POLLING_DELAY;
}
}
/**
* Constructor.
*/
public DirectoryChangeReactorImpl() {
this(LogHelper.getInstance());
}
/**
* Injected constructor.
*/
@Inject
public DirectoryChangeReactorImpl(LogHelper theLogger) {
log = theLogger;
startup();
}
/**
* Idempotent initialization.
*/
private void startup() {
synchronized (lock) {
if (scheduler != null) {
scheduler.shutdownNow();
}
scheduler = Executors.newScheduledThreadPool(1);
scheduleFuture();
}
}
/**
* Cause all watch services to close. This method is idempotent.
*/
public void shutdown() {
synchronized (lock) {
if (scheduler != null) {
scheduler.shutdownNow();
}
for (Iterator<WatchedEventHandler> iterator = activeWatchers.keySet().iterator(); iterator.hasNext(); ) {
WatchedEventHandler next = iterator.next();
activeWatchers.get(next).close();
iterator.remove();
}
}
}
@Override
public void registerHandler(WatchedEventHandler<Directory> theEventHandler) {
synchronized (lock) {
unregisterHandler(theEventHandler);
final WatchService watchService = new WatchService(theEventHandler.watchRecursively());
watchService.register(theEventHandler.watchTarget());
activeWatchers.put(theEventHandler, watchService);
}
}
@Override
public void unregisterHandler(WatchedEventHandler<Directory> theEventHandler) {
synchronized (lock) {
if (activeWatchers.containsKey(theEventHandler)) {
activeWatchers.get(theEventHandler).close();
activeWatchers.remove(theEventHandler);
}
}
}
/**
* Schedule the periodic poll.
*/
private void scheduleFuture() {
if (directoryPollerFuture != null
&& !directoryPollerFuture.isDone()
&& !directoryPollerFuture.isCancelled()) {
directoryPollerFuture.cancel(false);
}
directoryPollerFuture = scheduler.schedule(directoryPoller, pollingDelay, TimeUnit.MILLISECONDS);
}
/**
* This runnable is responsible for checking all watch services for new events.
*/
private final class DirectoryPoller implements Runnable {
@Override
public void run() {
try {
if (!activeWatchers.isEmpty()) {
for (Iterator<WatchedEventHandler> iterator = activeWatchers.keySet().iterator(); iterator.hasNext(); ) {
WatchedEventHandler next = iterator.next();
try {
WatchService watchService = activeWatchers.get(next);
Collection<? extends WatchService.WatchKey> watchKeyCollection = watchService.poll();
if (!watchKeyCollection.isEmpty()) {
for (WatchService.WatchKey watchKey : watchKeyCollection) {
next.handleEvents(watchKey.pollEvents());
}
}
}
catch (Exception e) {
log.error("Error notifying handler of watch event. Removing registration.", e);
iterator.remove();
}
}
}
}
finally {
scheduleFuture();
}
}
}
}

View File

@ -1,231 +0,0 @@
package net.sf.openrocket.util.watcher;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class checks for changes in directories. Supported events: file creation, subdirectory creation, file modification, file and subdirectory
* deletion.
* <p/>
* Synchronize calls to this class externally to ensure thread safety.
*/
final class DirectoryMonitor {
/**
* The directories being monitored.
*/
private Set<Directory> monitored = new HashSet<Directory>();
/**
* Flag that indicates if subdirectories should automatically be monitored when they are created.
*/
private boolean monitorSubdirectories = false;
/**
* Constructor. Self registration is set to false.
*/
DirectoryMonitor() {
this(false);
}
/**
* Constructor.
*
* @param monitorOnCreate if true auto-monitor new subdirectories
*/
DirectoryMonitor(boolean monitorOnCreate) {
monitorSubdirectories = monitorOnCreate;
}
/**
* Register a directory to be monitored for changes.
*
* @param dir directory to monitor
*/
void register(final Directory dir) {
if (dir != null && !monitored.contains(dir)) {
monitored.add(dir);
if (monitorSubdirectories) {
recurse(dir);
}
}
}
private void recurse(final Directory dir) {
synchronized (dir.getLock()) {
String[] list = dir.list();
for (String file : list) {
final File f = new File(dir.getTarget(), file);
if (f.isDirectory()) {
register(new Directory(f));
}
}
}
}
/**
* Clear/close and resources being monitored.
*/
void close() {
monitored.clear();
}
/**
* Unregister a directory.
*
* @param dir
*/
private void unregister(Directory dir) {
if (dir != null) {
monitored.remove(dir);
}
}
/**
*
* The main business logic of this directory monitor.
*
* @return a WatchEvent instance or null
*/
Collection<? extends WatchService.WatchKey> check() {
Map<Directory, WatchKeyImpl> result = new HashMap<Directory, WatchKeyImpl>();
for (Directory directory : monitored) {
synchronized (directory.getLock()) {
if (directory.exists()) {
Map<String, WatchedFile> watchedFiles = (Map<String, WatchedFile>) directory.getContents().clone();
String[] filesCurrentlyInDirectory = directory.list();
for (String s : filesCurrentlyInDirectory) {
if (directory.getContents().containsKey(s)) {
WatchEvent we = directory.getContents().get(s).check();
if (!we.equals(WatchEvent.NO_EVENT)) {
add(result, directory, we);
}
}
else {
final File f = new File(directory.getTarget(), s);
WatchedFile nf = new WatchedFile(f);
add(result, directory, nf.createEvent());
directory.getContents().put(s, nf);
if (f.isDirectory() && monitorSubdirectories) {
register(new Directory(f));
}
}
watchedFiles.remove(s);
}
for (String file : watchedFiles.keySet()) {
WatchEvent we = watchedFiles.get(file).check();
if (!we.equals(WatchEvent.NO_EVENT)) {
add(result, directory, we);
if (we.kind().equals(WatchEventKind.ENTRY_DELETE)) {
directory.getContents().remove(file);
}
}
}
}
else {
add(result, directory, new DirectoryWatchEvent(WatchEventKind.ENTRY_DELETE, directory.getTarget()));
unregister(directory);
}
}
}
return result.values();
}
private void add(Map<Directory, WatchKeyImpl> keyList, Directory dir, WatchEvent we) {
WatchKeyImpl key = keyList.get(dir);
if (key == null) {
key = new WatchKeyImpl(dir);
keyList.put(dir, key);
}
key.add(we);
}
/**
* WatchKey impl.
*/
private class WatchKeyImpl implements WatchService.WatchKey {
/**
* The target.
*/
private Directory watchKey;
/**
* The list of events.
*/
private List<WatchEvent<?>> list = new ArrayList<WatchEvent<?>>();
/**
* Constructor.
*
* @param theKey
*/
WatchKeyImpl(Directory theKey) {
watchKey = theKey;
}
@Override
public void cancel() {
unregister(watchKey);
}
@Override
public List<WatchEvent<?>> pollEvents() {
return list;
}
/**
* Add an event.
*
* @param event an event
*/
void add(WatchEvent<?> event) {
list.add(event);
}
}
/**
* A class that depicts an event upon a directory.
*/
private class DirectoryWatchEvent implements WatchEvent<File> {
/**
* The type of the event.
*/
private final Kind<File> type;
/**
* The resource target.
*/
private final File target;
/**
* Constructor.
*
* @param theType the kind of the event
* @param theTarget the target directory file
*/
DirectoryWatchEvent(Kind<File> theType, File theTarget) {
type = theType;
target = theTarget;
}
@Override
public File context() {
return target;
}
@Override
public Kind<File> kind() {
return type;
}
}
}

View File

@ -1,49 +0,0 @@
package net.sf.openrocket.util.watcher;
/**
* Mimics the JDK 7 implementation.
* <p/>
* <T> the type of the context object of the event
*/
public interface WatchEvent<T> {
/**
* Defines the target of the event.
*
* @return the entity for which the event was generated
*/
T context();
/**
* Defines the type of event.
*
* @return the kind of event
*/
WatchEvent.Kind<T> kind();
/**
* Defines an API for a kind of event.
*
* @param <T>
*/
static interface Kind<T> {
String name();
Class<T> type();
}
/**
* A null-object idiom event.
*/
public static final WatchEvent<Void> NO_EVENT = new WatchEvent<Void>() {
@Override
public Void context() {
return null;
}
@Override
public Kind<Void> kind() {
return null;
}
};
}

View File

@ -1,75 +0,0 @@
package net.sf.openrocket.util.watcher;
import java.io.File;
/**
* Mimics the kind of watch event in JDK 7.
*/
public final class WatchEventKind {
/**
* An entry was created.
*/
public static final WatchEvent.Kind<File> ENTRY_CREATE = new WatchEvent.Kind<File>() {
@Override
public String name() {
return "ENTRY_CREATE";
}
@Override
public Class<File> type() {
return File.class;
}
@Override
public String toString() {
return name();
}
};
/**
* An existing entry was deleted.
*/
public static final WatchEvent.Kind<File> ENTRY_DELETE = new WatchEvent.Kind<File>() {
@Override
public String name() {
return "ENTRY_DELETE";
}
@Override
public Class<File> type() {
return File.class;
}
@Override
public String toString() {
return name();
}
};
/**
* An existing entry was modified.
*/
public static final WatchEvent.Kind<File> ENTRY_MODIFY = new WatchEvent.Kind<File>() {
@Override
public String name() {
return "ENTRY_MODIFY";
}
@Override
public Class<File> type() {
return File.class;
}
@Override
public String toString() {
return name();
}
};
/**
* Disallow instantiation.
*/
private WatchEventKind() {
}
}

View File

@ -1,160 +0,0 @@
package net.sf.openrocket.util.watcher;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* A service that allows consumers to watch for changes to a directory or directories. This class manages the checking
* of state changes for one or more directories. Directories are the primary entity being monitored.
* Events detected include the creation of a file, creation of a subdirectory, the modification
* of a file, and the deletion of a file or subdirectory.
* <p/>
* JDK 7 includes a WatchService, upon which this is loosely based. This implementation is JDK 6 compatible.
* <p/>
* A limitation of this implementation is that it is polling based, not event driven. Also note, that this only monitors directories. If you want
* to monitor an individual file, it is recommended that you monitor the directory that the file resides within,
* then filter the WatchEvents accordingly.
*
* Example usage:
* <p>
* <pre>
* WatchService watcher = new WatchService();
* watcher.register(new File("/tmp"));
* ...
* Collection<? extends WatchKey> changed = watcher.poll();
* for (Iterator<WatchKey> iterator = co.iterator(); iterator.hasNext(); ) {
* WatchKey key = iterator.next();
* //Do something with the WatchEvents in the key
* }
*
* </pre>
* </p>
*/
public class WatchService {
/**
* An interface that defines keys of events.
*/
public static interface WatchKey {
/**
* Cancel the registration of the directory for which this key relates.
*/
void cancel();
/**
* Get a list of events detected for this key.
*
* @return a list, not null, of events
*/
List<WatchEvent<?>> pollEvents();
}
/**
* The manager of all directory monitoring.
*/
private final DirectoryMonitor monitor;
/**
* Constructor.
*/
public WatchService() {
monitor = new DirectoryMonitor();
}
/**
* Constructor.
*
* @param watchRecursively if true, directories will be watched recursively
*/
public WatchService(boolean watchRecursively) {
monitor = new DirectoryMonitor(watchRecursively);
}
/**
* Polling method to get a collection of keys that indicate events detected upon one or more files within a
* monitored directory. Each key represents one directory. The list of events represents changes to one or more
* files within that directory. Will not block and return immediately, even if there are no keys ready.
*
* @return a collection of keys; guaranteed not to be null but may be empty
*/
public Collection<? extends WatchKey> poll() {
return monitor.check();
}
/**
* Polling method to get a collection of keys that indicate events detected upon one or more files within a
* monitored directory. Each key represents one directory. The list of events represents changes to one or more
* files within that directory. Will block for a defined length of time if there are no keys ready.
*
* @param time the amount of time before the method returns
* @param unit the unit of time
*
* @return a collection of keys; guaranteed not to be null but may be empty
*/
public Collection<? extends WatchKey> poll(long time, TimeUnit unit) {
Collection<? extends WatchKey> result = monitor.check();
if (result != null && !result.isEmpty()) {
return result;
}
try {
Thread.sleep(unit.toMillis(time));
} catch (InterruptedException e) {
}
return monitor.check();
}
/**
* Blocking method to get a collection of keys that indicate events detected upon one or more files within a
* monitored directory. Each key represents one directory. The list of events represents changes to one or more
* files within that directory.
*
* @return a collection of keys; guaranteed not to be null but may be empty
*/
public Collection<? extends WatchKey> take() {
long wait = 60000;
Collection<? extends WatchKey> result = null;
do {
result = poll(wait, TimeUnit.MILLISECONDS);
} while (result == null || result.isEmpty());
return result;
}
/**
* Close the service and release any resources currently being used.
*/
public void close() {
monitor.close();
}
/**
* Register a directory to be watched by this service. The file f must not be null and must refer to a directory
* that exists. Registration is idempotent - it can be called multiple times for the same directory with no ill
* effect.
*
* @param f the target directory to watch
*
* @throws IllegalArgumentException thrown if f is null, does not exist, or is not a directory
*/
public void register(File f) throws IllegalArgumentException {
monitor.register(new Directory(f));
}
/**
* Register a directory to be watched by this service. The file f must not be null and must refer to a directory
* that exists. Registration is idempotent - it can be called multiple times for the same directory with no ill
* effect.
*
* @param dir the target directory to watch
*
* @throws IllegalArgumentException thrown if dir is null
*/
public void register(Directory dir) throws IllegalArgumentException {
if (dir == null) {
throw new IllegalArgumentException("The directory may not be null.");
}
monitor.register(dir);
}
}

View File

@ -1,33 +0,0 @@
package net.sf.openrocket.util.watcher;
import java.util.List;
/**
* The public contract that must be implemented by clients wanting to register an interest in, and receive notification of,
* changes to a directory or file.
*/
public interface WatchedEventHandler<W extends WatchedFile> {
/**
* Get the target being watched.
*
* @return a instance of a watched file
*/
W watchTarget();
/**
* If the target is a directory, then answer if subdirectories should also be watched for state changes. The watched target is a file, this has no
* meaning.
*
* @return true if directories are to be watched recursively (watch all subdirectories et. al.)
*/
boolean watchRecursively();
/**
* Callback method. This is invoked by the reactor whenever events are detected upon the target.
*
* @param theEvents a list of detected events; it's a list because if the target is a directory, potentially many files within the directory were
* affected
*/
void handleEvents(List<WatchEvent<?>> theEvents);
}

View File

@ -1,148 +0,0 @@
package net.sf.openrocket.util.watcher;
import java.io.File;
/**
* Models a file on the filesystem that is being watched for state changes (other than creation).
*/
class WatchedFile {
/**
* The last timestamp of the file.
*/
private long timeStamp;
/**
* The file to watch.
*/
private final File target;
/**
* Constructor.
*
* @param aFile the file to watch
*
* @throws IllegalArgumentException thrown if aFile is null
*/
WatchedFile(File aFile) throws IllegalArgumentException {
if (aFile == null) {
throw new IllegalArgumentException("The file may not be null.");
}
target = aFile;
timeStamp = target.lastModified();
}
/**
* Create a 'create' event.
*
* @return a watch event indicating the file was created
*/
WatchEvent<File> createEvent() {
return new FileWatchEvent(WatchEventKind.ENTRY_CREATE);
}
/**
* Get the watched target.
*
* @return the file being monitored
*/
File getTarget() {
return target;
}
/**
* Detects if any changes have been made to the file. This is a 'destructive' read in the sense that it is not
* idempotent. The act of checking for changes resets the internal state and subsequent checks will indicate
* no changes until the next physical change.
*
* @return a WatchEvent instance or null if no event
*/
public final WatchEvent check() {
if (!target.exists()) {
return new FileWatchEvent(WatchEventKind.ENTRY_DELETE);
}
long latest = target.lastModified();
if (timeStamp != latest) {
timeStamp = latest;
return new FileWatchEvent(WatchEventKind.ENTRY_MODIFY);
}
return WatchEvent.NO_EVENT;
}
/**
* Delegates existence check to the target.
*
* @return true if exists
*/
public boolean exists() {
return target.exists();
}
/**
* Determine equivalence to a given object.
*
* @param o another watched file
*
* @return true if the underlying file is the same
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof WatchedFile)) {
return false;
}
final WatchedFile that = (WatchedFile) o;
if (!target.equals(that.target)) {
return false;
}
return true;
}
/**
* Compute hash code.
*
* @return a hash value
*/
@Override
public int hashCode() {
return target.hashCode();
}
/**
* A class that depicts events that occur upon a file.
*/
protected class FileWatchEvent implements WatchEvent<File> {
/**
* The kind of event.
*/
private Kind<File> type;
/**
* Constructor.
*
* @param theType the
*/
FileWatchEvent(Kind<File> theType) {
type = theType;
}
@Override
public File context() {
return target;
}
@Override
public Kind<File> kind() {
return type;
}
}
}

View File

@ -1,162 +0,0 @@
package net.sf.openrocket.util.watcher;
import org.junit.Assert;
import org.junit.Test;
import java.io.File;
import java.util.List;
/**
*/
public class DirectoryChangeReactorImplTest {
@Test
public void testRegisterHandler() throws Exception {
DirectoryChangeReactorImpl impl = new DirectoryChangeReactorImpl();
final File tempDir = DirectoryTest.createTempDir();
tempDir.setWritable(true);
Directory directory = new Directory(tempDir);
File f1 = null;
File f2 = null;
File sub = null;
Directory subdir;
try {
WatchedEventHandlerImpl testHandler = new WatchedEventHandlerImpl(directory);
impl.registerHandler(testHandler);
String baseName = System.currentTimeMillis() + "--";
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
f1.createNewFile();
long totalSleepTime = 0;
WatchEvent.Kind<?> kind = null;
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 1000) {
kind = testHandler.getKind();
if (kind != null) {
break;
}
Thread.sleep(1000);
totalSleepTime += 1000;
}
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, kind);
Assert.assertEquals(testHandler.eventTarget, f1);
f1.setLastModified(System.currentTimeMillis() + 10000);
totalSleepTime = 0;
kind = null;
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 400) {
kind = testHandler.getKind();
if (kind != null) {
break;
}
Thread.sleep(1000);
totalSleepTime += 1000;
}
Assert.assertEquals(WatchEventKind.ENTRY_MODIFY, kind);
Assert.assertEquals(testHandler.eventTarget, f1);
f1.delete();
totalSleepTime = 0;
kind = null;
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 400) {
kind = testHandler.getKind();
if (kind != null) {
break;
}
Thread.sleep(1000);
totalSleepTime += 1000;
}
Assert.assertEquals(WatchEventKind.ENTRY_DELETE, kind);
Assert.assertEquals(testHandler.eventTarget, f1);
//test recursive nature of monitoring subdirectories
sub = DirectoryTest.createTempDir(tempDir);
subdir = new Directory(sub);
totalSleepTime = 0;
kind = null;
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 400) {
kind = testHandler.getKind();
if (kind != null) {
break;
}
Thread.sleep(1000);
totalSleepTime += 1000;
}
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, kind);
Assert.assertEquals(testHandler.eventTarget, sub);
f2 = new File(sub.getAbsolutePath(), baseName + "2");
f2.createNewFile();
totalSleepTime = 0;
kind = null;
while (totalSleepTime < DirectoryChangeReactorImpl.DEFAULT_POLLING_DELAY + 400) {
kind = testHandler.getKind();
if (kind != null) {
break;
}
Thread.sleep(1000);
totalSleepTime += 1000;
}
//Eventually, both events will show up, but it's system dependent which one we get first, and if
//they both arrive together or on different polling cycles. This could be embellished, but for now
//just see if at least one arrives.
if (kind.equals(WatchEventKind.ENTRY_CREATE)) {
Assert.assertEquals(f2, testHandler.eventTarget);
}
else if (kind.equals(WatchEventKind.ENTRY_MODIFY)) {
Assert.assertEquals(sub, testHandler.eventTarget);
}
}
finally {
if (f1 != null) {
f1.delete();
}
if (sub != null) {
sub.delete();
}
directory.getTarget().delete();
impl.shutdown();
}
}
static class WatchedEventHandlerImpl implements WatchedEventHandler<Directory> {
Directory file = null;
Object eventTarget = null;
WatchEvent.Kind<?> kind = null;
WatchedEventHandlerImpl(final Directory theFile) {
file = theFile;
}
@Override
public Directory watchTarget() {
return file;
}
@Override
public boolean watchRecursively() {
return true;
}
public WatchEvent.Kind<?> getKind() {
WatchEvent.Kind<?> tmp = kind;
kind = null;
return tmp;
}
@Override
public void handleEvents(final List<WatchEvent<?>> theEvents) {
for (int i = 0; i < theEvents.size(); i++) {
WatchEvent<?> watchEvent = theEvents.get(i);
kind = watchEvent.kind();
eventTarget = watchEvent.context();
}
}
}
}

View File

@ -1,83 +0,0 @@
package net.sf.openrocket.util.watcher;
import org.junit.Assert;
import org.junit.Test;
import java.io.File;
import java.util.Collection;
import java.util.List;
/**
*/
public class DirectoryMonitorTest {
@Test
public void testCheck() throws Exception {
final File tempDir = DirectoryTest.createTempDir();
tempDir.setWritable(true);
Directory directory = new Directory(tempDir);
File f1 = null;
File f2 = null;
File f3 = null;
try {
DirectoryMonitor monitor = new DirectoryMonitor();
monitor.register(directory);
String baseName = System.currentTimeMillis() + "--";
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
f1.createNewFile();
f2 = new File(tempDir.getAbsolutePath(), baseName + "2");
f2.createNewFile();
f3 = new File(tempDir.getAbsolutePath(), baseName + "3");
f3.createNewFile();
Collection<? extends WatchService.WatchKey> keys = monitor.check();
Assert.assertEquals(1, keys.size());
WatchService.WatchKey wk = keys.iterator().next();
List<WatchEvent<?>> events = wk.pollEvents();
Assert.assertEquals(3, events.size());
for (int i = 0; i < events.size(); i++) {
WatchEvent<?> watchEvent = events.get(i);
if (watchEvent.context().equals(f1)) {
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, watchEvent.kind());
}
else if (watchEvent.context().equals(f2)) {
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, watchEvent.kind());
}
else if (watchEvent.context().equals(f3)) {
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, watchEvent.kind());
}
else {
System.err.println(watchEvent.context().toString());
Assert.fail("Unknown target file.");
}
}
f1.setLastModified(System.currentTimeMillis() + 10007);
f1.setReadable(true);
Thread.sleep(1000);
keys = monitor.check();
Assert.assertEquals(1, keys.size());
WatchService.WatchKey watchEvent = keys.iterator().next();
Assert.assertEquals(1, watchEvent.pollEvents().size());
Assert.assertEquals(f1, watchEvent.pollEvents().get(0).context());
Assert.assertEquals(WatchEventKind.ENTRY_MODIFY, watchEvent.pollEvents().get(0).kind());
}
finally {
if (f1 != null) {
f1.delete();
}
if (f2 != null) {
f2.delete();
}
if (f3 != null) {
f3.delete();
}
directory.getTarget().delete();
}
}
}

View File

@ -1,177 +0,0 @@
package net.sf.openrocket.util.watcher;
import org.junit.Assert;
import org.junit.Test;
import java.io.File;
/**
*/
public class DirectoryTest {
@Test
public void testConstructor() throws Exception {
try {
new Directory(null);
Assert.fail();
}
catch (IllegalArgumentException iae) {
//success
}
try {
new Directory(new File("foo"));
Assert.fail();
}
catch (IllegalArgumentException iae) {
//success
}
}
@Test
public void testSize() throws Exception {
final File tempDir = createTempDir();
tempDir.setWritable(true);
Directory directory = new Directory(tempDir);
File f1 = null;
File f2 = null;
File f3 = null;
try {
Assert.assertTrue(directory.exists());
Assert.assertEquals(0, directory.size());
String baseName = System.currentTimeMillis() + "--";
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
f1.createNewFile();
f2 = new File(tempDir.getAbsolutePath(), baseName + "2");
f2.createNewFile();
f3 = new File(tempDir.getAbsolutePath(), baseName + "3");
f3.createNewFile();
Assert.assertEquals(3, directory.size());
}
finally {
if (f1 != null) {
f1.delete();
}
if (f2 != null) {
f2.delete();
}
if (f3 != null) {
f3.delete();
}
directory.getTarget().delete();
}
}
@Test
public void testList() throws Exception {
final File tempDir = createTempDir();
tempDir.setWritable(true);
Directory directory = new Directory(tempDir);
File f1 = null;
File f2 = null;
File f3 = null;
try {
Assert.assertTrue(directory.exists());
Assert.assertEquals(0, directory.size());
String baseName = System.currentTimeMillis() + "--";
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
f1.createNewFile();
f2 = new File(tempDir.getAbsolutePath(), baseName + "2");
f2.createNewFile();
f3 = new File(tempDir.getAbsolutePath(), baseName + "3");
f3.createNewFile();
String[] files = directory.list();
for (int i = 0; i < files.length; i++) {
String file = files[i];
if (file.endsWith("1")) {
Assert.assertEquals(baseName + "1", file);
}
else if (file.endsWith("2")) {
Assert.assertEquals(baseName + "2", file);
}
else if (file.endsWith("3")) {
Assert.assertEquals(baseName + "3", file);
}
else {
Assert.fail();
}
}
}
finally {
if (f1 != null) {
f1.delete();
}
if (f2 != null) {
f2.delete();
}
if (f3 != null) {
f3.delete();
}
directory.getTarget().delete();
}
}
@Test
public void testContents() throws Exception {
final File tempDir = createTempDir();
tempDir.setWritable(true);
Directory directory = new Directory(tempDir);
File f1 = null;
File f2 = null;
File f3 = null;
try {
String baseName = System.currentTimeMillis() + "--";
f1 = new File(tempDir.getAbsolutePath(), baseName + "1");
f1.createNewFile();
f2 = new File(tempDir.getAbsolutePath(), baseName + "2");
f2.createNewFile();
f3 = new File(tempDir.getAbsolutePath(), baseName + "3");
f3.createNewFile();
Assert.assertEquals(0, directory.getContents().size());
//Contents is initialized at the time Directory is created. Since we had to create it for the test,
//we need a second Directory instance.
Directory directory1 = new Directory(tempDir);
Assert.assertEquals(3, directory1.getContents().size());
}
finally {
if (f1 != null) {
f1.delete();
}
if (f2 != null) {
f2.delete();
}
if (f3 != null) {
f3.delete();
}
directory.getTarget().delete();
}
}
//Borrowed from Google's Guava.
public static File createTempDir() {
File baseDir = new File(System.getProperty("java.io.tmpdir"));
return createTempDir(baseDir);
}
public static File createTempDir(File parent) {
String baseName = System.currentTimeMillis() + "-";
for (int counter = 0; counter < 2; counter++) {
File tempDir = new File(parent, baseName + counter);
if (tempDir.mkdir()) {
return tempDir;
}
}
throw new IllegalStateException("Failed to create directory within "
+ 2 + " attempts (tried "
+ baseName + "0 to " + baseName + (2 - 1) + ')');
}
}

View File

@ -1,82 +0,0 @@
package net.sf.openrocket.util.watcher;
import org.junit.Assert;
import org.junit.Test;
import java.io.File;
/**
*/
public class WatchedFileTest {
@Test
public void testConstructor() throws Exception {
try {
new WatchedFile(null);
Assert.fail();
}
catch (IllegalArgumentException iae) {
//success
}
final File blah = new File("blah");
WatchedFile wf = new WatchedFile(blah);
Assert.assertEquals(blah, wf.getTarget());
}
@Test
public void testCreateEvent() throws Exception {
final File blah = new File("blah");
WatchedFile wf = new WatchedFile(blah);
Assert.assertEquals(blah, wf.createEvent().context());
Assert.assertEquals(WatchEventKind.ENTRY_CREATE, wf.createEvent().kind());
}
@Test
public void testExists() throws Exception {
final File blah = new File("blah");
WatchedFile wf = new WatchedFile(blah);
Assert.assertFalse(wf.exists());
}
@Test
public void testCheck() throws Exception {
final File blah = new File("blah");
WatchedFile wf = new WatchedFile(blah);
WatchEvent check = wf.check();
Assert.assertEquals(WatchEventKind.ENTRY_DELETE, check.kind());
File f = File.createTempFile("tmp", "tmp");
wf = new WatchedFile(f);
check = wf.check();
Assert.assertEquals(WatchEvent.NO_EVENT, check);
f.setLastModified(System.currentTimeMillis() - 60000);
check = wf.check();
Assert.assertEquals(WatchEventKind.ENTRY_MODIFY, check.kind());
Assert.assertEquals(f, check.context());
//Check for reset of state
check = wf.check();
Assert.assertEquals(WatchEvent.NO_EVENT, check);
}
@Test
public void testEquals() throws Exception {
final File blah = new File("blah");
final File blech = new File("blech");
WatchedFile wf1 = new WatchedFile(blah);
WatchedFile wf2 = new WatchedFile(blah);
WatchedFile wf3 = new WatchedFile(blech);
Assert.assertEquals(wf1, wf1);
Assert.assertEquals(wf1, wf2);
Assert.assertFalse(wf1.equals(wf3));
Assert.assertFalse(wf1.equals(null));
Assert.assertFalse(wf1.equals(new Object()));
Assert.assertEquals(wf1.hashCode(), wf2.hashCode());
}
}