Merge branch 'kruland-integration' of github.com:plaa/openrocket into
kruland-integration-ui Conflicts: core/src/net/sf/openrocket/file/openrocket/importt/MotorHandler.java
@ -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>
|
||||
|
||||
BIN
core/resources/datafiles/textures/balsa.png
Normal file
|
After Width: | Height: | Size: 553 KiB |
BIN
core/resources/datafiles/textures/cardboard.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
core/resources/datafiles/textures/chute.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
core/resources/datafiles/textures/hardboard.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
core/resources/datafiles/textures/motors/aerotech.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
core/resources/datafiles/textures/motors/estes.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
core/resources/datafiles/textures/motors/reusable.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
core/resources/datafiles/textures/spiral-wound-alpha.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
core/resources/datafiles/textures/wadding.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
core/resources/datafiles/textures/wood.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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>"
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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().");
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -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() {
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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) + ')');
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||