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("Moment of inertia:"));
312 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_INERTIA));
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");
320 panel.add(new JLabel("Stability:"));
321 combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
322 panel.add(combo, "sizegroup boxes, wrap para");
327 JButton button = new JButton("Default metric");
328 button.addActionListener(new ActionListener() {
330 public void actionPerformed(ActionEvent e) {
331 UnitGroup.setDefaultMetricUnits();
332 for (DefaultUnitSelector s : unitSelectors)
336 panel.add(button, "spanx, split 2, grow");
338 button = new JButton("Default imperial");
339 button.addActionListener(new ActionListener() {
341 public void actionPerformed(ActionEvent e) {
342 UnitGroup.setDefaultImperialUnits();
343 for (DefaultUnitSelector s : unitSelectors)
347 panel.add(button, "grow, wrap para");
350 panel.add(new StyledLabel("The effects will take place the next time you open a window.", -2),
361 private class DefaultUnitSelector extends AbstractListModel implements ComboBoxModel {
363 private final UnitGroup group;
365 public DefaultUnitSelector(UnitGroup group) {
367 unitSelectors.add(this);
371 public Object getSelectedItem() {
372 return group.getDefaultUnit();
376 public void setSelectedItem(Object item) {
378 // Clear selection - huh?
381 if (!(item instanceof Unit)) {
382 throw new IllegalArgumentException("Illegal argument " + item);
384 group.setDefaultUnit(group.getUnitIndex((Unit) item));
388 public Object getElementAt(int index) {
389 return group.getUnit(index);
393 public int getSize() {
394 return group.getUnitCount();
398 public void fireChange() {
399 this.fireContentsChanged(this, 0, this.getSize());
405 private class PrefChoiseSelector extends AbstractListModel implements ComboBoxModel {
406 private final String preference;
407 private final String[] descriptions;
409 public PrefChoiseSelector(String preference, String... descriptions) {
410 this.preference = preference;
411 this.descriptions = descriptions;
415 public Object getSelectedItem() {
416 return descriptions[Prefs.getChoise(preference, descriptions.length, 0)];
420 public void setSelectedItem(Object item) {
422 // Clear selection - huh?
425 if (!(item instanceof String)) {
426 throw new IllegalArgumentException("Illegal argument " + item);
429 for (index = 0; index < descriptions.length; index++) {
430 if (((String) item).equalsIgnoreCase(descriptions[index]))
433 if (index >= descriptions.length) {
434 throw new IllegalArgumentException("Illegal argument " + item);
437 Prefs.putChoise(preference, index);
441 public Object getElementAt(int index) {
442 return descriptions[index];
446 public int getSize() {
447 return descriptions.length;
452 private class PrefBooleanSelector extends AbstractListModel implements ComboBoxModel {
453 private final String preference;
454 private final String trueDesc, falseDesc;
455 private final boolean def;
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;
466 public Object getSelectedItem() {
467 if (Prefs.NODE.getBoolean(preference, def)) {
475 public void setSelectedItem(Object item) {
477 // Clear selection - huh?
480 if (!(item instanceof String)) {
481 throw new IllegalArgumentException("Illegal argument " + item);
484 if (trueDesc.equals(item)) {
485 Prefs.NODE.putBoolean(preference, true);
486 } else if (falseDesc.equals(item)) {
487 Prefs.NODE.putBoolean(preference, false);
489 throw new IllegalArgumentException("Illegal argument " + item);
494 public Object getElementAt(int index) {
497 return def ? trueDesc : falseDesc;
500 return def ? falseDesc : trueDesc;
503 throw new IndexOutOfBoundsException("Boolean asked for index=" + index);
508 public int getSize() {
514 private void checkForUpdates() {
515 final UpdateInfoRetriever retriever = new UpdateInfoRetriever();
520 final JDialog dialog = new JDialog(this, ModalityType.APPLICATION_MODAL);
521 JPanel panel = new JPanel(new MigLayout());
523 panel.add(new JLabel("Checking for updates..."), "wrap");
525 JProgressBar bar = new JProgressBar();
526 bar.setIndeterminate(true);
527 panel.add(bar, "growx, wrap para");
529 JButton cancel = new JButton("Cancel");
530 cancel.addActionListener(new ActionListener() {
532 public void actionPerformed(ActionEvent e) {
536 panel.add(cancel, "right");
539 GUIUtil.setDisposableDialogOptions(dialog, cancel);
542 // Timer to monitor progress
543 final Timer timer = new Timer(100, null);
544 final long startTime = System.currentTimeMillis();
546 ActionListener listener = new ActionListener() {
548 public void actionPerformed(ActionEvent e) {
549 if (!retriever.isRunning() || startTime + 10000 < System.currentTimeMillis()) {
555 timer.addActionListener(listener);
560 dialog.setVisible(true);
564 UpdateInfo info = retriever.getUpdateInfo();
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);
576 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
577 infoDialog.setVisible(true);
578 if (infoDialog.isReminderSelected()) {
579 Prefs.putString(Prefs.LAST_UPDATE, "");
581 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
588 //////// Singleton implementation ////////
590 private static PreferencesDialog dialog = null;
592 public static void showPreferences() {
593 if (dialog != null) {
596 dialog = new PreferencesDialog();
597 dialog.setVisible(true);