1 package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
3 import java.awt.BasicStroke;
5 import java.awt.Component;
6 import java.awt.Cursor;
9 import java.awt.Rectangle;
10 import java.awt.event.ActionEvent;
11 import java.awt.event.ActionListener;
12 import java.awt.event.MouseAdapter;
13 import java.awt.event.MouseEvent;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.prefs.Preferences;
19 import javax.swing.BorderFactory;
20 import javax.swing.DefaultComboBoxModel;
21 import javax.swing.JComboBox;
22 import javax.swing.JComponent;
23 import javax.swing.JLabel;
24 import javax.swing.JLayeredPane;
25 import javax.swing.JList;
26 import javax.swing.JPanel;
27 import javax.swing.JScrollPane;
28 import javax.swing.JSeparator;
29 import javax.swing.JTable;
30 import javax.swing.JTextArea;
31 import javax.swing.JTextField;
32 import javax.swing.ListCellRenderer;
33 import javax.swing.ListSelectionModel;
34 import javax.swing.RowFilter;
35 import javax.swing.SwingUtilities;
36 import javax.swing.event.DocumentEvent;
37 import javax.swing.event.DocumentListener;
38 import javax.swing.event.ListSelectionEvent;
39 import javax.swing.event.ListSelectionListener;
40 import javax.swing.table.TableModel;
41 import javax.swing.table.TableRowSorter;
43 import net.miginfocom.swing.MigLayout;
44 import net.sf.openrocket.database.ThrustCurveMotorSet;
45 import net.sf.openrocket.gui.components.StyledLabel;
46 import net.sf.openrocket.gui.components.StyledLabel.Style;
47 import net.sf.openrocket.gui.dialogs.motor.CloseableDialog;
48 import net.sf.openrocket.gui.dialogs.motor.MotorSelector;
49 import net.sf.openrocket.logging.LogHelper;
50 import net.sf.openrocket.motor.Motor;
51 import net.sf.openrocket.motor.MotorDigest;
52 import net.sf.openrocket.motor.ThrustCurveMotor;
53 import net.sf.openrocket.startup.Application;
54 import net.sf.openrocket.unit.UnitGroup;
55 import net.sf.openrocket.util.BugException;
56 import net.sf.openrocket.util.GUIUtil;
57 import net.sf.openrocket.util.Icons;
58 import net.sf.openrocket.util.Prefs;
60 import org.jfree.chart.ChartColor;
61 import org.jfree.chart.ChartFactory;
62 import org.jfree.chart.ChartPanel;
63 import org.jfree.chart.JFreeChart;
64 import org.jfree.chart.axis.ValueAxis;
65 import org.jfree.chart.plot.PlotOrientation;
66 import org.jfree.chart.plot.XYPlot;
67 import org.jfree.chart.title.TextTitle;
68 import org.jfree.data.xy.XYSeries;
69 import org.jfree.data.xy.XYSeriesCollection;
71 public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelector {
72 private static final LogHelper log = Application.getLogger();
74 private static final int SHOW_ALL = 0;
75 private static final int SHOW_SMALLER = 1;
76 private static final int SHOW_EXACT = 2;
77 private static final String[] SHOW_DESCRIPTIONS = {
79 "Show motors with diameter less than that of the motor mount",
80 "Show motors with diameter equal to that of the motor mount"
82 private static final int SHOW_MAX = 2;
84 private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50;
85 private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12;
87 private static final Paint[] CURVE_COLORS = ChartColor.createDefaultPaintArray();
89 private static final Color NO_COMMENT_COLOR = Color.GRAY;
90 private static final Color WITH_COMMENT_COLOR = Color.BLACK;
92 private final List<ThrustCurveMotorSet> database;
94 private final double diameter;
95 private CloseableDialog dialog = null;
98 private final ThrustCurveMotorDatabaseModel model;
99 private final JTable table;
100 private final TableRowSorter<TableModel> sorter;
102 private final JTextField searchField;
103 private String[] searchTerms = new String[0];
106 private final JLabel curveSelectionLabel;
107 private final JComboBox curveSelectionBox;
108 private final DefaultComboBoxModel curveSelectionModel;
110 private final JLabel totalImpulseLabel;
111 private final JLabel avgThrustLabel;
112 private final JLabel maxThrustLabel;
113 private final JLabel burnTimeLabel;
114 private final JLabel launchMassLabel;
115 private final JLabel emptyMassLabel;
116 private final JLabel dataPointsLabel;
118 private final JTextArea comment;
119 private final Font noCommentFont;
120 private final Font withCommentFont;
122 private final JFreeChart chart;
123 private final ChartPanel chartPanel;
124 private final JLabel zoomIcon;
126 private final JComboBox delayBox;
128 private ThrustCurveMotor selectedMotor;
129 private ThrustCurveMotorSet selectedMotorSet;
130 private double selectedDelay;
136 * @param current the currently selected ThrustCurveMotor, or <code>null</code> for none.
137 * @param delay the currently selected ejection charge delay.
138 * @param diameter the diameter of the motor mount.
140 public ThrustCurveMotorSelectionPanel(ThrustCurveMotor current, double delay, double diameter) {
141 super(new MigLayout("fill", "[grow][]"));
143 this.diameter = diameter;
146 // Construct the database (adding the current motor if not in the db already)
147 List<ThrustCurveMotorSet> db;
148 db = Application.getMotorSetDatabase().getMotorSets();
150 // If current motor is not found in db, add a new ThrustCurveMotorSet containing it
151 if (current != null) {
152 selectedMotor = current;
153 for (ThrustCurveMotorSet motorSet : db) {
154 if (motorSet.getMotors().contains(current)) {
155 selectedMotorSet = motorSet;
159 if (selectedMotorSet == null) {
160 db = new ArrayList<ThrustCurveMotorSet>(db);
161 ThrustCurveMotorSet extra = new ThrustCurveMotorSet();
162 extra.addMotor(current);
163 selectedMotorSet = extra;
165 Collections.sort(db);
177 panel = new JPanel(new MigLayout("fill"));
178 this.add(panel, "grow");
183 label = new StyledLabel("Select rocket motor:", Style.BOLD);
184 panel.add(label, "spanx, wrap para");
186 // Diameter selection
187 JComboBox filterComboBox = new JComboBox(SHOW_DESCRIPTIONS);
188 filterComboBox.addActionListener(new ActionListener() {
190 public void actionPerformed(ActionEvent e) {
191 JComboBox cb = (JComboBox) e.getSource();
192 int sel = cb.getSelectedIndex();
193 if ((sel < 0) || (sel > SHOW_MAX))
197 sorter.setRowFilter(new MotorRowFilterAll());
201 sorter.setRowFilter(new MotorRowFilterSmaller());
205 sorter.setRowFilter(new MotorRowFilterExact());
209 assert (false) : "Should not occur.";
211 Prefs.putChoise("MotorDiameterMatch", sel);
212 scrollSelectionVisible();
215 panel.add(filterComboBox, "spanx, growx, wrap para");
219 // Motor selection table
220 model = new ThrustCurveMotorDatabaseModel(database);
221 table = new JTable(model);
224 // Set comparators and widths
225 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
226 sorter = new TableRowSorter<TableModel>(model);
227 for (int i = 0; i < ThrustCurveMotorColumns.values().length; i++) {
228 ThrustCurveMotorColumns column = ThrustCurveMotorColumns.values()[i];
229 sorter.setComparator(i, column.getComparator());
230 table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
232 table.setRowSorter(sorter);
234 // Set selection and double-click listeners
235 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
237 public void valueChanged(ListSelectionEvent e) {
238 int row = table.getSelectedRow();
240 row = table.convertRowIndexToModel(row);
241 ThrustCurveMotorSet motorSet = model.getMotorSet(row);
242 log.user("Selected table row " + row + ": " + motorSet);
243 if (motorSet != selectedMotorSet) {
244 select(selectMotor(motorSet));
247 log.user("Selected table row " + row + ", nothing selected");
251 table.addMouseListener(new MouseAdapter() {
253 public void mouseClicked(MouseEvent e) {
254 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
255 if (dialog != null) {
263 JScrollPane scrollpane = new JScrollPane();
264 scrollpane.setViewportView(table);
265 panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para");
270 // Motor mount diameter label
271 label = new StyledLabel("Motor mount diameter: " +
272 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
273 panel.add(label, "gapright 30lp, spanx, split");
278 label = new StyledLabel("Search:");
279 panel.add(label, "");
281 searchField = new JTextField();
282 searchField.getDocument().addDocumentListener(new DocumentListener() {
284 public void changedUpdate(DocumentEvent e) {
289 public void insertUpdate(DocumentEvent e) {
294 public void removeUpdate(DocumentEvent e) {
298 private void update() {
299 String text = searchField.getText().trim();
300 String[] split = text.split("\\s+");
301 ArrayList<String> list = new ArrayList<String>();
302 for (String s : split) {
303 s = s.trim().toLowerCase();
304 if (s.length() > 0) {
308 searchTerms = list.toArray(new String[0]);
310 scrollSelectionVisible();
313 panel.add(searchField, "growx, wrap");
318 this.add(panel, "grow");
319 this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para");
320 panel = new JPanel(new MigLayout("fill"));
324 // Thrust curve selection
325 curveSelectionLabel = new JLabel("Select thrust curve:");
326 panel.add(curveSelectionLabel);
328 curveSelectionModel = new DefaultComboBoxModel();
329 curveSelectionBox = new JComboBox(curveSelectionModel);
330 curveSelectionBox.setRenderer(new CurveSelectionRenderer(curveSelectionBox.getRenderer()));
331 curveSelectionBox.addActionListener(new ActionListener() {
333 public void actionPerformed(ActionEvent e) {
334 Object value = curveSelectionBox.getSelectedItem();
336 select(((MotorHolder) value).getMotor());
340 panel.add(curveSelectionBox, "growx, wrap para");
346 // Ejection charge delay
347 panel.add(new JLabel("Ejection charge delay:"));
349 delayBox = new JComboBox();
350 delayBox.setEditable(true);
351 delayBox.addActionListener(new ActionListener() {
353 public void actionPerformed(ActionEvent e) {
354 JComboBox cb = (JComboBox) e.getSource();
355 String sel = (String) cb.getSelectedItem();
356 if (sel.equalsIgnoreCase("None")) {
357 selectedDelay = Motor.PLUGGED;
360 selectedDelay = Double.parseDouble(sel);
361 } catch (NumberFormatException ignore) {
367 panel.add(delayBox, "growx, wrap rel");
368 panel.add(new StyledLabel("(Number of seconds or \"None\")", -3), "skip, wrap para");
372 panel.add(new JSeparator(), "spanx, growx, wrap para");
377 panel.add(new JLabel("Total impulse:"));
378 totalImpulseLabel = new JLabel();
379 panel.add(totalImpulseLabel, "wrap");
381 panel.add(new JLabel("Avg. thrust:"));
382 avgThrustLabel = new JLabel();
383 panel.add(avgThrustLabel, "wrap");
385 panel.add(new JLabel("Max. thrust:"));
386 maxThrustLabel = new JLabel();
387 panel.add(maxThrustLabel, "wrap");
389 panel.add(new JLabel("Burn time:"));
390 burnTimeLabel = new JLabel();
391 panel.add(burnTimeLabel, "wrap");
393 panel.add(new JLabel("Launch mass:"));
394 launchMassLabel = new JLabel();
395 panel.add(launchMassLabel, "wrap");
397 panel.add(new JLabel("Empty mass:"));
398 emptyMassLabel = new JLabel();
399 panel.add(emptyMassLabel, "wrap");
401 panel.add(new JLabel("Data points:"));
402 dataPointsLabel = new JLabel();
403 panel.add(dataPointsLabel, "wrap para");
406 comment = new JTextArea(5, 5);
407 GUIUtil.changeFontSize(comment, -2);
408 withCommentFont = comment.getFont();
409 noCommentFont = withCommentFont.deriveFont(Font.ITALIC);
410 comment.setLineWrap(true);
411 comment.setWrapStyleWord(true);
412 comment.setEditable(false);
413 scrollpane = new JScrollPane(comment);
414 panel.add(scrollpane, "spanx, growx, wrap para");
420 chart = ChartFactory.createXYLineChart(
425 PlotOrientation.VERTICAL,
432 // Add the data and formatting to the plot
433 XYPlot plot = chart.getXYPlot();
435 changeLabelFont(plot.getRangeAxis(), -2);
436 changeLabelFont(plot.getDomainAxis(), -2);
438 chart.setTitle(new TextTitle("Thrust curve:", this.getFont()));
439 chart.setBackgroundPaint(this.getBackground());
440 plot.setBackgroundPaint(Color.WHITE);
441 plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
442 plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
444 chartPanel = new ChartPanel(chart,
450 chartPanel.setMouseZoomable(false);
451 chartPanel.setPopupMenu(null);
452 chartPanel.setMouseWheelEnabled(false);
453 chartPanel.setRangeZoomable(false);
454 chartPanel.setDomainZoomable(false);
456 chartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
457 chartPanel.addMouseListener(new MouseAdapter() {
459 public void mouseClicked(MouseEvent e) {
460 if (selectedMotor == null || selectedMotorSet == null)
462 if (e.getButton() == MouseEvent.BUTTON1) {
464 ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(selectedMotorSet, selectedMotor,
465 SwingUtilities.getWindowAncestor(ThrustCurveMotorSelectionPanel.this));
466 plotDialog.setVisible(true);
471 JLayeredPane layer = new CustomLayeredPane();
473 layer.setBorder(BorderFactory.createLineBorder(Color.BLUE));
475 layer.add(chartPanel, (Integer) 0);
477 zoomIcon = new JLabel(Icons.ZOOM_IN);
478 zoomIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
479 layer.add(zoomIcon, (Integer) 1);
482 panel.add(layer, "width 300:300:, height 180:180:, grow, spanx");
486 this.add(panel, "grow");
491 int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
492 filterComboBox.setSelectedIndex(showMode);
495 // Update the panel data
497 selectedDelay = delay;
505 public Motor getSelectedMotor() {
506 return selectedMotor;
511 public double getSelectedDelay() {
512 return selectedDelay;
517 public JComponent getDefaultFocus() {
522 public void selectedMotor(Motor motorSelection) {
523 if (!(motorSelection instanceof ThrustCurveMotor)) {
524 log.error("Received argument that was not ThrustCurveMotor: " + motorSelection);
528 ThrustCurveMotor motor = (ThrustCurveMotor) motorSelection;
529 ThrustCurveMotorSet set = findMotorSet(motor);
531 log.error("Could not find set for motor:" + motorSelection);
535 // Store selected motor in preferences node, set all others to false
536 Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
537 for (ThrustCurveMotor m : set.getMotors()) {
538 String digest = MotorDigest.digestMotor(m);
539 prefs.putBoolean(digest, m == motor);
543 public void setCloseableDialog(CloseableDialog dialog) {
544 this.dialog = dialog;
549 private void changeLabelFont(ValueAxis axis, float size) {
550 Font font = axis.getTickLabelFont();
551 font = font.deriveFont(font.getSize2D() + size);
552 axis.setTickLabelFont(font);
557 * Called when a different motor is selected from within the panel.
559 private void select(ThrustCurveMotor motor) {
560 if (selectedMotor == motor)
563 ThrustCurveMotorSet set = findMotorSet(motor);
565 throw new BugException("Could not find motor from database, motor=" + motor);
568 boolean updateDelays = (selectedMotorSet != set);
570 selectedMotor = motor;
571 selectedMotorSet = set;
579 private void updateData() {
581 if (selectedMotorSet == null) {
583 curveSelectionModel.removeAllElements();
584 curveSelectionBox.setEnabled(false);
585 curveSelectionLabel.setEnabled(false);
586 totalImpulseLabel.setText("");
587 avgThrustLabel.setText("");
588 maxThrustLabel.setText("");
589 burnTimeLabel.setText("");
590 launchMassLabel.setText("");
591 emptyMassLabel.setText("");
592 dataPointsLabel.setText("");
594 chart.getXYPlot().setDataset(new XYSeriesCollection());
599 List<ThrustCurveMotor> motors = selectedMotorSet.getMotors();
600 final int index = motors.indexOf(selectedMotor);
602 curveSelectionModel.removeAllElements();
603 for (int i = 0; i < motors.size(); i++) {
604 curveSelectionModel.addElement(new MotorHolder(motors.get(i), i));
606 curveSelectionBox.setSelectedIndex(index);
608 if (motors.size() > 1) {
609 curveSelectionBox.setEnabled(true);
610 curveSelectionLabel.setEnabled(true);
612 curveSelectionBox.setEnabled(false);
613 curveSelectionLabel.setEnabled(false);
616 totalImpulseLabel.setText(UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
617 selectedMotor.getTotalImpulseEstimate()));
618 avgThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
619 selectedMotor.getAverageThrustEstimate()));
620 maxThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
621 selectedMotor.getMaxThrustEstimate()));
622 burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
623 selectedMotor.getBurnTimeEstimate()));
624 launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
625 selectedMotor.getLaunchCG().weight));
626 emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
627 selectedMotor.getEmptyCG().weight));
628 dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1));
630 setComment(selectedMotor.getDescription());
634 XYPlot plot = chart.getXYPlot();
636 XYSeriesCollection dataset = new XYSeriesCollection();
637 for (int i = 0; i < motors.size(); i++) {
638 ThrustCurveMotor m = motors.get(i);
640 XYSeries series = new XYSeries("Thrust");
641 double[] time = m.getTimePoints();
642 double[] thrust = m.getThrustPoints();
644 for (int j = 0; j < time.length; j++) {
645 series.add(time[j], thrust[j]);
648 dataset.addSeries(series);
650 boolean selected = (i == index);
651 plot.getRenderer().setSeriesStroke(i, new BasicStroke(selected ? 3 : 1));
652 plot.getRenderer().setSeriesPaint(i, getColor(i));
655 plot.setDataset(dataset);
659 private void setComment(String s) {
661 if (s.length() == 0) {
662 comment.setText("No description available.");
663 comment.setFont(noCommentFont);
664 comment.setForeground(NO_COMMENT_COLOR);
667 comment.setFont(withCommentFont);
668 comment.setForeground(WITH_COMMENT_COLOR);
670 comment.setCaretPosition(0);
673 private void scrollSelectionVisible() {
674 if (selectedMotorSet != null) {
675 int index = table.convertRowIndexToView(model.getIndex(selectedMotorSet));
676 System.out.println("index=" + index);
677 table.getSelectionModel().setSelectionInterval(index, index);
678 Rectangle rect = table.getCellRect(index, 0, true);
679 rect = new Rectangle(rect.x, rect.y - 100, rect.width, rect.height + 200);
680 table.scrollRectToVisible(rect);
685 public static Color getColor(int index) {
686 return (Color) CURVE_COLORS[index % CURVE_COLORS.length];
691 * Find the ThrustCurveMotorSet that contains a motor.
693 * @param motor the motor to look for.
694 * @return the ThrustCurveMotorSet, or null if not found.
696 private ThrustCurveMotorSet findMotorSet(ThrustCurveMotor motor) {
697 for (ThrustCurveMotorSet set : database) {
698 if (set.getMotors().contains(motor)) {
709 * Select the default motor from this ThrustCurveMotorSet. This uses primarily motors
710 * that the user has previously used, and secondarily a heuristic method of selecting which
711 * thrust curve seems to be better or more reliable.
713 * @param set the motor set
714 * @return the default motor in this set
716 private ThrustCurveMotor selectMotor(ThrustCurveMotorSet set) {
717 if (set.getMotorCount() == 0) {
718 throw new BugException("Attempting to select motor from empty ThrustCurveMotorSet: " + set);
720 if (set.getMotorCount() == 1) {
721 return set.getMotors().get(0);
724 // Find which motor has been used the most recently
725 Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
726 for (ThrustCurveMotor m : set.getMotors()) {
727 String digest = MotorDigest.digestMotor(m);
728 if (prefs.getBoolean(digest, false)) {
733 // No motor has been used, use heuristics to select motor
734 // TODO: CRITICAL: Heuristics
735 return set.getMotors().get(0);
740 * Set the values in the delay combo box. If <code>reset</code> is <code>true</code>
741 * then sets the selected value as the value closest to selectedDelay, otherwise
742 * leaves selection alone.
744 private void setDelays(boolean reset) {
745 if (selectedMotor == null) {
747 delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
748 delayBox.setSelectedIndex(0);
752 List<Double> delays = selectedMotorSet.getDelays();
753 String[] delayStrings = new String[delays.size()];
754 double currentDelay = selectedDelay; // Store current setting locally
756 for (int i = 0; i < delays.size(); i++) {
757 delayStrings[i] = ThrustCurveMotor.getDelayString(delays.get(i), "None");
759 delayBox.setModel(new DefaultComboBoxModel(delayStrings));
763 // Find and set the closest value
764 double closest = Double.NaN;
765 for (int i = 0; i < delays.size(); i++) {
766 // if-condition to always become true for NaN
767 if (!(Math.abs(delays.get(i) - currentDelay) > Math.abs(closest - currentDelay))) {
768 closest = delays.get(i);
771 if (!Double.isNaN(closest)) {
772 selectedDelay = closest;
773 delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(closest, "None"));
775 delayBox.setSelectedItem("None");
780 selectedDelay = currentDelay;
781 delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, "None"));
791 //////////////////////
794 private class CurveSelectionRenderer implements ListCellRenderer {
796 private final ListCellRenderer renderer;
798 public CurveSelectionRenderer(ListCellRenderer renderer) {
799 this.renderer = renderer;
803 public Component getListCellRendererComponent(JList list, Object value, int index,
804 boolean isSelected, boolean cellHasFocus) {
806 Component c = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
807 if (value instanceof MotorHolder) {
808 MotorHolder m = (MotorHolder) value;
809 c.setForeground(getColor(m.getIndex()));
821 * Abstract adapter class.
823 private abstract class MotorRowFilter extends RowFilter<TableModel, Integer> {
825 public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
826 int index = entry.getIdentifier();
827 ThrustCurveMotorSet m = model.getMotorSet(index);
828 return filterByDiameter(m) && filterByString(m);
831 public abstract boolean filterByDiameter(ThrustCurveMotorSet m);
834 public boolean filterByString(ThrustCurveMotorSet m) {
835 main: for (String s : searchTerms) {
836 for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) {
837 String str = col.getValue(m).toString().toLowerCase();
838 if (str.indexOf(s) >= 0)
850 private class MotorRowFilterAll extends MotorRowFilter {
852 public boolean filterByDiameter(ThrustCurveMotorSet m) {
858 * Show motors smaller than the mount.
860 private class MotorRowFilterSmaller extends MotorRowFilter {
862 public boolean filterByDiameter(ThrustCurveMotorSet m) {
863 return (m.getDiameter() <= diameter + 0.0004);
868 * Show motors that fit the mount.
870 private class MotorRowFilterExact extends MotorRowFilter {
872 public boolean filterByDiameter(ThrustCurveMotorSet m) {
873 return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015));
879 * Custom layered pane that sets the bounds of the components on every layout.
881 public class CustomLayeredPane extends JLayeredPane {
883 public void doLayout() {
884 synchronized (getTreeLock()) {
887 chartPanel.setBounds(0, 0, w, h);
888 zoomIcon.setBounds(w - ZOOM_ICON_POSITION_NEGATIVE_X, ZOOM_ICON_POSITION_POSITIVE_Y, 50, 50);