Initial commit
[debian/openrocket] / src / net / sf / openrocket / gui / configdialog / ComponentConfigDialog.java
1 package net.sf.openrocket.gui.configdialog;
2
3
4 import java.awt.Component;
5 import java.awt.Container;
6 import java.awt.Point;
7 import java.awt.Window;
8 import java.awt.event.ComponentAdapter;
9 import java.awt.event.ComponentEvent;
10 import java.lang.reflect.Constructor;
11 import java.lang.reflect.InvocationTargetException;
12
13 import javax.swing.DefaultBoundedRangeModel;
14 import javax.swing.JDialog;
15 import javax.swing.JSlider;
16 import javax.swing.JSpinner;
17 import javax.swing.SpinnerNumberModel;
18
19 import net.sf.openrocket.document.OpenRocketDocument;
20 import net.sf.openrocket.gui.Resettable;
21 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
22 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
23 import net.sf.openrocket.rocketcomponent.RocketComponent;
24 import net.sf.openrocket.util.GUIUtil;
25 import net.sf.openrocket.util.Prefs;
26
27 /**
28  * A JFrame dialog that contains the configuration elements of one component.
29  * The contents of the dialog are instantiated from CONFIGDIALOGPACKAGE according
30  * to the current component.
31  * 
32  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
33  */
34
35 public class ComponentConfigDialog extends JDialog implements ComponentChangeListener {
36         private static final long serialVersionUID = 1L;
37         private static final String CONFIGDIALOGPACKAGE = "net.sf.openrocket.gui.configdialog";
38         private static final String CONFIGDIALOGPOSTFIX = "Config";
39         
40         
41         private static ComponentConfigDialog dialog = null;
42
43         
44         private OpenRocketDocument document = null;
45         private RocketComponent component = null;
46         private RocketComponentConfig configurator = null;
47         
48         private final Window parent;
49         
50         private ComponentConfigDialog(Window parent, OpenRocketDocument document, 
51                         RocketComponent component) {
52                 super(parent);
53                 this.parent = parent;
54                 
55                 setComponent(document, component);
56                 
57                 // Set window position according to preferences, and set prefs when moving
58                 Point position = Prefs.getWindowPosition(this.getClass());
59                 if (position == null)
60                         this.setLocationByPlatform(true);
61                 else
62                         this.setLocation(position);
63
64                 this.addComponentListener(new ComponentAdapter() {
65                         @Override
66                         public void componentMoved(ComponentEvent e) {
67                                 Prefs.setWindowPosition(ComponentConfigDialog.this.getClass(), 
68                                                 ComponentConfigDialog.this.getLocation());
69                         }
70                 });
71                 
72                 
73                 // Install ESC listener
74                 GUIUtil.installEscapeCloseOperation(this);
75         }
76         
77
78         /**
79          * Set the component being configured.  The listening connections of the old configurator
80          * will be removed and the new ones created.
81          * 
82          * @param component  Component to configure.
83          */
84         private void setComponent(OpenRocketDocument document, RocketComponent component) {
85                 if (this.document != null) {
86                         this.document.getRocket().removeComponentChangeListener(this);
87                 }
88
89                 if (configurator != null) {
90                         // Remove listeners by setting all applicable models to null
91                         setNullModels(configurator);  // null-safe
92
93 //                      mainPanel.remove(configurator);
94                 }
95                 
96                 this.document = document;
97                 this.component = component;
98                 this.document.getRocket().addComponentChangeListener(this);
99                 
100                 configurator = getDialogContents();
101                 this.setContentPane(configurator);
102                 configurator.updateFields();
103 //              mainPanel.add(configurator,"cell 0 0, growx, growy");
104                 
105                 setTitle(component.getComponentName()+" configuration");
106
107 //              Dimension pref = getPreferredSize();
108 //              Dimension real = getSize();
109 //              if (pref.width > real.width || pref.height > real.height)
110                 pack();
111         }
112         
113         /**
114          * Traverses recursively the component tree, and sets all applicable component 
115          * models to null, so as to remove the listener connections.
116          * 
117          * NOTE:  All components in the configuration dialogs that use custom models must be added
118          * to this method.
119          */
120         private void setNullModels(Component c) {
121                 if (c==null)
122                         return;
123                 
124                 // Remove models for known components
125                 //  Why the FSCK must this be so hard?!?!?
126
127                 if (c instanceof JSpinner) {
128                         ((JSpinner)c).setModel(new SpinnerNumberModel());
129                 } else if (c instanceof JSlider) {
130                         ((JSlider)c).setModel(new DefaultBoundedRangeModel());
131                 } else if (c instanceof Resettable) {
132                         ((Resettable)c).resetModel();
133                 }
134
135                 
136                 if (c instanceof Container) {
137                         Component[] cs = ((Container)c).getComponents();
138                         for (Component sub: cs)
139                                 setNullModels(sub);
140                 }
141
142         }
143         
144         
145         /**
146          * Return the configurator panel of the current component.
147          */
148         private RocketComponentConfig getDialogContents() {
149                 Constructor<? extends RocketComponentConfig> c = 
150                         findDialogContentsConstructor(component);
151                 if (c != null) {
152                         try {
153                                 return (RocketComponentConfig) c.newInstance(component);
154                         } catch (InstantiationException e) {
155                                 throw new RuntimeException("BUG in constructor reflection",e);
156                         } catch (IllegalAccessException e) {
157                                 throw new RuntimeException("BUG in constructor reflection",e);
158                         } catch (InvocationTargetException e) {
159                                 throw new RuntimeException("BUG in constructor reflection",e);
160                         }
161                 }
162                 
163                 // Should never be reached, since RocketComponentConfig should catch all
164                 // components without their own configurator.
165                 throw new RuntimeException("Unable to find any configurator for "+component);
166         }
167
168         /**
169          * Finds the Constructor of the given component's config dialog panel in 
170          * CONFIGDIALOGPACKAGE.
171          */
172         @SuppressWarnings("unchecked")
173         private static Constructor<? extends RocketComponentConfig> 
174                         findDialogContentsConstructor(RocketComponent component) {
175                 Class<?> currentclass;
176                 String currentclassname;
177                 String configclassname;
178                 
179                 Class<?> configclass;
180                 Constructor<? extends RocketComponentConfig> c;
181                 
182                 currentclass = component.getClass();
183                 while ((currentclass != null) && (currentclass != Object.class)) {
184                         currentclassname = currentclass.getCanonicalName();
185                         int index = currentclassname.lastIndexOf('.');
186                         if (index >= 0)
187                                 currentclassname = currentclassname.substring(index + 1);
188                         configclassname = CONFIGDIALOGPACKAGE + "." + currentclassname + 
189                                 CONFIGDIALOGPOSTFIX;
190                         
191                         try {
192                                 configclass = Class.forName(configclassname);
193                                 c = (Constructor<? extends RocketComponentConfig>)
194                                         configclass.getConstructor(RocketComponent.class);
195                                 return c;
196                         } catch (Exception ignore) { }
197
198                         currentclass = currentclass.getSuperclass();
199                 }
200                 return null;
201         }
202         
203         
204         
205
206         //////////  Static dialog  /////////
207         
208         /**
209          * A singleton configuration dialog.  Will create and show a new dialog if one has not 
210          * previously been used, or update the dialog and show it if a previous one exists.
211          * 
212          * @param document              the document to configure.
213          * @param component             the component to configure.
214          */
215         public static void showDialog(Window parent, OpenRocketDocument document, 
216                         RocketComponent component) {
217                 if (dialog != null)
218                         dialog.dispose();
219                 
220                 dialog = new ComponentConfigDialog(parent, document, component);
221                 dialog.setVisible(true);
222                 
223                 document.addUndoPosition("Modify "+component.getComponentName());
224         }
225         
226         
227         /* package */ 
228         static void showDialog(RocketComponent component) {
229                 showDialog(dialog.parent, dialog.document, component);
230         }
231         
232         /**
233          * Hides the configuration dialog.  May be used even if not currently visible.
234          */
235         public static void hideDialog() {
236                 if (dialog != null)
237                         dialog.setVisible(false);
238         }
239
240         
241         /**
242          * Add an undo position for the current document.  This is intended for use only
243          * by the currently open dialog.
244          * 
245          * @param description  Description of the undoable action
246          */
247         /*package*/ static void addUndoPosition(String description) {
248                 if (dialog == null) {
249                         throw new IllegalStateException("Dialog not open, report bug!");
250                 }
251                 dialog.document.addUndoPosition(description);
252         }
253         
254         /*package*/
255         static String getUndoDescription() {
256                 if (dialog == null) {
257                         throw new IllegalStateException("Dialog not open, report bug!");
258                 }
259                 return dialog.document.getUndoDescription();
260         }
261         
262         /**
263          * Returns whether the singleton configuration dialog is currently visible or not.
264          */
265         public static boolean isDialogVisible() {
266                 return (dialog!=null) && (dialog.isVisible());
267         }
268
269
270         public void componentChanged(ComponentChangeEvent e) {
271                 if (e.isTreeChange() || e.isUndoChange()) {
272                         
273                         // Hide dialog in case of tree or undo change
274                         dialog.setVisible(false);
275
276                 } else {
277                         configurator.updateFields();
278                 }
279         }
280         
281 }