enhanced motor handling, bug fixes, initial optimization code
[debian/openrocket] / src / net / sf / openrocket / gui / dialogs / preferences / PreferencesDialog.java
1 package net.sf.openrocket.gui.dialogs.preferences;
2
3 import java.awt.Dialog;
4 import java.awt.Window;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.awt.event.WindowAdapter;
8 import java.awt.event.WindowEvent;
9 import java.io.File;
10 import java.util.ArrayList;
11 import java.util.List;
12
13 import javax.swing.AbstractListModel;
14 import javax.swing.ComboBoxModel;
15 import javax.swing.JButton;
16 import javax.swing.JCheckBox;
17 import javax.swing.JComboBox;
18 import javax.swing.JDialog;
19 import javax.swing.JFileChooser;
20 import javax.swing.JLabel;
21 import javax.swing.JOptionPane;
22 import javax.swing.JPanel;
23 import javax.swing.JProgressBar;
24 import javax.swing.JTabbedPane;
25 import javax.swing.JTextField;
26 import javax.swing.Timer;
27 import javax.swing.event.DocumentEvent;
28 import javax.swing.event.DocumentListener;
29
30 import net.miginfocom.swing.MigLayout;
31 import net.sf.openrocket.communication.UpdateInfo;
32 import net.sf.openrocket.communication.UpdateInfoRetriever;
33 import net.sf.openrocket.gui.components.DescriptionArea;
34 import net.sf.openrocket.gui.components.StyledLabel;
35 import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
36 import net.sf.openrocket.gui.main.SimpleFileFilter;
37 import net.sf.openrocket.logging.LogHelper;
38 import net.sf.openrocket.startup.Application;
39 import net.sf.openrocket.unit.Unit;
40 import net.sf.openrocket.unit.UnitGroup;
41 import net.sf.openrocket.util.GUIUtil;
42 import net.sf.openrocket.util.Prefs;
43
44 public class PreferencesDialog extends JDialog {
45         private static final LogHelper log = Application.getLogger();
46         
47         private final List<DefaultUnitSelector> unitSelectors = new ArrayList<DefaultUnitSelector>();
48         
49         private File defaultDirectory = null;
50         
51         private PreferencesDialog() {
52                 super((Window) null, "Preferences", Dialog.ModalityType.APPLICATION_MODAL);
53                 
54                 JPanel panel = new JPanel(new MigLayout("fill, gap unrel", "[grow]", "[grow][]"));
55                 
56                 JTabbedPane tabbedPane = new JTabbedPane();
57                 panel.add(tabbedPane, "grow, wrap");
58                 
59
60                 tabbedPane.addTab("Units", null, unitsPane(), "Default units");
61                 tabbedPane.addTab("Materials", null, new MaterialEditPanel(), "Custom materials");
62                 tabbedPane.addTab("Options", null, optionsPane(), "Miscellaneous options");
63                 
64
65                 JButton close = new JButton("Close");
66                 close.addActionListener(new ActionListener() {
67                         @Override
68                         public void actionPerformed(ActionEvent arg0) {
69                                 PreferencesDialog.this.setVisible(false);
70                                 PreferencesDialog.this.dispose();
71                         }
72                 });
73                 panel.add(close, "span, right, tag close");
74                 
75                 this.setContentPane(panel);
76                 pack();
77                 this.setLocationRelativeTo(null);
78                 
79                 this.addWindowListener(new WindowAdapter() {
80                         @Override
81                         public void windowClosed(WindowEvent e) {
82                                 Prefs.storeDefaultUnits();
83                         }
84                 });
85                 
86                 GUIUtil.setDisposableDialogOptions(this, close);
87         }
88         
89         
90         private JPanel optionsPane() {
91                 JPanel panel = new JPanel(new MigLayout("fillx, ins 30lp n n n"));
92                 
93
94                 panel.add(new JLabel("Position to insert new body components:"), "gapright para");
95                 panel.add(new JComboBox(new PrefChoiseSelector(Prefs.BODY_COMPONENT_INSERT_POSITION_KEY,
96                                 "Always ask", "Insert in middle", "Add to end")), "wrap para, growx, sg combos");
97                 
98                 panel.add(new JLabel("Confirm deletion of simulations:"));
99                 panel.add(new JComboBox(new PrefBooleanSelector(Prefs.CONFIRM_DELETE_SIMULATION,
100                                 "Delete", "Confirm", true)), "wrap 40lp, growx, sg combos");
101                 
102
103                 panel.add(new JLabel("User-defined thrust curves:"), "spanx, wrap");
104                 final JTextField field = new JTextField();
105                 List<File> files = Prefs.getUserThrustCurveFiles();
106                 String str = "";
107                 for (File file : files) {
108                         if (str.length() > 0) {
109                                 str += ";";
110                         }
111                         str += file.getAbsolutePath();
112                 }
113                 field.setText(str);
114                 field.getDocument().addDocumentListener(new DocumentListener() {
115                         @Override
116                         public void removeUpdate(DocumentEvent e) {
117                                 changed();
118                         }
119                         
120                         @Override
121                         public void insertUpdate(DocumentEvent e) {
122                                 changed();
123                         }
124                         
125                         @Override
126                         public void changedUpdate(DocumentEvent e) {
127                                 changed();
128                         }
129                         
130                         private void changed() {
131                                 String text = field.getText();
132                                 List<File> list = new ArrayList<File>();
133                                 for (String s : text.split(";")) {
134                                         s = s.trim();
135                                         if (s.length() > 0) {
136                                                 list.add(new File(s));
137                                         }
138                                 }
139                                 Prefs.setUserThrustCurveFiles(list);
140                         }
141                 });
142                 panel.add(field, "w 100px, gapright unrel, spanx, growx, split");
143                 
144                 JButton button = new JButton("Add");
145                 button.addActionListener(new ActionListener() {
146                         @Override
147                         public void actionPerformed(ActionEvent e) {
148                                 JFileChooser chooser = new JFileChooser();
149                                 SimpleFileFilter filter = new SimpleFileFilter("All thrust curve files (*.eng; *.rse; *.zip; directories)",
150                                                 true, "eng", "rse", "zip");
151                                 chooser.addChoosableFileFilter(filter);
152                                 chooser.addChoosableFileFilter(new SimpleFileFilter("RASP motor files (*.eng)",
153                                                 true, "eng"));
154                                 chooser.addChoosableFileFilter(new SimpleFileFilter("RockSim engine files (*.rse)",
155                                                 true, "rse"));
156                                 chooser.addChoosableFileFilter(new SimpleFileFilter("ZIP archives (*.zip)",
157                                                 true, "zip"));
158                                 chooser.setFileFilter(filter);
159                                 chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
160                                 if (defaultDirectory != null) {
161                                         chooser.setCurrentDirectory(defaultDirectory);
162                                 }
163                                 
164                                 int returnVal = chooser.showDialog(PreferencesDialog.this, "Add");
165                                 if (returnVal == JFileChooser.APPROVE_OPTION) {
166                                         log.user("Adding user thrust curve: " + chooser.getSelectedFile());
167                                         defaultDirectory = chooser.getCurrentDirectory();
168                                         String text = field.getText().trim();
169                                         if (text.length() > 0) {
170                                                 text += ";";
171                                         }
172                                         text += chooser.getSelectedFile().getAbsolutePath();
173                                         field.setText(text);
174                                 }
175                         }
176                 });
177                 panel.add(button, "gapright unrel");
178                 
179                 button = new JButton("Reset");
180                 
181                 button.addActionListener(new ActionListener() {
182                         @Override
183                         public void actionPerformed(ActionEvent e) {
184                                 // First one sets to the default, but does not un-set the pref
185                                 field.setText(Prefs.getDefaultUserThrustCurveFile().getAbsolutePath());
186                                 Prefs.setUserThrustCurveFiles(null);
187                         }
188                 });
189                 panel.add(button, "wrap");
190                 
191                 DescriptionArea desc = new DescriptionArea("Add directories, RASP motor files (*.eng), " +
192                                 "RockSim engine files (*.rse) or ZIP archives separated by a semicolon (;) to load external " +
193                                 "thrust curves.  Changes will take effect the next time you start OpenRocket.", 3, -3, false);
194                 desc.setBackground(getBackground());
195                 panel.add(desc, "spanx, growx, wrap 40lp");
196                 
197
198
199
200                 final JCheckBox softwareUpdateBox = new JCheckBox("Check for software updates at startup");
201                 softwareUpdateBox.setSelected(Prefs.getCheckUpdates());
202                 softwareUpdateBox.addActionListener(new ActionListener() {
203                         @Override
204                         public void actionPerformed(ActionEvent e) {
205                                 Prefs.setCheckUpdates(softwareUpdateBox.isSelected());
206                         }
207                 });
208                 panel.add(softwareUpdateBox);
209                 
210                 button = new JButton("Check now");
211                 button.setToolTipText("Check for software updates now");
212                 button.addActionListener(new ActionListener() {
213                         @Override
214                         public void actionPerformed(ActionEvent e) {
215                                 checkForUpdates();
216                         }
217                 });
218                 panel.add(button, "right, wrap");
219                 
220
221                 return panel;
222         }
223         
224         private JPanel unitsPane() {
225                 JPanel panel = new JPanel(new MigLayout("", "[][]40lp[][]"));
226                 JComboBox combo;
227                 
228                 panel.add(new JLabel("Select your preferred units:"), "span, wrap paragraph");
229                 
230
231                 panel.add(new JLabel("Rocket dimensions:"));
232                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_LENGTH));
233                 panel.add(combo, "sizegroup boxes");
234                 
235                 panel.add(new JLabel("Line density:"));
236                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_LINE));
237                 panel.add(combo, "sizegroup boxes, wrap");
238                 
239
240
241                 panel.add(new JLabel("Motor dimensions:"));
242                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MOTOR_DIMENSIONS));
243                 panel.add(combo, "sizegroup boxes");
244                 
245                 panel.add(new JLabel("Surface density:"));
246                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_SURFACE));
247                 panel.add(combo, "sizegroup boxes, wrap");
248                 
249
250
251                 panel.add(new JLabel("Distance:"));
252                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DISTANCE));
253                 panel.add(combo, "sizegroup boxes");
254                 
255                 panel.add(new JLabel("Bulk density::"));
256                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_BULK));
257                 panel.add(combo, "sizegroup boxes, wrap");
258                 
259
260
261                 panel.add(new JLabel("Velocity:"));
262                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_VELOCITY));
263                 panel.add(combo, "sizegroup boxes");
264                 
265                 panel.add(new JLabel("Surface roughness:"));
266                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROUGHNESS));
267                 panel.add(combo, "sizegroup boxes, wrap");
268                 
269
270
271                 panel.add(new JLabel("Acceleration:"));
272                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ACCELERATION));
273                 panel.add(combo, "sizegroup boxes");
274                 
275                 panel.add(new JLabel("Area:"));
276                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_AREA));
277                 panel.add(combo, "sizegroup boxes, wrap");
278                 
279
280
281                 panel.add(new JLabel("Mass:"));
282                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MASS));
283                 panel.add(combo, "sizegroup boxes");
284                 
285                 panel.add(new JLabel("Angle:"));
286                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ANGLE));
287                 panel.add(combo, "sizegroup boxes, wrap");
288                 
289
290
291                 panel.add(new JLabel("Force:"));
292                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_FORCE));
293                 panel.add(combo, "sizegroup boxes");
294                 
295                 panel.add(new JLabel("Roll rate:"));
296                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROLL));
297                 panel.add(combo, "sizegroup boxes, wrap");
298                 
299
300
301                 panel.add(new JLabel("Total impulse:"));
302                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_IMPULSE));
303                 panel.add(combo, "sizegroup boxes");
304                 
305                 panel.add(new JLabel("Temperature:"));
306                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_TEMPERATURE));
307                 panel.add(combo, "sizegroup boxes, wrap");
308                 
309
310
311                 panel.add(new JLabel("Stability:"));
312                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
313                 panel.add(combo, "sizegroup boxes");
314                 
315                 panel.add(new JLabel("Pressure:"));
316                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_PRESSURE));
317                 panel.add(combo, "sizegroup boxes, wrap para");
318                 
319
320
321                 JButton button = new JButton("Default metric");
322                 button.addActionListener(new ActionListener() {
323                         @Override
324                         public void actionPerformed(ActionEvent e) {
325                                 UnitGroup.setDefaultMetricUnits();
326                                 for (DefaultUnitSelector s : unitSelectors)
327                                         s.fireChange();
328                         }
329                 });
330                 panel.add(button, "spanx, split 2, grow");
331                 
332                 button = new JButton("Default imperial");
333                 button.addActionListener(new ActionListener() {
334                         @Override
335                         public void actionPerformed(ActionEvent e) {
336                                 UnitGroup.setDefaultImperialUnits();
337                                 for (DefaultUnitSelector s : unitSelectors)
338                                         s.fireChange();
339                         }
340                 });
341                 panel.add(button, "grow, wrap para");
342                 
343
344                 panel.add(new StyledLabel("The effects will take place the next time you open a window.", -2),
345                                 "spanx, wrap");
346                 
347
348                 return panel;
349         }
350         
351         
352
353
354
355         private class DefaultUnitSelector extends AbstractListModel implements ComboBoxModel {
356                 
357                 private final UnitGroup group;
358                 
359                 public DefaultUnitSelector(UnitGroup group) {
360                         this.group = group;
361                         unitSelectors.add(this);
362                 }
363                 
364                 @Override
365                 public Object getSelectedItem() {
366                         return group.getDefaultUnit();
367                 }
368                 
369                 @Override
370                 public void setSelectedItem(Object item) {
371                         if (item == null) {
372                                 // Clear selection - huh?
373                                 return;
374                         }
375                         if (!(item instanceof Unit)) {
376                                 throw new IllegalArgumentException("Illegal argument " + item);
377                         }
378                         group.setDefaultUnit(group.getUnitIndex((Unit) item));
379                 }
380                 
381                 @Override
382                 public Object getElementAt(int index) {
383                         return group.getUnit(index);
384                 }
385                 
386                 @Override
387                 public int getSize() {
388                         return group.getUnitCount();
389                 }
390                 
391                 
392                 public void fireChange() {
393                         this.fireContentsChanged(this, 0, this.getSize());
394                 }
395         }
396         
397         
398
399         private class PrefChoiseSelector extends AbstractListModel implements ComboBoxModel {
400                 private final String preference;
401                 private final String[] descriptions;
402                 
403                 public PrefChoiseSelector(String preference, String... descriptions) {
404                         this.preference = preference;
405                         this.descriptions = descriptions;
406                 }
407                 
408                 @Override
409                 public Object getSelectedItem() {
410                         return descriptions[Prefs.getChoise(preference, descriptions.length, 0)];
411                 }
412                 
413                 @Override
414                 public void setSelectedItem(Object item) {
415                         if (item == null) {
416                                 // Clear selection - huh?
417                                 return;
418                         }
419                         if (!(item instanceof String)) {
420                                 throw new IllegalArgumentException("Illegal argument " + item);
421                         }
422                         int index;
423                         for (index = 0; index < descriptions.length; index++) {
424                                 if (((String) item).equalsIgnoreCase(descriptions[index]))
425                                         break;
426                         }
427                         if (index >= descriptions.length) {
428                                 throw new IllegalArgumentException("Illegal argument " + item);
429                         }
430                         
431                         Prefs.putChoise(preference, index);
432                 }
433                 
434                 @Override
435                 public Object getElementAt(int index) {
436                         return descriptions[index];
437                 }
438                 
439                 @Override
440                 public int getSize() {
441                         return descriptions.length;
442                 }
443         }
444         
445         
446         private class PrefBooleanSelector extends AbstractListModel implements ComboBoxModel {
447                 private final String preference;
448                 private final String trueDesc, falseDesc;
449                 private final boolean def;
450                 
451                 public PrefBooleanSelector(String preference, String falseDescription,
452                                 String trueDescription, boolean defaultState) {
453                         this.preference = preference;
454                         this.trueDesc = trueDescription;
455                         this.falseDesc = falseDescription;
456                         this.def = defaultState;
457                 }
458                 
459                 @Override
460                 public Object getSelectedItem() {
461                         if (Prefs.NODE.getBoolean(preference, def)) {
462                                 return trueDesc;
463                         } else {
464                                 return falseDesc;
465                         }
466                 }
467                 
468                 @Override
469                 public void setSelectedItem(Object item) {
470                         if (item == null) {
471                                 // Clear selection - huh?
472                                 return;
473                         }
474                         if (!(item instanceof String)) {
475                                 throw new IllegalArgumentException("Illegal argument " + item);
476                         }
477                         
478                         if (trueDesc.equals(item)) {
479                                 Prefs.NODE.putBoolean(preference, true);
480                         } else if (falseDesc.equals(item)) {
481                                 Prefs.NODE.putBoolean(preference, false);
482                         } else {
483                                 throw new IllegalArgumentException("Illegal argument " + item);
484                         }
485                 }
486                 
487                 @Override
488                 public Object getElementAt(int index) {
489                         switch (index) {
490                         case 0:
491                                 return def ? trueDesc : falseDesc;
492                                 
493                         case 1:
494                                 return def ? falseDesc : trueDesc;
495                                 
496                         default:
497                                 throw new IndexOutOfBoundsException("Boolean asked for index=" + index);
498                         }
499                 }
500                 
501                 @Override
502                 public int getSize() {
503                         return 2;
504                 }
505         }
506         
507         
508         private void checkForUpdates() {
509                 final UpdateInfoRetriever retriever = new UpdateInfoRetriever();
510                 retriever.start();
511                 
512
513                 // Progress dialog
514                 final JDialog dialog = new JDialog(this, ModalityType.APPLICATION_MODAL);
515                 JPanel panel = new JPanel(new MigLayout());
516                 
517                 panel.add(new JLabel("Checking for updates..."), "wrap");
518                 
519                 JProgressBar bar = new JProgressBar();
520                 bar.setIndeterminate(true);
521                 panel.add(bar, "growx, wrap para");
522                 
523                 JButton cancel = new JButton("Cancel");
524                 cancel.addActionListener(new ActionListener() {
525                         @Override
526                         public void actionPerformed(ActionEvent e) {
527                                 dialog.dispose();
528                         }
529                 });
530                 panel.add(cancel, "right");
531                 dialog.add(panel);
532                 
533                 GUIUtil.setDisposableDialogOptions(dialog, cancel);
534                 
535
536                 // Timer to monitor progress
537                 final Timer timer = new Timer(100, null);
538                 final long startTime = System.currentTimeMillis();
539                 
540                 ActionListener listener = new ActionListener() {
541                         @Override
542                         public void actionPerformed(ActionEvent e) {
543                                 if (!retriever.isRunning() || startTime + 10000 < System.currentTimeMillis()) {
544                                         timer.stop();
545                                         dialog.dispose();
546                                 }
547                         }
548                 };
549                 timer.addActionListener(listener);
550                 timer.start();
551                 
552
553                 // Wait for action
554                 dialog.setVisible(true);
555                 
556
557                 // Check result
558                 UpdateInfo info = retriever.getUpdateInfo();
559                 if (info == null) {
560                         JOptionPane.showMessageDialog(this,
561                                         "An error occurred while communicating with the server.",
562                                         "Unable to retrieve update information", JOptionPane.WARNING_MESSAGE, null);
563                 } else if (info.getLatestVersion() == null ||
564                                 info.getLatestVersion().equals("") ||
565                                 Prefs.getVersion().equalsIgnoreCase(info.getLatestVersion())) {
566                         JOptionPane.showMessageDialog(this,
567                                         "You are running the latest version of OpenRocket.",
568                                         "No updates available", JOptionPane.INFORMATION_MESSAGE, null);
569                 } else {
570                         UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
571                         infoDialog.setVisible(true);
572                         if (infoDialog.isReminderSelected()) {
573                                 Prefs.putString(Prefs.LAST_UPDATE, "");
574                         } else {
575                                 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
576                         }
577                 }
578                 
579         }
580         
581         
582         ////////  Singleton implementation  ////////
583         
584         private static PreferencesDialog dialog = null;
585         
586         public static void showPreferences() {
587                 if (dialog != null) {
588                         dialog.dispose();
589                 }
590                 dialog = new PreferencesDialog();
591                 dialog.setVisible(true);
592         }
593         
594
595 }