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.l10n.Translator;
51 import net.sf.openrocket.logging.LogHelper;
52 import net.sf.openrocket.motor.Motor;
53 import net.sf.openrocket.motor.MotorDigest;
54 import net.sf.openrocket.motor.ThrustCurveMotor;
55 import net.sf.openrocket.startup.Application;
56 import net.sf.openrocket.unit.UnitGroup;
57 import net.sf.openrocket.util.BugException;
58 import net.sf.openrocket.util.GUIUtil;
59 import net.sf.openrocket.util.Icons;
60 import net.sf.openrocket.util.Prefs;
61 import net.sf.openrocket.utils.MotorCorrelation;
63 import org.jfree.chart.ChartColor;
64 import org.jfree.chart.ChartFactory;
65 import org.jfree.chart.ChartPanel;
66 import org.jfree.chart.JFreeChart;
67 import org.jfree.chart.axis.ValueAxis;
68 import org.jfree.chart.plot.PlotOrientation;
69 import org.jfree.chart.plot.XYPlot;
70 import org.jfree.chart.title.TextTitle;
71 import org.jfree.data.xy.XYSeries;
72 import org.jfree.data.xy.XYSeriesCollection;
74 public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelector {
75 private static final LogHelper log = Application.getLogger();
76 private static final Translator trans = Application.getTranslator();
78 private static final double MOTOR_SIMILARITY_THRESHOLD = 0.95;
80 private static final int SHOW_ALL = 0;
81 private static final int SHOW_SMALLER = 1;
82 private static final int SHOW_EXACT = 2;
83 private static final String[] SHOW_DESCRIPTIONS = {
85 "Show motors with diameter less than that of the motor mount",
86 "Show motors with diameter equal to that of the motor mount"
88 private static final int SHOW_MAX = 2;
90 private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50;
91 private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12;
93 private static final Paint[] CURVE_COLORS = ChartColor.createDefaultPaintArray();
95 private static final Color NO_COMMENT_COLOR = Color.GRAY;
96 private static final Color WITH_COMMENT_COLOR = Color.BLACK;
98 private static final ThrustCurveMotorComparator MOTOR_COMPARATOR = new ThrustCurveMotorComparator();
102 private final List<ThrustCurveMotorSet> database;
104 private final double diameter;
105 private CloseableDialog dialog = null;
108 private final ThrustCurveMotorDatabaseModel model;
109 private final JTable table;
110 private final TableRowSorter<TableModel> sorter;
112 private final JCheckBox hideSimilarBox;
114 private final JTextField searchField;
115 private String[] searchTerms = new String[0];
118 private final JLabel curveSelectionLabel;
119 private final JComboBox curveSelectionBox;
120 private final DefaultComboBoxModel curveSelectionModel;
122 private final JLabel totalImpulseLabel;
123 private final JLabel avgThrustLabel;
124 private final JLabel maxThrustLabel;
125 private final JLabel burnTimeLabel;
126 private final JLabel launchMassLabel;
127 private final JLabel emptyMassLabel;
128 private final JLabel dataPointsLabel;
129 private final JLabel digestLabel;
131 private final JTextArea comment;
132 private final Font noCommentFont;
133 private final Font withCommentFont;
135 private final JFreeChart chart;
136 private final ChartPanel chartPanel;
137 private final JLabel zoomIcon;
139 private final JComboBox delayBox;
141 private ThrustCurveMotor selectedMotor;
142 private ThrustCurveMotorSet selectedMotorSet;
143 private double selectedDelay;
149 * @param current the currently selected ThrustCurveMotor, or <code>null</code> for none.
150 * @param delay the currently selected ejection charge delay.
151 * @param diameter the diameter of the motor mount.
153 public ThrustCurveMotorSelectionPanel(ThrustCurveMotor current, double delay, double diameter) {
154 super(new MigLayout("fill", "[grow][]"));
156 this.diameter = diameter;
159 // Construct the database (adding the current motor if not in the db already)
160 List<ThrustCurveMotorSet> db;
161 db = Application.getMotorSetDatabase().getMotorSets();
163 // If current motor is not found in db, add a new ThrustCurveMotorSet containing it
164 if (current != null) {
165 selectedMotor = current;
166 for (ThrustCurveMotorSet motorSet : db) {
167 if (motorSet.getMotors().contains(current)) {
168 selectedMotorSet = motorSet;
172 if (selectedMotorSet == null) {
173 db = new ArrayList<ThrustCurveMotorSet>(db);
174 ThrustCurveMotorSet extra = new ThrustCurveMotorSet();
175 extra.addMotor(current);
176 selectedMotorSet = extra;
178 Collections.sort(db);
190 panel = new JPanel(new MigLayout("fill"));
191 this.add(panel, "grow");
196 //// Select rocket motor:
197 label = new StyledLabel("Select rocket motor:", Style.BOLD);
198 panel.add(label, "spanx, wrap para");
200 // Diameter selection
201 JComboBox filterComboBox = new JComboBox(SHOW_DESCRIPTIONS);
202 filterComboBox.addActionListener(new ActionListener() {
204 public void actionPerformed(ActionEvent e) {
205 JComboBox cb = (JComboBox) e.getSource();
206 int sel = cb.getSelectedIndex();
207 if ((sel < 0) || (sel > SHOW_MAX))
211 sorter.setRowFilter(new MotorRowFilterAll());
215 sorter.setRowFilter(new MotorRowFilterSmaller());
219 sorter.setRowFilter(new MotorRowFilterExact());
223 throw new BugException("Invalid selection mode sel=" + sel);
225 Prefs.putChoise("MotorDiameterMatch", sel);
226 scrollSelectionVisible();
229 panel.add(filterComboBox, "spanx, growx, wrap rel");
231 //// Hide very similar thrust curves
232 hideSimilarBox = new JCheckBox("Hide very similar thrust curves");
233 GUIUtil.changeFontSize(hideSimilarBox, -1);
234 hideSimilarBox.setSelected(Prefs.getBoolean(Prefs.MOTOR_HIDE_SIMILAR, true));
235 hideSimilarBox.addActionListener(new ActionListener() {
237 public void actionPerformed(ActionEvent e) {
238 Prefs.putBoolean(Prefs.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected());
242 panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para");
245 // Motor selection table
246 model = new ThrustCurveMotorDatabaseModel(database);
247 table = new JTable(model);
250 // Set comparators and widths
251 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
252 sorter = new TableRowSorter<TableModel>(model);
253 for (int i = 0; i < ThrustCurveMotorColumns.values().length; i++) {
254 ThrustCurveMotorColumns column = ThrustCurveMotorColumns.values()[i];
255 sorter.setComparator(i, column.getComparator());
256 table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
258 table.setRowSorter(sorter);
260 // Set selection and double-click listeners
261 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
263 public void valueChanged(ListSelectionEvent e) {
264 int row = table.getSelectedRow();
266 row = table.convertRowIndexToModel(row);
267 ThrustCurveMotorSet motorSet = model.getMotorSet(row);
268 log.user("Selected table row " + row + ": " + motorSet);
269 if (motorSet != selectedMotorSet) {
270 select(selectMotor(motorSet));
273 log.user("Selected table row " + row + ", nothing selected");
277 table.addMouseListener(new MouseAdapter() {
279 public void mouseClicked(MouseEvent e) {
280 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
281 if (dialog != null) {
289 JScrollPane scrollpane = new JScrollPane();
290 scrollpane.setViewportView(table);
291 panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para");
296 // Motor mount diameter label
297 //// Motor mount diameter:
298 label = new StyledLabel("Motor mount diameter: " +
299 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
300 panel.add(label, "gapright 30lp, spanx, split");
306 label = new StyledLabel("Search:");
307 panel.add(label, "");
309 searchField = new JTextField();
310 searchField.getDocument().addDocumentListener(new DocumentListener() {
312 public void changedUpdate(DocumentEvent e) {
317 public void insertUpdate(DocumentEvent e) {
322 public void removeUpdate(DocumentEvent e) {
326 private void update() {
327 String text = searchField.getText().trim();
328 String[] split = text.split("\\s+");
329 ArrayList<String> list = new ArrayList<String>();
330 for (String s : split) {
331 s = s.trim().toLowerCase();
332 if (s.length() > 0) {
336 searchTerms = list.toArray(new String[0]);
338 scrollSelectionVisible();
341 panel.add(searchField, "growx, wrap");
346 this.add(panel, "grow");
347 this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para");
348 panel = new JPanel(new MigLayout("fill"));
352 // Thrust curve selection
353 //// Select thrust curve:
354 curveSelectionLabel = new JLabel("Select thrust curve:");
355 panel.add(curveSelectionLabel);
357 curveSelectionModel = new DefaultComboBoxModel();
358 curveSelectionBox = new JComboBox(curveSelectionModel);
359 curveSelectionBox.setRenderer(new CurveSelectionRenderer(curveSelectionBox.getRenderer()));
360 curveSelectionBox.addActionListener(new ActionListener() {
362 public void actionPerformed(ActionEvent e) {
363 Object value = curveSelectionBox.getSelectedItem();
365 select(((MotorHolder) value).getMotor());
369 panel.add(curveSelectionBox, "growx, wrap para");
375 // Ejection charge delay:
376 panel.add(new JLabel("Ejection charge delay:"));
378 delayBox = new JComboBox();
379 delayBox.setEditable(true);
380 delayBox.addActionListener(new ActionListener() {
382 public void actionPerformed(ActionEvent e) {
383 JComboBox cb = (JComboBox) e.getSource();
384 String sel = (String) cb.getSelectedItem();
385 if (sel.equalsIgnoreCase("None")) {
386 selectedDelay = Motor.PLUGGED;
389 selectedDelay = Double.parseDouble(sel);
390 } catch (NumberFormatException ignore) {
396 panel.add(delayBox, "growx, wrap rel");
397 //// (Number of seconds or \"None\")
398 panel.add(new StyledLabel("(Number of seconds or \"None\")", -3), "skip, wrap para");
402 panel.add(new JSeparator(), "spanx, growx, wrap para");
408 panel.add(new JLabel("Total impulse:"));
409 totalImpulseLabel = new JLabel();
410 panel.add(totalImpulseLabel, "wrap");
413 panel.add(new JLabel("Avg. thrust:"));
414 avgThrustLabel = new JLabel();
415 panel.add(avgThrustLabel, "wrap");
418 panel.add(new JLabel("Max. thrust:"));
419 maxThrustLabel = new JLabel();
420 panel.add(maxThrustLabel, "wrap");
423 panel.add(new JLabel("Burn time:"));
424 burnTimeLabel = new JLabel();
425 panel.add(burnTimeLabel, "wrap");
428 panel.add(new JLabel("Launch mass:"));
429 launchMassLabel = new JLabel();
430 panel.add(launchMassLabel, "wrap");
433 panel.add(new JLabel("Empty mass:"));
434 emptyMassLabel = new JLabel();
435 panel.add(emptyMassLabel, "wrap");
438 panel.add(new JLabel("Data points:"));
439 dataPointsLabel = new JLabel();
440 panel.add(dataPointsLabel, "wrap para");
442 if (System.getProperty("openrocket.debug.motordigest") != null) {
444 panel.add(new JLabel("Digest:"));
445 digestLabel = new JLabel();
446 panel.add(digestLabel, "w :300:, wrap para");
452 comment = new JTextArea(5, 5);
453 GUIUtil.changeFontSize(comment, -2);
454 withCommentFont = comment.getFont();
455 noCommentFont = withCommentFont.deriveFont(Font.ITALIC);
456 comment.setLineWrap(true);
457 comment.setWrapStyleWord(true);
458 comment.setEditable(false);
459 scrollpane = new JScrollPane(comment);
460 panel.add(scrollpane, "spanx, growx, wrap para");
466 chart = ChartFactory.createXYLineChart(
471 PlotOrientation.VERTICAL,
478 // Add the data and formatting to the plot
479 XYPlot plot = chart.getXYPlot();
481 changeLabelFont(plot.getRangeAxis(), -2);
482 changeLabelFont(plot.getDomainAxis(), -2);
485 chart.setTitle(new TextTitle("Thrust curve:", this.getFont()));
486 chart.setBackgroundPaint(this.getBackground());
487 plot.setBackgroundPaint(Color.WHITE);
488 plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
489 plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
491 chartPanel = new ChartPanel(chart,
497 chartPanel.setMouseZoomable(false);
498 chartPanel.setPopupMenu(null);
499 chartPanel.setMouseWheelEnabled(false);
500 chartPanel.setRangeZoomable(false);
501 chartPanel.setDomainZoomable(false);
503 chartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
504 chartPanel.addMouseListener(new MouseAdapter() {
506 public void mouseClicked(MouseEvent e) {
507 if (selectedMotor == null || selectedMotorSet == null)
509 if (e.getButton() == MouseEvent.BUTTON1) {
511 List<ThrustCurveMotor> motors = getFilteredCurves();
512 ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(motors,
513 motors.indexOf(selectedMotor),
514 SwingUtilities.getWindowAncestor(ThrustCurveMotorSelectionPanel.this));
515 plotDialog.setVisible(true);
520 JLayeredPane layer = new CustomLayeredPane();
522 layer.setBorder(BorderFactory.createLineBorder(Color.BLUE));
524 layer.add(chartPanel, (Integer) 0);
526 zoomIcon = new JLabel(Icons.ZOOM_IN);
527 zoomIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
528 layer.add(zoomIcon, (Integer) 1);
531 panel.add(layer, "width 300:300:, height 180:180:, grow, spanx");
535 this.add(panel, "grow");
540 int showMode = Prefs.getChoise(Prefs.MOTOR_DIAMETER_FILTER, SHOW_MAX, SHOW_EXACT);
541 filterComboBox.setSelectedIndex(showMode);
544 // Update the panel data
546 selectedDelay = delay;
553 public Motor getSelectedMotor() {
554 return selectedMotor;
559 public double getSelectedDelay() {
560 return selectedDelay;
565 public JComponent getDefaultFocus() {
570 public void selectedMotor(Motor motorSelection) {
571 if (!(motorSelection instanceof ThrustCurveMotor)) {
572 log.error("Received argument that was not ThrustCurveMotor: " + motorSelection);
576 ThrustCurveMotor motor = (ThrustCurveMotor) motorSelection;
577 ThrustCurveMotorSet set = findMotorSet(motor);
579 log.error("Could not find set for motor:" + motorSelection);
583 // Store selected motor in preferences node, set all others to false
584 Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
585 for (ThrustCurveMotor m : set.getMotors()) {
586 String digest = MotorDigest.digestMotor(m);
587 prefs.putBoolean(digest, m == motor);
591 public void setCloseableDialog(CloseableDialog dialog) {
592 this.dialog = dialog;
597 private void changeLabelFont(ValueAxis axis, float size) {
598 Font font = axis.getTickLabelFont();
599 font = font.deriveFont(font.getSize2D() + size);
600 axis.setTickLabelFont(font);
605 * Called when a different motor is selected from within the panel.
607 private void select(ThrustCurveMotor motor) {
608 if (selectedMotor == motor)
611 ThrustCurveMotorSet set = findMotorSet(motor);
613 throw new BugException("Could not find motor from database, motor=" + motor);
616 boolean updateDelays = (selectedMotorSet != set);
618 selectedMotor = motor;
619 selectedMotorSet = set;
627 private void updateData() {
629 if (selectedMotorSet == null) {
631 curveSelectionModel.removeAllElements();
632 curveSelectionBox.setEnabled(false);
633 curveSelectionLabel.setEnabled(false);
634 totalImpulseLabel.setText("");
635 avgThrustLabel.setText("");
636 maxThrustLabel.setText("");
637 burnTimeLabel.setText("");
638 launchMassLabel.setText("");
639 emptyMassLabel.setText("");
640 dataPointsLabel.setText("");
641 if (digestLabel != null) {
642 digestLabel.setText("");
645 chart.getXYPlot().setDataset(new XYSeriesCollection());
650 // Check which thrust curves to display
651 List<ThrustCurveMotor> motors = getFilteredCurves();
652 final int index = motors.indexOf(selectedMotor);
655 // Update the thrust curve selection box
656 curveSelectionModel.removeAllElements();
657 for (int i = 0; i < motors.size(); i++) {
658 curveSelectionModel.addElement(new MotorHolder(motors.get(i), i));
660 curveSelectionBox.setSelectedIndex(index);
662 if (motors.size() > 1) {
663 curveSelectionBox.setEnabled(true);
664 curveSelectionLabel.setEnabled(true);
666 curveSelectionBox.setEnabled(false);
667 curveSelectionLabel.setEnabled(false);
671 // Update thrust curve data
672 totalImpulseLabel.setText(UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
673 selectedMotor.getTotalImpulseEstimate()));
674 avgThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
675 selectedMotor.getAverageThrustEstimate()));
676 maxThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
677 selectedMotor.getMaxThrustEstimate()));
678 burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
679 selectedMotor.getBurnTimeEstimate()));
680 launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
681 selectedMotor.getLaunchCG().weight));
682 emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
683 selectedMotor.getEmptyCG().weight));
684 dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1));
685 if (digestLabel != null) {
686 digestLabel.setText(MotorDigest.digestMotor(selectedMotor));
689 setComment(selectedMotor.getDescription());
693 XYPlot plot = chart.getXYPlot();
695 XYSeriesCollection dataset = new XYSeriesCollection();
696 for (int i = 0; i < motors.size(); i++) {
697 ThrustCurveMotor m = motors.get(i);
700 XYSeries series = new XYSeries("Thrust");
701 double[] time = m.getTimePoints();
702 double[] thrust = m.getThrustPoints();
704 for (int j = 0; j < time.length; j++) {
705 series.add(time[j], thrust[j]);
708 dataset.addSeries(series);
710 boolean selected = (i == index);
711 plot.getRenderer().setSeriesStroke(i, new BasicStroke(selected ? 3 : 1));
712 plot.getRenderer().setSeriesPaint(i, getColor(i));
715 plot.setDataset(dataset);
719 private List<ThrustCurveMotor> getFilteredCurves() {
720 List<ThrustCurveMotor> motors = selectedMotorSet.getMotors();
721 if (hideSimilarBox.isSelected()) {
722 List<ThrustCurveMotor> filtered = new ArrayList<ThrustCurveMotor>(motors.size());
723 for (int i = 0; i < motors.size(); i++) {
724 ThrustCurveMotor m = motors.get(i);
725 if (m.equals(selectedMotor)) {
730 double similarity = MotorCorrelation.similarity(selectedMotor, m);
731 log.debug("Motor similarity: " + similarity);
732 if (similarity < MOTOR_SIMILARITY_THRESHOLD) {
739 Collections.sort(motors, MOTOR_COMPARATOR);
745 private void setComment(String s) {
747 if (s.length() == 0) {
748 comment.setText("No description available.");
749 comment.setFont(noCommentFont);
750 comment.setForeground(NO_COMMENT_COLOR);
753 comment.setFont(withCommentFont);
754 comment.setForeground(WITH_COMMENT_COLOR);
756 comment.setCaretPosition(0);
759 private void scrollSelectionVisible() {
760 if (selectedMotorSet != null) {
761 int index = table.convertRowIndexToView(model.getIndex(selectedMotorSet));
762 System.out.println("index=" + index);
763 table.getSelectionModel().setSelectionInterval(index, index);
764 Rectangle rect = table.getCellRect(index, 0, true);
765 rect = new Rectangle(rect.x, rect.y - 100, rect.width, rect.height + 200);
766 table.scrollRectToVisible(rect);
771 public static Color getColor(int index) {
772 return (Color) CURVE_COLORS[index % CURVE_COLORS.length];
777 * Find the ThrustCurveMotorSet that contains a motor.
779 * @param motor the motor to look for.
780 * @return the ThrustCurveMotorSet, or null if not found.
782 private ThrustCurveMotorSet findMotorSet(ThrustCurveMotor motor) {
783 for (ThrustCurveMotorSet set : database) {
784 if (set.getMotors().contains(motor)) {
795 * Select the default motor from this ThrustCurveMotorSet. This uses primarily motors
796 * that the user has previously used, and secondarily a heuristic method of selecting which
797 * thrust curve seems to be better or more reliable.
799 * @param set the motor set
800 * @return the default motor in this set
802 private ThrustCurveMotor selectMotor(ThrustCurveMotorSet set) {
803 if (set.getMotorCount() == 0) {
804 throw new BugException("Attempting to select motor from empty ThrustCurveMotorSet: " + set);
806 if (set.getMotorCount() == 1) {
807 return set.getMotors().get(0);
811 // Find which motor has been used the most recently
812 List<ThrustCurveMotor> list = set.getMotors();
813 Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
814 for (ThrustCurveMotor m : list) {
815 String digest = MotorDigest.digestMotor(m);
816 if (prefs.getBoolean(digest, false)) {
821 // No motor has been used
822 Collections.sort(list, MOTOR_COMPARATOR);
828 * Set the values in the delay combo box. If <code>reset</code> is <code>true</code>
829 * then sets the selected value as the value closest to selectedDelay, otherwise
830 * leaves selection alone.
832 private void setDelays(boolean reset) {
833 if (selectedMotor == null) {
835 delayBox.setModel(new DefaultComboBoxModel(new String[] { "None" }));
836 delayBox.setSelectedIndex(0);
840 List<Double> delays = selectedMotorSet.getDelays();
841 String[] delayStrings = new String[delays.size()];
842 double currentDelay = selectedDelay; // Store current setting locally
844 for (int i = 0; i < delays.size(); i++) {
845 delayStrings[i] = ThrustCurveMotor.getDelayString(delays.get(i), "None");
847 delayBox.setModel(new DefaultComboBoxModel(delayStrings));
851 // Find and set the closest value
852 double closest = Double.NaN;
853 for (int i = 0; i < delays.size(); i++) {
854 // if-condition to always become true for NaN
855 if (!(Math.abs(delays.get(i) - currentDelay) > Math.abs(closest - currentDelay))) {
856 closest = delays.get(i);
859 if (!Double.isNaN(closest)) {
860 selectedDelay = closest;
861 delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(closest, "None"));
863 delayBox.setSelectedItem("None");
868 selectedDelay = currentDelay;
869 delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, "None"));
879 //////////////////////
882 private class CurveSelectionRenderer implements ListCellRenderer {
884 private final ListCellRenderer renderer;
886 public CurveSelectionRenderer(ListCellRenderer renderer) {
887 this.renderer = renderer;
891 public Component getListCellRendererComponent(JList list, Object value, int index,
892 boolean isSelected, boolean cellHasFocus) {
894 Component c = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
895 if (value instanceof MotorHolder) {
896 MotorHolder m = (MotorHolder) value;
897 c.setForeground(getColor(m.getIndex()));
909 * Abstract adapter class.
911 private abstract class MotorRowFilter extends RowFilter<TableModel, Integer> {
913 public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
914 int index = entry.getIdentifier();
915 ThrustCurveMotorSet m = model.getMotorSet(index);
916 return filterByDiameter(m) && filterByString(m);
919 public abstract boolean filterByDiameter(ThrustCurveMotorSet m);
922 public boolean filterByString(ThrustCurveMotorSet m) {
923 main: for (String s : searchTerms) {
924 for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) {
925 String str = col.getValue(m).toString().toLowerCase();
926 if (str.indexOf(s) >= 0)
938 private class MotorRowFilterAll extends MotorRowFilter {
940 public boolean filterByDiameter(ThrustCurveMotorSet m) {
946 * Show motors smaller than the mount.
948 private class MotorRowFilterSmaller extends MotorRowFilter {
950 public boolean filterByDiameter(ThrustCurveMotorSet m) {
951 return (m.getDiameter() <= diameter + 0.0004);
956 * Show motors that fit the mount.
958 private class MotorRowFilterExact extends MotorRowFilter {
960 public boolean filterByDiameter(ThrustCurveMotorSet m) {
961 return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015));
967 * Custom layered pane that sets the bounds of the components on every layout.
969 public class CustomLayeredPane extends JLayeredPane {
971 public void doLayout() {
972 synchronized (getTreeLock()) {
975 chartPanel.setBounds(0, 0, w, h);
976 zoomIcon.setBounds(w - ZOOM_ICON_POSITION_NEGATIVE_X, ZOOM_ICON_POSITION_POSITIVE_Y, 50, 50);