1 package net.sf.openrocket.gui.dialogs.preferences;
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;
10 import java.util.ArrayList;
11 import java.util.List;
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;
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;
44 public class PreferencesDialog extends JDialog {
45 private static final LogHelper log = Application.getLogger();
47 private final List<DefaultUnitSelector> unitSelectors = new ArrayList<DefaultUnitSelector>();
49 private File defaultDirectory = null;
51 private PreferencesDialog() {
52 super((Window) null, "Preferences", Dialog.ModalityType.APPLICATION_MODAL);
54 JPanel panel = new JPanel(new MigLayout("fill, gap unrel", "[grow]", "[grow][]"));
56 JTabbedPane tabbedPane = new JTabbedPane();
57 panel.add(tabbedPane, "grow, wrap");
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");
65 JButton close = new JButton("Close");
66 close.addActionListener(new ActionListener() {
68 public void actionPerformed(ActionEvent arg0) {
69 PreferencesDialog.this.setVisible(false);
70 PreferencesDialog.this.dispose();
73 panel.add(close, "span, right, tag close");
75 this.setContentPane(panel);
77 this.setLocationRelativeTo(null);
79 this.addWindowListener(new WindowAdapter() {
81 public void windowClosed(WindowEvent e) {
82 Prefs.storeDefaultUnits();
86 GUIUtil.setDisposableDialogOptions(this, close);
90 private JPanel optionsPane() {
91 JPanel panel = new JPanel(new MigLayout("fillx, ins 30lp n n n"));
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");
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");
103 panel.add(new JLabel("User-defined thrust curves:"), "spanx, wrap");
104 final JTextField field = new JTextField();
105 List<File> files = Prefs.getUserThrustCurveFiles();
107 for (File file : files) {
108 if (str.length() > 0) {
111 str += file.getAbsolutePath();
114 field.getDocument().addDocumentListener(new DocumentListener() {
116 public void removeUpdate(DocumentEvent e) {
121 public void insertUpdate(DocumentEvent e) {
126 public void changedUpdate(DocumentEvent e) {
130 private void changed() {
131 String text = field.getText();
132 List<File> list = new ArrayList<File>();
133 for (String s : text.split(";")) {
135 if (s.length() > 0) {
136 list.add(new File(s));
139 Prefs.setUserThrustCurveFiles(list);
142 panel.add(field, "w 100px, gapright unrel, spanx, growx, split");
144 JButton button = new JButton("Add");
145 button.addActionListener(new ActionListener() {
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)",
154 chooser.addChoosableFileFilter(new SimpleFileFilter("RockSim engine files (*.rse)",
156 chooser.addChoosableFileFilter(new SimpleFileFilter("ZIP archives (*.zip)",
158 chooser.setFileFilter(filter);
159 chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
160 if (defaultDirectory != null) {
161 chooser.setCurrentDirectory(defaultDirectory);
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) {
172 text += chooser.getSelectedFile().getAbsolutePath();
177 panel.add(button, "gapright unrel");
179 button = new JButton("Reset");
181 button.addActionListener(new ActionListener() {
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);
189 panel.add(button, "wrap");
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");
200 final JCheckBox softwareUpdateBox = new JCheckBox("Check for software updates at startup");
201 softwareUpdateBox.setSelected(Prefs.getCheckUpdates());
202 softwareUpdateBox.addActionListener(new ActionListener() {
204 public void actionPerformed(ActionEvent e) {
205 Prefs.setCheckUpdates(softwareUpdateBox.isSelected());
208 panel.add(softwareUpdateBox);
210 button = new JButton("Check now");
211 button.setToolTipText("Check for software updates now");
212 button.addActionListener(new ActionListener() {
214 public void actionPerformed(ActionEvent e) {
218 panel.add(button, "right, wrap");
224 private JPanel unitsPane() {
225 JPanel panel = new JPanel(new MigLayout("", "[][]40lp[][]"));
228 panel.add(new JLabel("Select your preferred units:"), "span, wrap paragraph");
231 panel.add(new JLabel("Rocket dimensions:"));
232 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_LENGTH));
233 panel.add(combo, "sizegroup boxes");
235 panel.add(new JLabel("Line density:"));
236 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_LINE));
237 panel.add(combo, "sizegroup boxes, wrap");
241 panel.add(new JLabel("Motor dimensions:"));
242 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MOTOR_DIMENSIONS));
243 panel.add(combo, "sizegroup boxes");
245 panel.add(new JLabel("Surface density:"));
246 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_SURFACE));
247 panel.add(combo, "sizegroup boxes, wrap");
251 panel.add(new JLabel("Distance:"));
252 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DISTANCE));
253 panel.add(combo, "sizegroup boxes");
255 panel.add(new JLabel("Bulk density::"));
256 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_DENSITY_BULK));
257 panel.add(combo, "sizegroup boxes, wrap");
261 panel.add(new JLabel("Velocity:"));
262 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_VELOCITY));
263 panel.add(combo, "sizegroup boxes");
265 panel.add(new JLabel("Surface roughness:"));
266 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROUGHNESS));
267 panel.add(combo, "sizegroup boxes, wrap");
271 panel.add(new JLabel("Acceleration:"));
272 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ACCELERATION));
273 panel.add(combo, "sizegroup boxes");
275 panel.add(new JLabel("Area:"));
276 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_AREA));
277 panel.add(combo, "sizegroup boxes, wrap");
281 panel.add(new JLabel("Mass:"));
282 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_MASS));
283 panel.add(combo, "sizegroup boxes");
285 panel.add(new JLabel("Angle:"));
286 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ANGLE));
287 panel.add(combo, "sizegroup boxes, wrap");
291 panel.add(new JLabel("Force:"));
292 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_FORCE));
293 panel.add(combo, "sizegroup boxes");
295 panel.add(new JLabel("Roll rate:"));
296 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_ROLL));
297 panel.add(combo, "sizegroup boxes, wrap");
301 panel.add(new JLabel("Total impulse:"));
302 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_IMPULSE));
303 panel.add(combo, "sizegroup boxes");
305 panel.add(new JLabel("Temperature:"));
306 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_TEMPERATURE));
307 panel.add(combo, "sizegroup boxes, wrap");
311 panel.add(new JLabel("Stability:"));
312 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
313 panel.add(combo, "sizegroup boxes");
315 panel.add(new JLabel("Pressure:"));
316 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_PRESSURE));
317 panel.add(combo, "sizegroup boxes, wrap para");
321 JButton button = new JButton("Default metric");
322 button.addActionListener(new ActionListener() {
324 public void actionPerformed(ActionEvent e) {
325 UnitGroup.setDefaultMetricUnits();
326 for (DefaultUnitSelector s : unitSelectors)
330 panel.add(button, "spanx, split 2, grow");
332 button = new JButton("Default imperial");
333 button.addActionListener(new ActionListener() {
335 public void actionPerformed(ActionEvent e) {
336 UnitGroup.setDefaultImperialUnits();
337 for (DefaultUnitSelector s : unitSelectors)
341 panel.add(button, "grow, wrap para");
344 panel.add(new StyledLabel("The effects will take place the next time you open a window.", -2),
355 private class DefaultUnitSelector extends AbstractListModel implements ComboBoxModel {
357 private final UnitGroup group;
359 public DefaultUnitSelector(UnitGroup group) {
361 unitSelectors.add(this);
365 public Object getSelectedItem() {
366 return group.getDefaultUnit();
370 public void setSelectedItem(Object item) {
372 // Clear selection - huh?
375 if (!(item instanceof Unit)) {
376 throw new IllegalArgumentException("Illegal argument " + item);
378 group.setDefaultUnit(group.getUnitIndex((Unit) item));
382 public Object getElementAt(int index) {
383 return group.getUnit(index);
387 public int getSize() {
388 return group.getUnitCount();
392 public void fireChange() {
393 this.fireContentsChanged(this, 0, this.getSize());
399 private class PrefChoiseSelector extends AbstractListModel implements ComboBoxModel {
400 private final String preference;
401 private final String[] descriptions;
403 public PrefChoiseSelector(String preference, String... descriptions) {
404 this.preference = preference;
405 this.descriptions = descriptions;
409 public Object getSelectedItem() {
410 return descriptions[Prefs.getChoise(preference, descriptions.length, 0)];
414 public void setSelectedItem(Object item) {
416 // Clear selection - huh?
419 if (!(item instanceof String)) {
420 throw new IllegalArgumentException("Illegal argument " + item);
423 for (index = 0; index < descriptions.length; index++) {
424 if (((String) item).equalsIgnoreCase(descriptions[index]))
427 if (index >= descriptions.length) {
428 throw new IllegalArgumentException("Illegal argument " + item);
431 Prefs.putChoise(preference, index);
435 public Object getElementAt(int index) {
436 return descriptions[index];
440 public int getSize() {
441 return descriptions.length;
446 private class PrefBooleanSelector extends AbstractListModel implements ComboBoxModel {
447 private final String preference;
448 private final String trueDesc, falseDesc;
449 private final boolean def;
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;
460 public Object getSelectedItem() {
461 if (Prefs.NODE.getBoolean(preference, def)) {
469 public void setSelectedItem(Object item) {
471 // Clear selection - huh?
474 if (!(item instanceof String)) {
475 throw new IllegalArgumentException("Illegal argument " + item);
478 if (trueDesc.equals(item)) {
479 Prefs.NODE.putBoolean(preference, true);
480 } else if (falseDesc.equals(item)) {
481 Prefs.NODE.putBoolean(preference, false);
483 throw new IllegalArgumentException("Illegal argument " + item);
488 public Object getElementAt(int index) {
491 return def ? trueDesc : falseDesc;
494 return def ? falseDesc : trueDesc;
497 throw new IndexOutOfBoundsException("Boolean asked for index=" + index);
502 public int getSize() {
508 private void checkForUpdates() {
509 final UpdateInfoRetriever retriever = new UpdateInfoRetriever();
514 final JDialog dialog = new JDialog(this, ModalityType.APPLICATION_MODAL);
515 JPanel panel = new JPanel(new MigLayout());
517 panel.add(new JLabel("Checking for updates..."), "wrap");
519 JProgressBar bar = new JProgressBar();
520 bar.setIndeterminate(true);
521 panel.add(bar, "growx, wrap para");
523 JButton cancel = new JButton("Cancel");
524 cancel.addActionListener(new ActionListener() {
526 public void actionPerformed(ActionEvent e) {
530 panel.add(cancel, "right");
533 GUIUtil.setDisposableDialogOptions(dialog, cancel);
536 // Timer to monitor progress
537 final Timer timer = new Timer(100, null);
538 final long startTime = System.currentTimeMillis();
540 ActionListener listener = new ActionListener() {
542 public void actionPerformed(ActionEvent e) {
543 if (!retriever.isRunning() || startTime + 10000 < System.currentTimeMillis()) {
549 timer.addActionListener(listener);
554 dialog.setVisible(true);
558 UpdateInfo info = retriever.getUpdateInfo();
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);
570 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
571 infoDialog.setVisible(true);
572 if (infoDialog.isReminderSelected()) {
573 Prefs.putString(Prefs.LAST_UPDATE, "");
575 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
582 //////// Singleton implementation ////////
584 private static PreferencesDialog dialog = null;
586 public static void showPreferences() {
587 if (dialog != null) {
590 dialog = new PreferencesDialog();
591 dialog.setVisible(true);