version 1.0.0
[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                                         // TODO: HIGH: equals or == ?
210                                         if (!m.equals(selectedMotor)) {
211                                                 selectedMotor = model.getMotor(row);
212                                                 setDelays(true);  // Reset delay times
213                                         }
214                                 }
215                         }
216                 });
217                 table.addMouseListener(new MouseAdapter() {
218                         @Override
219                         public void mouseClicked(MouseEvent e) {
220                                 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
221                                         okClicked = true;
222                                         MotorChooserDialog.this.setVisible(false);
223                                 }
224                         }
225                 });
226                 // (Current selection and scrolling performed later)
227                 
228                 JScrollPane scrollpane = new JScrollPane();
229                 scrollpane.setViewportView(table);
230                 panel.add(scrollpane,"spanx, grow, width :700:, height :300:, wrap paragraph");
231                 
232                 
233                 // Ejection delay
234                 panel.add(new JLabel("Select ejection charge delay:"), "spanx, split 3, gap rel");
235                 
236                 delayBox = new JComboBox();
237                 delayBox.setEditable(true);
238                 delayBox.addActionListener(new ActionListener() {
239                         @Override
240                         public void actionPerformed(ActionEvent e) {
241                                 JComboBox cb = (JComboBox) e.getSource();
242                                 String sel = (String)cb.getSelectedItem();
243                                 if (sel.equalsIgnoreCase("None")) {
244                                         selectedDelay = Motor.PLUGGED;
245                                 } else {
246                                         try {
247                                                 selectedDelay = Double.parseDouble(sel);
248                                         } catch (NumberFormatException ignore) { }
249                                 }
250                                 setDelays(false);
251                         }
252                 });
253                 panel.add(delayBox,"gapright unrel");
254                 panel.add(new StyledLabel("(Number of seconds or \"None\")", -1), "wrap para");
255                 setDelays(false);
256                 
257                 
258                 JButton okButton = new JButton("OK");
259                 okButton.addActionListener(new ActionListener() {
260                         @Override
261                         public void actionPerformed(ActionEvent e) {
262                                 okClicked = true;
263                                 MotorChooserDialog.this.setVisible(false);
264                         }
265                 });
266                 panel.add(okButton,"spanx, split, tag ok");
267
268                 button = new JButton("Cancel");
269                 button.addActionListener(new ActionListener() {
270                         @Override
271                         public void actionPerformed(ActionEvent e) {
272                                 MotorChooserDialog.this.setVisible(false);
273                         }
274                 });
275                 panel.add(button,"tag cancel");
276
277                                 
278                 // Sets the filter:
279                 int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
280                 combo.setSelectedIndex(showMode);
281                 
282                 
283                 this.add(panel);
284                 this.pack();
285 //              this.setAlwaysOnTop(true);
286
287                 this.setLocationByPlatform(true);
288                 GUIUtil.setDisposableDialogOptions(this, okButton);
289                 
290                 // Table can be scrolled only after pack() has been called
291                 setSelectionVisible();
292                 
293                 // Focus the search field
294                 searchField.grabFocus();
295         }
296         
297         private void setSelectionVisible() {
298                 if (selectedMotor != null) {
299                         int index = table.convertRowIndexToView(model.getIndex(selectedMotor));
300                         table.getSelectionModel().setSelectionInterval(index, index);
301                         Rectangle rect = table.getCellRect(index, 0, true);
302                         rect = new Rectangle(rect.x,rect.y-100,rect.width,rect.height+200);
303                         table.scrollRectToVisible(rect);
304                 }
305         }
306         
307         
308         /**
309          * Set the values in the delay combo box.  If <code>reset</code> is <code>true</code>
310          * then sets the selected value as the value closest to selectedDelay, otherwise
311          * leaves selection alone.
312          */
313         private void setDelays(boolean reset) {
314                 if (selectedMotor == null) {
315                         
316                         delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
317                         delayBox.setSelectedIndex(0);
318                         
319                 } else {
320                         
321                         double[] delays = selectedMotor.getStandardDelays();
322                         String[] delayStrings = new String[delays.length];
323                         double currentDelay = selectedDelay;  // Store current setting locally
324                         
325                         for (int i=0; i < delays.length; i++) {
326                                 delayStrings[i] = Motor.getDelayString(delays[i], "None");
327                         }
328                         delayBox.setModel(new DefaultComboBoxModel(delayStrings));
329                         
330                         if (reset) {
331
332                                 // Find and set the closest value
333                                 double closest = Double.NaN;
334                                 for (int i=0; i < delays.length; i++) {
335                                         // if-condition to always become true for NaN
336                                         if (!(Math.abs(delays[i] - currentDelay) > 
337                                                   Math.abs(closest - currentDelay))) {
338                                                 closest = delays[i];
339                                         }
340                                 }
341                                 if (!Double.isNaN(closest)) {
342                                         selectedDelay = closest;
343                                         delayBox.setSelectedItem(Motor.getDelayString(closest, "None"));
344                                 } else {
345                                         delayBox.setSelectedItem("None");
346                                 }
347
348                         } else {
349                                 
350                                 selectedDelay = currentDelay;
351                                 delayBox.setSelectedItem(Motor.getDelayString(currentDelay, "None"));
352                                 
353                         }
354                         
355                 }
356         }
357
358         
359         
360         public Motor getSelectedMotor() {
361                 if (!okClicked)
362                         return null;
363                 return selectedMotor;
364         }
365         
366         
367         public double getSelectedDelay() {
368                 return selectedDelay;
369         }
370         
371
372         
373         
374         ////////////////  JTable elements  ////////////////
375         
376         
377         /**
378          * Enum defining the table columns.
379          */
380         private enum MotorColumns {
381                 MANUFACTURER("Manufacturer",100) {
382                         @Override
383                         public String getValue(Motor m) {
384                                 return m.getManufacturer().getDisplayName();
385                         }
386                         @Override
387                         public Comparator<?> getComparator() {
388                                 return Collator.getInstance();
389                         }
390                 },
391                 DESIGNATION("Designation") {
392                         @Override
393                         public String getValue(Motor m) {
394                                 return m.getDesignation();
395                         }
396                         @Override
397                         public Comparator<?> getComparator() {
398                                 return Motor.getDesignationComparator();
399                         }
400                 },
401                 TYPE("Type") {
402                         @Override
403                         public String getValue(Motor m) {
404                                 return m.getMotorType().getName();
405                         }
406                         @Override
407                         public Comparator<?> getComparator() {
408                                 return Collator.getInstance();
409                         }
410                 },
411                 DIAMETER("Diameter") {
412                         @Override
413                         public Object getValue(Motor m) {
414                                 return new Value(m.getDiameter(), UnitGroup.UNITS_MOTOR_DIMENSIONS);
415                         }
416                         @Override
417                         public Comparator<?> getComparator() {
418                                 return ValueComparator.INSTANCE;
419                         }
420                 },
421                 LENGTH("Length") {
422                         @Override
423                         public Object getValue(Motor m) {
424                                 return new Value(m.getLength(), UnitGroup.UNITS_MOTOR_DIMENSIONS);
425                         }
426                         @Override
427                         public Comparator<?> getComparator() {
428                                 return ValueComparator.INSTANCE;
429                         }
430                 },
431                 IMPULSE("Impulse") {
432                         @Override
433                         public Object getValue(Motor m) {
434                                 return new Value(m.getTotalImpulse(), UnitGroup.UNITS_IMPULSE);
435                         }
436                         @Override
437                         public Comparator<?> getComparator() {
438                                 return ValueComparator.INSTANCE;
439                         }
440                 },
441                 TIME("Burn time") {
442                         @Override
443                         public Object getValue(Motor m) {
444                                 return new Value(m.getAverageTime(), UnitGroup.UNITS_SHORT_TIME);
445                         }
446                         @Override
447                         public Comparator<?> getComparator() {
448                                 return ValueComparator.INSTANCE;
449                         }
450                 };
451                 
452                 
453                 private final String title;
454                 private final int width;
455                 
456                 MotorColumns(String title) {
457                         this(title, 50);
458                 }
459                 
460                 MotorColumns(String title, int width) {
461                         this.title = title;
462                         this.width = width;
463                 }
464                 
465                 
466                 public abstract Object getValue(Motor m);
467                 public abstract Comparator<?> getComparator();
468
469                 public String getTitle() {
470                         return title;
471                 }
472                 
473                 public int getWidth() {
474                         return width;
475                 }
476                 
477                 public String getToolTipText(Motor m) {
478                         String tip = "<html>";
479                         tip += "<b>" + m.toString() + "</b>";
480                         tip += " (" + m.getMotorType().getDescription() + ")<br><hr>";
481                         
482                         String desc = m.getDescription().trim();
483                         if (desc.length() > 0) {
484                                 tip += "<i>" + desc.replace("\n", "<br>") + "</i><br><hr>";
485                         }
486                         
487                         tip += ("Diameter: " + 
488                                         UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getDiameter()) +
489                                         "<br>");
490                         tip += ("Length: " + 
491                                         UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getLength()) +
492                                         "<br>");
493                         tip += ("Maximum thrust: " + 
494                                         UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getMaxThrust()) +
495                                         "<br>");
496                         tip += ("Average thrust: " + 
497                                         UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getAverageThrust()) +
498                                         "<br>");
499                         tip += ("Burn time: " + 
500                                         UnitGroup.UNITS_SHORT_TIME.getDefaultUnit()
501                                         .toStringUnit(m.getAverageTime()) + "<br>");
502                         tip += ("Total impulse: " +
503                                         UnitGroup.UNITS_IMPULSE.getDefaultUnit()
504                                         .toStringUnit(m.getTotalImpulse()) + "<br>");
505                         tip += ("Launch mass: " + 
506                                         UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getMass(0)) +
507                                         "<br>");
508                         tip += ("Empty mass: " + 
509                                         UnitGroup.UNITS_MASS.getDefaultUnit()
510                                         .toStringUnit(m.getMass(Double.MAX_VALUE)));
511                         return tip;
512                 }
513                 
514         }
515         
516         
517         /**
518          * The JTable model.  Includes an extra motor, given in the constructor,
519          * if it is not already in the database.
520          */
521         private class MotorDatabaseModel extends AbstractTableModel {
522                 private final Motor extra;
523                 
524                 public MotorDatabaseModel(Motor current) {
525                         if (Databases.MOTOR.contains(current))
526                                 extra = null;
527                         else
528                                 extra = current;
529                 }
530                 
531                 @Override
532                 public int getColumnCount() {
533                         return MotorColumns.values().length;
534                 }
535
536                 @Override
537                 public int getRowCount() {
538                         if (extra == null)
539                                 return Databases.MOTOR.size();
540                         else
541                                 return Databases.MOTOR.size()+1;
542                 }
543
544                 @Override
545                 public Object getValueAt(int rowIndex, int columnIndex) {
546                         MotorColumns column = getColumn(columnIndex);
547                         if (extra == null) {
548                                 return column.getValue(Databases.MOTOR.get(rowIndex));
549                         } else {
550                                 if (rowIndex == 0)
551                                         return column.getValue(extra);
552                                 else
553                                         return column.getValue(Databases.MOTOR.get(rowIndex - 1));
554                         }
555                 }
556                 
557                 @Override
558                 public String getColumnName(int columnIndex) {
559                         return getColumn(columnIndex).getTitle();
560                 }
561                 
562                 
563                 public Motor getMotor(int rowIndex) {
564                         if (extra == null) {
565                                 return Databases.MOTOR.get(rowIndex);
566                         } else {
567                                 if (rowIndex == 0)
568                                         return extra;
569                                 else
570                                         return Databases.MOTOR.get(rowIndex-1);
571                         }
572                 }
573                 
574                 public int getIndex(Motor m) {
575                         if (extra == null) {
576                                 return Databases.MOTOR.indexOf(m);
577                         } else {
578                                 if (extra.equals(m))
579                                         return 0;
580                                 else
581                                         return Databases.MOTOR.indexOf(m)+1;
582                         }
583                 }
584                 
585                 private MotorColumns getColumn(int index) {
586                         return MotorColumns.values()[index];
587                 }
588         }
589
590         
591         ////////  Row filters
592         
593         /**
594          * Abstract adapter class.
595          */
596         private abstract class MotorRowFilter extends RowFilter<TableModel,Integer> {
597                 @Override
598                 public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
599                         int index = entry.getIdentifier();
600                         Motor m = model.getMotor(index);
601                         return filterByDiameter(m) && filterByString(m);
602                 }
603                 
604                 public abstract boolean filterByDiameter(Motor m);
605                 
606                 
607                 public boolean filterByString(Motor m) {
608                         main: for (String s : searchTerms) {
609                                 for (MotorColumns col : MotorColumns.values()) {
610                                         String str = col.getValue(m).toString().toLowerCase();
611                                         if (str.indexOf(s) >= 0)
612                                                 continue main;
613                                 }
614                                 return false;
615                         }
616                         return true;
617                 }
618         }
619         
620         /**
621          * Show all motors.
622          */
623         private class MotorRowFilterAll extends MotorRowFilter {
624                 @Override
625                 public boolean filterByDiameter(Motor m) {
626                         return true;
627                 }
628         }
629         
630         /**
631          * Show motors smaller than the mount.
632          */
633         private class MotorRowFilterSmaller extends MotorRowFilter {
634                 @Override
635                 public boolean filterByDiameter(Motor m) {
636                         return (m.getDiameter() <= diameter + 0.0004);
637                 }
638         }
639         
640         /**
641          * Show motors that fit the mount.
642          */
643         private class MotorRowFilterExact extends MotorRowFilter {
644                 @Override
645                 public boolean filterByDiameter(Motor m) {
646                         return ((m.getDiameter() <= diameter + 0.0004) &&
647                                         (m.getDiameter() >= diameter - 0.0015));
648                 }
649         }
650         
651 }