1 package net.sf.openrocket.gui.main;
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.ArrayList;
13 import java.util.Comparator;
14 import java.util.Locale;
15 import java.util.regex.Matcher;
16 import java.util.regex.Pattern;
18 import javax.swing.DefaultComboBoxModel;
19 import javax.swing.JButton;
20 import javax.swing.JComboBox;
21 import javax.swing.JDialog;
22 import javax.swing.JLabel;
23 import javax.swing.JPanel;
24 import javax.swing.JScrollPane;
25 import javax.swing.JTable;
26 import javax.swing.JTextField;
27 import javax.swing.ListSelectionModel;
28 import javax.swing.RowFilter;
29 import javax.swing.event.DocumentEvent;
30 import javax.swing.event.DocumentListener;
31 import javax.swing.event.ListSelectionEvent;
32 import javax.swing.event.ListSelectionListener;
33 import javax.swing.table.AbstractTableModel;
34 import javax.swing.table.TableModel;
35 import javax.swing.table.TableRowSorter;
37 import net.miginfocom.swing.MigLayout;
38 import net.sf.openrocket.database.Databases;
39 import net.sf.openrocket.gui.components.ResizeLabel;
40 import net.sf.openrocket.rocketcomponent.Motor;
41 import net.sf.openrocket.unit.UnitGroup;
42 import net.sf.openrocket.util.GUIUtil;
43 import net.sf.openrocket.util.Prefs;
45 public class MotorChooserDialog extends JDialog {
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 = {
52 "Show motors with diameter less than that of the motor mount",
53 "Show motors with diameter equal to that of the motor mount"
55 private static final int SHOW_MAX = 2;
57 private final JTextField searchField;
58 private String[] searchTerms = new String[0];
60 private final double diameter;
62 private Motor selectedMotor = null;
63 private double selectedDelay = 0;
66 private TableRowSorter<TableModel> sorter;
67 private JComboBox delayBox;
68 private MotorDatabaseModel model;
70 private boolean okClicked = false;
73 public MotorChooserDialog(double diameter) {
74 this(null,5,diameter,null);
77 public MotorChooserDialog(Motor current, double delay, double diameter) {
78 this(current,delay,diameter,null);
81 public MotorChooserDialog(Motor current, double delay, double diameter, Frame owner) {
82 super(owner, "Select a rocket motor", true);
86 this.selectedMotor = current;
87 this.selectedDelay = delay;
88 this.diameter = diameter;
90 JPanel panel = new JPanel(new MigLayout("fill", "[grow][]"));
93 JLabel label = new JLabel("Select a rocket motor:");
94 label.setFont(label.getFont().deriveFont(Font.BOLD));
95 panel.add(label,"growx");
97 label = new JLabel("Motor mount diameter: " +
98 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
99 panel.add(label,"gapleft para, wrap paragraph");
102 // Diameter selection
103 JComboBox combo = new JComboBox(SHOW_DESCRIPTIONS);
104 combo.addActionListener(new ActionListener() {
106 public void actionPerformed(ActionEvent e) {
107 JComboBox cb = (JComboBox) e.getSource();
108 int sel = cb.getSelectedIndex();
109 if ((sel < 0) || (sel > SHOW_MAX))
113 sorter.setRowFilter(new MotorRowFilterAll());
117 sorter.setRowFilter(new MotorRowFilterSmaller());
121 sorter.setRowFilter(new MotorRowFilterExact());
125 assert(false) : "Should not occur.";
127 Prefs.putChoise("MotorDiameterMatch", sel);
128 setSelectionVisible();
131 panel.add(combo,"growx 1000");
135 label = new JLabel("Search:");
136 panel.add(label, "gapleft para, split 2");
138 searchField = new JTextField();
139 searchField.getDocument().addDocumentListener(new DocumentListener() {
141 public void changedUpdate(DocumentEvent e) {
145 public void insertUpdate(DocumentEvent e) {
149 public void removeUpdate(DocumentEvent e) {
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) {
163 searchTerms = list.toArray(new String[0]);
167 panel.add(searchField, "growx 1, wrap");
171 // Table, overridden to show meaningful tooltip texts
172 model = new MotorDatabaseModel(current);
173 table = new JTable(model) {
175 public String getToolTipText(MouseEvent e) {
176 java.awt.Point p = e.getPoint();
177 int colIndex = columnAtPoint(p);
178 int viewRow = rowAtPoint(p);
181 int rowIndex = convertRowIndexToModel(viewRow);
182 Motor motor = model.getMotor(rowIndex);
184 if (colIndex < 0 || colIndex >= MotorColumns.values().length)
187 return MotorColumns.values()[colIndex].getToolTipText(motor);
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());
199 table.setRowSorter(sorter);
201 // Set selection and double-click listeners
202 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
204 public void valueChanged(ListSelectionEvent e) {
205 int row = table.getSelectedRow();
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
216 table.addMouseListener(new MouseAdapter() {
218 public void mouseClicked(MouseEvent e) {
219 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
221 MotorChooserDialog.this.setVisible(false);
225 // (Current selection and scrolling performed later)
227 JScrollPane scrollpane = new JScrollPane();
228 scrollpane.setViewportView(table);
229 panel.add(scrollpane,"spanx, grow, width :700:, height :300:, wrap paragraph");
233 panel.add(new JLabel("Select ejection charge delay:"), "spanx, split 3, gap rel");
235 delayBox = new JComboBox();
236 delayBox.setEditable(true);
237 delayBox.addActionListener(new ActionListener() {
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;
246 selectedDelay = Double.parseDouble(sel);
247 } catch (NumberFormatException ignore) { }
252 panel.add(delayBox,"gapright unrel");
253 panel.add(new ResizeLabel("(Number of seconds or \"None\")", -1), "wrap para");
257 JButton okButton = new JButton("OK");
258 okButton.addActionListener(new ActionListener() {
260 public void actionPerformed(ActionEvent e) {
262 MotorChooserDialog.this.setVisible(false);
265 panel.add(okButton,"spanx, split, tag ok");
267 button = new JButton("Cancel");
268 button.addActionListener(new ActionListener() {
270 public void actionPerformed(ActionEvent e) {
271 MotorChooserDialog.this.setVisible(false);
274 panel.add(button,"tag cancel");
278 int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
279 combo.setSelectedIndex(showMode);
284 // this.setAlwaysOnTop(true);
286 GUIUtil.setDefaultButton(okButton);
287 GUIUtil.installEscapeCloseOperation(this);
289 // Table can be scrolled only after pack() has been called
290 setSelectionVisible();
292 // Focus the search field
293 searchField.grabFocus();
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);
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.
312 private void setDelays(boolean reset) {
313 if (selectedMotor == null) {
315 delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
316 delayBox.setSelectedIndex(0);
320 double[] delays = selectedMotor.getStandardDelays();
321 String[] delayStrings = new String[delays.length];
322 double currentDelay = selectedDelay; // Store current setting locally
324 for (int i=0; i < delays.length; i++) {
325 delayStrings[i] = Motor.getDelayString(delays[i], "None");
327 delayBox.setModel(new DefaultComboBoxModel(delayStrings));
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))) {
340 if (!Double.isNaN(closest)) {
341 selectedDelay = closest;
342 delayBox.setSelectedItem(Motor.getDelayString(closest, "None"));
344 delayBox.setSelectedItem("None");
349 selectedDelay = currentDelay;
350 delayBox.setSelectedItem(Motor.getDelayString(currentDelay, "None"));
359 public Motor getSelectedMotor() {
362 return selectedMotor;
366 public double getSelectedDelay() {
367 return selectedDelay;
373 //////////////// JTable elements ////////////////
377 * Enum defining the table columns.
379 private enum MotorColumns {
380 MANUFACTURER("Manufacturer",100) {
382 public String getValue(Motor m) {
383 return m.getManufacturer();
386 // public String getToolTipText(Motor m) {
387 // return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
390 public Comparator<?> getComparator() {
391 return Collator.getInstance();
394 DESIGNATION("Designation") {
396 public String getValue(Motor m) {
397 return m.getDesignation();
400 // public String getToolTipText(Motor m) {
401 // return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
404 public Comparator<?> getComparator() {
405 return Motor.getDesignationComparator();
410 public String getValue(Motor m) {
411 return m.getMotorType().getName();
414 // public String getToolTipText(Motor m) {
415 // return m.getMotorType().getDescription();
418 public Comparator<?> getComparator() {
419 return Collator.getInstance();
422 DIAMETER("Diameter") {
424 public String getValue(Motor m) {
425 return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
429 public Comparator<?> getComparator() {
430 return getNumericalComparator();
435 public String getValue(Motor m) {
436 return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
440 public Comparator<?> getComparator() {
441 return getNumericalComparator();
446 public String getValue(Motor m) {
447 return UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
448 m.getTotalImpulse());
451 public Comparator<?> getComparator() {
452 return getNumericalComparator();
457 public String getValue(Motor m) {
458 return UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
462 public Comparator<?> getComparator() {
463 return getNumericalComparator();
468 private final String title;
469 private final int width;
471 MotorColumns(String title) {
475 MotorColumns(String title, int width) {
481 public abstract String getValue(Motor m);
482 public abstract Comparator<?> getComparator();
484 public String getTitle() {
488 public int getWidth() {
492 public String getToolTipText(Motor m) {
493 String tip = "<html>";
494 tip += "<b>" + m.toString() + "</b>";
495 tip += " (" + m.getMotorType().getDescription() + ")<br><hr>";
497 String desc = m.getDescription().trim();
498 if (desc.length() > 0) {
499 tip += "<i>" + desc.replace("\n", "<br>") + "</i><br><hr>";
502 tip += ("Diameter: " +
503 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getDiameter()) +
506 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getLength()) +
508 tip += ("Maximum thrust: " +
509 UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getMaxThrust()) +
511 tip += ("Average thrust: " +
512 UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getAverageThrust()) +
514 tip += ("Burn time: " +
515 UnitGroup.UNITS_SHORT_TIME.getDefaultUnit()
516 .toStringUnit(m.getAverageTime()) + "<br>");
517 tip += ("Total impulse: " +
518 UnitGroup.UNITS_IMPULSE.getDefaultUnit()
519 .toStringUnit(m.getTotalImpulse()) + "<br>");
520 tip += ("Launch mass: " +
521 UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getMass(0)) +
523 tip += ("Empty mass: " +
524 UnitGroup.UNITS_MASS.getDefaultUnit()
525 .toStringUnit(m.getMass(Double.MAX_VALUE)));
533 * The JTable model. Includes an extra motor, given in the constructor,
534 * if it is not already in the database.
536 private class MotorDatabaseModel extends AbstractTableModel {
537 private final Motor extra;
539 public MotorDatabaseModel(Motor current) {
540 if (Databases.MOTOR.contains(current))
547 public int getColumnCount() {
548 return MotorColumns.values().length;
552 public int getRowCount() {
554 return Databases.MOTOR.size();
556 return Databases.MOTOR.size()+1;
560 public Object getValueAt(int rowIndex, int columnIndex) {
561 MotorColumns column = getColumn(columnIndex);
563 return column.getValue(Databases.MOTOR.get(rowIndex));
566 return column.getValue(extra);
568 return column.getValue(Databases.MOTOR.get(rowIndex - 1));
573 public String getColumnName(int columnIndex) {
574 return getColumn(columnIndex).getTitle();
578 public Motor getMotor(int rowIndex) {
580 return Databases.MOTOR.get(rowIndex);
585 return Databases.MOTOR.get(rowIndex-1);
589 public int getIndex(Motor m) {
591 return Databases.MOTOR.indexOf(m);
596 return Databases.MOTOR.indexOf(m)+1;
600 private MotorColumns getColumn(int index) {
601 return MotorColumns.values()[index];
609 * Abstract adapter class.
611 private abstract class MotorRowFilter extends RowFilter<TableModel,Integer> {
613 public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
614 int index = entry.getIdentifier();
615 Motor m = model.getMotor(index);
616 return filterByDiameter(m) && filterByString(m);
619 public abstract boolean filterByDiameter(Motor m);
622 public boolean filterByString(Motor m) {
623 main: for (String s : searchTerms) {
624 for (MotorColumns col : MotorColumns.values()) {
625 String str = col.getValue(m).toLowerCase();
626 if (str.indexOf(s) >= 0)
638 private class MotorRowFilterAll extends MotorRowFilter {
640 public boolean filterByDiameter(Motor m) {
646 * Show motors smaller than the mount.
648 private class MotorRowFilterSmaller extends MotorRowFilter {
650 public boolean filterByDiameter(Motor m) {
651 return (m.getDiameter() <= diameter + 0.0004);
656 * Show motors that fit the mount.
658 private class MotorRowFilterExact extends MotorRowFilter {
660 public boolean filterByDiameter(Motor m) {
661 return ((m.getDiameter() <= diameter + 0.0004) &&
662 (m.getDiameter() >= diameter - 0.0015));
667 private static Comparator<String> numericalComparator = null;
668 private static Comparator<String> getNumericalComparator() {
669 if (numericalComparator == null)
670 numericalComparator = new NumericalComparator();
671 return numericalComparator;
674 private static class NumericalComparator implements Comparator<String> {
675 private Pattern pattern =
676 Pattern.compile("^\\s*([0-9]*[.,][0-9]+|[0-9]+[.,]?[0-9]*).*?$");
677 private Collator collator = null;
679 public int compare(String s1, String s2) {
682 m1 = pattern.matcher(s1);
683 m2 = pattern.matcher(s2);
684 if (m1.find() && m2.find()) {
685 double d1 = Double.parseDouble(m1.group(1));
686 double d2 = Double.parseDouble(m2.group(1));
688 return (int)((d1-d2)*1000);
691 if (collator == null)
692 collator = Collator.getInstance(Locale.US);
693 return collator.compare(s1, s2);