allow chamber type choice
[sw/motorsim] / gui / 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.Dimension;\r
5 import java.awt.event.ActionEvent;\r
6 import java.awt.event.ActionListener;\r
7 import java.awt.event.ComponentEvent;\r
8 import java.awt.event.ComponentListener;\r
9 import java.awt.event.FocusEvent;\r
10 import java.awt.event.FocusListener;\r
11 import java.beans.PropertyChangeEvent;\r
12 import java.beans.PropertyChangeListener;\r
13 import java.beans.PropertyVetoException;\r
14 import java.util.Collection;\r
15 import java.util.HashMap;\r
16 import java.util.List;\r
17 import java.util.Map;\r
18 import java.util.Vector;\r
19 \r
20 import javax.measure.quantity.Length;\r
21 import javax.measure.unit.SI;\r
22 import javax.swing.Box;\r
23 import javax.swing.BoxLayout;\r
24 import javax.swing.DefaultComboBoxModel;\r
25 import javax.swing.JButton;\r
26 import javax.swing.JComboBox;\r
27 import javax.swing.JFrame;\r
28 import javax.swing.JLabel;\r
29 import javax.swing.JPanel;\r
30 import javax.swing.JProgressBar;\r
31 import javax.swing.JSplitPane;\r
32 import javax.swing.JTabbedPane;\r
33 import javax.swing.JTextArea;\r
34 import javax.swing.JTextField;\r
35 import javax.swing.SwingUtilities;\r
36 import javax.swing.UIManager;\r
37 import javax.swing.WindowConstants;\r
38 \r
39 import org.apache.log4j.Logger;\r
40 import org.jscience.physics.amount.Amount;\r
41 \r
42 import com.billkuker.rocketry.motorsim.Burn;\r
43 import com.billkuker.rocketry.motorsim.Chamber;\r
44 import com.billkuker.rocketry.motorsim.ChangeListening;\r
45 import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
46 import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
47 import com.billkuker.rocketry.motorsim.Fuel;\r
48 import com.billkuker.rocketry.motorsim.Grain;\r
49 import com.billkuker.rocketry.motorsim.Motor;\r
50 import com.billkuker.rocketry.motorsim.Nozzle;\r
51 import com.billkuker.rocketry.motorsim.RocketScience;\r
52 import com.billkuker.rocketry.motorsim.cases.Schedule40;\r
53 import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
54 import com.billkuker.rocketry.motorsim.grain.CSlot;\r
55 import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
56 import com.billkuker.rocketry.motorsim.grain.EndBurner;\r
57 import com.billkuker.rocketry.motorsim.grain.Finocyl;\r
58 import com.billkuker.rocketry.motorsim.grain.Moonburner;\r
59 import com.billkuker.rocketry.motorsim.grain.MultiGrain;\r
60 import com.billkuker.rocketry.motorsim.grain.RodAndTubeGrain;\r
61 import com.billkuker.rocketry.motorsim.grain.Star;\r
62 import com.billkuker.rocketry.motorsim.visual.BurnPanel;\r
63 import com.billkuker.rocketry.motorsim.visual.ClassChooser;\r
64 import com.billkuker.rocketry.motorsim.visual.Editor;\r
65 import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
66 import com.billkuker.rocketry.motorsim.visual.HardwarePanel;\r
67 import com.billkuker.rocketry.motorsim.visual.SummaryPanel;\r
68 \r
69 public class MotorEditor extends JPanel implements PropertyChangeListener {\r
70         private static final long serialVersionUID = 1L;\r
71         private static Logger log = Logger.getLogger(MotorEditor.class);\r
72         Motor motor;\r
73         GrainEditor grainEditor;\r
74         BurnTab bt;\r
75         Burn burn;\r
76         SummaryPanel sp;\r
77         \r
78         JTabbedPane tabs;\r
79 \r
80         private Vector<BurnWatcher> burnWatchers = new Vector<BurnWatcher>();\r
81         private DefaultComboBoxModel availableFuels = new DefaultComboBoxModel();\r
82         \r
83         public void addFuel(Fuel f){\r
84                 availableFuels.addElement(f);\r
85         }\r
86 \r
87         //private static final int XML_TAB = 0;\r
88         private static final int CASING_TAB = 0;\r
89         private static final int GRAIN_TAB = 1;\r
90         private static final int BURN_TAB = 2;\r
91 \r
92         private List<Class<? extends Grain>> grainTypes = new Vector<Class<? extends Grain>>();\r
93         {\r
94                 grainTypes.add(CoredCylindricalGrain.class);\r
95                 grainTypes.add(Finocyl.class);\r
96                 grainTypes.add(Star.class);\r
97                 grainTypes.add(Moonburner.class);\r
98                 grainTypes.add(RodAndTubeGrain.class);\r
99                 grainTypes.add(CSlot.class);\r
100                 grainTypes.add(EndBurner.class);\r
101         }\r
102         \r
103         private List<Class<? extends Chamber>> chamberTypes = new Vector<Class<? extends Chamber>>();\r
104         {\r
105                 chamberTypes.add(CylindricalChamber.class);\r
106                 chamberTypes.add(Schedule40.class);\r
107         }\r
108 \r
109         private abstract class Chooser<T> extends JPanel {\r
110                 private static final long serialVersionUID = 1L;\r
111                 private List<Class<? extends T>> types;\r
112                 private Map<Class<? extends T>, T> old = new HashMap<Class<? extends T>, T>();\r
113 \r
114                 @SuppressWarnings("unchecked")\r
115                 public Chooser(T initial, List<Class<? extends T>> ts) {\r
116                         types = ts;\r
117                         if ( initial != null )\r
118                                 old.put((Class<? extends T>)initial.getClass(), initial);\r
119                         setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
120                         for (final Class<? extends T> c : types) {\r
121                                 JButton b = new JButton(c.getSimpleName());\r
122                                 add(b);\r
123                                 b.addActionListener(new ActionListener() {\r
124                                         public void actionPerformed(ActionEvent e) {\r
125                                                 try {\r
126                                                         T val = old.get(c);\r
127                                                         if ( val == null ){\r
128                                                                 System.err.println("CREATED NEW =========================");\r
129                                                                 val = c.newInstance();\r
130                                                                 old.put(c, val);\r
131                                                         }\r
132                                                         choiceMade(val);\r
133                                                 } catch (InstantiationException e1) {\r
134                                                         e1.printStackTrace();\r
135                                                 } catch (IllegalAccessException e1) {\r
136                                                         e1.printStackTrace();\r
137                                                 }\r
138                                         }\r
139                                 });\r
140                         }\r
141                 }\r
142 \r
143                 protected abstract void choiceMade(T o);\r
144         }\r
145 \r
146         private class BurnTab extends JPanel {\r
147                 private static final long serialVersionUID = 1L;\r
148                 private Thread currentThread;\r
149                 \r
150                 public BurnTab() {\r
151                         setLayout(new BorderLayout());\r
152                         setName("Simulation Results");\r
153                         reBurn();\r
154                 }\r
155                 \r
156                 private class BurnCanceled extends RuntimeException{\r
157                         private static final long serialVersionUID = 1L;\r
158                 };\r
159 \r
160                 public void reBurn() {\r
161                         removeAll();\r
162                         currentThread = new Thread() {\r
163                                 public void run() {\r
164                                         final Thread me = this;\r
165                                         try {\r
166                                                 final Burn b = new Burn(motor);\r
167                                                 b.addBurnProgressListener(\r
168                                                                 new Burn.BurnProgressListener() {\r
169                                                                         @Override\r
170                                                                         public void burnComplete(){};\r
171                                                                         @Override\r
172                                                                         public void setProgress(float f) {\r
173                                                                                 if ( currentThread != me ){\r
174                                                                                         throw new BurnCanceled();\r
175                                                                                 }\r
176                                                                         }\r
177                                                                 });\r
178                                                 if ( sp != null )\r
179                                                         MotorEditor.this.remove(sp);\r
180                                                 MotorEditor.this.add(sp = new SummaryPanel(b), BorderLayout.NORTH);\r
181                                                 revalidate();\r
182                                                 b.burn();\r
183 \r
184                                                 final BurnPanel bp = new BurnPanel(b);\r
185                                                 SwingUtilities.invokeLater(new Thread() {\r
186                                                         public void run() {\r
187                                                                 add(bp, BorderLayout.CENTER);\r
188                                                                 for (BurnWatcher bw : burnWatchers)\r
189                                                                         bw.replace(burn, b);\r
190                                                                 burn = b;\r
191                                                                 revalidate();\r
192                                                         }\r
193                                                 });\r
194                                         } catch (BurnCanceled c){\r
195                                                 log.info("Burn Canceled!");\r
196                                         } catch (Exception e) {\r
197                                                 JTextArea t = new JTextArea(e.getMessage());\r
198                                                 t.setEditable(false);\r
199                                                 add(t);\r
200                                         }\r
201                                 }\r
202                         };\r
203                         currentThread.start();\r
204                 }\r
205         }\r
206 \r
207         private class GrainEditor extends JSplitPane {\r
208                 private static final long serialVersionUID = 1L;\r
209 \r
210                 public GrainEditor(final Grain g) {\r
211                         super(JSplitPane.HORIZONTAL_SPLIT);\r
212                         setName("Grain Geometry");\r
213                         setRightComponent(new GrainPanel(g));\r
214                         if (g instanceof Grain.Composite) {\r
215                                 final JPanel p = new JPanel();\r
216                                 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));\r
217                                 p.add(new Editor(g));\r
218                                 for (Grain gg : ((Grain.Composite) g).getGrains()) {\r
219                                         final int grainEditorIndex = p.getComponentCount() + 1;\r
220                                         p.add(new Chooser<Grain>(gg, grainTypes) {\r
221                                                 private static final long serialVersionUID = 1L;\r
222 \r
223                                                 @Override\r
224                                                 protected void choiceMade(Grain ng) {\r
225                                                         if (g instanceof MultiGrain) {\r
226                                                                 ((MultiGrain) g).setGrain(ng);\r
227                                                                 p.remove(grainEditorIndex);\r
228                                                                 p.add(new Editor(ng), grainEditorIndex);\r
229                                                                 p.remove(0);\r
230                                                                 p.add(new Editor(g), 0);\r
231                                                         }\r
232                                                 }\r
233                                         });\r
234                                         p.add(new Editor(gg));\r
235                                         if (gg instanceof ChangeListening.Subject) {\r
236                                                 ((ChangeListening.Subject) gg)\r
237                                                                 .addPropertyChangeListener(MotorEditor.this);\r
238                                         }\r
239                                 }\r
240                                 setLeftComponent(p);\r
241                         } else {\r
242                                 setLeftComponent(new Editor(g));\r
243                         }\r
244                         // setDividerLocation(.25);\r
245                         // setResizeWeight(.25);\r
246                         if (g instanceof ChangeListening.Subject) {\r
247                                 ((ChangeListening.Subject) g)\r
248                                                 .addPropertyChangeListener(MotorEditor.this);\r
249                         }\r
250                 }\r
251         }\r
252 \r
253         private class CaseEditor extends JSplitPane implements ComponentListener {\r
254                 private static final long serialVersionUID = 1L;\r
255                 \r
256                 private HardwarePanel hp;\r
257                 private JPanel casing;\r
258                 private JPanel nozzle;\r
259                 private Editor casingEditor;\r
260                 private Editor nozzleEditor;\r
261                 \r
262                 private void setup() {\r
263                         if (casingEditor != null)\r
264                                 casing.remove(casingEditor);\r
265                         casing.add(casingEditor = new Editor(motor.getChamber()));\r
266                         if (nozzleEditor != null)\r
267                                 nozzle.remove(nozzleEditor);\r
268                         nozzle.add(nozzleEditor = new Editor(motor.getNozzle()));\r
269                         if (hp != null)\r
270                                 remove(hp);\r
271                         setBottomComponent(hp = new HardwarePanel(motor.getNozzle(),\r
272                                         motor.getChamber()));\r
273                         if (motor.getNozzle() instanceof ChangeListening.Subject) {\r
274                                 ((ChangeListening.Subject) motor.getNozzle())\r
275                                                 .addPropertyChangeListener(MotorEditor.this);\r
276                         }\r
277                         if (motor.getChamber() instanceof ChangeListening.Subject) {\r
278                                 ((ChangeListening.Subject) motor.getChamber())\r
279                                                 .addPropertyChangeListener(MotorEditor.this);\r
280                         }\r
281                 }\r
282 \r
283                 public CaseEditor() {\r
284                         super(JSplitPane.VERTICAL_SPLIT);\r
285                         setName("General Parameters");\r
286                         this.addComponentListener(this);\r
287                         \r
288                         JPanel parts = new JPanel();\r
289                         parts.setLayout(new BoxLayout(parts, BoxLayout.X_AXIS));\r
290                         setTopComponent(parts);\r
291                         \r
292                         JPanel nameAndFuel = new JPanel();\r
293                         nameAndFuel.setLayout(new BoxLayout(nameAndFuel, BoxLayout.Y_AXIS));\r
294 \r
295                         nameAndFuel.add(new JLabel("Name:"));\r
296                         nameAndFuel.add(new JTextField(motor.getName()) {\r
297                                 private static final long serialVersionUID = 1L;\r
298                                 {\r
299                                         setMinimumSize(new Dimension(200, 20));\r
300                                         setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
301                                         final JTextField t = this;\r
302                                         addFocusListener(new FocusListener() {\r
303 \r
304                                                 @Override\r
305                                                 public void focusLost(FocusEvent e) {\r
306                                                         String n = t.getText();\r
307                                                         if (!"".equals(n) && !n.equals(motor.getName())) {\r
308                                                                 motor.setName(n);\r
309                                                         } else {\r
310                                                                 t.setText(motor.getName());\r
311                                                         }\r
312                                                 }\r
313 \r
314                                                 @Override\r
315                                                 public void focusGained(FocusEvent e) {\r
316 \r
317                                                 }\r
318                                         });\r
319 \r
320                                 }\r
321                         });\r
322                         \r
323                         nameAndFuel.add(new JLabel("Fuel:"));\r
324                         nameAndFuel.add( new JComboBox(availableFuels){\r
325                                 private static final long serialVersionUID = 1L;\r
326                                 {\r
327                                         this.setSelectedItem(motor.getFuel());\r
328                                         setMinimumSize(new Dimension(200, 20));\r
329                                         setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
330                                         addActionListener(new ActionListener(){\r
331                                                 @Override\r
332                                                 public void actionPerformed(ActionEvent e) {\r
333                                                         motor.setFuel((Fuel)getSelectedItem());\r
334                                                         System.out.println("FUEL CHANGED");\r
335                                                 }});\r
336                                 }\r
337                         });\r
338                         \r
339                         nameAndFuel.add(new JLabel("Casing:"));\r
340                         nameAndFuel.add(new ClassChooser<Chamber>(chamberTypes, motor.getChamber()) {\r
341                                 private static final long serialVersionUID = 1L;\r
342                                 {\r
343                                         setMinimumSize(new Dimension(200, 20));\r
344                                         setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
345                                 }\r
346                                 @Override\r
347                                 protected Chamber classSelected(Class<? extends Chamber> clazz, Chamber c) {\r
348                                         try {\r
349                                                 if ( c != null ){\r
350                                                         motor.setChamber(c);\r
351                                                 } else {\r
352                                                         motor.setChamber(clazz.newInstance());\r
353                                                 }\r
354                                                 return motor.getChamber();\r
355                                         } catch (InstantiationException e) {\r
356                                                 // TODO Auto-generated catch block\r
357                                                 e.printStackTrace();\r
358                                         } catch (IllegalAccessException e) {\r
359                                                 // TODO Auto-generated catch block\r
360                                                 e.printStackTrace();\r
361                                         }\r
362                                         return null;\r
363                                 }\r
364                         });\r
365                         \r
366                         \r
367                         nameAndFuel.add(Box.createVerticalGlue());\r
368                         parts.add(nameAndFuel);\r
369                         \r
370                         casing = new JPanel();\r
371                         casing.setLayout(new BoxLayout(casing, BoxLayout.Y_AXIS));\r
372                         casing.add(new JLabel("Casing:"));\r
373                         parts.add(casing);\r
374                         \r
375                         nozzle = new JPanel();\r
376                         nozzle.setLayout(new BoxLayout(nozzle, BoxLayout.Y_AXIS));\r
377                         nozzle.add(new JLabel("Nozzle:"));\r
378                         parts.add(nozzle);\r
379                         \r
380                         motor.addPropertyChangeListener(new PropertyChangeListener() {\r
381                                 @Override\r
382                                 public void propertyChange(PropertyChangeEvent arg0) {\r
383                                         setup();\r
384                                         setResizeWeight(.5);\r
385                                         setDividerLocation(.5);\r
386                                 }\r
387                         });\r
388                         \r
389                         setup();\r
390                 }\r
391 \r
392                 @Override\r
393                 public void componentHidden(ComponentEvent arg0) {\r
394 \r
395                 }\r
396 \r
397                 @Override\r
398                 public void componentMoved(ComponentEvent arg0) {\r
399 \r
400                 }\r
401 \r
402                 @Override\r
403                 public void componentResized(ComponentEvent arg0) {\r
404                         setResizeWeight(.5);\r
405                         setDividerLocation(.5);\r
406                 }\r
407 \r
408                 @Override\r
409                 public void componentShown(ComponentEvent arg0) {\r
410 \r
411                 }\r
412         }\r
413 \r
414 \r
415         public MotorEditor(Motor m, Collection<Fuel> fuels) {\r
416                 \r
417                 setLayout( new BorderLayout());\r
418                 tabs = new JTabbedPane(JTabbedPane.BOTTOM);\r
419                 add(tabs, BorderLayout.CENTER);\r
420 \r
421                 for ( Fuel f : fuels )\r
422                         addFuel(f);\r
423                 setMotor(m);\r
424         }\r
425 \r
426         public Motor getMotor() {\r
427                 return motor;\r
428         }\r
429 \r
430 \r
431         private void setMotor(Motor m) {\r
432                 if (motor != null)\r
433                         motor.removePropertyChangeListener(this);\r
434                 motor = m;\r
435                 motor.addPropertyChangeListener(this);\r
436                 if (grainEditor != null)\r
437                         remove(grainEditor);\r
438                 while (tabs.getTabCount() > 1)\r
439                         tabs.removeTabAt(1);\r
440                 tabs.add(new CaseEditor(), CASING_TAB);\r
441                 tabs.add(new GrainEditor(motor.getGrain()), GRAIN_TAB);\r
442                 tabs.add(bt = new BurnTab(), BURN_TAB);\r
443         }\r
444 \r
445         public static Motor defaultMotor() {\r
446                 Motor m = new Motor();\r
447                 m.setName("Example Motor");\r
448                 m.setFuel(new KNSU());\r
449 \r
450                 CylindricalChamber c = new CylindricalChamber();\r
451                 c.setLength(Amount.valueOf(200, SI.MILLIMETER));\r
452                 c.setID(Amount.valueOf(30, SI.MILLIMETER));\r
453                 m.setChamber(c);\r
454 \r
455                 Schedule40 pvc = new Schedule40();\r
456                 pvc.setLength(Amount.valueOf(200, SI.MILLIMETER));\r
457                 m.setChamber(pvc);\r
458                 \r
459                 CoredCylindricalGrain g = new CoredCylindricalGrain();\r
460                 try {\r
461                         g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
462                         g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
463                         g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
464                 } catch (PropertyVetoException v) {\r
465                         throw new Error(v);\r
466                 }\r
467                 \r
468 \r
469                 m.setGrain(new MultiGrain(g, 2));\r
470 \r
471                 ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
472                 n.setThroatDiameter(Amount.valueOf(7.962, SI.MILLIMETER));\r
473                 n.setExitDiameter(Amount.valueOf(13.79, SI.MILLIMETER));\r
474                 n.setEfficiency(.85);\r
475                 m.setNozzle(n);\r
476 \r
477                 return m;\r
478         }\r
479 \r
480         public void focusOnObject(Object o) {\r
481                 if (o instanceof Grain)\r
482                         tabs.setSelectedIndex(GRAIN_TAB);\r
483                 if (o instanceof Chamber || o instanceof Nozzle)\r
484                         tabs.setSelectedIndex(CASING_TAB);\r
485         }\r
486 \r
487         public void addBurnWatcher(BurnWatcher bw) {\r
488                 burnWatchers.add(bw);\r
489         }\r
490 \r
491         @Deprecated\r
492         public void showAsWindow() {\r
493                 JFrame f = new JFrame();\r
494                 f.setSize(1024, 768);\r
495                 f.setContentPane(this);\r
496                 f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
497                 f.setVisible(true);\r
498         }\r
499 \r
500         public static void main(String args[]) throws Exception {\r
501                 try {\r
502                         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\r
503                 } catch (Exception e1) {\r
504                         e1.printStackTrace();\r
505                 }\r
506                 Vector<Fuel> ff = new Vector<Fuel>();\r
507                 ff.add(new KNSU());\r
508                 //new MotorEditor(defaultMotor(), ff).showAsWindow();\r
509         }\r
510 \r
511         public void propertyChange(PropertyChangeEvent evt) {\r
512                 // Dont re-burn for a name change!\r
513                 if (!evt.getPropertyName().equals("Name")){\r
514                         bt.reBurn();\r
515                 } else {\r
516                         for (BurnWatcher bw : burnWatchers)\r
517                                 bw.replace(burn, burn);\r
518                 }\r
519         }\r
520 \r
521 }\r