Bug fixes and startup checks
[debian/openrocket] / src / net / sf / openrocket / gui / main / MotorChooserDialog.java
1 package net.sf.openrocket.gui.main;
2
3
4 import java.awt.Font;
5 import java.awt.Frame;
6 import java.awt.Rectangle;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.event.MouseAdapter;
10 import java.awt.event.MouseEvent;
11 import java.text.Collator;
12 import java.util.Comparator;
13 import java.util.Locale;
14 import java.util.regex.Matcher;
15 import java.util.regex.Pattern;
16
17 import javax.swing.DefaultComboBoxModel;
18 import javax.swing.JButton;
19 import javax.swing.JComboBox;
20 import javax.swing.JDialog;
21 import javax.swing.JLabel;
22 import javax.swing.JPanel;
23 import javax.swing.JScrollPane;
24 import javax.swing.JTable;
25 import javax.swing.ListSelectionModel;
26 import javax.swing.RowFilter;
27 import javax.swing.event.ListSelectionEvent;
28 import javax.swing.event.ListSelectionListener;
29 import javax.swing.table.AbstractTableModel;
30 import javax.swing.table.TableModel;
31 import javax.swing.table.TableRowSorter;
32
33 import net.miginfocom.swing.MigLayout;
34 import net.sf.openrocket.database.Databases;
35 import net.sf.openrocket.gui.components.ResizeLabel;
36 import net.sf.openrocket.rocketcomponent.Motor;
37 import net.sf.openrocket.unit.UnitGroup;
38 import net.sf.openrocket.util.GUIUtil;
39 import net.sf.openrocket.util.Prefs;
40
41 public class MotorChooserDialog extends JDialog {
42         
43         private static final int SHOW_ALL = 0;
44         private static final int SHOW_SMALLER = 1;
45         private static final int SHOW_EXACT = 2;
46         private static final String[] SHOW_DESCRIPTIONS = {
47                 "Show all motors",
48                 "Show motors with diameter less than that of the motor mount",
49                 "Show motors with diameter equal to that of the motor mount"
50         };
51         private static final int SHOW_MAX = 2;
52         
53
54         private final double diameter;
55
56         private Motor selectedMotor = null;
57         private double selectedDelay = 0;
58
59         private JTable table;
60         private TableRowSorter<TableModel> sorter;
61         private JComboBox delayBox;
62         private MotorDatabaseModel model;
63         
64         private boolean okClicked = false;
65
66         
67         public MotorChooserDialog(double diameter) {
68                 this(null,5,diameter,null);
69         }
70         
71         public MotorChooserDialog(Motor current, double delay, double diameter) {
72                 this(current,delay,diameter,null);
73         }
74         
75         public MotorChooserDialog(Motor current, double delay, double diameter, Frame owner) {
76                 super(owner, "Select a rocket motor", true);
77                 
78                 JButton button;
79
80                 this.selectedMotor = current;
81                 this.selectedDelay = delay;
82                 this.diameter = diameter;
83                 
84                 JPanel panel = new JPanel(new MigLayout("fill"));
85
86                 // Label
87                 JLabel label = new JLabel("Select a rocket motor:");
88                 label.setFont(label.getFont().deriveFont(Font.BOLD));
89                 panel.add(label,"split 2, growx");
90                 
91                 label = new JLabel("Motor mount diameter: " +
92                                 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
93                 panel.add(label,"alignx 100%, wrap paragraph");
94                 
95                 
96                 // Diameter selection
97                 JComboBox combo = new JComboBox(SHOW_DESCRIPTIONS);
98                 combo.addActionListener(new ActionListener() {
99                         @Override
100                         public void actionPerformed(ActionEvent e) {
101                                 JComboBox cb = (JComboBox) e.getSource();
102                                 int sel = cb.getSelectedIndex();
103                                 if ((sel < 0) || (sel > SHOW_MAX))
104                                         sel = SHOW_ALL;
105                                 switch (sel) {
106                                 case SHOW_ALL:
107                                         System.out.println("Setting filter: all");
108                                         sorter.setRowFilter(new MotorRowFilterAll());
109                                         break;
110                                         
111                                 case SHOW_SMALLER:
112                                         System.out.println("Setting filter: smaller");
113                                         sorter.setRowFilter(new MotorRowFilterSmaller());
114                                         break;
115                                         
116                                 case SHOW_EXACT:
117                                         System.out.println("Setting filter: exact");
118                                         sorter.setRowFilter(new MotorRowFilterExact());
119                                         break;
120                                         
121                                 default:
122                                         assert(false) : "Should not occur.";    
123                                 }
124                                 Prefs.putChoise("MotorDiameterMatch", sel);
125                                 setSelectionVisible();
126                         }
127                 });
128                 panel.add(combo,"growx, wrap");
129
130                 
131                 // Table, overridden to show meaningful tooltip texts
132                 model = new MotorDatabaseModel(current);
133                 table = new JTable(model) {
134                         @Override
135                         public String getToolTipText(MouseEvent e) {
136                         java.awt.Point p = e.getPoint();
137                         int colIndex = columnAtPoint(p);
138                         int viewRow = rowAtPoint(p);
139                         if (viewRow < 0)
140                                 return null;
141                         int rowIndex = convertRowIndexToModel(viewRow);
142                         Motor motor = model.getMotor(rowIndex);
143
144                         if (colIndex < 0 || colIndex >= MotorColumns.values().length)
145                                 return null;
146
147                         return MotorColumns.values()[colIndex].getToolTipText(motor);
148                         }
149                 };
150                 
151                 // Set comparators and widths
152                 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
153                 sorter = new TableRowSorter<TableModel>(model);
154                 for (int i=0; i < MotorColumns.values().length; i++) {
155                         MotorColumns column = MotorColumns.values()[i];
156                         sorter.setComparator(i, column.getComparator());
157                         table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
158                 }
159                 table.setRowSorter(sorter);
160
161                 // Set selection and double-click listeners
162                 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
163                         @Override
164                         public void valueChanged(ListSelectionEvent e) {
165                                 int row = table.getSelectedRow();
166                                 if (row >= 0) {
167                                         row = table.convertRowIndexToModel(row);
168                                         Motor m = model.getMotor(row);
169                                         if (!m.equals(selectedMotor)) {
170                                                 selectedMotor = model.getMotor(row);
171                                                 setDelays(true);  // Reset delay times
172                                         }
173                                 }
174                         }
175                 });
176                 table.addMouseListener(new MouseAdapter() {
177                         @Override
178                         public void mouseClicked(MouseEvent e) {
179                                 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
180                                         okClicked = true;
181                                         MotorChooserDialog.this.setVisible(false);
182                                 }
183                         }
184                 });
185                 // (Current selection and scrolling performed later)
186                 
187                 JScrollPane scrollpane = new JScrollPane();
188                 scrollpane.setViewportView(table);
189                 panel.add(scrollpane,"grow, width :700:, height :300:, wrap paragraph");
190                 
191                 
192                 // Ejection delay
193                 panel.add(new JLabel("Select ejection charge delay:"), "split 3, gap rel");
194                 
195                 delayBox = new JComboBox();
196                 delayBox.setEditable(true);
197                 delayBox.addActionListener(new ActionListener() {
198                         @Override
199                         public void actionPerformed(ActionEvent e) {
200                                 JComboBox cb = (JComboBox) e.getSource();
201                                 String sel = (String)cb.getSelectedItem();
202                                 if (sel.equalsIgnoreCase("None")) {
203                                         selectedDelay = Motor.PLUGGED;
204                                 } else {
205                                         try {
206                                                 selectedDelay = Double.parseDouble(sel);
207                                         } catch (NumberFormatException ignore) { }
208                                 }
209                                 setDelays(false);
210                         }
211                 });
212                 panel.add(delayBox,"gapright unrel");
213                 panel.add(new ResizeLabel("(Number of seconds or \"None\")", -1), "wrap para");
214                 setDelays(false);
215                 
216                 
217                 JButton okButton = new JButton("OK");
218                 okButton.addActionListener(new ActionListener() {
219                         @Override
220                         public void actionPerformed(ActionEvent e) {
221                                 okClicked = true;
222                                 MotorChooserDialog.this.setVisible(false);
223                         }
224                 });
225                 panel.add(okButton,"split, tag ok");
226
227                 button = new JButton("Cancel");
228                 button.addActionListener(new ActionListener() {
229                         @Override
230                         public void actionPerformed(ActionEvent e) {
231                                 MotorChooserDialog.this.setVisible(false);
232                         }
233                 });
234                 panel.add(button,"tag cancel");
235
236                                 
237                 // Sets the filter:
238                 int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
239                 combo.setSelectedIndex(showMode);
240                 
241                 
242                 this.add(panel);
243                 this.pack();
244 //              this.setAlwaysOnTop(true);
245
246                 GUIUtil.setDefaultButton(okButton);
247                 GUIUtil.installEscapeCloseOperation(this);
248                 
249                 // Table can be scrolled only after pack() has been called
250                 setSelectionVisible();
251         }
252         
253         private void setSelectionVisible() {
254                 if (selectedMotor != null) {
255                         int index = table.convertRowIndexToView(model.getIndex(selectedMotor));
256                         table.getSelectionModel().setSelectionInterval(index, index);
257                         Rectangle rect = table.getCellRect(index, 0, true);
258                         rect = new Rectangle(rect.x,rect.y-100,rect.width,rect.height+200);
259                         table.scrollRectToVisible(rect);
260                 }
261         }
262         
263         
264         /**
265          * Set the values in the delay combo box.  If <code>reset</code> is <code>true</code>
266          * then sets the selected value as the value closest to selectedDelay, otherwise
267          * leaves selection alone.
268          */
269         private void setDelays(boolean reset) {
270                 if (selectedMotor == null) {
271                         
272                         delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
273                         delayBox.setSelectedIndex(0);
274                         
275                 } else {
276                         
277                         double[] delays = selectedMotor.getStandardDelays();
278                         String[] delayStrings = new String[delays.length];
279                         double currentDelay = selectedDelay;  // Store current setting locally
280                         
281                         for (int i=0; i < delays.length; i++) {
282                                 delayStrings[i] = Motor.getDelayString(delays[i], "None");
283                         }
284                         delayBox.setModel(new DefaultComboBoxModel(delayStrings));
285                         
286                         if (reset) {
287
288                                 // Find and set the closest value
289                                 double closest = Double.NaN;
290                                 for (int i=0; i < delays.length; i++) {
291                                         // if-condition to always become true for NaN
292                                         if (!(Math.abs(delays[i] - currentDelay) > 
293                                                   Math.abs(closest - currentDelay))) {
294                                                 closest = delays[i];
295                                         }
296                                 }
297                                 if (!Double.isNaN(closest)) {
298                                         selectedDelay = closest;
299                                         delayBox.setSelectedItem(Motor.getDelayString(closest, "None"));
300                                 } else {
301                                         delayBox.setSelectedItem("None");
302                                 }
303
304                         } else {
305                                 
306                                 selectedDelay = currentDelay;
307                                 delayBox.setSelectedItem(Motor.getDelayString(currentDelay, "None"));
308                                 
309                         }
310                         
311                 }
312         }
313
314         
315         
316         public Motor getSelectedMotor() {
317                 if (!okClicked)
318                         return null;
319                 return selectedMotor;
320         }
321         
322         
323         public double getSelectedDelay() {
324                 return selectedDelay;
325         }
326         
327
328         
329         
330         ////////////////  JTable elements  ////////////////
331         
332         
333         /**
334          * Enum defining the table columns.
335          */
336         private enum MotorColumns {
337                 MANUFACTURER("Manufacturer",100) {
338                         @Override
339                         public String getValue(Motor m) {
340                                 return m.getManufacturer();
341                         }
342 //                      @Override
343 //                      public String getToolTipText(Motor m) {
344 //                              return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
345 //                      }
346                         @Override
347                         public Comparator<?> getComparator() {
348                                 return Collator.getInstance();
349                         }
350                 },
351                 DESIGNATION("Designation") {
352                         @Override
353                         public String getValue(Motor m) {
354                                 return m.getDesignation();
355                         }
356 //                      @Override
357 //                      public String getToolTipText(Motor m) {
358 //                              return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
359 //                      }
360                         @Override
361                         public Comparator<?> getComparator() {
362                                 return Motor.getDesignationComparator();
363                         }
364                 },
365                 TYPE("Type") {
366                         @Override
367                         public String getValue(Motor m) {
368                                 return m.getMotorType().getName();
369                         }
370 //                      @Override
371 //                      public String getToolTipText(Motor m) {
372 //                              return m.getMotorType().getDescription();
373 //                      }
374                         @Override
375                         public Comparator<?> getComparator() {
376                                 return Collator.getInstance();
377                         }
378                 },
379                 DIAMETER("Diameter") {
380                         @Override
381                         public String getValue(Motor m) {
382                                 return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
383                                                 m.getDiameter());
384                         }
385                         @Override
386                         public Comparator<?> getComparator() {
387                                 return getNumericalComparator();
388                         }
389                 },
390                 LENGTH("Length") {
391                         @Override
392                         public String getValue(Motor m) {
393                                 return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
394                                                 m.getLength());
395                         }
396                         @Override
397                         public Comparator<?> getComparator() {
398                                 return getNumericalComparator();
399                         }
400                 },
401                 IMPULSE("Impulse") {
402                         @Override
403                         public String getValue(Motor m) {
404                                 return UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
405                                                 m.getTotalImpulse());
406                         }
407                         @Override
408                         public Comparator<?> getComparator() {
409                                 return getNumericalComparator();
410                         }
411                 },
412                 TIME("Burn time") {
413                         @Override
414                         public String getValue(Motor m) {
415                                 return UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
416                                                 m.getAverageTime());
417                         }
418                         @Override
419                         public Comparator<?> getComparator() {
420                                 return getNumericalComparator();
421                         }
422                 };
423                 
424                 
425                 private final String title;
426                 private final int width;
427                 
428                 MotorColumns(String title) {
429                         this(title, 50);
430                 }
431                 
432                 MotorColumns(String title, int width) {
433                         this.title = title;
434                         this.width = width;
435                 }
436                 
437                 
438                 public abstract String getValue(Motor m);
439                 public abstract Comparator<?> getComparator();
440
441                 public String getTitle() {
442                         return title;
443                 }
444                 
445                 public int getWidth() {
446                         return width;
447                 }
448                 
449                 public String getToolTipText(Motor m) {
450                         String tip = "<html>";
451                         tip += "<b>" + m.toString() + "</b>";
452                         tip += " (" + m.getMotorType().getDescription() + ")<br><hr>";
453                         
454                         String desc = m.getDescription().trim();
455                         if (desc.length() > 0) {
456                                 tip += "<i>" + desc.replace("\n", "<br>") + "</i><br><hr>";
457                         }
458                         
459                         tip += ("Diameter: " + 
460                                         UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getDiameter()) +
461                                         "<br>");
462                         tip += ("Length: " + 
463                                         UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getLength()) +
464                                         "<br>");
465                         tip += ("Maximum thrust: " + 
466                                         UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getMaxThrust()) +
467                                         "<br>");
468                         tip += ("Average thrust: " + 
469                                         UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getAverageThrust()) +
470                                         "<br>");
471                         tip += ("Burn time: " + 
472                                         UnitGroup.UNITS_SHORT_TIME.getDefaultUnit()
473                                         .toStringUnit(m.getAverageTime()) + "<br>");
474                         tip += ("Total impulse: " +
475                                         UnitGroup.UNITS_IMPULSE.getDefaultUnit()
476                                         .toStringUnit(m.getTotalImpulse()) + "<br>");
477                         tip += ("Launch mass: " + 
478                                         UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getMass(0)) +
479                                         "<br>");
480                         tip += ("Empty mass: " + 
481                                         UnitGroup.UNITS_MASS.getDefaultUnit()
482                                         .toStringUnit(m.getMass(Double.MAX_VALUE)));
483                         return tip;
484                 }
485                 
486         }
487         
488         
489         /**
490          * The JTable model.  Includes an extra motor, given in the constructor,
491          * if it is not already in the database.
492          */
493         private class MotorDatabaseModel extends AbstractTableModel {
494                 private final Motor extra;
495                 
496                 public MotorDatabaseModel(Motor current) {
497                         if (Databases.MOTOR.contains(current))
498                                 extra = null;
499                         else
500                                 extra = current;
501                 }
502                 
503                 @Override
504                 public int getColumnCount() {
505                         return MotorColumns.values().length;
506                 }
507
508                 @Override
509                 public int getRowCount() {
510                         if (extra == null)
511                                 return Databases.MOTOR.size();
512                         else
513                                 return Databases.MOTOR.size()+1;
514                 }
515
516                 @Override
517                 public Object getValueAt(int rowIndex, int columnIndex) {
518                         MotorColumns column = getColumn(columnIndex);
519                         if (extra == null) {
520                                 return column.getValue(Databases.MOTOR.get(rowIndex));
521                         } else {
522                                 if (rowIndex == 0)
523                                         return column.getValue(extra);
524                                 else
525                                         return column.getValue(Databases.MOTOR.get(rowIndex - 1));
526                         }
527                 }
528                 
529                 @Override
530                 public String getColumnName(int columnIndex) {
531                         return getColumn(columnIndex).getTitle();
532                 }
533                 
534                 
535                 public Motor getMotor(int rowIndex) {
536                         if (extra == null) {
537                                 return Databases.MOTOR.get(rowIndex);
538                         } else {
539                                 if (rowIndex == 0)
540                                         return extra;
541                                 else
542                                         return Databases.MOTOR.get(rowIndex-1);
543                         }
544                 }
545                 
546                 public int getIndex(Motor m) {
547                         if (extra == null) {
548                                 return Databases.MOTOR.indexOf(m);
549                         } else {
550                                 if (extra.equals(m))
551                                         return 0;
552                                 else
553                                         return Databases.MOTOR.indexOf(m)+1;
554                         }
555                 }
556                 
557                 private MotorColumns getColumn(int index) {
558                         return MotorColumns.values()[index];
559                 }
560         }
561
562         
563         ////////  Row filters
564         
565         /**
566          * Abstract adapter class.
567          */
568         private abstract class MotorRowFilter extends RowFilter<TableModel,Integer> {
569                 @Override
570                 public boolean include(
571                                 RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
572                         int index = entry.getIdentifier();
573                         Motor m = model.getMotor(index);
574                         return include(m);
575                 }
576                 
577                 public abstract boolean include(Motor m);
578         }
579         
580         /**
581          * Show all motors.
582          */
583         private class MotorRowFilterAll extends MotorRowFilter {
584                 @Override
585                 public boolean include(Motor m) {
586                         return true;
587                 }
588         }
589         
590         /**
591          * Show motors smaller than the mount.
592          */
593         private class MotorRowFilterSmaller extends MotorRowFilter {
594                 @Override
595                 public boolean include(Motor m) {
596                         return (m.getDiameter() <= diameter + 0.0004);
597                 }
598         }
599         
600         /**
601          * Show motors that fit the mount.
602          */
603         private class MotorRowFilterExact extends MotorRowFilter {
604                 @Override
605                 public boolean include(Motor m) {
606                         return ((m.getDiameter() <= diameter + 0.0004) &&
607                                         (m.getDiameter() >= diameter - 0.0015));
608                 }
609         }
610         
611         
612         private static Comparator<String> numericalComparator = null;
613         private static Comparator<String> getNumericalComparator() {
614                 if (numericalComparator == null)
615                         numericalComparator = new NumericalComparator();
616                 return numericalComparator;
617         }
618         
619         private static class NumericalComparator implements Comparator<String> {
620                 private Pattern pattern = 
621                         Pattern.compile("^\\s*([0-9]*[.,][0-9]+|[0-9]+[.,]?[0-9]*).*?$");
622                 private Collator collator = null;
623                 @Override
624                 public int compare(String s1, String s2) {
625                         Matcher m1, m2;
626                         
627                         m1 = pattern.matcher(s1);
628                         m2 = pattern.matcher(s2);
629                         if (m1.find() && m2.find()) {
630                                 double d1 = Double.parseDouble(m1.group(1));
631                                 double d2 = Double.parseDouble(m2.group(1));
632                                 
633                                 return (int)((d1-d2)*1000);
634                         }
635                         
636                         if (collator == null)
637                                 collator = Collator.getInstance(Locale.US);
638                         return collator.compare(s1, s2);
639                 }
640         }
641         
642 }