Implement simulation extensions

Squashed commit of the following:

commit 058f603c23accbcdbe9110645f19164da7d57c85
Author: Sampo Niskanen <sampo.niskanen@iki.fi>
Date:   Sat Apr 12 10:52:21 2014 +0300

    Implmenent simulation extensions

commit a1ca913c7d7793a9209a5f98235336270db6ce10
Author: Sampo Niskanen <sampo.niskanen@iki.fi>
Date:   Fri Apr 11 19:45:12 2014 +0300

    WIP

commit 916f0bc8961360c0906b413485ca4e3700033740
Author: Sampo Niskanen <sampo.niskanen@iki.fi>
Date:   Tue Apr 1 20:23:25 2014 +0300

    WIP
This commit is contained in:
Sampo Niskanen 2014-04-12 10:54:17 +03:00
parent 11328bc1fc
commit b6e3a57b47
29 changed files with 1593 additions and 194 deletions

View File

@ -377,11 +377,9 @@ simedtdlg.lbl.Timestep = Time step:
simedtdlg.lbl.ttip.Timestep1 = <html>The time between simulation steps.<br>A smaller time step results in a more accurate but slower simulation.<br>
simedtdlg.lbl.ttip.Timestep2 = The 4<sup>th</sup> order simulation method is quite accurate with a time step of
simedtdlg.but.ttip.resettodefault = Reset the time step to its default value (
simedtdlg.border.Simlist = Simulator listeners
simedtdlg.txt.longA1 = <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
simedtdlg.txt.longA2 = For details on writing simulation listeners, see the OpenRocket technical documentation.
simedtdlg.lbl.Curlist = Current listeners:
simedtdlg.lbl.Addsimlist = Add simulation listener
simedtdlg.border.SimExt = Simulation extensions
simedtdlg.SimExt.desc = <html><i>Simulation extensions</i> enable advanced features and custom functionality during flight simulations. You can for example do hardware-in-the-loop testing with them.
simedtdlg.SimExt.noExtensions = No simulation extensions defined
simedtdlg.lbl.Noflightdata = No flight data available.
simedtdlg.lbl.runsimfirst = Please run the simulation first.
simedtdlg.chart.Simflight = Simulated flight
@ -394,6 +392,12 @@ simedtdlg.IntensityDesc.High = High
simedtdlg.IntensityDesc.Veryhigh = Very high
simedtdlg.IntensityDesc.Extreme = Extreme
SimulationExtension.airstart.name = Air-start ({alt})
SimulationExtension.javacode.name = Java code
SimulationExtension.javacode.name.none = none
SimulationExtension.javacode.desc = Add a custom SimulationListener to the simulation
SimulationExtension.javacode.className = Fully-qualified Java class name:
SimulationEditDialog.btn.plot = Plot
SimulationEditDialog.btn.export = Export
SimulationEditDialog.btn.edit = Edit

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

View File

@ -23,7 +23,7 @@ import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
@ -146,17 +146,8 @@ public class OpenRocketDocument implements ComponentChangeListener {
// simulation listeners
for (Simulation sim : simulations) {
for (String className : sim.getSimulationListeners()) {
SimulationListener l = null;
try {
Class<?> c = Class.forName(className);
l = (SimulationListener) c.newInstance();
Collections.addAll(allTypes, l.getFlightDataTypes());
//System.out.println("This document has expression datatype from "+l.getName());
} catch (Exception e) {
log.error("Could not instantiate listener: " + className);
}
for (SimulationExtension c : sim.getSimulationExtensions()) {
allTypes.addAll(c.getFlightDataTypes());
}
}

View File

@ -21,7 +21,7 @@ import net.sf.openrocket.simulation.SimulationEngine;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.SimulationStepper;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationListenerException;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
@ -76,7 +76,8 @@ public class Simulation implements ChangeSource, Cloneable {
// TODO: HIGH: Change to use actual conditions class??
private SimulationOptions options;
private ArrayList<String> simulationListeners = new ArrayList<String>();
private ArrayList<SimulationExtension> simulationExtensions = new ArrayList<SimulationExtension>();
private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
@ -116,7 +117,7 @@ public class Simulation implements ChangeSource, Cloneable {
public Simulation(Rocket rocket, Status status, String name, SimulationOptions options,
List<String> listeners, FlightData data) {
List<SimulationExtension> extensions, FlightData data) {
if (rocket == null)
throw new IllegalArgumentException("rocket cannot be null");
@ -142,8 +143,8 @@ public class Simulation implements ChangeSource, Cloneable {
this.options = options;
options.addChangeListener(new ConditionListener());
if (listeners != null) {
this.simulationListeners.addAll(listeners);
if (extensions != null) {
this.simulationExtensions.addAll(extensions);
}
@ -196,14 +197,14 @@ public class Simulation implements ChangeSource, Cloneable {
/**
* Get the list of simulation listeners. The returned list is the one used by
* Get the list of simulation extensions. The returned list is the one used by
* this object; changes to it will reflect changes in the simulation.
*
* @return the actual list of simulation listeners.
* @return the actual list of simulation extensions.
*/
public List<String> getSimulationListeners() {
public List<SimulationExtension> getSimulationExtensions() {
mutex.verify();
return simulationListeners;
return simulationExtensions;
}
@ -293,16 +294,8 @@ public class Simulation implements ChangeSource, Cloneable {
simulationConditions.getSimulationListenerList().add(l);
}
for (String className : simulationListeners) {
SimulationListener l = null;
try {
Class<?> c = Class.forName(className);
l = (SimulationListener) c.newInstance();
} catch (Exception e) {
throw new SimulationListenerException("Could not instantiate listener of " +
"class: " + className, e);
}
simulationConditions.getSimulationListenerList().add(l);
for (SimulationExtension extension : simulationExtensions) {
extension.initialize(simulationConditions);
}
long t1, t2;
@ -410,7 +403,9 @@ public class Simulation implements ChangeSource, Cloneable {
copy.mutex = SafetyMutex.newInstance();
copy.status = Status.NOT_SIMULATED;
copy.options = this.options.clone();
copy.simulationListeners = this.simulationListeners.clone();
for (SimulationExtension c : this.simulationExtensions) {
copy.simulationExtensions.add(c.clone());
}
copy.listeners = new ArrayList<EventListener>();
copy.simulatedConditions = null;
copy.simulatedConfiguration = null;
@ -442,7 +437,9 @@ public class Simulation implements ChangeSource, Cloneable {
copy.name = this.name;
copy.options.copyFrom(this.options);
copy.simulatedConfiguration = this.simulatedConfiguration;
copy.simulationListeners = this.simulationListeners.clone();
for (SimulationExtension c : this.simulationExtensions) {
copy.simulationExtensions.add(c.clone());
}
copy.simulationStepperClass = this.simulationStepperClass;
copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;

View File

@ -30,8 +30,10 @@ import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.BuildProperties;
import net.sf.openrocket.util.Config;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Reflection;
import net.sf.openrocket.util.TextUtil;
@ -455,9 +457,18 @@ public class OpenRocketSaver extends RocketSaver {
indent--;
writeln("</conditions>");
for (String s : simulation.getSimulationListeners()) {
writeElement("listener", TextUtil.escapeXML(s));
for (SimulationExtension extension : simulation.getSimulationExtensions()) {
Config config = extension.getConfig();
writeln("<extension extensionid=\"" + TextUtil.escapeXML(extension.getId()) + "\">");
indent++;
if (config != null) {
for (String key : config.keySet()) {
Object value = config.get(key, null);
writeEntry(key, value);
}
}
indent--;
writeln("</extension>");
}
// Write basic simulation data
@ -512,6 +523,39 @@ public class OpenRocketSaver extends RocketSaver {
}
private void writeEntry(String key, Object value) throws IOException {
if (value == null) {
return;
}
String keyAttr;
if (key != null) {
keyAttr = "key=\"" + key + "\" ";
} else {
keyAttr = "";
}
if (value instanceof Boolean) {
writeln("<entry " + keyAttr + "type=\"boolean\">" + value + "</entry>");
} else if (value instanceof Number) {
writeln("<entry " + keyAttr + "type=\"number\">" + value + "</entry>");
} else if (value instanceof String) {
writeln("<entry " + keyAttr + "type=\"string\">" + value + "</entry>");
} else if (value instanceof List) {
List<?> list = (List<?>) value;
writeln("<entry " + keyAttr + "type=\"list\">");
indent++;
for (Object o : list) {
writeEntry(null, o);
}
indent--;
writeln("</entry>");
} else {
// Unknown type
log.error("Unknown configuration value type " + value.getClass() + " value=" + value);
}
}
private void saveFlightDataBranch(FlightDataBranch branch, double timeSkip)
throws IOException {
double previousTime = -100000;

View File

@ -0,0 +1,104 @@
package net.sf.openrocket.file.openrocket.importt;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import net.sf.openrocket.aerodynamics.WarningSet;
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.util.ArrayList;
import net.sf.openrocket.util.Config;
import org.xml.sax.SAXException;
public class ConfigHandler extends AbstractElementHandler {
private ConfigHandler listHandler;
private Config config = new Config();
private List<Object> list = new ArrayList<Object>();
@Override
public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
if (element.equals("entry") && "list".equals(attributes.get("type"))) {
listHandler = new ConfigHandler();
return listHandler;
} else {
return PlainTextHandler.INSTANCE;
}
}
@Override
public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
if (element.equals("entry")) {
String key = attributes.get("key");
String type = attributes.get("type");
Object value = null;
if ("boolean".equals(type)) {
value = Boolean.valueOf(content);
} else if ("string".equals(type)) {
value = content;
} else if ("number".equals(type)) {
value = parseNumber(content);
} else if ("list".equals(type)) {
value = listHandler.list;
}
if (value != null) {
if (key != null) {
config.put(key, value);
} else {
list.add(value);
}
}
} else {
super.closeElement(element, attributes, content, warnings);
}
}
private Number parseNumber(String str) {
try {
str = str.trim();
if (str.matches("^[+-]?[0-9]+$")) {
BigInteger value = new BigInteger(str, 10);
if (value.equals(BigInteger.valueOf(value.intValue()))) {
return value.intValue();
} else if (value.equals(BigInteger.valueOf(value.longValue()))) {
return value.longValue();
} else {
return value;
}
} else {
BigDecimal value = new BigDecimal(str);
if (value.equals(BigDecimal.valueOf(value.doubleValue()))) {
return value.doubleValue();
} else {
return value;
}
}
} catch (NumberFormatException e) {
return null;
}
}
public Config getConfig() {
return config;
}
public static void main(String[] args) {
Random rnd = new Random();
for (int i = 0; i < 1000000000; i++) {
double d = Double.longBitsToDouble(rnd.nextLong());
if (Double.isNaN(d))
continue;
String s = "" + d;
BigDecimal dec = new BigDecimal(s);
if (dec.doubleValue() != d) {
System.out.println("Fail: d=" + d + " dec=" + dec);
}
}
}
}

View File

@ -3,6 +3,7 @@ package net.sf.openrocket.file.openrocket.importt;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument;
@ -14,6 +15,13 @@ import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.file.simplesax.PlainTextHandler;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.simulation.extension.SimulationExtensionProvider;
import net.sf.openrocket.simulation.extension.impl.JavaCode;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.StringUtil;
import com.google.inject.Key;
class SingleSimulationHandler extends AbstractElementHandler {
@ -24,9 +32,10 @@ class SingleSimulationHandler extends AbstractElementHandler {
private String name;
private SimulationConditionsHandler conditionHandler;
private ConfigHandler configHandler;
private FlightDataHandler dataHandler;
private final List<String> listeners = new ArrayList<String>();
private final List<SimulationExtension> extensions = new ArrayList<SimulationExtension>();
public SingleSimulationHandler(OpenRocketDocument doc, DocumentLoadingContext context) {
this.doc = doc;
@ -47,6 +56,9 @@ class SingleSimulationHandler extends AbstractElementHandler {
} else if (element.equals("conditions")) {
conditionHandler = new SimulationConditionsHandler(doc.getRocket(), context);
return conditionHandler;
} else if (element.equals("extension")) {
configHandler = new ConfigHandler();
return configHandler;
} else if (element.equals("flightdata")) {
dataHandler = new FlightDataHandler(this, context);
return dataHandler;
@ -71,7 +83,23 @@ class SingleSimulationHandler extends AbstractElementHandler {
warnings.add("Unknown calculator '" + content.trim() + "' specified, ignoring.");
}
} else if (element.equals("listener") && content.trim().length() > 0) {
listeners.add(content.trim());
extensions.add(compatibilityExtension(content.trim()));
} else if (element.equals("extension") && !StringUtil.isEmpty(attributes.get("extensionid"))) {
String id = attributes.get("extensionid");
SimulationExtension extension = null;
Set<SimulationExtensionProvider> extensionProviders = Application.getInjector().getInstance(new Key<Set<SimulationExtensionProvider>>() {
});
for (SimulationExtensionProvider p : extensionProviders) {
if (p.getIds().contains(id)) {
extension = p.getInstance(id);
}
}
if (extension != null) {
extension.setConfig(configHandler.getConfig());
extensions.add(extension);
} else {
warnings.add("Simulation extension with id '" + id + "' not found.");
}
}
}
@ -105,8 +133,16 @@ class SingleSimulationHandler extends AbstractElementHandler {
data = dataHandler.getFlightData();
Simulation simulation = new Simulation(doc.getRocket(), status, name,
conditions, listeners, data);
conditions, extensions, data);
doc.addSimulation(simulation);
}
private SimulationExtension compatibilityExtension(String className) {
JavaCode extension = Application.getInjector().getInstance(JavaCode.class);
extension.setClassName(className);
return extension;
}
}

View File

@ -42,9 +42,8 @@ public class PluginModule extends AbstractModule {
if (c.isInterface())
continue;
for (Class<?> intf : c.getInterfaces()) {
if (interfaces.contains(intf)) {
for (Class<?> intf : interfaces) {
if (intf.isAssignableFrom(c)) {
// Ugly hack to enable dynamic binding... Can this be done type-safely?
Multibinder<Object> binder = (Multibinder<Object>) findBinder(intf);
binder.addBinding().to(c);

View File

@ -0,0 +1,100 @@
package net.sf.openrocket.simulation.extension;
import java.util.Collections;
import java.util.List;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.util.AbstractChangeSource;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Config;
import com.google.inject.Inject;
/**
* An abstract implementation of a SimulationExtension.
*/
public abstract class AbstractSimulationExtension extends AbstractChangeSource implements SimulationExtension, Cloneable {
@Inject
protected Translator trans;
protected Config config = new Config();
private final String name;
/**
* Use the current class name as the extension name. You should override
* getName if you use this constructor.
*/
protected AbstractSimulationExtension() {
this(null);
}
/**
* Use the provided name as a static name for this extension.
*/
protected AbstractSimulationExtension(String name) {
if (name != null) {
this.name = name;
} else {
this.name = this.getClass().getSimpleName();
}
}
/**
* By default, returns the canonical name of this class.
*/
@Override
public String getId() {
return this.getClass().getCanonicalName();
}
/**
* By default, returns the name provided to the constructor.
*/
@Override
public String getName() {
return name;
}
/**
* By default, returns null.
*/
@Override
public String getDescription() {
return null;
}
/**
* By default, returns an empty list.
*/
@Override
public List<FlightDataType> getFlightDataTypes() {
return Collections.emptyList();
}
/**
* By default, returns a new object obtained by calling Object.clone().
*/
@Override
public SimulationExtension clone() {
try {
AbstractSimulationExtension copy = (AbstractSimulationExtension) super.clone();
copy.config = this.config.clone();
return copy;
} catch (CloneNotSupportedException e) {
throw new BugException(e);
}
}
@Override
public Config getConfig() {
return config.clone();
}
@Override
public void setConfig(Config config) {
this.config = config.clone();
fireChangeEvent();
}
}

View File

@ -0,0 +1,65 @@
package net.sf.openrocket.simulation.extension;
import java.util.Arrays;
import java.util.List;
import net.sf.openrocket.l10n.Translator;
import com.google.inject.Inject;
import com.google.inject.Injector;
/**
* An abstract implementation of a SimulationExtensionProvider. The constructor is
* provided by the class of the SimulationExtension and the name of the extension.
*/
public abstract class AbstractSimulationExtensionProvider implements SimulationExtensionProvider {
@Inject
private Injector injector;
@Inject
protected Translator trans;
protected final Class<? extends SimulationExtension> extensionClass;
private final String[] name;
/**
* Sole constructor.
*
* @param extensionClass the simulation extension class
* @param name the name returned by getName
*/
protected AbstractSimulationExtensionProvider(Class<? extends SimulationExtension> extensionClass, String... name) {
this.extensionClass = extensionClass;
this.name = name;
}
/**
* By default returns the canonical name of the simulation extension class.
*/
@Override
public List<String> getIds() {
return Arrays.asList(extensionClass.getCanonicalName());
}
/**
* By default returns the provided extension name for the first ID that getIds returns.
*/
@Override
public List<String> getName(String id) {
if (id.equals(getIds().get(0))) {
return Arrays.asList(name);
}
return null;
}
/**
* By default returns a new instance of the simulation extension class instantiated by
* Class.newInstance.
*/
@Override
public SimulationExtension getInstance(String id) {
return injector.getInstance(extensionClass);
}
}

View File

@ -0,0 +1,81 @@
package net.sf.openrocket.simulation.extension;
import java.util.List;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.Config;
public interface SimulationExtension {
/**
* Return the simulation extension ID that is used when storing this
* extension to a file.
*
* @return the extension ID
*/
public String getId();
/**
* Return a short description of this extension. The name may contain
* elements from the extension's configuration, for example
* "Air start (150m)".
*
* @return a short name / description of this extension to be shown in the UI (must not be null)
*/
public String getName();
/**
* Return a longer description text for this extension, if available.
* This description may be shown in the UI as extra information about
* the extension.
*
* @return a longer description about this extension, or null if not available
*/
public String getDescription();
/**
* Initialize this simulation extension for running within a simulation.
* This method is called before running a simulation. It can either modify
* the simulation conditions or add simulation listeners to it.
*
* @param conditions the simulation conditions to be run
* @param configuration the extension configuration
*/
public void initialize(SimulationConditions conditions) throws SimulationException;
/**
* Return a list of any flight data types this simulation extension creates.
* This should only contain new types created by this extension, not existing
* types that the extension adds to the flight data.
*/
public List<FlightDataType> getFlightDataTypes();
/**
* Return a copy of this simulation extension, with all configuration deep-copied.
*
* @return a new copy of this simulation extension
*/
public SimulationExtension clone();
/**
* Return a Config object describing the current configuration of this simulation
* extension. The extension may keep its configuration in a Config object, or create
* it when requested.
*
* @return the simulation extension configuration.
*/
public Config getConfig();
/**
* Set this simulation extension's configuration. The extension should load all its
* configuration from the provided Config object.
*
* @param config the configuration to set
*/
public void setConfig(Config config);
}

View File

@ -0,0 +1,133 @@
package net.sf.openrocket.simulation.extension;
import java.util.HashMap;
import java.util.List;
import net.sf.openrocket.util.ArrayList;
/**
* A map containing simulation extension configuration. This map can
* store values of type int, long, float, double, boolean, String,
* List and SimulationExtensionConfiguration.
*/
public final class SimulationExtensionConfiguration extends HashMap<String, Object> {
private SimulationExtension extension;
public SimulationExtension getExtension() {
return extension;
}
public void setExtension(SimulationExtension extension) {
this.extension = extension;
}
@Override
public Object put(String key, Object value) {
Class<?> c = value.getClass();
if (c != Long.class && c != Integer.class &&
c != Double.class && c != Float.class &&
c != Boolean.class &&
!(value instanceof SimulationExtensionConfiguration) &&
!(value instanceof List)) {
throw new UnsupportedOperationException("Invalid configuration parameter type: " + c + " key=" + key + " value=" + value);
}
return super.put(key, value);
}
public long getLong(String key, long def) {
Object o = get(key);
if (o instanceof Number) {
return ((Number) o).longValue();
} else {
return def;
}
}
public int getInt(String key, int def) {
Object o = get(key);
if (o instanceof Number) {
return ((Number) o).intValue();
} else {
return def;
}
}
public double getDouble(String key, double def) {
Object o = get(key);
if (o instanceof Number) {
return ((Number) o).doubleValue();
} else {
return def;
}
}
public float getFloat(String key, float def) {
Object o = get(key);
if (o instanceof Number) {
return ((Number) o).floatValue();
} else {
return def;
}
}
public boolean getBoolean(String key, boolean def) {
Object o = get(key);
if (o instanceof Boolean) {
return (Boolean) o;
} else {
return def;
}
}
public String getString(String key, String def) {
Object o = get(key);
if (o instanceof String) {
return (String) o;
} else {
return def;
}
}
/**
* Deep-clone this object.
*/
@Override
public SimulationExtensionConfiguration clone() {
SimulationExtensionConfiguration copy = new SimulationExtensionConfiguration();
copy.extension = this.extension;
for (String key : this.keySet()) {
Object value = this.get(key);
if (value instanceof SimulationExtensionConfiguration) {
copy.put(key, ((SimulationExtensionConfiguration) value).clone());
} else if (value instanceof List) {
copy.put(key, cloneList((List<?>) value));
} else {
copy.put(key, value);
}
}
return copy;
}
private Object cloneList(List<?> original) {
ArrayList<Object> list = new ArrayList<Object>();
for (Object value : original) {
if (value instanceof SimulationExtensionConfiguration) {
list.add(((SimulationExtensionConfiguration) value).clone());
} else if (value instanceof List) {
list.add(cloneList((List<?>) value));
} else {
list.add(value);
}
}
return list;
}
}

View File

@ -0,0 +1,46 @@
package net.sf.openrocket.simulation.extension;
import java.util.List;
import net.sf.openrocket.plugin.Plugin;
@Plugin
public interface SimulationExtensionProvider {
/**
* Return a list of simulation extension ID's that this provider supports.
* The ID is used to identify the plugin when storing files. It should follow
* the conventions of Java package and class naming.
*
* @return a list of ID strings
*/
public List<String> getIds();
/**
* Return the UI name for a simulation extension. The first values
* are nested menus, with the last one the actual entry, for example
* ["Launch conditions", "Air-start"].
*
* If the ID does not represent an extension that should be displayed
* in the UI, this method must return null. For example, if an extension
* has multiple ID's, this method must return the menu name for only one
* of the ID's.
*
* These can be localized, and the system may attempt to localize
* English-language names automatically (mainly for the menus).
*
* @param id the extension ID
* @return the UI name for the extension, or null for no display
*/
public List<String> getName(String id);
/**
* Return a new instance of a simulation extension. This is a new instance
* that should have some default configuration.
*
* @param id the extension ID
* @return a new simulation extension instance
*/
public SimulationExtension getInstance(String id);
}

View File

@ -0,0 +1,41 @@
package net.sf.openrocket.simulation.extension.impl;
import net.sf.openrocket.l10n.L10N;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
public class AirStart extends AbstractSimulationExtension {
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
conditions.getSimulationListenerList().add(new AirStartListener());
}
@Override
public String getName() {
String name = trans.get("SimulationExtension.airstart.name");
return L10N.replace(name, "{alt}", UnitGroup.UNITS_DISTANCE.toStringUnit(getLaunchAltitude()));
}
public double getLaunchAltitude() {
return config.getDouble("launchAltitude", 0.0);
}
public void setLaunchAltitude(double launchAltitude) {
config.put("launchAltitude", launchAltitude);
fireChangeEvent();
}
private class AirStartListener extends AbstractSimulationListener {
@Override
public void startSimulation(SimulationStatus status) throws SimulationException {
status.setRocketPosition(new Coordinate(0, 0, getLaunchAltitude()));
}
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.impl;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class AirStartProvider extends AbstractSimulationExtensionProvider {
public AirStartProvider() {
super(AirStart.class, "Launch conditions", "Air-start");
}
}

View File

@ -0,0 +1,77 @@
package net.sf.openrocket.simulation.extension.impl;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.StringUtil;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class JavaCode extends AbstractSimulationExtension {
@Inject
private Injector injector;
public JavaCode() {
config.put("my_string", "foobar");
config.put("my_int", 123);
config.put("my_long", 123456789012345L);
config.put("my_float", 12.345f);
config.put("my_double", 12.345e99);
config.put("my_bigint", new BigInteger("12345678901234567890", 10));
config.put("my_bool", true);
List<Object> list = new ArrayList<Object>();
list.add(true);
list.add(123);
list.add(123.456);
list.add(Arrays.asList(1, 2, 3));
list.add("foo");
config.put("my_list", list);
}
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
String className = getClassName();
try {
if (!StringUtil.isEmpty(className)) {
Class<?> clazz = Class.forName(className);
if (!SimulationListener.class.isAssignableFrom(clazz)) {
throw new SimulationException("Class " + className + " does not implement SimulationListener");
}
SimulationListener listener = (SimulationListener) injector.getInstance(clazz);
conditions.getSimulationListenerList().add(listener);
}
} catch (ClassNotFoundException e) {
throw new SimulationException("Could not find class " + className);
}
}
@Override
public String getName() {
String name = trans.get("SimulationExtension.javacode.name") + ": ";
String className = getClassName();
if (!StringUtil.isEmpty(className)) {
name = name + className;
} else {
name = name + trans.get("SimulationExtension.javacode.name.none");
}
return name;
}
public String getClassName() {
return config.getString("className", "");
}
public void setClassName(String className) {
config.put("className", className);
fireChangeEvent();
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.impl;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class JavaCodeProvider extends AbstractSimulationExtensionProvider {
public JavaCodeProvider() {
super(JavaCode.class, "User code", "Java code");
}
}

View File

@ -0,0 +1,152 @@
package net.sf.openrocket.util;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
public class Config {
private LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
public void put(String key, String value) {
validateType(value);
map.put(key, value);
}
public void put(String key, Number value) {
validateType(value);
map.put(key, clone(value));
}
public void put(String key, Boolean value) {
validateType(value);
map.put(key, value);
}
public void put(String key, List<?> value) {
validateType(value);
map.put(key, clone(value));
}
public void put(String key, Object value) {
validateType(value);
map.put(key, clone(value));
}
public Object get(String key, Object def) {
return get(key, def, Object.class);
}
public Boolean getBoolean(String key, Boolean def) {
return get(key, def, Boolean.class);
}
public Integer getInt(String key, Integer def) {
Number number = get(key, null, Number.class);
if (number == null) {
return def;
} else {
return number.intValue();
}
}
public Long getLong(String key, Long def) {
Number number = get(key, null, Number.class);
if (number == null) {
return def;
} else {
return number.longValue();
}
}
public Double getDouble(String key, Double def) {
Number number = get(key, null, Number.class);
if (number == null) {
return def;
} else {
return number.doubleValue();
}
}
public String getString(String key, String def) {
return get(key, def, String.class);
}
public List<?> getList(String key, List<?> def) {
return get(key, def, List.class);
}
public boolean containsKey(String key) {
return map.containsKey(key);
}
public Set<String> keySet() {
return Collections.unmodifiableMap(map).keySet();
}
@Override
public Config clone() {
Config copy = new Config();
for (Entry<String, Object> entry : map.entrySet()) {
copy.map.put(entry.getKey(), clone(entry.getValue()));
}
return copy;
}
@SuppressWarnings("unchecked")
private <T> T get(String key, T def, Class<T> type) {
Object value = map.get(key);
if (type.isInstance(value)) {
return (T) value;
} else {
return def;
}
}
private void validateType(Object value) {
if (value == null) {
throw new NullPointerException("Attempting to add null value to Config object");
} else if (value instanceof Boolean) {
// ok
} else if (value instanceof Number) {
// ok
} else if (value instanceof String) {
// ok
} else if (value instanceof List<?>) {
List<?> list = (List<?>) value;
for (Object v : list) {
validateType(v);
}
} else {
throw new IllegalArgumentException("Attempting to add value of type " + value.getClass() + " to Config object, value=" + value);
}
}
private Object clone(Object value) {
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long ||
value instanceof Float || value instanceof Double || value instanceof Boolean || value instanceof String) {
// immutable
return value;
} else if (value instanceof Number) {
return new BigDecimal(value.toString());
} else if (value instanceof List<?>) {
List<?> list = (List<?>) value;
ArrayList<Object> copy = new ArrayList<Object>(list.size());
for (Object o : list) {
copy.add(clone(o));
}
return copy;
} else {
throw new IllegalStateException("Config contained value = " + value + " type = " + ((value != null) ? value.getClass() : "null"));
}
}
}

View File

@ -0,0 +1,164 @@
package net.sf.openrocket.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
public class TestConfig {
private Config config = new Config();
@Test
public void testDoubles() {
config.put("double", Math.PI);
config.put("bigdecimal", new BigDecimal(Math.PI));
assertEquals(Math.PI, config.getDouble("double", null), 0);
assertEquals(Math.PI, config.getDouble("bigdecimal", null), 0);
assertEquals(3, (int) config.getInt("double", null));
}
@Test
public void testInts() {
config.put("int", 123);
config.put("biginteger", new BigDecimal(Math.PI));
config.put("bigdecimal", new BigDecimal(Math.PI));
assertEquals(123, (int) config.getInt("int", null));
assertEquals(3, (int) config.getInt("bigdecimal", null));
assertEquals(3, (int) config.getInt("biginteger", null));
}
@Test
public void testDefaultValue() {
assertEquals(true, config.getBoolean("foo", true));
assertEquals(123, (int) config.getInt("foo", 123));
assertEquals(123L, (long) config.getLong("foo", 123L));
assertEquals(1.23, (double) config.getDouble("foo", 1.23), 0);
assertEquals("bar", config.getString("foo", "bar"));
assertEquals(Arrays.asList("foo"), config.getList("foo", Arrays.asList("foo")));
}
@Test
public void testNullDefaultValue() {
assertEquals(null, config.getBoolean("foo", null));
assertEquals(null, config.getInt("foo", null));
assertEquals(null, config.getLong("foo", null));
assertEquals(null, config.getDouble("foo", null));
assertEquals(null, config.getString("foo", null));
assertEquals(null, config.getList("foo", null));
}
@Test
public void testStoringList() {
List<Object> list = new ArrayList<Object>();
list.add("Foo");
list.add(123);
list.add(Math.PI);
list.add(true);
config.put("list", list);
assertEquals(Arrays.asList("Foo", 123, Math.PI, true), config.getList("list", null));
}
@Test
public void testModifyingStoredList() {
List<Object> list = new ArrayList<Object>();
list.add("Foo");
list.add(123);
list.add(Math.PI);
list.add(true);
config.put("list", list);
list.add("hello");
assertEquals(Arrays.asList("Foo", 123, Math.PI, true), config.getList("list", null));
}
@Test
public void testModifyingStoredNumber() {
AtomicInteger ai = new AtomicInteger(100);
config.put("ai", ai);
ai.incrementAndGet();
assertEquals(100, (int) config.getInt("ai", null));
}
@Test
public void testClone() {
config.put("string", "foo");
config.put("int", 123);
config.put("double", Math.PI);
AtomicInteger ai = new AtomicInteger(100);
config.put("atomicinteger", ai);
List<Object> list = new ArrayList<Object>();
list.add("Foo");
config.put("list", list);
Config copy = config.clone();
config.put("extra", "foo");
ai.incrementAndGet();
assertFalse(copy.containsKey("extra"));
assertEquals("foo", copy.getString("string", null));
assertEquals(123, (int) copy.getInt("int", null));
assertEquals(100, (int) copy.getInt("atomicinteger", null));
assertEquals(Math.PI, (double) copy.getDouble("double", null), 0);
assertEquals(Arrays.asList("Foo"), copy.getList("list", null));
}
@Test
public void testStoringNullValue() {
try {
config.put("foo", (Boolean) null);
fail();
} catch (NullPointerException e) {
}
try {
config.put("foo", (String) null);
fail();
} catch (NullPointerException e) {
}
try {
config.put("foo", (Number) null);
fail();
} catch (NullPointerException e) {
}
try {
config.put("foo", (List<?>) null);
fail();
} catch (NullPointerException e) {
}
}
@Test
public void testStoringListWithInvalidTypes() {
List<Object> list = new ArrayList<Object>();
list.add("Foo");
list.add(new Date());
try {
config.put("foo", list);
fail();
} catch (IllegalArgumentException e) {
}
}
@Test
public void testStoringListWithNull() {
List<Object> list = new ArrayList<Object>();
list.add("Foo");
list.add(new Date());
try {
config.put("foo", list);
fail();
} catch (IllegalArgumentException e) {
}
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-6-openjdk-amd64"/>
<classpathentry kind="lib" path="lib/jogl/jogl-all.jar"/>
<classpathentry kind="lib" path="lib/iText-5.0.2.jar"/>
<classpathentry kind="lib" path="lib/jcommon-1.0.18.jar"/>

View File

@ -5,7 +5,15 @@ org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonN
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=error
org.eclipse.jdt.core.compiler.problem.deadCode=warning
@ -14,6 +22,7 @@ org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
org.eclipse.jdt.core.compiler.problem.fallthroughCase=error
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
@ -34,7 +43,7 @@ org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
@ -87,3 +96,4 @@ org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.6

View File

@ -26,6 +26,7 @@ import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.startup.Application;
@ -91,8 +92,10 @@ public class SimulationEditDialog extends JDialog {
if (simulation.length > 1) {
for (int i = 1; i < simulation.length; i++) {
simulation[i].getOptions().copyConditionsFrom(simulation[0].getOptions());
simulation[i].getSimulationListeners().clear();
simulation[i].getSimulationListeners().addAll(simulation[0].getSimulationListeners());
simulation[i].getSimulationExtensions().clear();
for (SimulationExtension c : simulation[0].getSimulationExtensions()) {
simulation[i].getSimulationExtensions().add(c.clone());
}
}
}
}

View File

@ -1,21 +1,26 @@
package net.sf.openrocket.gui.simulation;
import java.awt.Component;
import java.awt.Color;
import java.awt.Dialog.ModalityType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.ListCellRenderer;
import javax.swing.MenuElement;
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
@ -25,23 +30,31 @@ import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.adaptors.EnumModel;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.DescriptionArea;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.simulation.RK4SimulationStepper;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.simulation.listeners.example.CSVSaveListener;
import net.sf.openrocket.simulation.extension.SimulationExtension;
import net.sf.openrocket.simulation.extension.SimulationExtensionProvider;
import net.sf.openrocket.simulation.extension.SwingSimulationExtensionConfigurator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.GeodeticComputationStrategy;
import com.google.inject.Key;
class SimulationOptionsPanel extends JPanel {
private static final Translator trans = Application.getTranslator();
final Simulation simulation;
private JPanel currentExtensions;
SimulationOptionsPanel(final Simulation simulation) {
super(new MigLayout("fill"));
this.simulation = simulation;
@ -162,133 +175,255 @@ class SimulationOptionsPanel extends JPanel {
//// Simulation listeners
//// Simulation extensions
sub = new JPanel(new MigLayout("fill, gap 0 0"));
//// Simulator listeners
sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simlist")));
this.add(sub, "growx, growy");
sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.SimExt")));
this.add(sub, "wmin 300lp, growx, growy");
DescriptionArea desc = new DescriptionArea(5);
//// <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
//// For details on writing simulation listeners, see the OpenRocket technical documentation.
desc.setText(trans.get("simedtdlg.txt.longA1") +
trans.get("simedtdlg.txt.longA2"));
sub.add(desc, "aligny 0, growx, wrap para");
desc.setText(trans.get("simedtdlg.SimExt.desc"));
sub.add(desc, "aligny 0, hmin 100lp, growx, wrap para");
//// Current listeners:
label = new JLabel(trans.get("simedtdlg.lbl.Curlist"));
sub.add(label, "spanx, wrap rel");
final ListenerListModel listenerModel = new ListenerListModel();
final JList list = new JList(listenerModel);
list.setCellRenderer(new ListenerCellRenderer());
JScrollPane scroll = new JScrollPane(list);
// scroll.setPreferredSize(new Dimension(1,1));
sub.add(scroll, "height 1px, grow, wrap rel");
final JButton addExtension = new JButton("Add extension");
final JPopupMenu menu = getExtensionMenu();
addExtension.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
menu.show(addExtension, 5, addExtension.getBounds().height);
}
});
sub.add(addExtension, "growx, wrap 0");
//// Add button
button = new JButton(trans.get("simedtdlg.but.add"));
currentExtensions = new JPanel(new MigLayout("fillx, gap 0 0, ins 0"));
JScrollPane scroll = new JScrollPane(currentExtensions);
// &#$%! scroll pane will not honor "growy"...
sub.add(scroll, "growx, growy, h 100%");
updateCurrentExtensions();
}
private JPopupMenu getExtensionMenu() {
Set<SimulationExtensionProvider> extensions = Application.getInjector().getInstance(new Key<Set<SimulationExtensionProvider>>() {
});
JPopupMenu basemenu = new JPopupMenu();
for (final SimulationExtensionProvider provider : extensions) {
List<String> ids = provider.getIds();
for (final String id : ids) {
List<String> menuItems = provider.getName(id);
if (menuItems != null) {
JComponent menu = findMenu(basemenu, menuItems);
JMenuItem item = new JMenuItem(menuItems.get(menuItems.size() - 1));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
SimulationExtension e = provider.getInstance(id);
simulation.getSimulationExtensions().add(e);
updateCurrentExtensions();
}
});
menu.add(item);
}
}
}
return basemenu;
}
private JComponent findMenu(MenuElement menu, List<String> menuItems) {
for (int i = 0; i < menuItems.size() - 1; i++) {
String menuItem = menuItems.get(i);
MenuElement found = null;
for (MenuElement e : menu.getSubElements()) {
if (e instanceof JMenu && ((JMenu) e).getText().equals(menuItem)) {
found = e;
break;
}
}
if (found != null) {
menu = found;
} else {
JMenu m = new JMenu(menuItem);
((JComponent) menu).add(m);
menu = m;
}
}
return (JComponent) menu;
}
private void updateCurrentExtensions() {
currentExtensions.removeAll();
if (simulation.getSimulationExtensions().isEmpty()) {
StyledLabel l = new StyledLabel(trans.get("simedtdlg.SimExt.noExtensions"), Style.ITALIC);
l.setForeground(Color.DARK_GRAY);
currentExtensions.add(l, "growx, pad 5 5 5 5, wrap");
} else {
for (SimulationExtension e : simulation.getSimulationExtensions()) {
currentExtensions.add(new SimulationExtensionPanel(e), "growx, wrap");
}
}
// Both needed:
this.revalidate();
this.repaint();
}
private class SimulationExtensionPanel extends JPanel {
public SimulationExtensionPanel(final SimulationExtension extension) {
super(new MigLayout("fillx, gapx 0"));
this.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
this.add(new JLabel(extension.getName()), "spanx, growx, wrap");
JButton button;
this.add(new JPanel(), "spanx, split, growx, right");
if (findConfigurator(extension) != null) {
button = new JButton(Icons.CONFIGURE);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String previous = Application.getPreferences().getString("previousListenerName", "");
String input = (String) JOptionPane.showInputDialog(SwingUtilities.getRoot(SimulationOptionsPanel.this),
new Object[] {
//// Type the full Java class name of the simulation listener, for example:
"Type the full Java class name of the simulation listener, for example:",
"<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
//// Add simulation listener
trans.get("simedtdlg.lbl.Addsimlist"),
JOptionPane.QUESTION_MESSAGE,
null, null,
previous
);
if (input == null || input.equals(""))
return;
Application.getPreferences().putString("previousListenerName", input);
simulation.getSimulationListeners().add(input);
listenerModel.fireContentsChanged();
findConfigurator(extension).configure(extension, simulation,
SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
updateCurrentExtensions();
}
});
sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para");
this.add(button, "right");
}
//// Remove button
button = new JButton(trans.get("simedtdlg.but.remove"));
if (extension.getDescription() != null) {
button = new JButton(Icons.HELP);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int[] selected = list.getSelectedIndices();
Arrays.sort(selected);
for (int i = selected.length - 1; i >= 0; i--) {
simulation.getSimulationListeners().remove(selected[i]);
}
listenerModel.fireContentsChanged();
final JDialog dialog = new JDialog(SwingUtilities.windowForComponent(SimulationOptionsPanel.this),
extension.getName(), ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel(new MigLayout("fill"));
DescriptionArea area = new DescriptionArea(extension.getDescription(), 10, 0);
panel.add(area, "width 400lp, wrap para");
JButton close = new JButton(trans.get("button.close"));
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
}
});
sub.add(button, "sizegroup buttons, alignx 50%");
panel.add(close, "right");
dialog.add(panel);
GUIUtil.setDisposableDialogOptions(dialog, close);
dialog.setLocationRelativeTo(SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
dialog.setVisible(true);
}
});
this.add(button, "right");
}
private class ListenerCellRenderer extends JLabel implements ListCellRenderer {
button = new JButton(Icons.DELETE);
button.addActionListener(new ActionListener() {
@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
String s = value.toString();
setText(s);
public void actionPerformed(ActionEvent arg0) {
Iterator<SimulationExtension> iter = simulation.getSimulationExtensions().iterator();
while (iter.hasNext()) {
// Compare with identity
if (iter.next() == extension) {
iter.remove();
break;
}
}
updateCurrentExtensions();
}
});
this.add(button, "right");
// Attempt instantiating, catch any exceptions
Exception ex = null;
try {
Class<?> c = Class.forName(s);
@SuppressWarnings("unused")
SimulationListener l = (SimulationListener) c.newInstance();
} catch (Exception e) {
ex = e;
}
if (ex == null) {
setIcon(Icons.SIMULATION_LISTENER_OK);
//// Listener instantiated successfully.
setToolTipText("Listener instantiated successfully.");
} else {
setIcon(Icons.SIMULATION_LISTENER_ERROR);
//// <html>Unable to instantiate listener due to exception:<br>
setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
ex.toString());
}
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setOpaque(true);
return this;
private SwingSimulationExtensionConfigurator findConfigurator(SimulationExtension extension) {
Set<SwingSimulationExtensionConfigurator> configurators = Application.getInjector().getInstance(new Key<Set<SwingSimulationExtensionConfigurator>>() {
});
for (SwingSimulationExtensionConfigurator c : configurators) {
if (c.support(extension)) {
return c;
}
}
private class ListenerListModel extends AbstractListModel {
@Override
public String getElementAt(int index) {
if (index < 0 || index >= getSize())
return null;
return simulation.getSimulationListeners().get(index);
}
@Override
public int getSize() {
return simulation.getSimulationListeners().size();
}
public void fireContentsChanged() {
super.fireContentsChanged(this, 0, getSize());
}
}
//
//
// private class ExtensionListModel extends AbstractListModel {
// @Override
// public SimulationExtensionConfiguration getElementAt(int index) {
// if (index < 0 || index >= getSize())
// return null;
// return simulation.getSimulationExtensions().get(index);
// }
//
// @Override
// public int getSize() {
// return simulation.getSimulationExtensions().size();
// }
// }
//
//
// private class ExtensionCellRenderer extends JPanel implements ListCellRenderer {
// private JLabel label;
//
// public ExtensionCellRenderer() {
// super(new MigLayout("fill"));
// label = new JLabel();
//
// }
//
// @Override
// public Component getListCellRendererComponent(JList list, Object value,
// int index, boolean isSelected, boolean cellHasFocus) {
// SimulationExtensionConfiguration config = (SimulationExtensionConfiguration) value;
//
//
//
// String s = value.toString();
// setText(s);
//
// // Attempt instantiating, catch any exceptions
// Exception ex = null;
// try {
// Class<?> c = Class.forName(s);
// @SuppressWarnings("unused")
// SimulationListener l = (SimulationListener) c.newInstance();
// } catch (Exception e) {
// ex = e;
// }
//
// if (ex == null) {
// setIcon(Icons.SIMULATION_LISTENER_OK);
// //// Listener instantiated successfully.
// setToolTipText("Listener instantiated successfully.");
// } else {
// setIcon(Icons.SIMULATION_LISTENER_ERROR);
// //// <html>Unable to instantiate listener due to exception:<br>
// setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
// ex.toString());
// }
//
// if (isSelected) {
// setBackground(list.getSelectionBackground());
// setForeground(list.getSelectionForeground());
// } else {
// setBackground(list.getBackground());
// setForeground(list.getForeground());
// }
// setOpaque(true);
// return this;
// }
// }
}

View File

@ -71,9 +71,6 @@ import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeSelectionModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.gui.Resettable;
import net.sf.openrocket.logging.Markers;
import net.sf.openrocket.startup.Application;
@ -81,6 +78,9 @@ import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Invalidatable;
import net.sf.openrocket.util.MemoryManagement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GUIUtil {
private static final Logger log = LoggerFactory.getLogger(GUIUtil.class);
@ -147,6 +147,7 @@ public class GUIUtil {
installEscapeCloseOperation(dialog);
setWindowIcons(dialog);
addModelNullingListener(dialog);
dialog.setLocationRelativeTo(dialog.getOwner());
dialog.setLocationByPlatform(true);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.pack();

View File

@ -8,13 +8,13 @@ import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Icons {
private static final Logger log = LoggerFactory.getLogger(Icons.class);
@ -77,6 +77,8 @@ public class Icons {
public static final Icon DELETE = loadImageIcon("pix/icons/delete.png", "Delete");
public static final Icon EDIT = loadImageIcon("pix/icons/pencil.png", "Edit");
public static final Icon CONFIGURE = loadImageIcon("pix/icons/configure.png", "Configure");
public static final Icon HELP = loadImageIcon("pix/icons/help-about.png", "Help");
public static final Icon UP = loadImageIcon("pix/icons/up.png", "Up");
public static final Icon DOWN = loadImageIcon("pix/icons/down.png", "Down");

View File

@ -0,0 +1,69 @@
package net.sf.openrocket.simulation.extension;
import java.awt.Dialog.ModalityType;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import com.google.inject.Inject;
public abstract class AbstractSwingSimulationExtensionConfigurator<E extends SimulationExtension> implements SwingSimulationExtensionConfigurator {
@Inject
protected Translator trans;
private final Class<E> extensionClass;
protected AbstractSwingSimulationExtensionConfigurator(Class<E> extensionClass) {
this.extensionClass = extensionClass;
}
@Override
public boolean support(SimulationExtension extension) {
return extensionClass.isInstance(extension);
}
@SuppressWarnings("unchecked")
@Override
public void configure(SimulationExtension extension, Simulation simulation, Window parent) {
final JDialog dialog = new JDialog(parent, getTitle(extension, simulation), ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel(new MigLayout("fill"));
JPanel sub = new JPanel(new MigLayout("fill, ins 0"));
panel.add(getConfigurationComponent((E) extension, simulation, sub), "grow, wrap para");
JButton close = new JButton(trans.get("button.close"));
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
}
});
panel.add(close, "right");
dialog.add(panel);
GUIUtil.setDisposableDialogOptions(dialog, close);
dialog.setVisible(true);
}
/**
* Return a title for the dialog window. By default uses the extension's name.
*/
protected String getTitle(SimulationExtension extension, Simulation simulation) {
return extension.getName();
}
protected abstract JComponent getConfigurationComponent(E extension, Simulation simulation, JPanel panel);
}

View File

@ -0,0 +1,29 @@
package net.sf.openrocket.simulation.extension;
import java.awt.Window;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.plugin.Plugin;
@Plugin
public interface SwingSimulationExtensionConfigurator {
/**
* Test whether this configurator supports configuring an extension.
*
* @param extension the extension to test
* @return true if this configurator can configure the specified extension
*/
public boolean support(SimulationExtension extension);
/**
* Open an application-modal dialog for configuring a simulation extension.
* Close the dialog when ready.
*
* @param extension the extension to configure
* @param simulation the simulation the extension is attached to
* @param parent the parent window for the dialog
*/
public void configure(SimulationExtension extension, Simulation simulation, Window parent);
}

View File

@ -0,0 +1,43 @@
package net.sf.openrocket.simulation.extension.impl;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
import net.sf.openrocket.unit.UnitGroup;
@Plugin
public class AirStartConfigurator extends AbstractSwingSimulationExtensionConfigurator<AirStart> {
public AirStartConfigurator() {
super(AirStart.class);
}
@Override
protected JComponent getConfigurationComponent(AirStart extension, Simulation simulation, JPanel panel) {
panel.add(new JLabel("Launch altitude:"));
DoubleModel m = new DoubleModel(extension, "LaunchAltitude", UnitGroup.UNITS_DISTANCE, 0);
JSpinner spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
panel.add(spin, "w 65lp!");
UnitSelector unit = new UnitSelector(m);
panel.add(unit, "w 25");
BasicSlider slider = new BasicSlider(m.getSliderModel(0, 1000));
panel.add(slider, "w 75lp, wrap");
return panel;
}
}

View File

@ -0,0 +1,47 @@
package net.sf.openrocket.simulation.extension.impl;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
@Plugin
public class JavaCodeConfigurator extends AbstractSwingSimulationExtensionConfigurator<JavaCode> {
public JavaCodeConfigurator() {
super(JavaCode.class);
}
@Override
protected JComponent getConfigurationComponent(final JavaCode extension, Simulation simulation, JPanel panel) {
panel.add(new JLabel(trans.get("SimulationExtension.javacode.desc")), "wrap para");
panel.add(new JLabel(trans.get("SimulationExtension.javacode.className")), "wrap rel");
final JTextField textField = new JTextField(extension.getClassName());
textField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
update();
}
public void removeUpdate(DocumentEvent e) {
update();
}
public void insertUpdate(DocumentEvent e) {
update();
}
public void update() {
extension.setClassName(textField.getText());
}
});
panel.add(textField, "growx");
return panel;
}
}