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