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.JCheckBox;
22 import javax.swing.JComboBox;
23 import javax.swing.JComponent;
24 import javax.swing.JLabel;
25 import javax.swing.JLayeredPane;
26 import javax.swing.JList;
27 import javax.swing.JPanel;
28 import javax.swing.JScrollPane;
29 import javax.swing.JSeparator;
30 import javax.swing.JTable;
31 import javax.swing.JTextArea;
32 import javax.swing.JTextField;
33 import javax.swing.ListCellRenderer;
34 import javax.swing.ListSelectionModel;
35 import javax.swing.RowFilter;
36 import javax.swing.SwingUtilities;
37 import javax.swing.event.DocumentEvent;
38 import javax.swing.event.DocumentListener;
39 import javax.swing.event.ListSelectionEvent;
40 import javax.swing.event.ListSelectionListener;
41 import javax.swing.table.TableModel;
42 import javax.swing.table.TableRowSorter;
44 import net.miginfocom.swing.MigLayout;
45 import net.sf.openrocket.database.ThrustCurveMotorSet;
46 import net.sf.openrocket.gui.components.StyledLabel;
47 import net.sf.openrocket.gui.components.StyledLabel.Style;
48 import net.sf.openrocket.gui.dialogs.motor.CloseableDialog;
49 import net.sf.openrocket.gui.dialogs.motor.MotorSelector;
50 import net.sf.openrocket.logging.LogHelper;
51 import net.sf.openrocket.motor.Motor;
52 import net.sf.openrocket.motor.MotorDigest;
53 import net.sf.openrocket.motor.ThrustCurveMotor;
54 import net.sf.openrocket.startup.Application;
55 import net.sf.openrocket.unit.UnitGroup;
56 import net.sf.openrocket.util.BugException;
57 import net.sf.openrocket.util.GUIUtil;
58 import net.sf.openrocket.util.Icons;
59 import net.sf.openrocket.util.Prefs;
60 import net.sf.openrocket.utils.MotorCorrelation;
62 import org.jfree.chart.ChartColor;
63 import org.jfree.chart.ChartFactory;
64 import org.jfree.chart.ChartPanel;
65 import org.jfree.chart.JFreeChart;
66 import org.jfree.chart.axis.ValueAxis;
67 import org.jfree.chart.plot.PlotOrientation;
68 import org.jfree.chart.plot.XYPlot;
69 import org.jfree.chart.title.TextTitle;
70 import org.jfree.data.xy.XYSeries;
71 import org.jfree.data.xy.XYSeriesCollection;
73 public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelector {
74 private static final LogHelper log = Application.getLogger();
76 private static final double MOTOR_SIMILARITY_THRESHOLD = 0.95;
78 private static final int SHOW_ALL = 0;
79 private static final int SHOW_SMALLER = 1;
80 private static final int SHOW_EXACT = 2;
81 private static final String[] SHOW_DESCRIPTIONS = {
83 "Show motors with diameter less than that of the motor mount",
84 "Show motors with diameter equal to that of the motor mount"
86 private static final int SHOW_MAX = 2;
88 private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50;
89 private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12;
91 private static final Paint[] CURVE_COLORS = ChartColor.createDefaultPaintArray();
93 private static final Color NO_COMMENT_COLOR = Color.GRAY;
94 private static final Color WITH_COMMENT_COLOR = Color.BLACK;
96 private static final ThrustCurveMotorComparator MOTOR_COMPARATOR = new ThrustCurveMotorComparator();
100 private final List<ThrustCurveMotorSet> database;
102 private final double diameter;
103 private CloseableDialog dialog = null;
106 private final ThrustCurveMotorDatabaseModel model;
107 private final JTable table;
108 private final TableRowSorter<TableModel> sorter;
110 private final JCheckBox hideSimilarBox;
112 private final JTextField searchField;
113 private String[] searchTerms = new String[0];
116 private final JLabel curveSelectionLabel;
117 private final JComboBox curveSelectionBox;
118 private final DefaultComboBoxModel curveSelectionModel;
120 private final JLabel totalImpulseLabel;
121 private final JLabel avgThrustLabel;
122 private final JLabel maxThrustLabel;
123 private final JLabel burnTimeLabel;
124 private final JLabel launchMassLabel;
125 private final JLabel emptyMassLabel;
126 private final JLabel dataPointsLabel;
127 private final JLabel digestLabel;
129 private final JTextArea comment;
130 private final Font noCommentFont;
131 private final Font withCommentFont;
133 private final JFreeChart chart;
134 private final ChartPanel chartPanel;
135 private final JLabel zoomIcon;
137 private final JComboBox delayBox;
139 private ThrustCurveMotor selectedMotor;
140 private ThrustCurveMotorSet selectedMotorSet;
141 private double selectedDelay;
147 * @param current the currently selected ThrustCurveMotor, or <code>null</code> for none.
148 * @param delay the currently selected ejection charge delay.
149 * @param diameter the diameter of the motor mount.
151 public ThrustCurveMotorSelectionPanel(ThrustCurveMotor current, double delay, double diameter) {
152 super(new MigLayout("fill", "[grow][]"));
154 this.diameter = diameter;
157 // Construct the database (adding the current motor if not in the db already)
158 List<ThrustCurveMotorSet> db;
159 db = Application.getMotorSetDatabase().getMotorSets();
161 // If current motor is not found in db, add a new ThrustCurveMotorSet containing it
162 if (current != null) {
163 selectedMotor = current;
164 for (ThrustCurveMotorSet motorSet : db) {
165 if (motorSet.getMotors().contains(current)) {
166 selectedMotorSet = motorSet;
170 if (selectedMotorSet == null) {
171 db = new ArrayList<ThrustCurveMotorSet>(db);
172 ThrustCurveMotorSet extra = new ThrustCurveMotorSet();
173 extra.addMotor(current);
174 selectedMotorSet = extra;
176 Collections.sort(db);
188 panel = new JPanel(new MigLayout("fill"));
189 this.add(panel, "grow");
194 label = new StyledLabel("Select rocket motor:", Style.BOLD);
195 panel.add(label, "spanx, wrap para");
197 // Diameter selection
198 JComboBox filterComboBox = new JComboBox(SHOW_DESCRIPTIONS);
199 filterComboBox.addActionListener(new ActionListener() {
201 public void actionPerformed(ActionEvent e) {
202 JComboBox cb = (JComboBox) e.getSource();
203 int sel = cb.getSelectedIndex();
204 if ((sel < 0) || (sel > SHOW_MAX))
208 sorter.setRowFilter(new MotorRowFilterAll());
212 sorter.setRowFilter(new MotorRowFilterSmaller());
216 sorter.setRowFilter(new MotorRowFilterExact());
220 assert (false) : "Should not occur.";
222 Prefs.putChoise("MotorDiameterMatch", sel);
223 scrollSelectionVisible();
226 panel.add(filterComboBox, "spanx, growx, wrap rel");
229 hideSimilarBox = new JCheckBox("Hide very similar thrust curves");
230 GUIUtil.changeFontSize(hideSimilarBox, -1);
231 hideSimilarBox.setSelected(Prefs.getBoolean(Prefs.MOTOR_HIDE_SIMILAR, true));
232 hideSimilarBox.addActionListener(new ActionListener() {
234 public void actionPerformed(ActionEvent e) {
235 Prefs.putBoolean(Prefs.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected());
239 panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para");
242 // Motor selection table
243 model = new ThrustCurveMotorDatabaseModel(database);
244 table = new JTable(model);
247 // Set comparators and widths
248 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
249 sorter = new TableRowSorter<TableModel>(model);
250 for (int i = 0; i < ThrustCurveMotorColumns.values().length; i++) {
251 ThrustCurveMotorColumns column = ThrustCurveMotorColumns.values()[i];
252 sorter.setComparator(i, column.getComparator());
253 table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
255 table.setRowSorter(sorter);
257 // Set selection and double-click listeners
258 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
260 public void valueChanged(ListSelectionEvent e) {
261 int row = table.getSelectedRow();
263 row = table.convertRowIndexToModel(row);
264 ThrustCurveMotorSet motorSet = model.getMotorSet(row);
265 log.user("Selected table row " + row + ": " + motorSet);
266 if (motorSet != selectedMotorSet) {
267 select(selectMotor(motorSet));
270 log.user("Selected table row " + row + ", nothing selected");
274 table.addMouseListener(new MouseAdapter() {
276 public void mouseClicked(MouseEvent e) {
277 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
278 if (dialog != null) {
286 JScrollPane scrollpane = new JScrollPane();
287 scrollpane.setViewportView(table);
288 panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para");
293 // Motor mount diameter label
294 label = new StyledLabel("Motor mount diameter: " +
295 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
296 panel.add(label, "gapright 30lp, spanx, split");
301 label = new StyledLabel("Search:");
302 panel.add(label, "");
304 searchField = new JTextField();
305 searchField.getDocument().addDocumentListener(new DocumentListener() {
307 public void changedUpdate(DocumentEvent e) {
312 public void insertUpdate(DocumentEvent e) {
317 public void removeUpdate(DocumentEvent e) {
321 private void update() {
322 String text = searchField.getText().trim();
323 String[] split = text.split("\\s+");
324 ArrayList<String> list = new ArrayList<String>();
325 for (String s : split) {
326 s = s.trim().toLowerCase();
327 if (s.length() > 0) {
331 searchTerms = list.toArray(new String[0]);
333 scrollSelectionVisible();
336 panel.add(searchField, "growx, wrap");
341 this.add(panel, "grow");
342 this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para");
343 panel = new JPanel(new MigLayout("fill"));
347 // Thrust curve selection
348 curveSelectionLabel = new JLabel("Select thrust curve:");
349 panel.add(curveSelectionLabel);
351 curveSelectionModel = new DefaultComboBoxModel();
352 curveSelectionBox = new JComboBox(curveSelectionModel);
353 curveSelectionBox.setRenderer(new CurveSelectionRenderer(curveSelectionBox.getRenderer()));
354 curveSelectionBox.addActionListener(new ActionListener() {
356 public void actionPerformed(ActionEvent e) {
357 Object value = curveSelectionBox.getSelectedItem();
359 select(((MotorHolder) value).getMotor());
363 panel.add(curveSelectionBox, "growx, wrap para");
369 // Ejection charge delay
370 panel.add(new JLabel("Ejection charge delay:"));
372 delayBox = new JComboBox();
373 delayBox.setEditable(true);
374 delayBox.addActionListener(new ActionListener() {
376 public void actionPerformed(ActionEvent e) {
377 JComboBox cb = (JComboBox) e.getSource();
378 String sel = (String) cb.getSelectedItem();
379 if (sel.equalsIgnoreCase("None")) {
380 selectedDelay = Motor.PLUGGED;
383 selectedDelay = Double.parseDouble(sel);
384 } catch (NumberFormatException ignore) {
390 panel.add(delayBox, "growx, wrap rel");
391 panel.add(new StyledLabel("(Number of seconds or \"None\")", -3), "skip, wrap para");
395 panel.add(new JSeparator(), "spanx, growx, wrap para");
400 panel.add(new JLabel("Total impulse:"));
401 totalImpulseLabel = new JLabel();
402 panel.add(totalImpulseLabel, "wrap");
404 panel.add(new JLabel("Avg. thrust:"));
405 avgThrustLabel = new JLabel();
406 panel.add(avgThrustLabel, "wrap");
408 panel.add(new JLabel("Max. thrust:"));
409 maxThrustLabel = new JLabel();
410 panel.add(maxThrustLabel, "wrap");
412 panel.add(new JLabel("Burn time:"));
413 burnTimeLabel = new JLabel();
414 panel.add(burnTimeLabel, "wrap");
416 panel.add(new JLabel("Launch mass:"));
417 launchMassLabel = new JLabel();
418 panel.add(launchMassLabel, "wrap");
420 panel.add(new JLabel("Empty mass:"));
421 emptyMassLabel = new JLabel();
422 panel.add(emptyMassLabel, "wrap");
424 panel.add(new JLabel("Data points:"));
425 dataPointsLabel = new JLabel();
426 panel.add(dataPointsLabel, "wrap para");
428 if (System.getProperty("openrocket.debug.motordigest") != null) {
429 panel.add(new JLabel("Digest:"));
430 digestLabel = new JLabel();
431 panel.add(digestLabel, "w :300:, wrap para");
437 comment = new JTextArea(5, 5);
438 GUIUtil.changeFontSize(comment, -2);
439 withCommentFont = comment.getFont();
440 noCommentFont = withCommentFont.deriveFont(Font.ITALIC);
441 comment.setLineWrap(true);
442 comment.setWrapStyleWord(true);
443 comment.setEditable(false);
444 scrollpane = new JScrollPane(comment);
445 panel.add(scrollpane, "spanx, growx, wrap para");
451 chart = ChartFactory.createXYLineChart(
456 PlotOrientation.VERTICAL,
463 // Add the data and formatting to the plot
464 XYPlot plot = chart.getXYPlot();
466 changeLabelFont(plot.getRangeAxis(), -2);
467 changeLabelFont(plot.getDomainAxis(), -2);
469 chart.setTitle(new TextTitle("Thrust curve:", this.getFont()));
470 chart.setBackgroundPaint(this.getBackground());
471 plot.setBackgroundPaint(Color.WHITE);
472 plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
473 plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
475 chartPanel = new ChartPanel(chart,
481 chartPanel.setMouseZoomable(false);
482 chartPanel.setPopupMenu(null);
483 chartPanel.setMouseWheelEnabled(false);
484 chartPanel.setRangeZoomable(false);
485 chartPanel.setDomainZoomable(false);
487 chartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
488 chartPanel.addMouseListener(new MouseAdapter() {
490 public void mouseClicked(MouseEvent e) {
491 if (selectedMotor == null || selectedMotorSet == null)
493 if (e.getButton() == MouseEvent.BUTTON1) {
495 List<ThrustCurveMotor> motors = getFilteredCurves();
496 ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(motors,
497 motors.indexOf(selectedMotor),
498 SwingUtilities.getWindowAncestor(ThrustCurveMotorSelectionPanel.this));
499 plotDialog.setVisible(true);
504 JLayeredPane layer = new CustomLayeredPane();
506 layer.setBorder(BorderFactory.createLineBorder(Color.BLUE));
508 layer.add(chartPanel, (Integer) 0);
510 zoomIcon = new JLabel(Icons.ZOOM_IN);
511 zoomIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
512 layer.add(zoomIcon, (Integer) 1);
515 panel.add(layer, "width 300:300:, height 180:180:, grow, spanx");
519 this.add(panel, "grow");
524 int showMode = Prefs.getChoise(Prefs.MOTOR_DIAMETER_FILTER, SHOW_MAX, SHOW_EXACT);
525 filterComboBox.setSelectedIndex(showMode);
528 // Update the panel data
530 selectedDelay = delay;
537 public Motor getSelectedMotor() {
538 return selectedMotor;
543 public double getSelectedDelay() {
544 return selectedDelay;
549 public JComponent getDefaultFocus() {
554 public void selectedMotor(Motor motorSelection) {
555 if (!(motorSelection instanceof ThrustCurveMotor)) {
556 log.error("Received argument that was not ThrustCurveMotor: " + motorSelection);
560 ThrustCurveMotor motor = (ThrustCurveMotor) motorSelection;
561 ThrustCurveMotorSet set = findMotorSet(motor);
563 log.error("Could not find set for motor:" + motorSelection);
567 // Store selected motor in preferences node, set all others to false
568 Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
569 for (ThrustCurveMotor m : set.getMotors()) {
570 String digest = MotorDigest.digestMotor(m);
571 prefs.putBoolean(digest, m == motor);
575 public void setCloseableDialog(CloseableDialog dialog) {
576 this.dialog = dialog;
581 private void changeLabelFont(ValueAxis axis, float size) {
582 Font font = axis.getTickLabelFont();
583 font = font.deriveFont(font.getSize2D() + size);
584 axis.setTickLabelFont(font);
589 * Called when a different motor is selected from within the panel.
591 private void select(ThrustCurveMotor motor) {
592 if (selectedMotor == motor)
595 ThrustCurveMotorSet set = findMotorSet(motor);
597 throw new BugException("Could not find motor from database, motor=" + motor);
600 boolean updateDelays = (selectedMotorSet != set);
602 selectedMotor = motor;
603 selectedMotorSet = set;
611 private void updateData() {
613 if (selectedMotorSet == null) {
615 curveSelectionModel.removeAllElements();
616 curveSelectionBox.setEnabled(false);
617 curveSelectionLabel.setEnabled(false);
618 totalImpulseLabel.setText("");
619 avgThrustLabel.setText("");
620 maxThrustLabel.setText("");
621 burnTimeLabel.setText("");
622 launchMassLabel.setText("");
623 emptyMassLabel.setText("");
624 dataPointsLabel.setText("");
625 if (digestLabel != null) {
626 digestLabel.setText("");
629 chart.getXYPlot().setDataset(new XYSeriesCollection());
634 // Check which thrust curves to display
635 List<ThrustCurveMotor> motors = getFilteredCurves();
636 final int index = motors.indexOf(selectedMotor);
639 // Update the thrust curve selection box
640 curveSelectionModel.removeAllElements();
641 for (int i = 0; i < motors.size(); i++) {
642 curveSelectionModel.addElement(new MotorHolder(motors.get(i), i));
644 curveSelectionBox.setSelectedIndex(index);
646 if (motors.size() > 1) {
647 curveSelectionBox.setEnabled(true);
648 curveSelectionLabel.setEnabled(true);
650 curveSelectionBox.setEnabled(false);
651 curveSelectionLabel.setEnabled(false);
655 // Update thrust curve data
656 totalImpulseLabel.setText(UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
657 selectedMotor.getTotalImpulseEstimate()));
658 avgThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
659 selectedMotor.getAverageThrustEstimate()));
660 maxThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
661 selectedMotor.getMaxThrustEstimate()));
662 burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
663 selectedMotor.getBurnTimeEstimate()));
664 launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
665 selectedMotor.getLaunchCG().weight));
666 emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
667 selectedMotor.getEmptyCG().weight));
668 dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1));
669 if (digestLabel != null) {
670 digestLabel.setText(MotorDigest.digestMotor(selectedMotor));
673 setComment(selectedMotor.getDescription());
677 XYPlot plot = chart.getXYPlot();
679 XYSeriesCollection dataset = new XYSeriesCollection();
680 for (int i = 0; i < motors.size(); i++) {
681 ThrustCurveMotor m = motors.get(i);
683 XYSeries series = new XYSeries("Thrust");
684 double[] time = m.getTimePoints();
685 double[] thrust = m.getThrustPoints();
687 for (int j = 0; j < time.length; j++) {
688 series.add(time[j], thrust[j]);
691 dataset.addSeries(series);
693 boolean selected = (i == index);
694 plot.getRenderer().setSeriesStroke(i, new BasicStroke(selected ? 3 : 1));
695 plot.getRenderer().setSeriesPaint(i, getColor(i));
698 plot.setDataset(dataset);
702 private List<ThrustCurveMotor> getFilteredCurves() {
703 List<ThrustCurveMotor> motors = selectedMotorSet.getMotors();
704 if (hideSimilarBox.isSelected()) {
705 List<ThrustCurveMotor> filtered = new ArrayList<ThrustCurveMotor>(motors.size());
706 for (int i = 0; i < motors.size(); i++) {
707 ThrustCurveMotor m = motors.get(i);
708 if (m.equals(selectedMotor)) {
713 double similarity = MotorCorrelation.similarity(selectedMotor, m);
714 log.debug("Motor similarity: " + similarity);
715 if (similarity < MOTOR_SIMILARITY_THRESHOLD) {
722 Collections.sort(motors, MOTOR_COMPARATOR);
728 private void setComment(String s) {
730 if (s.length() == 0) {
731 comment.setText("No description available.");
732 comment.setFont(noCommentFont);
733 comment.setForeground(NO_COMMENT_COLOR);
736 comment.setFont(withCommentFont);
737 comment.setForeground(WITH_COMMENT_COLOR);
739 comment.setCaretPosition(0);
742 private void scrollSelectionVisible() {
743 if (selectedMotorSet != null) {
744 int index = table.convertRowIndexToView(model.getIndex(selectedMotorSet));
745 System.out.println("index=" + index);
746 table.getSelectionModel().setSelectionInterval(index, index);
747 Rectangle rect = table.getCellRect(index, 0, true);
748 rect = new Rectangle(rect.x, rect.y - 100, rect.width, rect.height + 200);
749 table.scrollRectToVisible(rect);
754 public static Color getColor(int index) {
755 return (Color) CURVE_COLORS[index % CURVE_COLORS.length];
760 * Find the ThrustCurveMotorSet that contains a motor.
762 * @param motor the motor to look for.
763 * @return the ThrustCurveMotorSet, or null if not found.
765 private ThrustCurveMotorSet findMotorSet(ThrustCurveMotor motor) {
766 for (ThrustCurveMotorSet set : database) {
767 if (set.getMotors().contains(motor)) {
778 * Select the default motor from this ThrustCurveMotorSet. This uses primarily motors
779 * that the user has previously used, and secondarily a heuristic method of selecting which
780 * thrust curve seems to be better or more reliable.
782 * @param set the motor set
783 * @return the default motor in this set
785 private ThrustCurveMotor selectMotor(ThrustCurveMotorSet set) {
786 if (set.getMotorCount() == 0) {
787 throw new BugException("Attempting to select motor from empty ThrustCurveMotorSet: " + set);
789 if (set.getMotorCount() == 1) {
790 return set.getMotors().get(0);
794 // Find which motor has been used the most recently
795 List<ThrustCurveMotor> list = set.getMotors();
796 Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
797 for (ThrustCurveMotor m : list) {
798 String digest = MotorDigest.digestMotor(m);
799 if (prefs.getBoolean(digest, false)) {
804 // No motor has been used
805 Collections.sort(list, MOTOR_COMPARATOR);
811 * Set the values in the delay combo box. If <code>reset</code> is <code>true</code>
812 * then sets the selected value as the value closest to selectedDelay, otherwise
813 * leaves selection alone.
815 private void setDelays(boolean reset) {
816 if (selectedMotor == null) {
818 delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
819 delayBox.setSelectedIndex(0);
823 List<Double> delays = selectedMotorSet.getDelays();
824 String[] delayStrings = new String[delays.size()];
825 double currentDelay = selectedDelay; // Store current setting locally
827 for (int i = 0; i < delays.size(); i++) {
828 delayStrings[i] = ThrustCurveMotor.getDelayString(delays.get(i), "None");
830 delayBox.setModel(new DefaultComboBoxModel(delayStrings));
834 // Find and set the closest value
835 double closest = Double.NaN;
836 for (int i = 0; i < delays.size(); i++) {
837 // if-condition to always become true for NaN
838 if (!(Math.abs(delays.get(i) - currentDelay) > Math.abs(closest - currentDelay))) {
839 closest = delays.get(i);
842 if (!Double.isNaN(closest)) {
843 selectedDelay = closest;
844 delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(closest, "None"));
846 delayBox.setSelectedItem("None");
851 selectedDelay = currentDelay;
852 delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, "None"));
862 //////////////////////
865 private class CurveSelectionRenderer implements ListCellRenderer {
867 private final ListCellRenderer renderer;
869 public CurveSelectionRenderer(ListCellRenderer renderer) {
870 this.renderer = renderer;
874 public Component getListCellRendererComponent(JList list, Object value, int index,
875 boolean isSelected, boolean cellHasFocus) {
877 Component c = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
878 if (value instanceof MotorHolder) {
879 MotorHolder m = (MotorHolder) value;
880 c.setForeground(getColor(m.getIndex()));
892 * Abstract adapter class.
894 private abstract class MotorRowFilter extends RowFilter<TableModel, Integer> {
896 public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
897 int index = entry.getIdentifier();
898 ThrustCurveMotorSet m = model.getMotorSet(index);
899 return filterByDiameter(m) && filterByString(m);
902 public abstract boolean filterByDiameter(ThrustCurveMotorSet m);
905 public boolean filterByString(ThrustCurveMotorSet m) {
906 main: for (String s : searchTerms) {
907 for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) {
908 String str = col.getValue(m).toString().toLowerCase();
909 if (str.indexOf(s) >= 0)
921 private class MotorRowFilterAll extends MotorRowFilter {
923 public boolean filterByDiameter(ThrustCurveMotorSet m) {
929 * Show motors smaller than the mount.
931 private class MotorRowFilterSmaller extends MotorRowFilter {
933 public boolean filterByDiameter(ThrustCurveMotorSet m) {
934 return (m.getDiameter() <= diameter + 0.0004);
939 * Show motors that fit the mount.
941 private class MotorRowFilterExact extends MotorRowFilter {
943 public boolean filterByDiameter(ThrustCurveMotorSet m) {
944 return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015));
950 * Custom layered pane that sets the bounds of the components on every layout.
952 public class CustomLayeredPane extends JLayeredPane {
954 public void doLayout() {
955 synchronized (getTreeLock()) {
958 chartPanel.setBounds(0, 0, w, h);
959 zoomIcon.setBounds(w - ZOOM_ICON_POSITION_NEGATIVE_X, ZOOM_ICON_POSITION_POSITIVE_Y, 50, 50);