updates for 0.9.3
[debian/openrocket] / src / net / sf / openrocket / gui / dialogs / MotorChooserDialog.java
diff --git a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java
new file mode 100644 (file)
index 0000000..3a5a154
--- /dev/null
@@ -0,0 +1,699 @@
+package net.sf.openrocket.gui.dialogs;
+
+
+import java.awt.Dialog;
+import java.awt.Font;
+import java.awt.Rectangle;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.RowFilter;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableModel;
+import javax.swing.table.TableRowSorter;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.database.Databases;
+import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.rocketcomponent.Motor;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Prefs;
+
+public class MotorChooserDialog extends JDialog {
+       
+       private static final int SHOW_ALL = 0;
+       private static final int SHOW_SMALLER = 1;
+       private static final int SHOW_EXACT = 2;
+       private static final String[] SHOW_DESCRIPTIONS = {
+               "Show all motors",
+               "Show motors with diameter less than that of the motor mount",
+               "Show motors with diameter equal to that of the motor mount"
+       };
+       private static final int SHOW_MAX = 2;
+
+       private final JTextField searchField; 
+       private String[] searchTerms = new String[0];
+
+       private final double diameter;
+
+       private Motor selectedMotor = null;
+       private double selectedDelay = 0;
+
+       private JTable table;
+       private TableRowSorter<TableModel> sorter;
+       private JComboBox delayBox;
+       private MotorDatabaseModel model;
+       
+       private boolean okClicked = false;
+
+       
+       public MotorChooserDialog(double diameter) {
+               this(null,5,diameter,null);
+       }
+       
+       public MotorChooserDialog(Motor current, double delay, double diameter) {
+               this(current,delay,diameter,null);
+       }
+       
+       public MotorChooserDialog(Motor current, double delay, double diameter, Window owner) {
+               super(owner, "Select a rocket motor", Dialog.ModalityType.APPLICATION_MODAL);
+               
+               JButton button;
+
+               this.selectedMotor = current;
+               this.selectedDelay = delay;
+               this.diameter = diameter;
+               
+               JPanel panel = new JPanel(new MigLayout("fill", "[grow][]"));
+
+               // Label
+               JLabel label = new JLabel("Select a rocket motor:");
+               label.setFont(label.getFont().deriveFont(Font.BOLD));
+               panel.add(label,"growx");
+               
+               label = new JLabel("Motor mount diameter: " +
+                               UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
+               panel.add(label,"gapleft para, wrap paragraph");
+               
+               
+               // Diameter selection
+               JComboBox combo = new JComboBox(SHOW_DESCRIPTIONS);
+               combo.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               JComboBox cb = (JComboBox) e.getSource();
+                               int sel = cb.getSelectedIndex();
+                               if ((sel < 0) || (sel > SHOW_MAX))
+                                       sel = SHOW_ALL;
+                               switch (sel) {
+                               case SHOW_ALL:
+                                       sorter.setRowFilter(new MotorRowFilterAll());
+                                       break;
+                                       
+                               case SHOW_SMALLER:
+                                       sorter.setRowFilter(new MotorRowFilterSmaller());
+                                       break;
+                                       
+                               case SHOW_EXACT:
+                                       sorter.setRowFilter(new MotorRowFilterExact());
+                                       break;
+                                       
+                               default:
+                                       assert(false) : "Should not occur.";    
+                               }
+                               Prefs.putChoise("MotorDiameterMatch", sel);
+                               setSelectionVisible();
+                       }
+               });
+               panel.add(combo,"growx 1000");
+
+               
+               
+               label = new JLabel("Search:");
+               panel.add(label, "gapleft para, split 2");
+               
+               searchField = new JTextField();
+               searchField.getDocument().addDocumentListener(new DocumentListener() {
+                       @Override
+                       public void changedUpdate(DocumentEvent e) {
+                               update();
+                       }
+                       @Override
+                       public void insertUpdate(DocumentEvent e) {
+                               update();
+                       }
+                       @Override
+                       public void removeUpdate(DocumentEvent e) {
+                               update();
+                       }
+                       
+                       private void update() {
+                               String text = searchField.getText().trim();
+                               String[] split = text.split("\\s+");
+                               ArrayList<String> list = new ArrayList<String>();
+                               for (String s: split) {
+                                       s = s.trim().toLowerCase();
+                                       if (s.length() > 0) {
+                                               list.add(s);
+                                       }
+                               }
+                               searchTerms = list.toArray(new String[0]);
+                               sorter.sort();
+                       }
+               });
+               panel.add(searchField, "growx 1, wrap");
+               
+               
+               
+               // Table, overridden to show meaningful tooltip texts
+               model = new MotorDatabaseModel(current);
+               table = new JTable(model) {
+                       @Override
+                       public String getToolTipText(MouseEvent e) {
+                       java.awt.Point p = e.getPoint();
+                       int colIndex = columnAtPoint(p);
+                       int viewRow = rowAtPoint(p);
+                       if (viewRow < 0)
+                               return null;
+                       int rowIndex = convertRowIndexToModel(viewRow);
+                       Motor motor = model.getMotor(rowIndex);
+
+                       if (colIndex < 0 || colIndex >= MotorColumns.values().length)
+                               return null;
+
+                       return MotorColumns.values()[colIndex].getToolTipText(motor);
+                       }
+               };
+               
+               // Set comparators and widths
+               table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               sorter = new TableRowSorter<TableModel>(model);
+               for (int i=0; i < MotorColumns.values().length; i++) {
+                       MotorColumns column = MotorColumns.values()[i];
+                       sorter.setComparator(i, column.getComparator());
+                       table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
+               }
+               table.setRowSorter(sorter);
+
+               // Set selection and double-click listeners
+               table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+                       @Override
+                       public void valueChanged(ListSelectionEvent e) {
+                               int row = table.getSelectedRow();
+                               if (row >= 0) {
+                                       row = table.convertRowIndexToModel(row);
+                                       Motor m = model.getMotor(row);
+                                       if (!m.equals(selectedMotor)) {
+                                               selectedMotor = model.getMotor(row);
+                                               setDelays(true);  // Reset delay times
+                                       }
+                               }
+                       }
+               });
+               table.addMouseListener(new MouseAdapter() {
+                       @Override
+                       public void mouseClicked(MouseEvent e) {
+                               if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
+                                       okClicked = true;
+                                       MotorChooserDialog.this.setVisible(false);
+                               }
+                       }
+               });
+               // (Current selection and scrolling performed later)
+               
+               JScrollPane scrollpane = new JScrollPane();
+               scrollpane.setViewportView(table);
+               panel.add(scrollpane,"spanx, grow, width :700:, height :300:, wrap paragraph");
+               
+               
+               // Ejection delay
+               panel.add(new JLabel("Select ejection charge delay:"), "spanx, split 3, gap rel");
+               
+               delayBox = new JComboBox();
+               delayBox.setEditable(true);
+               delayBox.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               JComboBox cb = (JComboBox) e.getSource();
+                               String sel = (String)cb.getSelectedItem();
+                               if (sel.equalsIgnoreCase("None")) {
+                                       selectedDelay = Motor.PLUGGED;
+                               } else {
+                                       try {
+                                               selectedDelay = Double.parseDouble(sel);
+                                       } catch (NumberFormatException ignore) { }
+                               }
+                               setDelays(false);
+                       }
+               });
+               panel.add(delayBox,"gapright unrel");
+               panel.add(new ResizeLabel("(Number of seconds or \"None\")", -1), "wrap para");
+               setDelays(false);
+               
+               
+               JButton okButton = new JButton("OK");
+               okButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               okClicked = true;
+                               MotorChooserDialog.this.setVisible(false);
+                       }
+               });
+               panel.add(okButton,"spanx, split, tag ok");
+
+               button = new JButton("Cancel");
+               button.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               MotorChooserDialog.this.setVisible(false);
+                       }
+               });
+               panel.add(button,"tag cancel");
+
+                               
+               // Sets the filter:
+               int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
+               combo.setSelectedIndex(showMode);
+               
+               
+               this.add(panel);
+               this.pack();
+//             this.setAlwaysOnTop(true);
+
+               GUIUtil.setDefaultButton(okButton);
+               GUIUtil.installEscapeCloseOperation(this);
+               this.setLocationByPlatform(true);
+               
+               // Table can be scrolled only after pack() has been called
+               setSelectionVisible();
+               
+               // Focus the search field
+               searchField.grabFocus();
+       }
+       
+       private void setSelectionVisible() {
+               if (selectedMotor != null) {
+                       int index = table.convertRowIndexToView(model.getIndex(selectedMotor));
+                       table.getSelectionModel().setSelectionInterval(index, index);
+                       Rectangle rect = table.getCellRect(index, 0, true);
+                       rect = new Rectangle(rect.x,rect.y-100,rect.width,rect.height+200);
+                       table.scrollRectToVisible(rect);
+               }
+       }
+       
+       
+       /**
+        * Set the values in the delay combo box.  If <code>reset</code> is <code>true</code>
+        * then sets the selected value as the value closest to selectedDelay, otherwise
+        * leaves selection alone.
+        */
+       private void setDelays(boolean reset) {
+               if (selectedMotor == null) {
+                       
+                       delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
+                       delayBox.setSelectedIndex(0);
+                       
+               } else {
+                       
+                       double[] delays = selectedMotor.getStandardDelays();
+                       String[] delayStrings = new String[delays.length];
+                       double currentDelay = selectedDelay;  // Store current setting locally
+                       
+                       for (int i=0; i < delays.length; i++) {
+                               delayStrings[i] = Motor.getDelayString(delays[i], "None");
+                       }
+                       delayBox.setModel(new DefaultComboBoxModel(delayStrings));
+                       
+                       if (reset) {
+
+                               // Find and set the closest value
+                               double closest = Double.NaN;
+                               for (int i=0; i < delays.length; i++) {
+                                       // if-condition to always become true for NaN
+                                       if (!(Math.abs(delays[i] - currentDelay) > 
+                                                 Math.abs(closest - currentDelay))) {
+                                               closest = delays[i];
+                                       }
+                               }
+                               if (!Double.isNaN(closest)) {
+                                       selectedDelay = closest;
+                                       delayBox.setSelectedItem(Motor.getDelayString(closest, "None"));
+                               } else {
+                                       delayBox.setSelectedItem("None");
+                               }
+
+                       } else {
+                               
+                               selectedDelay = currentDelay;
+                               delayBox.setSelectedItem(Motor.getDelayString(currentDelay, "None"));
+                               
+                       }
+                       
+               }
+       }
+
+       
+       
+       public Motor getSelectedMotor() {
+               if (!okClicked)
+                       return null;
+               return selectedMotor;
+       }
+       
+       
+       public double getSelectedDelay() {
+               return selectedDelay;
+       }
+       
+
+       
+       
+       ////////////////  JTable elements  ////////////////
+       
+       
+       /**
+        * Enum defining the table columns.
+        */
+       private enum MotorColumns {
+               MANUFACTURER("Manufacturer",100) {
+                       @Override
+                       public String getValue(Motor m) {
+                               return m.getManufacturer();
+                       }
+//                     @Override
+//                     public String getToolTipText(Motor m) {
+//                             return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
+//                     }
+                       @Override
+                       public Comparator<?> getComparator() {
+                               return Collator.getInstance();
+                       }
+               },
+               DESIGNATION("Designation") {
+                       @Override
+                       public String getValue(Motor m) {
+                               return m.getDesignation();
+                       }
+//                     @Override
+//                     public String getToolTipText(Motor m) {
+//                             return "<html>" + m.getDescription().replace((CharSequence)"\n", "<br>");
+//                     }
+                       @Override
+                       public Comparator<?> getComparator() {
+                               return Motor.getDesignationComparator();
+                       }
+               },
+               TYPE("Type") {
+                       @Override
+                       public String getValue(Motor m) {
+                               return m.getMotorType().getName();
+                       }
+//                     @Override
+//                     public String getToolTipText(Motor m) {
+//                             return m.getMotorType().getDescription();
+//                     }
+                       @Override
+                       public Comparator<?> getComparator() {
+                               return Collator.getInstance();
+                       }
+               },
+               DIAMETER("Diameter") {
+                       @Override
+                       public String getValue(Motor m) {
+                               return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
+                                               m.getDiameter());
+                       }
+                       @Override
+                       public Comparator<?> getComparator() {
+                               return getNumericalComparator();
+                       }
+               },
+               LENGTH("Length") {
+                       @Override
+                       public String getValue(Motor m) {
+                               return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(
+                                               m.getLength());
+                       }
+                       @Override
+                       public Comparator<?> getComparator() {
+                               return getNumericalComparator();
+                       }
+               },
+               IMPULSE("Impulse") {
+                       @Override
+                       public String getValue(Motor m) {
+                               return UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
+                                               m.getTotalImpulse());
+                       }
+                       @Override
+                       public Comparator<?> getComparator() {
+                               return getNumericalComparator();
+                       }
+               },
+               TIME("Burn time") {
+                       @Override
+                       public String getValue(Motor m) {
+                               return UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
+                                               m.getAverageTime());
+                       }
+                       @Override
+                       public Comparator<?> getComparator() {
+                               return getNumericalComparator();
+                       }
+               };
+               
+               
+               private final String title;
+               private final int width;
+               
+               MotorColumns(String title) {
+                       this(title, 50);
+               }
+               
+               MotorColumns(String title, int width) {
+                       this.title = title;
+                       this.width = width;
+               }
+               
+               
+               public abstract String getValue(Motor m);
+               public abstract Comparator<?> getComparator();
+
+               public String getTitle() {
+                       return title;
+               }
+               
+               public int getWidth() {
+                       return width;
+               }
+               
+               public String getToolTipText(Motor m) {
+                       String tip = "<html>";
+                       tip += "<b>" + m.toString() + "</b>";
+                       tip += " (" + m.getMotorType().getDescription() + ")<br><hr>";
+                       
+                       String desc = m.getDescription().trim();
+                       if (desc.length() > 0) {
+                               tip += "<i>" + desc.replace("\n", "<br>") + "</i><br><hr>";
+                       }
+                       
+                       tip += ("Diameter: " + 
+                                       UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getDiameter()) +
+                                       "<br>");
+                       tip += ("Length: " + 
+                                       UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(m.getLength()) +
+                                       "<br>");
+                       tip += ("Maximum thrust: " + 
+                                       UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getMaxThrust()) +
+                                       "<br>");
+                       tip += ("Average thrust: " + 
+                                       UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(m.getAverageThrust()) +
+                                       "<br>");
+                       tip += ("Burn time: " + 
+                                       UnitGroup.UNITS_SHORT_TIME.getDefaultUnit()
+                                       .toStringUnit(m.getAverageTime()) + "<br>");
+                       tip += ("Total impulse: " +
+                                       UnitGroup.UNITS_IMPULSE.getDefaultUnit()
+                                       .toStringUnit(m.getTotalImpulse()) + "<br>");
+                       tip += ("Launch mass: " + 
+                                       UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getMass(0)) +
+                                       "<br>");
+                       tip += ("Empty mass: " + 
+                                       UnitGroup.UNITS_MASS.getDefaultUnit()
+                                       .toStringUnit(m.getMass(Double.MAX_VALUE)));
+                       return tip;
+               }
+               
+       }
+       
+       
+       /**
+        * The JTable model.  Includes an extra motor, given in the constructor,
+        * if it is not already in the database.
+        */
+       private class MotorDatabaseModel extends AbstractTableModel {
+               private final Motor extra;
+               
+               public MotorDatabaseModel(Motor current) {
+                       if (Databases.MOTOR.contains(current))
+                               extra = null;
+                       else
+                               extra = current;
+               }
+               
+               @Override
+               public int getColumnCount() {
+                       return MotorColumns.values().length;
+               }
+
+               @Override
+               public int getRowCount() {
+                       if (extra == null)
+                               return Databases.MOTOR.size();
+                       else
+                               return Databases.MOTOR.size()+1;
+               }
+
+               @Override
+               public Object getValueAt(int rowIndex, int columnIndex) {
+                       MotorColumns column = getColumn(columnIndex);
+                       if (extra == null) {
+                               return column.getValue(Databases.MOTOR.get(rowIndex));
+                       } else {
+                               if (rowIndex == 0)
+                                       return column.getValue(extra);
+                               else
+                                       return column.getValue(Databases.MOTOR.get(rowIndex - 1));
+                       }
+               }
+               
+               @Override
+               public String getColumnName(int columnIndex) {
+                       return getColumn(columnIndex).getTitle();
+               }
+               
+               
+               public Motor getMotor(int rowIndex) {
+                       if (extra == null) {
+                               return Databases.MOTOR.get(rowIndex);
+                       } else {
+                               if (rowIndex == 0)
+                                       return extra;
+                               else
+                                       return Databases.MOTOR.get(rowIndex-1);
+                       }
+               }
+               
+               public int getIndex(Motor m) {
+                       if (extra == null) {
+                               return Databases.MOTOR.indexOf(m);
+                       } else {
+                               if (extra.equals(m))
+                                       return 0;
+                               else
+                                       return Databases.MOTOR.indexOf(m)+1;
+                       }
+               }
+               
+               private MotorColumns getColumn(int index) {
+                       return MotorColumns.values()[index];
+               }
+       }
+
+       
+       ////////  Row filters
+       
+       /**
+        * Abstract adapter class.
+        */
+       private abstract class MotorRowFilter extends RowFilter<TableModel,Integer> {
+               @Override
+               public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
+                       int index = entry.getIdentifier();
+                       Motor m = model.getMotor(index);
+                       return filterByDiameter(m) && filterByString(m);
+               }
+               
+               public abstract boolean filterByDiameter(Motor m);
+               
+               
+               public boolean filterByString(Motor m) {
+                       main: for (String s : searchTerms) {
+                               for (MotorColumns col : MotorColumns.values()) {
+                                       String str = col.getValue(m).toLowerCase();
+                                       if (str.indexOf(s) >= 0)
+                                               continue main;
+                               }
+                               return false;
+                       }
+                       return true;
+               }
+       }
+       
+       /**
+        * Show all motors.
+        */
+       private class MotorRowFilterAll extends MotorRowFilter {
+               @Override
+               public boolean filterByDiameter(Motor m) {
+                       return true;
+               }
+       }
+       
+       /**
+        * Show motors smaller than the mount.
+        */
+       private class MotorRowFilterSmaller extends MotorRowFilter {
+               @Override
+               public boolean filterByDiameter(Motor m) {
+                       return (m.getDiameter() <= diameter + 0.0004);
+               }
+       }
+       
+       /**
+        * Show motors that fit the mount.
+        */
+       private class MotorRowFilterExact extends MotorRowFilter {
+               @Override
+               public boolean filterByDiameter(Motor m) {
+                       return ((m.getDiameter() <= diameter + 0.0004) &&
+                                       (m.getDiameter() >= diameter - 0.0015));
+               }
+       }
+       
+       
+       private static Comparator<String> numericalComparator = null;
+       private static Comparator<String> getNumericalComparator() {
+               if (numericalComparator == null)
+                       numericalComparator = new NumericalComparator();
+               return numericalComparator;
+       }
+       
+       private static class NumericalComparator implements Comparator<String> {
+               private Pattern pattern = 
+                       Pattern.compile("^\\s*([0-9]*[.,][0-9]+|[0-9]+[.,]?[0-9]*).*?$");
+               private Collator collator = null;
+               @Override
+               public int compare(String s1, String s2) {
+                       Matcher m1, m2;
+                       
+                       m1 = pattern.matcher(s1);
+                       m2 = pattern.matcher(s2);
+                       if (m1.find() && m2.find()) {
+                               double d1 = Double.parseDouble(m1.group(1));
+                               double d2 = Double.parseDouble(m2.group(1));
+                               
+                               return (int)((d1-d2)*1000);
+                       }
+                       
+                       if (collator == null)
+                               collator = Collator.getInstance(Locale.US);
+                       return collator.compare(s1, s2);
+               }
+       }
+       
+}