Added EndBurner with Punt
[sw/motorsim] / src / com / billkuker / rocketry / motorsim / visual / workbench / MotorEditor.java
1 package com.billkuker.rocketry.motorsim.visual.workbench;\r
2 \r
3 import java.awt.BorderLayout;\r
4 import java.awt.event.ActionEvent;\r
5 import java.awt.event.ActionListener;\r
6 import java.awt.event.FocusEvent;\r
7 import java.awt.event.FocusListener;\r
8 import java.beans.PropertyChangeEvent;\r
9 import java.beans.PropertyChangeListener;\r
10 import java.beans.PropertyVetoException;\r
11 import java.io.IOException;\r
12 import java.util.HashMap;\r
13 import java.util.Map;\r
14 import java.util.Vector;\r
15 \r
16 import javax.measure.quantity.Pressure;\r
17 import javax.measure.quantity.Velocity;\r
18 import javax.measure.unit.SI;\r
19 import javax.swing.BoxLayout;\r
20 import javax.swing.JButton;\r
21 import javax.swing.JFrame;\r
22 import javax.swing.JPanel;\r
23 import javax.swing.JProgressBar;\r
24 import javax.swing.JSplitPane;\r
25 import javax.swing.JTabbedPane;\r
26 import javax.swing.JTextArea;\r
27 import javax.swing.JTextField;\r
28 import javax.swing.SwingUtilities;\r
29 import javax.swing.UIManager;\r
30 import javax.swing.WindowConstants;\r
31 \r
32 import org.apache.log4j.Logger;\r
33 import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;\r
34 import org.fife.ui.rsyntaxtextarea.SyntaxConstants;\r
35 import org.jscience.physics.amount.Amount;\r
36 \r
37 import com.billkuker.rocketry.motorsim.Burn;\r
38 import com.billkuker.rocketry.motorsim.Chamber;\r
39 import com.billkuker.rocketry.motorsim.ChangeListening;\r
40 import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
41 import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
42 import com.billkuker.rocketry.motorsim.Fuel;\r
43 import com.billkuker.rocketry.motorsim.Grain;\r
44 import com.billkuker.rocketry.motorsim.Motor;\r
45 import com.billkuker.rocketry.motorsim.Nozzle;\r
46 import com.billkuker.rocketry.motorsim.fuel.EditableFuel;\r
47 import com.billkuker.rocketry.motorsim.fuel.KNDX;\r
48 import com.billkuker.rocketry.motorsim.fuel.KNER;\r
49 import com.billkuker.rocketry.motorsim.fuel.KNSB;\r
50 import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
51 import com.billkuker.rocketry.motorsim.grain.CSlot;\r
52 import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
53 import com.billkuker.rocketry.motorsim.grain.EndBurner;\r
54 import com.billkuker.rocketry.motorsim.grain.Finocyl;\r
55 import com.billkuker.rocketry.motorsim.grain.Moonburner;\r
56 import com.billkuker.rocketry.motorsim.grain.MultiGrain;\r
57 import com.billkuker.rocketry.motorsim.grain.RodAndTubeGrain;\r
58 import com.billkuker.rocketry.motorsim.io.MotorIO;\r
59 import com.billkuker.rocketry.motorsim.visual.BurnPanel;\r
60 import com.billkuker.rocketry.motorsim.visual.Chart;\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.NozzlePanel;\r
64 \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
69         Motor motor;\r
70         GrainEditor grainEditor;\r
71         BurnTab bt;\r
72         Burn burn;\r
73 \r
74         private Vector<BurnWatcher> burnWatchers = new Vector<BurnWatcher>();\r
75 \r
76         private static final int XML_TAB = 0;\r
77         private static final int CASING_TAB = 1;\r
78         private static final int GRAIN_TAB = 2;\r
79         private static final int FUEL_TAB = 3;\r
80         private static final int BURN_TAB = 4;\r
81 \r
82         @SuppressWarnings("unchecked")\r
83         private Class[] grainTypes = { CoredCylindricalGrain.class, Finocyl.class,\r
84                         Moonburner.class, RodAndTubeGrain.class, CSlot.class, EndBurner.class };\r
85 \r
86         @SuppressWarnings("unchecked")\r
87         private Class[] fuelTypes = { KNSB.class, KNSU.class, KNER.class,\r
88                         KNDX.class, EditableFuel.class };\r
89 \r
90         private abstract class Chooser<T> extends JPanel {\r
91                 private static final long serialVersionUID = 1L;\r
92                 private Class<? extends T>[] types;\r
93                 private Map<Class<? extends T>, T> old = new HashMap<Class<? extends T>, T>();\r
94 \r
95                 public Chooser(T initial, Class<? extends T>... ts) {\r
96                         types = ts;\r
97                         if ( initial != null )\r
98                                 old.put((Class<? extends T>)initial.getClass(), initial);\r
99                         setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
100                         for (final Class<? extends T> c : types) {\r
101                                 JButton b = new JButton(c.getSimpleName());\r
102                                 add(b);\r
103                                 b.addActionListener(new ActionListener() {\r
104                                         public void actionPerformed(ActionEvent e) {\r
105                                                 try {\r
106                                                         T val = old.get(c);\r
107                                                         if ( val == null ){\r
108                                                                 System.err.println("CREATED NEW =========================");\r
109                                                                 val = c.newInstance();\r
110                                                                 old.put(c, val);\r
111                                                         }\r
112                                                         choiceMade(val);\r
113                                                 } catch (InstantiationException e1) {\r
114                                                         e1.printStackTrace();\r
115                                                 } catch (IllegalAccessException e1) {\r
116                                                         e1.printStackTrace();\r
117                                                 }\r
118                                         }\r
119                                 });\r
120                         }\r
121                 }\r
122 \r
123                 protected abstract void choiceMade(T o);\r
124         }\r
125 \r
126         private class BurnTab extends JPanel {\r
127                 private static final long serialVersionUID = 1L;\r
128                 private Thread currentThread;\r
129                 \r
130                 public BurnTab() {\r
131                         setLayout(new BorderLayout());\r
132                         setName("Burn");\r
133                         reBurn();\r
134                 }\r
135                 \r
136                 private class BurnCanceled extends RuntimeException{\r
137                         private static final long serialVersionUID = 1L;\r
138                 };\r
139 \r
140                 public void reBurn() {\r
141                         removeAll();\r
142                         currentThread = new Thread() {\r
143                                 public void run() {\r
144                                         final Thread me = this;\r
145                                         final JProgressBar bar = new JProgressBar(0, 100);\r
146                                         add(bar, BorderLayout.NORTH);\r
147                                         try {\r
148                                                 final Burn b = new Burn(motor,\r
149                                                                 new Burn.BurnProgressListener() {\r
150                                                                         @Override\r
151                                                                         public void setProgress(float f) {\r
152                                                                                 bar.setValue((int) (f * 100));\r
153                                                                                 if ( currentThread != me ){\r
154                                                                                         throw new BurnCanceled();\r
155                                                                                 }\r
156                                                                         }\r
157                                                                 });\r
158 \r
159                                                 final BurnPanel bp = new BurnPanel(b);\r
160                                                 SwingUtilities.invokeLater(new Thread() {\r
161                                                         public void run() {\r
162                                                                 remove(bar);\r
163                                                                 add(bp, BorderLayout.CENTER);\r
164 \r
165                                                                 for (BurnWatcher bw : burnWatchers)\r
166                                                                         bw.replace(burn, b);\r
167                                                                 burn = b;\r
168 \r
169                                                                 revalidate();\r
170                                                         }\r
171                                                 });\r
172                                         } catch (BurnCanceled c){\r
173                                                 log.info("Burn Canceled!");\r
174                                         } catch (Exception e) {\r
175                                                 remove(bar);\r
176                                                 JTextArea t = new JTextArea(e.getMessage());\r
177                                                 t.setEditable(false);\r
178                                                 add(t);\r
179                                         }\r
180                                 }\r
181                         };\r
182                         currentThread.start();\r
183                 }\r
184         }\r
185 \r
186         private class GrainEditor extends JSplitPane {\r
187                 private static final long serialVersionUID = 1L;\r
188 \r
189                 public GrainEditor(final Grain g) {\r
190                         super(JSplitPane.HORIZONTAL_SPLIT);\r
191                         setName("Grain");\r
192                         setRightComponent(new GrainPanel(g));\r
193                         if (g instanceof Grain.Composite) {\r
194                                 final JPanel p = new JPanel();\r
195                                 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));\r
196                                 p.add(new Editor(g));\r
197                                 for (Grain gg : ((Grain.Composite) g).getGrains()) {\r
198                                         final int grainEditorIndex = p.getComponentCount() + 1;\r
199                                         p.add(new Chooser<Grain>(gg, grainTypes) {\r
200                                                 private static final long serialVersionUID = 1L;\r
201 \r
202                                                 @Override\r
203                                                 protected void choiceMade(Grain ng) {\r
204                                                         if (g instanceof MultiGrain) {\r
205                                                                 ((MultiGrain) g).setGrain(ng);\r
206                                                                 p.remove(grainEditorIndex);\r
207                                                                 p.add(new Editor(ng), grainEditorIndex);\r
208                                                                 p.remove(0);\r
209                                                                 p.add(new Editor(g), 0);\r
210                                                         }\r
211                                                 }\r
212                                         });\r
213                                         p.add(new Editor(gg));\r
214                                         if (gg instanceof ChangeListening.Subject) {\r
215                                                 ((ChangeListening.Subject) gg)\r
216                                                                 .addPropertyChangeListener(MotorEditor.this);\r
217                                         }\r
218                                 }\r
219                                 setLeftComponent(p);\r
220                         } else {\r
221                                 setLeftComponent(new Editor(g));\r
222                         }\r
223                         // setDividerLocation(.25);\r
224                         // setResizeWeight(.25);\r
225                         if (g instanceof ChangeListening.Subject) {\r
226                                 ((ChangeListening.Subject) g)\r
227                                                 .addPropertyChangeListener(MotorEditor.this);\r
228                         }\r
229                 }\r
230         }\r
231 \r
232         private class FuelEditor extends JSplitPane {\r
233                 private static final long serialVersionUID = 1L;\r
234 \r
235                 public FuelEditor(Fuel f) {\r
236                         super(JSplitPane.HORIZONTAL_SPLIT);\r
237                         setName("Fuel");\r
238                         Chart<Pressure, Velocity> burnRate;\r
239                         try {\r
240                                 burnRate = new Chart<Pressure, Velocity>(SI.MEGA(SI.PASCAL),\r
241                                                 SI.METERS_PER_SECOND, f, "burnRate");\r
242                         } catch (NoSuchMethodException e) {\r
243                                 throw new Error(e);\r
244                         }\r
245                         burnRate.setDomain(burnRate.new IntervalDomain(Amount.valueOf(0, SI\r
246                                         .MEGA(SI.PASCAL)), Amount.valueOf(11, SI.MEGA(SI.PASCAL)),\r
247                                         20));\r
248 \r
249                         final JPanel p = new JPanel();\r
250                         p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));\r
251 \r
252                         p.add(new Chooser<Fuel>(null, fuelTypes) {\r
253                                 private static final long serialVersionUID = 1L;\r
254 \r
255                                 @Override\r
256                                 protected void choiceMade(Fuel o) {\r
257                                         motor.setFuel(o);\r
258                                         removeTabAt(FUEL_TAB);\r
259                                         MotorEditor.this.add(new FuelEditor(motor.getFuel()),\r
260                                                         FUEL_TAB);\r
261                                         setSelectedIndex(FUEL_TAB);\r
262                                 }\r
263                         });\r
264                         p.add(new Editor(f));\r
265                         try {\r
266                                 p.add(new Editor(f.getCombustionProduct()));\r
267                         } catch (Exception e) {\r
268 \r
269                         }\r
270 \r
271                         setLeftComponent(p);\r
272                         setRightComponent(burnRate);\r
273                         // setDividerLocation(.25);\r
274                         // setResizeWeight(.25);\r
275                         if (f instanceof ChangeListening.Subject) {\r
276                                 ((ChangeListening.Subject) f)\r
277                                                 .addPropertyChangeListener(MotorEditor.this);\r
278                         }\r
279                 }\r
280         }\r
281 \r
282         private class CaseEditor extends JSplitPane {\r
283                 private static final long serialVersionUID = 1L;\r
284 \r
285                 public CaseEditor(Nozzle n, Chamber c) {\r
286                         super(JSplitPane.HORIZONTAL_SPLIT);\r
287                         setName("Casing");\r
288                         JPanel parts = new JPanel();\r
289                         parts.setLayout(new BoxLayout(parts, BoxLayout.Y_AXIS));\r
290                         setLeftComponent(parts);\r
291                         setRightComponent(new NozzlePanel(n));\r
292 \r
293                         parts.add(new JTextField(motor.getName()) {\r
294                                 private static final long serialVersionUID = 1L;\r
295                                 {\r
296                                         final JTextField t = this;\r
297                                         addFocusListener(new FocusListener() {\r
298 \r
299                                                 @Override\r
300                                                 public void focusLost(FocusEvent e) {\r
301                                                         String n = t.getText();\r
302                                                         if (!"".equals(n) && !n.equals(motor.getName())) {\r
303                                                                 motor.setName(n);\r
304                                                         } else {\r
305                                                                 t.setText(motor.getName());\r
306                                                         }\r
307                                                 }\r
308 \r
309                                                 @Override\r
310                                                 public void focusGained(FocusEvent e) {\r
311 \r
312                                                 }\r
313                                         });\r
314 \r
315                                 }\r
316                         });\r
317 \r
318                         parts.add(new Editor(c));\r
319                         parts.add(new Editor(n));\r
320 \r
321                         if (n instanceof ChangeListening.Subject) {\r
322                                 ((ChangeListening.Subject) n)\r
323                                                 .addPropertyChangeListener(MotorEditor.this);\r
324                         }\r
325                         if (c instanceof ChangeListening.Subject) {\r
326                                 ((ChangeListening.Subject) c)\r
327                                                 .addPropertyChangeListener(MotorEditor.this);\r
328                         }\r
329                 }\r
330         }\r
331 \r
332         {\r
333                 text.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML);\r
334 \r
335         }\r
336 \r
337         public MotorEditor(Motor m) {\r
338                 super(JTabbedPane.BOTTOM);\r
339                 text.setName("XML");\r
340                 text.setEditable(false);\r
341                 add(text, XML_TAB);\r
342                 setMotor(m, true);\r
343         }\r
344 \r
345         public Motor getMotor() {\r
346                 return motor;\r
347         }\r
348 \r
349         private void reText() {\r
350                 try {\r
351                         text.setText(MotorIO.writeMotor(motor));\r
352                 } catch (IOException e) {\r
353                         throw new Error(e);\r
354                 }\r
355         }\r
356 \r
357         private void setMotor(Motor m, boolean retext) {\r
358                 if (motor != null)\r
359                         motor.removePropertyChangeListener(this);\r
360                 motor = m;\r
361                 motor.addPropertyChangeListener(this);\r
362                 if (retext)\r
363                         reText();\r
364                 if (grainEditor != null)\r
365                         remove(grainEditor);\r
366                 while (getTabCount() > 1)\r
367                         removeTabAt(1);\r
368                 add(new CaseEditor(motor.getNozzle(), motor.getChamber()), CASING_TAB);\r
369                 add(new GrainEditor(motor.getGrain()), GRAIN_TAB);\r
370                 add(new FuelEditor(motor.getFuel()), FUEL_TAB);\r
371                 add(bt = new BurnTab(), BURN_TAB);\r
372         }\r
373 \r
374         @Deprecated\r
375         public static Motor defaultMotor() {\r
376                 Motor m = new Motor();\r
377                 m.setName("Example Motor");\r
378                 m.setFuel(new KNSU());\r
379 \r
380                 CylindricalChamber c = new CylindricalChamber();\r
381                 c.setLength(Amount.valueOf(200, SI.MILLIMETER));\r
382                 c.setID(Amount.valueOf(30, SI.MILLIMETER));\r
383                 m.setChamber(c);\r
384 \r
385                 CoredCylindricalGrain g = new CoredCylindricalGrain();\r
386                 try {\r
387                         g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
388                         g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
389                         g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
390                 } catch (PropertyVetoException v) {\r
391                         throw new Error(v);\r
392                 }\r
393 \r
394                 m.setGrain(new MultiGrain(g, 2));\r
395 \r
396                 ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
397                 n.setThroatDiameter(Amount.valueOf(7.962, SI.MILLIMETER));\r
398                 n.setExitDiameter(Amount.valueOf(13.79, SI.MILLIMETER));\r
399                 n.setEfficiency(.85);\r
400                 m.setNozzle(n);\r
401 \r
402                 return m;\r
403         }\r
404 \r
405         public void focusOnObject(Object o) {\r
406                 if (o instanceof Grain)\r
407                         setSelectedIndex(GRAIN_TAB);\r
408                 if (o instanceof Chamber || o instanceof Nozzle)\r
409                         setSelectedIndex(CASING_TAB);\r
410                 if (o instanceof Fuel || o instanceof Fuel.CombustionProduct)\r
411                         setSelectedIndex(FUEL_TAB);\r
412         }\r
413 \r
414         public void addBurnWatcher(BurnWatcher bw) {\r
415                 burnWatchers.add(bw);\r
416         }\r
417 \r
418         @Deprecated\r
419         public void showAsWindow() {\r
420                 JFrame f = new JFrame();\r
421                 f.setSize(1024, 768);\r
422                 f.setContentPane(this);\r
423                 f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
424                 f.setVisible(true);\r
425         }\r
426 \r
427         public static void main(String args[]) throws Exception {\r
428                 try {\r
429                         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\r
430                 } catch (Exception e1) {\r
431                         e1.printStackTrace();\r
432                 }\r
433                 new MotorEditor(defaultMotor()).showAsWindow();\r
434         }\r
435 \r
436         public void propertyChange(PropertyChangeEvent evt) {\r
437                 reText();\r
438                 // Dont re-burn for a name change!\r
439                 if (!evt.getPropertyName().equals("Name")){\r
440                         bt.reBurn();\r
441                 } else {\r
442                         for (BurnWatcher bw : burnWatchers)\r
443                                 bw.replace(burn, burn);\r
444                 }\r
445         }\r
446 \r
447 }\r