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