1 package com.billkuker.rocketry.motorsim.visual.workbench;
\r
3 import java.awt.BorderLayout;
\r
4 import java.awt.Dimension;
\r
5 import java.awt.event.ActionEvent;
\r
6 import java.awt.event.ActionListener;
\r
7 import java.awt.event.FocusEvent;
\r
8 import java.awt.event.FocusListener;
\r
9 import java.beans.PropertyChangeEvent;
\r
10 import java.beans.PropertyChangeListener;
\r
11 import java.beans.PropertyVetoException;
\r
12 import java.io.IOException;
\r
13 import java.util.HashMap;
\r
14 import java.util.Map;
\r
15 import java.util.Vector;
\r
17 import javax.measure.quantity.Length;
\r
18 import javax.measure.unit.SI;
\r
19 import javax.swing.Box;
\r
20 import javax.swing.BoxLayout;
\r
21 import javax.swing.ComboBoxModel;
\r
22 import javax.swing.JButton;
\r
23 import javax.swing.JComboBox;
\r
24 import javax.swing.JFrame;
\r
25 import javax.swing.JLabel;
\r
26 import javax.swing.JPanel;
\r
27 import javax.swing.JProgressBar;
\r
28 import javax.swing.JSplitPane;
\r
29 import javax.swing.JTabbedPane;
\r
30 import javax.swing.JTextArea;
\r
31 import javax.swing.JTextField;
\r
32 import javax.swing.SwingUtilities;
\r
33 import javax.swing.UIManager;
\r
34 import javax.swing.WindowConstants;
\r
36 import org.apache.log4j.Logger;
\r
37 import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
\r
38 import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
\r
39 import org.jscience.physics.amount.Amount;
\r
41 import com.billkuker.rocketry.motorsim.Burn;
\r
42 import com.billkuker.rocketry.motorsim.Chamber;
\r
43 import com.billkuker.rocketry.motorsim.ChangeListening;
\r
44 import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;
\r
45 import com.billkuker.rocketry.motorsim.CylindricalChamber;
\r
46 import com.billkuker.rocketry.motorsim.Fuel;
\r
47 import com.billkuker.rocketry.motorsim.Grain;
\r
48 import com.billkuker.rocketry.motorsim.Motor;
\r
49 import com.billkuker.rocketry.motorsim.Nozzle;
\r
50 import com.billkuker.rocketry.motorsim.RocketScience;
\r
51 import com.billkuker.rocketry.motorsim.fuel.KNSU;
\r
52 import com.billkuker.rocketry.motorsim.grain.CSlot;
\r
53 import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;
\r
54 import com.billkuker.rocketry.motorsim.grain.EndBurner;
\r
55 import com.billkuker.rocketry.motorsim.grain.Finocyl;
\r
56 import com.billkuker.rocketry.motorsim.grain.Moonburner;
\r
57 import com.billkuker.rocketry.motorsim.grain.MultiGrain;
\r
58 import com.billkuker.rocketry.motorsim.grain.RodAndTubeGrain;
\r
59 import com.billkuker.rocketry.motorsim.io.MotorIO;
\r
60 import com.billkuker.rocketry.motorsim.visual.BurnPanel;
\r
61 import com.billkuker.rocketry.motorsim.visual.Editor;
\r
62 import com.billkuker.rocketry.motorsim.visual.GrainPanel;
\r
63 import com.billkuker.rocketry.motorsim.visual.HardwarePanel;
\r
65 public class MotorEditor extends JTabbedPane implements PropertyChangeListener {
\r
66 private static final long serialVersionUID = 1L;
\r
67 private static Logger log = Logger.getLogger(MotorEditor.class);
\r
68 RSyntaxTextArea text = new RSyntaxTextArea();
\r
70 GrainEditor grainEditor;
\r
74 private Vector<BurnWatcher> burnWatchers = new Vector<BurnWatcher>();
\r
75 private ComboBoxModel availableFuels;
\r
77 //private static final int XML_TAB = 0;
\r
78 private static final int CASING_TAB = 0;
\r
79 private static final int GRAIN_TAB = 1;
\r
80 private static final int BURN_TAB = 2;
\r
82 @SuppressWarnings("unchecked")
\r
83 private Class[] grainTypes = { CoredCylindricalGrain.class, Finocyl.class,
\r
84 Moonburner.class, RodAndTubeGrain.class, CSlot.class, EndBurner.class };
\r
86 private abstract class Chooser<T> extends JPanel {
\r
87 private static final long serialVersionUID = 1L;
\r
88 private Class<? extends T>[] types;
\r
89 private Map<Class<? extends T>, T> old = new HashMap<Class<? extends T>, T>();
\r
91 public Chooser(T initial, Class<? extends T>... ts) {
\r
93 if ( initial != null )
\r
94 old.put((Class<? extends T>)initial.getClass(), initial);
\r
95 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
\r
96 for (final Class<? extends T> c : types) {
\r
97 JButton b = new JButton(c.getSimpleName());
\r
99 b.addActionListener(new ActionListener() {
\r
100 public void actionPerformed(ActionEvent e) {
\r
102 T val = old.get(c);
\r
103 if ( val == null ){
\r
104 System.err.println("CREATED NEW =========================");
\r
105 val = c.newInstance();
\r
109 } catch (InstantiationException e1) {
\r
110 e1.printStackTrace();
\r
111 } catch (IllegalAccessException e1) {
\r
112 e1.printStackTrace();
\r
119 protected abstract void choiceMade(T o);
\r
122 private class BurnTab extends JPanel {
\r
123 private static final long serialVersionUID = 1L;
\r
124 private Thread currentThread;
\r
127 setLayout(new BorderLayout());
\r
128 setName("Simulation Results");
\r
132 private class BurnCanceled extends RuntimeException{
\r
133 private static final long serialVersionUID = 1L;
\r
136 public void reBurn() {
\r
138 currentThread = new Thread() {
\r
139 public void run() {
\r
140 final Thread me = this;
\r
141 final JProgressBar bar = new JProgressBar(0, 100);
\r
142 add(bar, BorderLayout.NORTH);
\r
143 final JLabel progress = new JLabel();
\r
144 add(progress, BorderLayout.CENTER);
\r
146 final Burn b = new Burn(motor,
\r
147 new Burn.BurnProgressListener() {
\r
149 public void setProgress(float f) {
\r
150 int pct = (int)(f*100);
\r
152 Amount<Length> web = motor.getGrain().webThickness();
\r
153 Amount<Length> remaining = web.times(1.0 - f);
\r
155 progress.setText("Progress: " + pct + "% (" + RocketScience.ammountToRoundedString(remaining) + " web thickness remaining)");
\r
156 if ( currentThread != me ){
\r
157 throw new BurnCanceled();
\r
162 final BurnPanel bp = new BurnPanel(b);
\r
163 SwingUtilities.invokeLater(new Thread() {
\r
164 public void run() {
\r
166 add(bp, BorderLayout.CENTER);
\r
168 for (BurnWatcher bw : burnWatchers)
\r
169 bw.replace(burn, b);
\r
175 } catch (BurnCanceled c){
\r
176 log.info("Burn Canceled!");
\r
177 } catch (Exception e) {
\r
179 JTextArea t = new JTextArea(e.getMessage());
\r
180 t.setEditable(false);
\r
185 currentThread.start();
\r
189 private class GrainEditor extends JSplitPane {
\r
190 private static final long serialVersionUID = 1L;
\r
192 public GrainEditor(final Grain g) {
\r
193 super(JSplitPane.HORIZONTAL_SPLIT);
\r
194 setName("Grain Geometry");
\r
195 setRightComponent(new GrainPanel(g));
\r
196 if (g instanceof Grain.Composite) {
\r
197 final JPanel p = new JPanel();
\r
198 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
\r
199 p.add(new Editor(g));
\r
200 for (Grain gg : ((Grain.Composite) g).getGrains()) {
\r
201 final int grainEditorIndex = p.getComponentCount() + 1;
\r
202 p.add(new Chooser<Grain>(gg, grainTypes) {
\r
203 private static final long serialVersionUID = 1L;
\r
206 protected void choiceMade(Grain ng) {
\r
207 if (g instanceof MultiGrain) {
\r
208 ((MultiGrain) g).setGrain(ng);
\r
209 p.remove(grainEditorIndex);
\r
210 p.add(new Editor(ng), grainEditorIndex);
\r
212 p.add(new Editor(g), 0);
\r
216 p.add(new Editor(gg));
\r
217 if (gg instanceof ChangeListening.Subject) {
\r
218 ((ChangeListening.Subject) gg)
\r
219 .addPropertyChangeListener(MotorEditor.this);
\r
222 setLeftComponent(p);
\r
224 setLeftComponent(new Editor(g));
\r
226 // setDividerLocation(.25);
\r
227 // setResizeWeight(.25);
\r
228 if (g instanceof ChangeListening.Subject) {
\r
229 ((ChangeListening.Subject) g)
\r
230 .addPropertyChangeListener(MotorEditor.this);
\r
235 private class CaseEditor extends JSplitPane {
\r
236 private static final long serialVersionUID = 1L;
\r
238 public CaseEditor(Nozzle n, Chamber c) {
\r
239 super(JSplitPane.VERTICAL_SPLIT);
\r
240 setName("General Parameters");
\r
242 JPanel parts = new JPanel();
\r
243 parts.setLayout(new BoxLayout(parts, BoxLayout.X_AXIS));
\r
244 setTopComponent(parts);
\r
245 setBottomComponent(new HardwarePanel(n, c));
\r
247 JPanel nameAndFuel = new JPanel();
\r
248 nameAndFuel.setLayout(new BoxLayout(nameAndFuel, BoxLayout.Y_AXIS));
\r
250 nameAndFuel.add(new JLabel("Name:"));
\r
251 nameAndFuel.add(new JTextField(motor.getName()) {
\r
252 private static final long serialVersionUID = 1L;
\r
254 setMinimumSize(new Dimension(200, 20));
\r
255 setMaximumSize(new Dimension(Short.MAX_VALUE, 20));
\r
256 final JTextField t = this;
\r
257 addFocusListener(new FocusListener() {
\r
260 public void focusLost(FocusEvent e) {
\r
261 String n = t.getText();
\r
262 if (!"".equals(n) && !n.equals(motor.getName())) {
\r
265 t.setText(motor.getName());
\r
270 public void focusGained(FocusEvent e) {
\r
277 nameAndFuel.add(new JLabel("Fuel:"));
\r
278 nameAndFuel.add( new JComboBox(availableFuels){{
\r
279 setMinimumSize(new Dimension(200, 20));
\r
280 setMaximumSize(new Dimension(Short.MAX_VALUE, 20));
\r
281 addActionListener(new ActionListener(){
\r
283 public void actionPerformed(ActionEvent e) {
\r
284 motor.setFuel((Fuel)getSelectedItem());
\r
285 System.out.println("FUEL CHANGED");
\r
288 nameAndFuel.add(Box.createVerticalGlue());
\r
289 parts.add(nameAndFuel);
\r
291 JPanel casing = new JPanel();
\r
292 casing.setLayout(new BoxLayout(casing, BoxLayout.Y_AXIS));
\r
293 casing.add(new JLabel("Casing:"));
\r
294 casing.add(new Editor(c));
\r
297 JPanel nozzle = new JPanel();
\r
298 nozzle.setLayout(new BoxLayout(nozzle, BoxLayout.Y_AXIS));
\r
299 nozzle.add(new JLabel("Nozzle:"));
\r
300 nozzle.add(new Editor(n));
\r
303 if (n instanceof ChangeListening.Subject) {
\r
304 ((ChangeListening.Subject) n)
\r
305 .addPropertyChangeListener(MotorEditor.this);
\r
307 if (c instanceof ChangeListening.Subject) {
\r
308 ((ChangeListening.Subject) c)
\r
309 .addPropertyChangeListener(MotorEditor.this);
\r
315 text.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML);
\r
319 public MotorEditor(Motor m, ComboBoxModel fuels) {
\r
320 super(JTabbedPane.BOTTOM);
\r
321 this.availableFuels = fuels;
\r
322 text.setName("XML");
\r
323 text.setEditable(false);
\r
324 //add(text, XML_TAB);
\r
328 public Motor getMotor() {
\r
332 private void reText() {
\r
334 text.setText(MotorIO.writeMotor(motor));
\r
335 } catch (IOException e) {
\r
336 throw new Error(e);
\r
340 private void setMotor(Motor m, boolean retext) {
\r
342 motor.removePropertyChangeListener(this);
\r
344 motor.addPropertyChangeListener(this);
\r
347 if (grainEditor != null)
\r
348 remove(grainEditor);
\r
349 while (getTabCount() > 1)
\r
351 add(new CaseEditor(motor.getNozzle(), motor.getChamber()), CASING_TAB);
\r
352 add(new GrainEditor(motor.getGrain()), GRAIN_TAB);
\r
353 add(bt = new BurnTab(), BURN_TAB);
\r
357 public static Motor defaultMotor() {
\r
358 Motor m = new Motor();
\r
359 m.setName("Example Motor");
\r
360 m.setFuel(new KNSU());
\r
362 CylindricalChamber c = new CylindricalChamber();
\r
363 c.setLength(Amount.valueOf(200, SI.MILLIMETER));
\r
364 c.setID(Amount.valueOf(30, SI.MILLIMETER));
\r
367 CoredCylindricalGrain g = new CoredCylindricalGrain();
\r
369 g.setLength(Amount.valueOf(70, SI.MILLIMETER));
\r
370 g.setOD(Amount.valueOf(30, SI.MILLIMETER));
\r
371 g.setID(Amount.valueOf(10, SI.MILLIMETER));
\r
372 } catch (PropertyVetoException v) {
\r
373 throw new Error(v);
\r
376 m.setGrain(new MultiGrain(g, 2));
\r
378 ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();
\r
379 n.setThroatDiameter(Amount.valueOf(7.962, SI.MILLIMETER));
\r
380 n.setExitDiameter(Amount.valueOf(13.79, SI.MILLIMETER));
\r
381 n.setEfficiency(.85);
\r
387 public void focusOnObject(Object o) {
\r
388 if (o instanceof Grain)
\r
389 setSelectedIndex(GRAIN_TAB);
\r
390 if (o instanceof Chamber || o instanceof Nozzle)
\r
391 setSelectedIndex(CASING_TAB);
\r
394 public void addBurnWatcher(BurnWatcher bw) {
\r
395 burnWatchers.add(bw);
\r
399 public void showAsWindow() {
\r
400 JFrame f = new JFrame();
\r
401 f.setSize(1024, 768);
\r
402 f.setContentPane(this);
\r
403 f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
\r
404 f.setVisible(true);
\r
407 public static void main(String args[]) throws Exception {
\r
409 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
\r
410 } catch (Exception e1) {
\r
411 e1.printStackTrace();
\r
413 Vector<Fuel> ff = new Vector<Fuel>();
\r
414 ff.add(new KNSU());
\r
415 //new MotorEditor(defaultMotor(), ff).showAsWindow();
\r
418 public void propertyChange(PropertyChangeEvent evt) {
\r
420 // Dont re-burn for a name change!
\r
421 if (!evt.getPropertyName().equals("Name")){
\r
424 for (BurnWatcher bw : burnWatchers)
\r
425 bw.replace(burn, burn);
\r