SafetyMutex and rocket optimization updates
[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("Moment of inertia:"));
312                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_INERTIA));
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");
318                 
319
320                 panel.add(new JLabel("Stability:"));
321                 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
322                 panel.add(combo, "sizegroup boxes, wrap para");
323                 
324
325
326
327                 JButton button = new JButton("Default metric");
328                 button.addActionListener(new ActionListener() {
329                         @Override
330                         public void actionPerformed(ActionEvent e) {
331                                 UnitGroup.setDefaultMetricUnits();
332                                 for (DefaultUnitSelector s : unitSelectors)
333                                         s.fireChange();
334                         }
335                 });
336                 panel.add(button, "spanx, split 2, grow");
337                 
338                 button = new JButton("Default imperial");
339                 button.addActionListener(new ActionListener() {
340                         @Override
341                         public void actionPerformed(ActionEvent e) {
342                                 UnitGroup.setDefaultImperialUnits();
343                                 for (DefaultUnitSelector s : unitSelectors)
344                                         s.fireChange();
345                         }
346                 });
347                 panel.add(button, "grow, wrap para");
348                 
349
350                 panel.add(new StyledLabel("The effects will take place the next time you open a window.", -2),
351                                 "spanx, wrap");
352                 
353
354                 return panel;
355         }
356         
357         
358
359
360
361         private class DefaultUnitSelector extends AbstractListModel implements ComboBoxModel {
362                 
363                 private final UnitGroup group;
364                 
365                 public DefaultUnitSelector(UnitGroup group) {
366                         this.group = group;
367                         unitSelectors.add(this);
368                 }
369                 
370                 @Override
371                 public Object getSelectedItem() {
372                         return group.getDefaultUnit();
373                 }
374                 
375                 @Override
376                 public void setSelectedItem(Object item) {
377                         if (item == null) {
378                                 // Clear selection - huh?
379                                 return;
380                         }
381                         if (!(item instanceof Unit)) {
382                                 throw new IllegalArgumentException("Illegal argument " + item);
383                         }
384                         group.setDefaultUnit(group.getUnitIndex((Unit) item));
385                 }
386                 
387                 @Override
388                 public Object getElementAt(int index) {
389                         return group.getUnit(index);
390                 }
391                 
392                 @Override
393                 public int getSize() {
394                         return group.getUnitCount();
395                 }
396                 
397                 
398                 public void fireChange() {
399                         this.fireContentsChanged(this, 0, this.getSize());
400                 }
401         }
402         
403         
404
405         private class PrefChoiseSelector extends AbstractListModel implements ComboBoxModel {
406                 private final String preference;
407                 private final String[] descriptions;
408                 
409                 public PrefChoiseSelector(String preference, String... descriptions) {
410                         this.preference = preference;
411                         this.descriptions = descriptions;
412                 }
413                 
414                 @Override
415                 public Object getSelectedItem() {
416                         return descriptions[Prefs.getChoise(preference, descriptions.length, 0)];
417                 }
418                 
419                 @Override
420                 public void setSelectedItem(Object item) {
421                         if (item == null) {
422                                 // Clear selection - huh?
423                                 return;
424                         }
425                         if (!(item instanceof String)) {
426                                 throw new IllegalArgumentException("Illegal argument " + item);
427                         }
428                         int index;
429                         for (index = 0; index < descriptions.length; index++) {
430                                 if (((String) item).equalsIgnoreCase(descriptions[index]))
431                                         break;
432                         }
433                         if (index >= descriptions.length) {
434                                 throw new IllegalArgumentException("Illegal argument " + item);
435                         }
436                         
437                         Prefs.putChoise(preference, index);
438                 }
439                 
440                 @Override
441                 public Object getElementAt(int index) {
442                         return descriptions[index];
443                 }
444                 
445                 @Override
446                 public int getSize() {
447                         return descriptions.length;
448                 }
449         }
450         
451         
452         private class PrefBooleanSelector extends AbstractListModel implements ComboBoxModel {
453                 private final String preference;
454                 private final String trueDesc, falseDesc;
455                 private final boolean def;
456                 
457                 public PrefBooleanSelector(String preference, String falseDescription,
458                                 String trueDescription, boolean defaultState) {
459                         this.preference = preference;
460                         this.trueDesc = trueDescription;
461                         this.falseDesc = falseDescription;
462                         this.def = defaultState;
463                 }
464                 
465                 @Override
466                 public Object getSelectedItem() {
467                         if (Prefs.NODE.getBoolean(preference, def)) {
468                                 return trueDesc;
469                         } else {
470                                 return falseDesc;
471                         }
472                 }
473                 
474                 @Override
475                 public void setSelectedItem(Object item) {
476                         if (item == null) {
477                                 // Clear selection - huh?
478                                 return;
479                         }
480                         if (!(item instanceof String)) {
481                                 throw new IllegalArgumentException("Illegal argument " + item);
482                         }
483                         
484                         if (trueDesc.equals(item)) {
485                                 Prefs.NODE.putBoolean(preference, true);
486                         } else if (falseDesc.equals(item)) {
487                                 Prefs.NODE.putBoolean(preference, false);
488                         } else {
489                                 throw new IllegalArgumentException("Illegal argument " + item);
490                         }
491                 }
492                 
493                 @Override
494                 public Object getElementAt(int index) {
495                         switch (index) {
496                         case 0:
497                                 return def ? trueDesc : falseDesc;
498                                 
499                         case 1:
500                                 return def ? falseDesc : trueDesc;
501                                 
502                         default:
503                                 throw new IndexOutOfBoundsException("Boolean asked for index=" + index);
504                         }
505                 }
506                 
507                 @Override
508                 public int getSize() {
509                         return 2;
510                 }
511         }
512         
513         
514         private void checkForUpdates() {
515                 final UpdateInfoRetriever retriever = new UpdateInfoRetriever();
516                 retriever.start();
517                 
518
519                 // Progress dialog
520                 final JDialog dialog = new JDialog(this, ModalityType.APPLICATION_MODAL);
521                 JPanel panel = new JPanel(new MigLayout());
522                 
523                 panel.add(new JLabel("Checking for updates..."), "wrap");
524                 
525                 JProgressBar bar = new JProgressBar();
526                 bar.setIndeterminate(true);
527                 panel.add(bar, "growx, wrap para");
528                 
529                 JButton cancel = new JButton("Cancel");
530                 cancel.addActionListener(new ActionListener() {
531                         @Override
532                         public void actionPerformed(ActionEvent e) {
533                                 dialog.dispose();
534                         }
535                 });
536                 panel.add(cancel, "right");
537                 dialog.add(panel);
538                 
539                 GUIUtil.setDisposableDialogOptions(dialog, cancel);
540                 
541
542                 // Timer to monitor progress
543                 final Timer timer = new Timer(100, null);
544                 final long startTime = System.currentTimeMillis();
545                 
546                 ActionListener listener = new ActionListener() {
547                         @Override
548                         public void actionPerformed(ActionEvent e) {
549                                 if (!retriever.isRunning() || startTime + 10000 < System.currentTimeMillis()) {
550                                         timer.stop();
551                                         dialog.dispose();
552                                 }
553                         }
554                 };
555                 timer.addActionListener(listener);
556                 timer.start();
557                 
558
559                 // Wait for action
560                 dialog.setVisible(true);
561                 
562
563                 // Check result
564                 UpdateInfo info = retriever.getUpdateInfo();
565                 if (info == null) {
566                         JOptionPane.showMessageDialog(this,
567                                         "An error occurred while communicating with the server.",
568                                         "Unable to retrieve update information", JOptionPane.WARNING_MESSAGE, null);
569                 } else if (info.getLatestVersion() == null ||
570                                 info.getLatestVersion().equals("") ||
571                                 Prefs.getVersion().equalsIgnoreCase(info.getLatestVersion())) {
572                         JOptionPane.showMessageDialog(this,
573                                         "You are running the latest version of OpenRocket.",
574                                         "No updates available", JOptionPane.INFORMATION_MESSAGE, null);
575                 } else {
576                         UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
577                         infoDialog.setVisible(true);
578                         if (infoDialog.isReminderSelected()) {
579                                 Prefs.putString(Prefs.LAST_UPDATE, "");
580                         } else {
581                                 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
582                         }
583                 }
584                 
585         }
586         
587         
588         ////////  Singleton implementation  ////////
589         
590         private static PreferencesDialog dialog = null;
591         
592         public static void showPreferences() {
593                 if (dialog != null) {
594                         dialog.dispose();
595                 }
596                 dialog = new PreferencesDialog();
597                 dialog.setVisible(true);
598         }
599         
600
601 }