1 package net.sf.openrocket.gui.dialogs;
4 import java.awt.Dialog;
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 import java.util.Locale;
16 import java.util.regex.Matcher;
17 import java.util.regex.Pattern;
19 import javax.swing.DefaultComboBoxModel;
20 import javax.swing.JButton;
21 import javax.swing.JComboBox;
22 import javax.swing.JDialog;
23 import javax.swing.JLabel;
24 import javax.swing.JPanel;
25 import javax.swing.JScrollPane;
26 import javax.swing.JTable;
27 import javax.swing.JTextField;
28 import javax.swing.ListSelectionModel;
29 import javax.swing.RowFilter;
30 import javax.swing.event.DocumentEvent;
31 import javax.swing.event.DocumentListener;
32 import javax.swing.event.ListSelectionEvent;
33 import javax.swing.event.ListSelectionListener;
34 import javax.swing.table.AbstractTableModel;
35 import javax.swing.table.TableModel;
36 import javax.swing.table.TableRowSorter;
38 import net.miginfocom.swing.MigLayout;
39 import net.sf.openrocket.database.Databases;
40 import net.sf.openrocket.gui.components.ResizeLabel;
41 import net.sf.openrocket.rocketcomponent.Motor;
42 import net.sf.openrocket.unit.UnitGroup;
43 import net.sf.openrocket.util.GUIUtil;
44 import net.sf.openrocket.util.Prefs;
46 public class MotorChooserDialog extends JDialog {
48 private static final int SHOW_ALL = 0;
49 private static final int SHOW_SMALLER = 1;
50 private static final int SHOW_EXACT = 2;
51 private static final String[] SHOW_DESCRIPTIONS = {
53 "Show motors with diameter less than that of the motor mount",
54 "Show motors with diameter equal to that of the motor mount"
56 private static final int SHOW_MAX = 2;
58 private final JTextField searchField;
59 private String[] searchTerms = new String[0];
61 private final double diameter;
63 private Motor selectedMotor = null;
64 private double selectedDelay = 0;
67 private TableRowSorter<TableModel> sorter;
68 private JComboBox delayBox;
69 private MotorDatabaseModel model;
71 private boolean okClicked = false;
74 public MotorChooserDialog(double diameter) {
75 this(null,5,diameter,null);
78 public MotorChooserDialog(Motor current, double delay, double diameter) {
79 this(current,delay,diameter,null);
82 public MotorChooserDialog(Motor current, double delay, double diameter, Window owner) {
83 super(owner, "Select a rocket motor", Dialog.ModalityType.APPLICATION_MODAL);
87 this.selectedMotor = current;
88 this.selectedDelay = delay;
89 this.diameter = diameter;
91 JPanel panel = new JPanel(new MigLayout("fill", "[grow][]"));
94 JLabel label = new JLabel("Select a rocket motor:");
95 label.setFont(label.getFont().deriveFont(Font.BOLD));
96 panel.add(label,"growx");
98 label = new JLabel("Motor mount diameter: " +
99 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
100 panel.add(label,"gapleft para, wrap paragraph");
103 // Diameter selection
104 JComboBox combo = new JComboBox(SHOW_DESCRIPTIONS);
105 combo.addActionListener(new ActionListener() {
107 public void actionPerformed(ActionEvent e) {
108 JComboBox cb = (JComboBox) e.getSource();
109 int sel = cb.getSelectedIndex();
110 if ((sel < 0) || (sel > SHOW_MAX))
114 sorter.setRowFilter(new MotorRowFilterAll());
118 sorter.setRowFilter(new MotorRowFilterSmaller());
122 sorter.setRowFilter(new MotorRowFilterExact());
126 assert(false) : "Should not occur.";
128 Prefs.putChoise("MotorDiameterMatch", sel);
129 setSelectionVisible();
132 panel.add(combo,"growx 1000");
136 label = new JLabel("Search:");
137 panel.add(label, "gapleft para, split 2");
139 searchField = new JTextField();
140 searchField.getDocument().addDocumentListener(new DocumentListener() {
142 public void changedUpdate(DocumentEvent e) {
146 public void insertUpdate(DocumentEvent e) {
150 public void removeUpdate(DocumentEvent e) {
154 private void update() {
155 String text = searchField.getText().trim();
156 String[] split = text.split("\\s+");
157 ArrayList<String> list = new ArrayList<String>();
158 for (String s: split) {
159 s = s.trim().toLowerCase();
160 if (s.length() > 0) {
164 searchTerms = list.toArray(new String[0]);
168 panel.add(searchField, "growx 1, wrap");
172 // Table, overridden to show meaningful tooltip texts
173 model = new MotorDatabaseModel(current);
174 table = new JTable(model) {
176 public String getToolTipText(MouseEvent e) {
177 java.awt.Point p = e.getPoint();
178 int colIndex = columnAtPoint(p);
179 int viewRow = rowAtPoint(p);
182 int rowIndex = convertRowIndexToModel(viewRow);
183 Motor motor = model.getMotor(rowIndex);
185 if (colIndex < 0 || colIndex >= MotorColumns.values().length)
188 return MotorColumns.values()[colIndex].getToolTipText(motor);
192 // Set comparators and widths
193 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
194 sorter = new TableRowSorter<TableModel>(model);
195 for (int i=0; i < MotorColumns.values().length; i++) {
196 MotorColumns column = MotorColumns.values()[i];
197 sorter.setComparator(i, column.getComparator());
198 table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
200 table.setRowSorter(sorter);
202 // Set selection and double-click listeners
203 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
205 public void valueChanged(ListSelectionEvent e) {
206 int row = table.getSelectedRow();
208 row = table.convertRowIndexToModel(row);
209 Motor m = model.getMotor(row);
210 if (!m.equals(selectedMotor)) {
211 selectedMotor = model.getMotor(row);
212 setDelays(true); // Reset delay times
217 table.addMouseListener(new MouseAdapter() {
219 public void mouseClicked(MouseEvent e) {
220 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
222 MotorChooserDialog.this.setVisible(false);
226 // (Current selection and scrolling performed later)
228 JScrollPane scrollpane = new JScrollPane();
229 scrollpane.setViewportView(table);
230 panel.add(scrollpane,"spanx, grow, width :700:, height :300:, wrap paragraph");
234 panel.add(new JLabel("Select ejection charge delay:"), "spanx, split 3, gap rel");
236 delayBox = new JComboBox();
237 delayBox.setEditable(true);
238 delayBox.addActionListener(new ActionListener() {
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;
247 selectedDelay = Double.parseDouble(sel);
248 } catch (NumberFormatException ignore) { }
253 panel.add(delayBox,"gapright unrel");
254 panel.add(new ResizeLabel("(Number of seconds or \"None\")", -1), "wrap para");
258 JButton okButton = new JButton("OK");
259 okButton.addActionListener(new ActionListener() {
261 public void actionPerformed(ActionEvent e) {
263 MotorChooserDialog.this.setVisible(false);
266 panel.add(okButton,"spanx, split, tag ok");
268 button = new JButton("Cancel");
269 button.addActionListener(new ActionListener() {
271 public void actionPerformed(ActionEvent e) {
272 MotorChooserDialog.this.setVisible(false);
275 panel.add(button,"tag cancel");
279 int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
280 combo.setSelectedIndex(showMode);
285 // this.setAlwaysOnTop(true);
287 GUIUtil.setDefaultButton(okButton);
288 GUIUtil.installEscapeCloseOperation(this);
289 this.setLocationByPlatform(true);
291 // Table can be scrolled only after pack() has been called
292 setSelectionVisible();
294 // Focus the search field
295 searchField.grabFocus();
298 private void setSelectionVisible() {
299 if (selectedMotor != null) {
300 int index = table.convertRowIndexToView(model.getIndex(selectedMotor));
301 table.getSelectionModel().setSelectionInterval(index, index);
302 Rectangle rect = table.getCellRect(index, 0, true);
303 rect = new Rectangle(rect.x,rect.y-100,rect.width,rect.height+200);
304 table.scrollRectToVisible(rect);
310 * Set the values in the delay combo box. If <code>reset</code> is <code>true</code>
311 * then sets the selected value as the value closest to selectedDelay, otherwise
312 * leaves selection alone.
314 private void setDelays(boolean reset) {
315 if (selectedMotor == null) {
317 delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
318 delayBox.setSelectedIndex(0);
322 double[] delays = selectedMotor.getStandardDelays();
323 String[] delayStrings = new String[delays.length];
324 double currentDelay = selectedDelay; // Store current setting locally
326 for (int i=0; i < delays.length; i++) {
327 delayStrings[i] = Motor.getDelayString(delays[i], "None");
329 delayBox.setModel(new DefaultComboBoxModel(delayStrings));
333 // Find and set the closest value
334 double closest = Double.NaN;
335 for (int i=0; i < delays.length; i++) {
336 // if-condition to always become true for NaN
337 if (!(Math.abs(delays[i] - currentDelay) >
338 Math.abs(closest - currentDelay))) {
342 if (!Double.isNaN(closest)) {
343 selectedDelay = closest;
344 delayBox.setSelectedItem(Motor.getDelayString(closest, "None"));
346 delayBox.setSelectedItem("None");
351 selectedDelay = currentDelay;
352 delayBox.setSelectedItem(Motor.getDelayString(currentDelay, "None"));
361 public Motor getSelectedMotor() {
364 return selectedMotor;
368 public double getSelectedDelay() {
369 return selectedDelay;
375 //////////////// JTable elements ////////////////
379 * Enum defining the table columns.
381 private enum MotorColumns {
382 MANUFACTURER("Manufacturer",100) {
384 public String getValue(Motor m) {
385 return m.getManufacturer();
388 // public String getToolTipText(Motor m) {
389 // return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
392 public Comparator<?> getComparator() {
393 return Collator.getInstance();
396 DESIGNATION("Designation") {
398 public String getValue(Motor m) {
399 return m.getDesignation();
402 // public String getToolTipText(Motor m) {
403 // return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
406 public Comparator<?> getComparator() {
407 return Motor.getDesignationComparator();
412 public String getValue(Motor m) {
413 return m.getMotorType().getName();
416 // public String getToolTipText(Motor m) {
417 // return m.getMotorType().getDescription();
420 public Comparator<?> getComparator() {
421 return Collator.getInstance();
424 DIAMETER("Diameter") {
426 public String getValue(Motor m) {
427 return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
431 public Comparator<?> getComparator() {
432 return getNumericalComparator();
437 public String getValue(Motor m) {
438 return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
442 public Comparator<?> getComparator() {
443 return getNumericalComparator();
448 public String getValue(Motor m) {
449 return UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
450 m.getTotalImpulse());
453 public Comparator<?> getComparator() {
454 return getNumericalComparator();
459 public String getValue(Motor m) {
460 return UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
464 public Comparator<?> getComparator() {
465 return getNumericalComparator();
470 private final String title;
471 private final int width;
473 MotorColumns(String title) {
477 MotorColumns(String title, int width) {
483 public abstract String getValue(Motor m);
484 public abstract Comparator<?> getComparator();
486 public String getTitle() {
490 public int getWidth() {
494 public String getToolTipText(Motor m) {
495 String tip = "<html>";
496 tip += "<b>" + m.toString() + "</b>";
497 tip += " (" + m.getMotorType().getDescription() + ")<br><hr>";
499 String desc = m.getDescription().trim();
500 if (desc.length() > 0) {
501 tip += "<i>" + desc.replace("\n", "<br>") + "</i><br><hr>";
504 tip += ("Diameter: " +
505 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getDiameter()) +
508 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getLength()) +
510 tip += ("Maximum thrust: " +
511 UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getMaxThrust()) +
513 tip += ("Average thrust: " +
514 UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getAverageThrust()) +
516 tip += ("Burn time: " +
517 UnitGroup.UNITS_SHORT_TIME.getDefaultUnit()
518 .toStringUnit(m.getAverageTime()) + "<br>");
519 tip += ("Total impulse: " +
520 UnitGroup.UNITS_IMPULSE.getDefaultUnit()
521 .toStringUnit(m.getTotalImpulse()) + "<br>");
522 tip += ("Launch mass: " +
523 UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getMass(0)) +
525 tip += ("Empty mass: " +
526 UnitGroup.UNITS_MASS.getDefaultUnit()
527 .toStringUnit(m.getMass(Double.MAX_VALUE)));
535 * The JTable model. Includes an extra motor, given in the constructor,
536 * if it is not already in the database.
538 private class MotorDatabaseModel extends AbstractTableModel {
539 private final Motor extra;
541 public MotorDatabaseModel(Motor current) {
542 if (Databases.MOTOR.contains(current))
549 public int getColumnCount() {
550 return MotorColumns.values().length;
554 public int getRowCount() {
556 return Databases.MOTOR.size();
558 return Databases.MOTOR.size()+1;
562 public Object getValueAt(int rowIndex, int columnIndex) {
563 MotorColumns column = getColumn(columnIndex);
565 return column.getValue(Databases.MOTOR.get(rowIndex));
568 return column.getValue(extra);
570 return column.getValue(Databases.MOTOR.get(rowIndex - 1));
575 public String getColumnName(int columnIndex) {
576 return getColumn(columnIndex).getTitle();
580 public Motor getMotor(int rowIndex) {
582 return Databases.MOTOR.get(rowIndex);
587 return Databases.MOTOR.get(rowIndex-1);
591 public int getIndex(Motor m) {
593 return Databases.MOTOR.indexOf(m);
598 return Databases.MOTOR.indexOf(m)+1;
602 private MotorColumns getColumn(int index) {
603 return MotorColumns.values()[index];
611 * Abstract adapter class.
613 private abstract class MotorRowFilter extends RowFilter<TableModel,Integer> {
615 public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
616 int index = entry.getIdentifier();
617 Motor m = model.getMotor(index);
618 return filterByDiameter(m) && filterByString(m);
621 public abstract boolean filterByDiameter(Motor m);
624 public boolean filterByString(Motor m) {
625 main: for (String s : searchTerms) {
626 for (MotorColumns col : MotorColumns.values()) {
627 String str = col.getValue(m).toLowerCase();
628 if (str.indexOf(s) >= 0)
640 private class MotorRowFilterAll extends MotorRowFilter {
642 public boolean filterByDiameter(Motor m) {
648 * Show motors smaller than the mount.
650 private class MotorRowFilterSmaller extends MotorRowFilter {
652 public boolean filterByDiameter(Motor m) {
653 return (m.getDiameter() <= diameter + 0.0004);
658 * Show motors that fit the mount.
660 private class MotorRowFilterExact extends MotorRowFilter {
662 public boolean filterByDiameter(Motor m) {
663 return ((m.getDiameter() <= diameter + 0.0004) &&
664 (m.getDiameter() >= diameter - 0.0015));
669 private static Comparator<String> numericalComparator = null;
670 private static Comparator<String> getNumericalComparator() {
671 if (numericalComparator == null)
672 numericalComparator = new NumericalComparator();
673 return numericalComparator;
676 private static class NumericalComparator implements Comparator<String> {
677 private Pattern pattern =
678 Pattern.compile("^\\s*([0-9]*[.,][0-9]+|[0-9]+[.,]?[0-9]*).*?$");
679 private Collator collator = null;
681 public int compare(String s1, String s2) {
684 m1 = pattern.matcher(s1);
685 m2 = pattern.matcher(s2);
686 if (m1.find() && m2.find()) {
687 double d1 = Double.parseDouble(m1.group(1));
688 double d2 = Double.parseDouble(m2.group(1));
690 return (int)((d1-d2)*1000);
693 if (collator == null)
694 collator = Collator.getInstance(Locale.US);
695 return collator.compare(s1, s2);