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.Comparator;
13 import java.util.Locale;
14 import java.util.regex.Matcher;
15 import java.util.regex.Pattern;
17 import javax.swing.DefaultComboBoxModel;
18 import javax.swing.JButton;
19 import javax.swing.JComboBox;
20 import javax.swing.JDialog;
21 import javax.swing.JLabel;
22 import javax.swing.JPanel;
23 import javax.swing.JScrollPane;
24 import javax.swing.JTable;
25 import javax.swing.ListSelectionModel;
26 import javax.swing.RowFilter;
27 import javax.swing.event.ListSelectionEvent;
28 import javax.swing.event.ListSelectionListener;
29 import javax.swing.table.AbstractTableModel;
30 import javax.swing.table.TableModel;
31 import javax.swing.table.TableRowSorter;
33 import net.miginfocom.swing.MigLayout;
34 import net.sf.openrocket.database.Databases;
35 import net.sf.openrocket.gui.components.ResizeLabel;
36 import net.sf.openrocket.rocketcomponent.Motor;
37 import net.sf.openrocket.unit.UnitGroup;
38 import net.sf.openrocket.util.GUIUtil;
39 import net.sf.openrocket.util.Prefs;
41 public class MotorChooserDialog extends JDialog {
43 private static final int SHOW_ALL = 0;
44 private static final int SHOW_SMALLER = 1;
45 private static final int SHOW_EXACT = 2;
46 private static final String[] SHOW_DESCRIPTIONS = {
48 "Show motors with diameter less than that of the motor mount",
49 "Show motors with diameter equal to that of the motor mount"
51 private static final int SHOW_MAX = 2;
54 private final double diameter;
56 private Motor selectedMotor = null;
57 private double selectedDelay = 0;
60 private TableRowSorter<TableModel> sorter;
61 private JComboBox delayBox;
62 private MotorDatabaseModel model;
64 private boolean okClicked = false;
67 public MotorChooserDialog(double diameter) {
68 this(null,5,diameter,null);
71 public MotorChooserDialog(Motor current, double delay, double diameter) {
72 this(current,delay,diameter,null);
75 public MotorChooserDialog(Motor current, double delay, double diameter, Frame owner) {
76 super(owner, "Select a rocket motor", true);
80 this.selectedMotor = current;
81 this.selectedDelay = delay;
82 this.diameter = diameter;
84 JPanel panel = new JPanel(new MigLayout("fill"));
87 JLabel label = new JLabel("Select a rocket motor:");
88 label.setFont(label.getFont().deriveFont(Font.BOLD));
89 panel.add(label,"split 2, growx");
91 label = new JLabel("Motor mount diameter: " +
92 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
93 panel.add(label,"alignx 100%, wrap paragraph");
97 JComboBox combo = new JComboBox(SHOW_DESCRIPTIONS);
98 combo.addActionListener(new ActionListener() {
100 public void actionPerformed(ActionEvent e) {
101 JComboBox cb = (JComboBox) e.getSource();
102 int sel = cb.getSelectedIndex();
103 if ((sel < 0) || (sel > SHOW_MAX))
107 System.out.println("Setting filter: all");
108 sorter.setRowFilter(new MotorRowFilterAll());
112 System.out.println("Setting filter: smaller");
113 sorter.setRowFilter(new MotorRowFilterSmaller());
117 System.out.println("Setting filter: exact");
118 sorter.setRowFilter(new MotorRowFilterExact());
122 assert(false) : "Should not occur.";
124 Prefs.putChoise("MotorDiameterMatch", sel);
125 setSelectionVisible();
128 panel.add(combo,"growx, wrap");
131 // Table, overridden to show meaningful tooltip texts
132 model = new MotorDatabaseModel(current);
133 table = new JTable(model) {
135 public String getToolTipText(MouseEvent e) {
136 java.awt.Point p = e.getPoint();
137 int colIndex = columnAtPoint(p);
138 int viewRow = rowAtPoint(p);
141 int rowIndex = convertRowIndexToModel(viewRow);
142 Motor motor = model.getMotor(rowIndex);
144 if (colIndex < 0 || colIndex >= MotorColumns.values().length)
147 return MotorColumns.values()[colIndex].getToolTipText(motor);
151 // Set comparators and widths
152 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
153 sorter = new TableRowSorter<TableModel>(model);
154 for (int i=0; i < MotorColumns.values().length; i++) {
155 MotorColumns column = MotorColumns.values()[i];
156 sorter.setComparator(i, column.getComparator());
157 table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
159 table.setRowSorter(sorter);
161 // Set selection and double-click listeners
162 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
164 public void valueChanged(ListSelectionEvent e) {
165 int row = table.getSelectedRow();
167 row = table.convertRowIndexToModel(row);
168 Motor m = model.getMotor(row);
169 if (!m.equals(selectedMotor)) {
170 selectedMotor = model.getMotor(row);
171 setDelays(true); // Reset delay times
176 table.addMouseListener(new MouseAdapter() {
178 public void mouseClicked(MouseEvent e) {
179 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
181 MotorChooserDialog.this.setVisible(false);
185 // (Current selection and scrolling performed later)
187 JScrollPane scrollpane = new JScrollPane();
188 scrollpane.setViewportView(table);
189 panel.add(scrollpane,"grow, width :700:, height :300:, wrap paragraph");
193 panel.add(new JLabel("Select ejection charge delay:"), "split 3, gap rel");
195 delayBox = new JComboBox();
196 delayBox.setEditable(true);
197 delayBox.addActionListener(new ActionListener() {
199 public void actionPerformed(ActionEvent e) {
200 JComboBox cb = (JComboBox) e.getSource();
201 String sel = (String)cb.getSelectedItem();
202 if (sel.equalsIgnoreCase("None")) {
203 selectedDelay = Motor.PLUGGED;
206 selectedDelay = Double.parseDouble(sel);
207 } catch (NumberFormatException ignore) { }
212 panel.add(delayBox,"gapright unrel");
213 panel.add(new ResizeLabel("(Number of seconds or \"None\")", -1), "wrap para");
217 JButton okButton = new JButton("OK");
218 okButton.addActionListener(new ActionListener() {
220 public void actionPerformed(ActionEvent e) {
222 MotorChooserDialog.this.setVisible(false);
225 panel.add(okButton,"split, tag ok");
227 button = new JButton("Cancel");
228 button.addActionListener(new ActionListener() {
230 public void actionPerformed(ActionEvent e) {
231 MotorChooserDialog.this.setVisible(false);
234 panel.add(button,"tag cancel");
238 int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
239 combo.setSelectedIndex(showMode);
244 // this.setAlwaysOnTop(true);
246 GUIUtil.setDefaultButton(okButton);
247 GUIUtil.installEscapeCloseOperation(this);
249 // Table can be scrolled only after pack() has been called
250 setSelectionVisible();
253 private void setSelectionVisible() {
254 if (selectedMotor != null) {
255 int index = table.convertRowIndexToView(model.getIndex(selectedMotor));
256 table.getSelectionModel().setSelectionInterval(index, index);
257 Rectangle rect = table.getCellRect(index, 0, true);
258 rect = new Rectangle(rect.x,rect.y-100,rect.width,rect.height+200);
259 table.scrollRectToVisible(rect);
265 * Set the values in the delay combo box. If <code>reset</code> is <code>true</code>
266 * then sets the selected value as the value closest to selectedDelay, otherwise
267 * leaves selection alone.
269 private void setDelays(boolean reset) {
270 if (selectedMotor == null) {
272 delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
273 delayBox.setSelectedIndex(0);
277 double[] delays = selectedMotor.getStandardDelays();
278 String[] delayStrings = new String[delays.length];
279 double currentDelay = selectedDelay; // Store current setting locally
281 for (int i=0; i < delays.length; i++) {
282 delayStrings[i] = Motor.getDelayString(delays[i], "None");
284 delayBox.setModel(new DefaultComboBoxModel(delayStrings));
288 // Find and set the closest value
289 double closest = Double.NaN;
290 for (int i=0; i < delays.length; i++) {
291 // if-condition to always become true for NaN
292 if (!(Math.abs(delays[i] - currentDelay) >
293 Math.abs(closest - currentDelay))) {
297 if (!Double.isNaN(closest)) {
298 selectedDelay = closest;
299 delayBox.setSelectedItem(Motor.getDelayString(closest, "None"));
301 delayBox.setSelectedItem("None");
306 selectedDelay = currentDelay;
307 delayBox.setSelectedItem(Motor.getDelayString(currentDelay, "None"));
316 public Motor getSelectedMotor() {
319 return selectedMotor;
323 public double getSelectedDelay() {
324 return selectedDelay;
330 //////////////// JTable elements ////////////////
334 * Enum defining the table columns.
336 private enum MotorColumns {
337 MANUFACTURER("Manufacturer",100) {
339 public String getValue(Motor m) {
340 return m.getManufacturer();
343 // public String getToolTipText(Motor m) {
344 // return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
347 public Comparator<?> getComparator() {
348 return Collator.getInstance();
351 DESIGNATION("Designation") {
353 public String getValue(Motor m) {
354 return m.getDesignation();
357 // public String getToolTipText(Motor m) {
358 // return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
361 public Comparator<?> getComparator() {
362 return Motor.getDesignationComparator();
367 public String getValue(Motor m) {
368 return m.getMotorType().getName();
371 // public String getToolTipText(Motor m) {
372 // return m.getMotorType().getDescription();
375 public Comparator<?> getComparator() {
376 return Collator.getInstance();
379 DIAMETER("Diameter") {
381 public String getValue(Motor m) {
382 return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
386 public Comparator<?> getComparator() {
387 return getNumericalComparator();
392 public String getValue(Motor m) {
393 return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
397 public Comparator<?> getComparator() {
398 return getNumericalComparator();
403 public String getValue(Motor m) {
404 return UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
405 m.getTotalImpulse());
408 public Comparator<?> getComparator() {
409 return getNumericalComparator();
414 public String getValue(Motor m) {
415 return UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
419 public Comparator<?> getComparator() {
420 return getNumericalComparator();
425 private final String title;
426 private final int width;
428 MotorColumns(String title) {
432 MotorColumns(String title, int width) {
438 public abstract String getValue(Motor m);
439 public abstract Comparator<?> getComparator();
441 public String getTitle() {
445 public int getWidth() {
449 public String getToolTipText(Motor m) {
450 String tip = "<html>";
451 tip += "<b>" + m.toString() + "</b>";
452 tip += " (" + m.getMotorType().getDescription() + ")<br><hr>";
454 String desc = m.getDescription().trim();
455 if (desc.length() > 0) {
456 tip += "<i>" + desc.replace("\n", "<br>") + "</i><br><hr>";
459 tip += ("Diameter: " +
460 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getDiameter()) +
463 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getLength()) +
465 tip += ("Maximum thrust: " +
466 UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getMaxThrust()) +
468 tip += ("Average thrust: " +
469 UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getAverageThrust()) +
471 tip += ("Burn time: " +
472 UnitGroup.UNITS_SHORT_TIME.getDefaultUnit()
473 .toStringUnit(m.getAverageTime()) + "<br>");
474 tip += ("Total impulse: " +
475 UnitGroup.UNITS_IMPULSE.getDefaultUnit()
476 .toStringUnit(m.getTotalImpulse()) + "<br>");
477 tip += ("Launch mass: " +
478 UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getMass(0)) +
480 tip += ("Empty mass: " +
481 UnitGroup.UNITS_MASS.getDefaultUnit()
482 .toStringUnit(m.getMass(Double.MAX_VALUE)));
490 * The JTable model. Includes an extra motor, given in the constructor,
491 * if it is not already in the database.
493 private class MotorDatabaseModel extends AbstractTableModel {
494 private final Motor extra;
496 public MotorDatabaseModel(Motor current) {
497 if (Databases.MOTOR.contains(current))
504 public int getColumnCount() {
505 return MotorColumns.values().length;
509 public int getRowCount() {
511 return Databases.MOTOR.size();
513 return Databases.MOTOR.size()+1;
517 public Object getValueAt(int rowIndex, int columnIndex) {
518 MotorColumns column = getColumn(columnIndex);
520 return column.getValue(Databases.MOTOR.get(rowIndex));
523 return column.getValue(extra);
525 return column.getValue(Databases.MOTOR.get(rowIndex - 1));
530 public String getColumnName(int columnIndex) {
531 return getColumn(columnIndex).getTitle();
535 public Motor getMotor(int rowIndex) {
537 return Databases.MOTOR.get(rowIndex);
542 return Databases.MOTOR.get(rowIndex-1);
546 public int getIndex(Motor m) {
548 return Databases.MOTOR.indexOf(m);
553 return Databases.MOTOR.indexOf(m)+1;
557 private MotorColumns getColumn(int index) {
558 return MotorColumns.values()[index];
566 * Abstract adapter class.
568 private abstract class MotorRowFilter extends RowFilter<TableModel,Integer> {
570 public boolean include(
571 RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
572 int index = entry.getIdentifier();
573 Motor m = model.getMotor(index);
577 public abstract boolean include(Motor m);
583 private class MotorRowFilterAll extends MotorRowFilter {
585 public boolean include(Motor m) {
591 * Show motors smaller than the mount.
593 private class MotorRowFilterSmaller extends MotorRowFilter {
595 public boolean include(Motor m) {
596 return (m.getDiameter() <= diameter + 0.0004);
601 * Show motors that fit the mount.
603 private class MotorRowFilterExact extends MotorRowFilter {
605 public boolean include(Motor m) {
606 return ((m.getDiameter() <= diameter + 0.0004) &&
607 (m.getDiameter() >= diameter - 0.0015));
612 private static Comparator<String> numericalComparator = null;
613 private static Comparator<String> getNumericalComparator() {
614 if (numericalComparator == null)
615 numericalComparator = new NumericalComparator();
616 return numericalComparator;
619 private static class NumericalComparator implements Comparator<String> {
620 private Pattern pattern =
621 Pattern.compile("^\\s*([0-9]*[.,][0-9]+|[0-9]+[.,]?[0-9]*).*?$");
622 private Collator collator = null;
624 public int compare(String s1, String s2) {
627 m1 = pattern.matcher(s1);
628 m2 = pattern.matcher(s2);
629 if (m1.find() && m2.find()) {
630 double d1 = Double.parseDouble(m1.group(1));
631 double d2 = Double.parseDouble(m2.group(1));
633 return (int)((d1-d2)*1000);
636 if (collator == null)
637 collator = Collator.getInstance(Locale.US);
638 return collator.compare(s1, s2);