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