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.Locale;
18 import java.util.prefs.Preferences;
20 import javax.swing.BorderFactory;
21 import javax.swing.DefaultComboBoxModel;
22 import javax.swing.JCheckBox;
23 import javax.swing.JComboBox;
24 import javax.swing.JComponent;
25 import javax.swing.JLabel;
26 import javax.swing.JLayeredPane;
27 import javax.swing.JList;
28 import javax.swing.JPanel;
29 import javax.swing.JScrollPane;
30 import javax.swing.JSeparator;
31 import javax.swing.JTable;
32 import javax.swing.JTextArea;
33 import javax.swing.JTextField;
34 import javax.swing.ListCellRenderer;
35 import javax.swing.ListSelectionModel;
36 import javax.swing.RowFilter;
37 import javax.swing.SwingUtilities;
38 import javax.swing.event.DocumentEvent;
39 import javax.swing.event.DocumentListener;
40 import javax.swing.event.ListSelectionEvent;
41 import javax.swing.event.ListSelectionListener;
42 import javax.swing.table.TableModel;
43 import javax.swing.table.TableRowSorter;
45 import net.miginfocom.swing.MigLayout;
46 import net.sf.openrocket.database.ThrustCurveMotorSet;
47 import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
48 import net.sf.openrocket.gui.components.StyledLabel;
49 import net.sf.openrocket.gui.components.StyledLabel.Style;
50 import net.sf.openrocket.gui.dialogs.motor.CloseableDialog;
51 import net.sf.openrocket.gui.dialogs.motor.MotorSelector;
52 import net.sf.openrocket.gui.util.GUIUtil;
53 import net.sf.openrocket.gui.util.Icons;
54 import net.sf.openrocket.gui.util.SwingPreferences;
55 import net.sf.openrocket.l10n.Translator;
56 import net.sf.openrocket.logging.LogHelper;
57 import net.sf.openrocket.motor.Motor;
58 import net.sf.openrocket.motor.ThrustCurveMotor;
59 import net.sf.openrocket.startup.Application;
60 import net.sf.openrocket.unit.UnitGroup;
61 import net.sf.openrocket.util.BugException;
62 import net.sf.openrocket.utils.MotorCorrelation;
64 import org.jfree.chart.ChartColor;
65 import org.jfree.chart.ChartFactory;
66 import org.jfree.chart.ChartPanel;
67 import org.jfree.chart.JFreeChart;
68 import org.jfree.chart.axis.ValueAxis;
69 import org.jfree.chart.plot.PlotOrientation;
70 import org.jfree.chart.plot.XYPlot;
71 import org.jfree.chart.title.TextTitle;
72 import org.jfree.data.xy.XYSeries;
73 import org.jfree.data.xy.XYSeriesCollection;
75 public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelector {
76 private static final LogHelper log = Application.getLogger();
77 private static final Translator trans = Application.getTranslator();
79 private static final double MOTOR_SIMILARITY_THRESHOLD = 0.95;
81 private static final int SHOW_ALL = 0;
82 private static final int SHOW_SMALLER = 1;
83 private static final int SHOW_EXACT = 2;
84 private static final String[] SHOW_DESCRIPTIONS = {
86 trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc1"),
87 //// Show motors with diameter less than that of the motor mount
88 trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2"),
89 //// Show motors with diameter equal to that of the motor mount
90 trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3")
92 private static final int SHOW_MAX = 2;
94 private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50;
95 private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12;
97 private static final Paint[] CURVE_COLORS = ChartColor.createDefaultPaintArray();
99 private static final Color NO_COMMENT_COLOR = Color.GRAY;
100 private static final Color WITH_COMMENT_COLOR = Color.BLACK;
102 private static final ThrustCurveMotorComparator MOTOR_COMPARATOR = new ThrustCurveMotorComparator();
106 private final List<ThrustCurveMotorSet> database;
108 private final double diameter;
109 private CloseableDialog dialog = null;
112 private final ThrustCurveMotorDatabaseModel model;
113 private final JTable table;
114 private final TableRowSorter<TableModel> sorter;
116 private final JCheckBox hideSimilarBox;
118 private final JTextField searchField;
119 private String[] searchTerms = new String[0];
122 private final JLabel curveSelectionLabel;
123 private final JComboBox curveSelectionBox;
124 private final DefaultComboBoxModel curveSelectionModel;
126 private final JLabel totalImpulseLabel;
127 private final JLabel classificationLabel;
128 private final JLabel avgThrustLabel;
129 private final JLabel maxThrustLabel;
130 private final JLabel burnTimeLabel;
131 private final JLabel launchMassLabel;
132 private final JLabel emptyMassLabel;
133 private final JLabel dataPointsLabel;
134 private final JLabel digestLabel;
136 private final JTextArea comment;
137 private final Font noCommentFont;
138 private final Font withCommentFont;
140 private final JFreeChart chart;
141 private final ChartPanel chartPanel;
142 private final JLabel zoomIcon;
144 private final JComboBox delayBox;
146 private ThrustCurveMotor selectedMotor;
147 private ThrustCurveMotorSet selectedMotorSet;
148 private double selectedDelay;
154 * @param current the currently selected ThrustCurveMotor, or <code>null</code> for none.
155 * @param delay the currently selected ejection charge delay.
156 * @param diameter the diameter of the motor mount.
158 public ThrustCurveMotorSelectionPanel(ThrustCurveMotor current, double delay, double diameter) {
159 super(new MigLayout("fill", "[grow][]"));
161 this.diameter = diameter;
164 // Construct the database (adding the current motor if not in the db already)
165 List<ThrustCurveMotorSet> db;
166 // TODO - ugly blind cast.
167 db = ((ThrustCurveMotorSetDatabase) Application.getMotorSetDatabase()).getMotorSets();
169 // If current motor is not found in db, add a new ThrustCurveMotorSet containing it
170 if (current != null) {
171 selectedMotor = current;
172 for (ThrustCurveMotorSet motorSet : db) {
173 if (motorSet.getMotors().contains(current)) {
174 selectedMotorSet = motorSet;
178 if (selectedMotorSet == null) {
179 db = new ArrayList<ThrustCurveMotorSet>(db);
180 ThrustCurveMotorSet extra = new ThrustCurveMotorSet();
181 extra.addMotor(current);
182 selectedMotorSet = extra;
184 Collections.sort(db);
196 panel = new JPanel(new MigLayout("fill"));
197 this.add(panel, "grow");
202 //// Select rocket motor:
203 label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Selrocketmotor"), Style.BOLD);
204 panel.add(label, "spanx, wrap para");
206 // Diameter selection
207 JComboBox filterComboBox = new JComboBox(SHOW_DESCRIPTIONS);
208 filterComboBox.addActionListener(new ActionListener() {
210 public void actionPerformed(ActionEvent e) {
211 JComboBox cb = (JComboBox) e.getSource();
212 int sel = cb.getSelectedIndex();
213 if ((sel < 0) || (sel > SHOW_MAX))
217 sorter.setRowFilter(new MotorRowFilterAll());
221 sorter.setRowFilter(new MotorRowFilterSmaller());
225 sorter.setRowFilter(new MotorRowFilterExact());
229 throw new BugException("Invalid selection mode sel=" + sel);
231 Application.getPreferences().putChoice("MotorDiameterMatch", sel);
232 scrollSelectionVisible();
235 panel.add(filterComboBox, "spanx, growx, wrap rel");
237 //// Hide very similar thrust curves
238 hideSimilarBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideSimilar"));
239 GUIUtil.changeFontSize(hideSimilarBox, -1);
240 hideSimilarBox.setSelected(Application.getPreferences().getBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, true));
241 hideSimilarBox.addActionListener(new ActionListener() {
243 public void actionPerformed(ActionEvent e) {
244 Application.getPreferences().putBoolean(net.sf.openrocket.startup.Preferences.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected());
248 panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para");
251 // Motor selection table
252 model = new ThrustCurveMotorDatabaseModel(database);
253 table = new JTable(model);
256 // Set comparators and widths
257 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
258 sorter = new TableRowSorter<TableModel>(model);
259 for (int i = 0; i < ThrustCurveMotorColumns.values().length; i++) {
260 ThrustCurveMotorColumns column = ThrustCurveMotorColumns.values()[i];
261 sorter.setComparator(i, column.getComparator());
262 table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
264 table.setRowSorter(sorter);
266 // Set selection and double-click listeners
267 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
269 public void valueChanged(ListSelectionEvent e) {
270 int row = table.getSelectedRow();
272 row = table.convertRowIndexToModel(row);
273 ThrustCurveMotorSet motorSet = model.getMotorSet(row);
274 log.user("Selected table row " + row + ": " + motorSet);
275 if (motorSet != selectedMotorSet) {
276 select(selectMotor(motorSet));
279 log.user("Selected table row " + row + ", nothing selected");
283 table.addMouseListener(new MouseAdapter() {
285 public void mouseClicked(MouseEvent e) {
286 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
287 if (dialog != null) {
295 JScrollPane scrollpane = new JScrollPane();
296 scrollpane.setViewportView(table);
297 panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para");
302 // Motor mount diameter label
303 //// Motor mount diameter:
304 label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Motormountdia") + " " +
305 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
306 panel.add(label, "gapright 30lp, spanx, split");
312 label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Search"));
313 panel.add(label, "");
315 searchField = new JTextField();
316 searchField.getDocument().addDocumentListener(new DocumentListener() {
318 public void changedUpdate(DocumentEvent e) {
323 public void insertUpdate(DocumentEvent e) {
328 public void removeUpdate(DocumentEvent e) {
332 private void update() {
333 String text = searchField.getText().trim();
334 String[] split = text.split("\\s+");
335 ArrayList<String> list = new ArrayList<String>();
336 for (String s : split) {
337 s = s.trim().toLowerCase(Locale.getDefault());
338 if (s.length() > 0) {
342 searchTerms = list.toArray(new String[0]);
344 scrollSelectionVisible();
347 panel.add(searchField, "growx, wrap");
352 this.add(panel, "grow");
353 this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para");
354 panel = new JPanel(new MigLayout("fill"));
358 // Thrust curve selection
359 //// Select thrust curve:
360 curveSelectionLabel = new JLabel(trans.get("TCMotorSelPan.lbl.Selectthrustcurve"));
361 panel.add(curveSelectionLabel);
363 curveSelectionModel = new DefaultComboBoxModel();
364 curveSelectionBox = new JComboBox(curveSelectionModel);
365 curveSelectionBox.setRenderer(new CurveSelectionRenderer(curveSelectionBox.getRenderer()));
366 curveSelectionBox.addActionListener(new ActionListener() {
368 public void actionPerformed(ActionEvent e) {
369 Object value = curveSelectionBox.getSelectedItem();
371 select(((MotorHolder) value).getMotor());
375 panel.add(curveSelectionBox, "growx, wrap para");
381 // Ejection charge delay:
382 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Ejectionchargedelay")));
384 delayBox = new JComboBox();
385 delayBox.setEditable(true);
386 delayBox.addActionListener(new ActionListener() {
388 public void actionPerformed(ActionEvent e) {
389 JComboBox cb = (JComboBox) e.getSource();
390 String sel = (String) cb.getSelectedItem();
392 if (sel.equalsIgnoreCase(trans.get("TCMotorSelPan.equalsIgnoreCase.None"))) {
393 selectedDelay = Motor.PLUGGED;
396 selectedDelay = Double.parseDouble(sel);
397 } catch (NumberFormatException ignore) {
403 panel.add(delayBox, "growx, wrap rel");
404 //// (Number of seconds or \"None\")
405 panel.add(new StyledLabel(trans.get("TCMotorSelPan.lbl.NumberofsecondsorNone"), -3), "skip, wrap para");
409 panel.add(new JSeparator(), "spanx, growx, wrap para");
415 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Totalimpulse")));
416 totalImpulseLabel = new JLabel();
417 panel.add(totalImpulseLabel, "split");
418 classificationLabel = new JLabel();
419 classificationLabel.setEnabled(false); // Gray out
420 panel.add(classificationLabel, "gapleft unrel, wrap");
423 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Avgthrust")));
424 avgThrustLabel = new JLabel();
425 panel.add(avgThrustLabel, "wrap");
428 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Maxthrust")));
429 maxThrustLabel = new JLabel();
430 panel.add(maxThrustLabel, "wrap");
433 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Burntime")));
434 burnTimeLabel = new JLabel();
435 panel.add(burnTimeLabel, "wrap");
438 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Launchmass")));
439 launchMassLabel = new JLabel();
440 panel.add(launchMassLabel, "wrap");
443 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Emptymass")));
444 emptyMassLabel = new JLabel();
445 panel.add(emptyMassLabel, "wrap");
448 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Datapoints")));
449 dataPointsLabel = new JLabel();
450 panel.add(dataPointsLabel, "wrap para");
452 if (System.getProperty("openrocket.debug.motordigest") != null) {
454 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Digest")));
455 digestLabel = new JLabel();
456 panel.add(digestLabel, "w :300:, wrap para");
462 comment = new JTextArea(5, 5);
463 GUIUtil.changeFontSize(comment, -2);
464 withCommentFont = comment.getFont();
465 noCommentFont = withCommentFont.deriveFont(Font.ITALIC);
466 comment.setLineWrap(true);
467 comment.setWrapStyleWord(true);
468 comment.setEditable(false);
469 scrollpane = new JScrollPane(comment);
470 panel.add(scrollpane, "spanx, growx, wrap para");
476 chart = ChartFactory.createXYLineChart(
481 PlotOrientation.VERTICAL,
488 // Add the data and formatting to the plot
489 XYPlot plot = chart.getXYPlot();
491 changeLabelFont(plot.getRangeAxis(), -2);
492 changeLabelFont(plot.getDomainAxis(), -2);
495 chart.setTitle(new TextTitle(trans.get("TCMotorSelPan.title.Thrustcurve"), this.getFont()));
496 chart.setBackgroundPaint(this.getBackground());
497 plot.setBackgroundPaint(Color.WHITE);
498 plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
499 plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
501 chartPanel = new ChartPanel(chart,
507 chartPanel.setMouseZoomable(false);
508 chartPanel.setPopupMenu(null);
509 chartPanel.setMouseWheelEnabled(false);
510 chartPanel.setRangeZoomable(false);
511 chartPanel.setDomainZoomable(false);
513 chartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
514 chartPanel.addMouseListener(new MouseAdapter() {
516 public void mouseClicked(MouseEvent e) {
517 if (selectedMotor == null || selectedMotorSet == null)
519 if (e.getButton() == MouseEvent.BUTTON1) {
521 List<ThrustCurveMotor> motors = getFilteredCurves();
522 ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(motors,
523 motors.indexOf(selectedMotor),
524 SwingUtilities.getWindowAncestor(ThrustCurveMotorSelectionPanel.this));
525 plotDialog.setVisible(true);
530 JLayeredPane layer = new CustomLayeredPane();
532 layer.setBorder(BorderFactory.createLineBorder(Color.BLUE));
534 layer.add(chartPanel, (Integer) 0);
536 zoomIcon = new JLabel(Icons.ZOOM_IN);
537 zoomIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
538 layer.add(zoomIcon, (Integer) 1);
541 panel.add(layer, "width 300:300:, height 180:180:, grow, spanx");
545 this.add(panel, "grow");
550 int showMode = Application.getPreferences().getChoice(net.sf.openrocket.startup.Preferences.MOTOR_DIAMETER_FILTER, SHOW_MAX, SHOW_EXACT);
551 filterComboBox.setSelectedIndex(showMode);
554 // Update the panel data
556 selectedDelay = delay;
563 public Motor getSelectedMotor() {
564 return selectedMotor;
569 public double getSelectedDelay() {
570 return selectedDelay;
575 public JComponent getDefaultFocus() {
580 public void selectedMotor(Motor motorSelection) {
581 if (!(motorSelection instanceof ThrustCurveMotor)) {
582 log.error("Received argument that was not ThrustCurveMotor: " + motorSelection);
586 ThrustCurveMotor motor = (ThrustCurveMotor) motorSelection;
587 ThrustCurveMotorSet set = findMotorSet(motor);
589 log.error("Could not find set for motor:" + motorSelection);
593 // Store selected motor in preferences node, set all others to false
594 Preferences prefs = ((SwingPreferences) Application.getPreferences()).getNode(net.sf.openrocket.startup.Preferences.PREFERRED_THRUST_CURVE_MOTOR_NODE);
595 for (ThrustCurveMotor m : set.getMotors()) {
596 String digest = m.getDigest();
597 prefs.putBoolean(digest, m == motor);
601 public void setCloseableDialog(CloseableDialog dialog) {
602 this.dialog = dialog;
607 private void changeLabelFont(ValueAxis axis, float size) {
608 Font font = axis.getTickLabelFont();
609 font = font.deriveFont(font.getSize2D() + size);
610 axis.setTickLabelFont(font);
615 * Called when a different motor is selected from within the panel.
617 private void select(ThrustCurveMotor motor) {
618 if (selectedMotor == motor)
621 ThrustCurveMotorSet set = findMotorSet(motor);
623 throw new BugException("Could not find motor from database, motor=" + motor);
626 boolean updateDelays = (selectedMotorSet != set);
628 selectedMotor = motor;
629 selectedMotorSet = set;
637 private void updateData() {
639 if (selectedMotorSet == null) {
641 curveSelectionModel.removeAllElements();
642 curveSelectionBox.setEnabled(false);
643 curveSelectionLabel.setEnabled(false);
644 totalImpulseLabel.setText("");
645 totalImpulseLabel.setToolTipText(null);
646 classificationLabel.setText("");
647 classificationLabel.setToolTipText(null);
648 avgThrustLabel.setText("");
649 maxThrustLabel.setText("");
650 burnTimeLabel.setText("");
651 launchMassLabel.setText("");
652 emptyMassLabel.setText("");
653 dataPointsLabel.setText("");
654 if (digestLabel != null) {
655 digestLabel.setText("");
658 chart.getXYPlot().setDataset(new XYSeriesCollection());
663 // Check which thrust curves to display
664 List<ThrustCurveMotor> motors = getFilteredCurves();
665 final int index = motors.indexOf(selectedMotor);
668 // Update the thrust curve selection box
669 curveSelectionModel.removeAllElements();
670 for (int i = 0; i < motors.size(); i++) {
671 curveSelectionModel.addElement(new MotorHolder(motors.get(i), i));
673 curveSelectionBox.setSelectedIndex(index);
675 if (motors.size() > 1) {
676 curveSelectionBox.setEnabled(true);
677 curveSelectionLabel.setEnabled(true);
679 curveSelectionBox.setEnabled(false);
680 curveSelectionLabel.setEnabled(false);
684 // Update thrust curve data
685 double impulse = selectedMotor.getTotalImpulseEstimate();
686 MotorClass mc = MotorClass.getMotorClass(impulse);
687 totalImpulseLabel.setText(UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(impulse));
688 classificationLabel.setText("(" + mc.getDescription(impulse) + ")");
689 totalImpulseLabel.setToolTipText(mc.getClassDescription());
690 classificationLabel.setToolTipText(mc.getClassDescription());
692 avgThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
693 selectedMotor.getAverageThrustEstimate()));
694 maxThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
695 selectedMotor.getMaxThrustEstimate()));
696 burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
697 selectedMotor.getBurnTimeEstimate()));
698 launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
699 selectedMotor.getLaunchCG().weight));
700 emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
701 selectedMotor.getEmptyCG().weight));
702 dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1));
703 if (digestLabel != null) {
704 digestLabel.setText(selectedMotor.getDigest());
707 setComment(selectedMotor.getDescription());
711 XYPlot plot = chart.getXYPlot();
713 XYSeriesCollection dataset = new XYSeriesCollection();
714 for (int i = 0; i < motors.size(); i++) {
715 ThrustCurveMotor m = motors.get(i);
718 XYSeries series = new XYSeries(trans.get("TCMotorSelPan.title.Thrust"));
719 double[] time = m.getTimePoints();
720 double[] thrust = m.getThrustPoints();
722 for (int j = 0; j < time.length; j++) {
723 series.add(time[j], thrust[j]);
726 dataset.addSeries(series);
728 boolean selected = (i == index);
729 plot.getRenderer().setSeriesStroke(i, new BasicStroke(selected ? 3 : 1));
730 plot.getRenderer().setSeriesPaint(i, getColor(i));
733 plot.setDataset(dataset);
736 private List<ThrustCurveMotor> getFilteredCurves() {
737 List<ThrustCurveMotor> motors = selectedMotorSet.getMotors();
738 if (hideSimilarBox.isSelected()) {
739 List<ThrustCurveMotor> filtered = new ArrayList<ThrustCurveMotor>(motors.size());
740 for (int i = 0; i < motors.size(); i++) {
741 ThrustCurveMotor m = motors.get(i);
742 if (m.equals(selectedMotor)) {
747 double similarity = MotorCorrelation.similarity(selectedMotor, m);
748 log.debug("Motor similarity: " + similarity);
749 if (similarity < MOTOR_SIMILARITY_THRESHOLD) {
756 Collections.sort(motors, MOTOR_COMPARATOR);
762 private void setComment(String s) {
764 if (s.length() == 0) {
765 //// No description available.
766 comment.setText("No description available.");
767 comment.setFont(noCommentFont);
768 comment.setForeground(NO_COMMENT_COLOR);
771 comment.setFont(withCommentFont);
772 comment.setForeground(WITH_COMMENT_COLOR);
774 comment.setCaretPosition(0);
777 private void scrollSelectionVisible() {
778 if (selectedMotorSet != null) {
779 int index = table.convertRowIndexToView(model.getIndex(selectedMotorSet));
780 System.out.println("index=" + index);
781 table.getSelectionModel().setSelectionInterval(index, index);
782 Rectangle rect = table.getCellRect(index, 0, true);
783 rect = new Rectangle(rect.x, rect.y - 100, rect.width, rect.height + 200);
784 table.scrollRectToVisible(rect);
789 public static Color getColor(int index) {
790 return (Color) CURVE_COLORS[index % CURVE_COLORS.length];
795 * Find the ThrustCurveMotorSet that contains a motor.
797 * @param motor the motor to look for.
798 * @return the ThrustCurveMotorSet, or null if not found.
800 private ThrustCurveMotorSet findMotorSet(ThrustCurveMotor motor) {
801 for (ThrustCurveMotorSet set : database) {
802 if (set.getMotors().contains(motor)) {
813 * Select the default motor from this ThrustCurveMotorSet. This uses primarily motors
814 * that the user has previously used, and secondarily a heuristic method of selecting which
815 * thrust curve seems to be better or more reliable.
817 * @param set the motor set
818 * @return the default motor in this set
820 private ThrustCurveMotor selectMotor(ThrustCurveMotorSet set) {
821 if (set.getMotorCount() == 0) {
822 throw new BugException("Attempting to select motor from empty ThrustCurveMotorSet: " + set);
824 if (set.getMotorCount() == 1) {
825 return set.getMotors().get(0);
829 // Find which motor has been used the most recently
830 List<ThrustCurveMotor> list = set.getMotors();
831 Preferences prefs = ((SwingPreferences) Application.getPreferences()).getNode(net.sf.openrocket.startup.Preferences.PREFERRED_THRUST_CURVE_MOTOR_NODE);
832 for (ThrustCurveMotor m : list) {
833 String digest = m.getDigest();
834 if (prefs.getBoolean(digest, false)) {
839 // No motor has been used
840 Collections.sort(list, MOTOR_COMPARATOR);
846 * Set the values in the delay combo box. If <code>reset</code> is <code>true</code>
847 * then sets the selected value as the value closest to selectedDelay, otherwise
848 * leaves selection alone.
850 private void setDelays(boolean reset) {
851 if (selectedMotor == null) {
854 delayBox.setModel(new DefaultComboBoxModel(new String[] { trans.get("TCMotorSelPan.delayBox.None") }));
855 delayBox.setSelectedIndex(0);
859 List<Double> delays = selectedMotorSet.getDelays();
860 String[] delayStrings = new String[delays.size()];
861 double currentDelay = selectedDelay; // Store current setting locally
863 for (int i = 0; i < delays.size(); i++) {
865 delayStrings[i] = ThrustCurveMotor.getDelayString(delays.get(i), trans.get("TCMotorSelPan.delayBox.None"));
867 delayBox.setModel(new DefaultComboBoxModel(delayStrings));
871 // Find and set the closest value
872 double closest = Double.NaN;
873 for (int i = 0; i < delays.size(); i++) {
874 // if-condition to always become true for NaN
875 if (!(Math.abs(delays.get(i) - currentDelay) > Math.abs(closest - currentDelay))) {
876 closest = delays.get(i);
879 if (!Double.isNaN(closest)) {
880 selectedDelay = closest;
882 delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(closest, trans.get("TCMotorSelPan.delayBox.None")));
884 delayBox.setSelectedItem("None");
889 selectedDelay = currentDelay;
891 delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, trans.get("TCMotorSelPan.delayBox.None")));
901 //////////////////////
904 private class CurveSelectionRenderer implements ListCellRenderer {
906 private final ListCellRenderer renderer;
908 public CurveSelectionRenderer(ListCellRenderer renderer) {
909 this.renderer = renderer;
913 public Component getListCellRendererComponent(JList list, Object value, int index,
914 boolean isSelected, boolean cellHasFocus) {
916 Component c = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
917 if (value instanceof MotorHolder) {
918 MotorHolder m = (MotorHolder) value;
919 c.setForeground(getColor(m.getIndex()));
931 * Abstract adapter class.
933 private abstract class MotorRowFilter extends RowFilter<TableModel, Integer> {
935 public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
936 int index = entry.getIdentifier();
937 ThrustCurveMotorSet m = model.getMotorSet(index);
938 return filterByDiameter(m) && filterByString(m);
941 public abstract boolean filterByDiameter(ThrustCurveMotorSet m);
944 public boolean filterByString(ThrustCurveMotorSet m) {
945 main: for (String s : searchTerms) {
946 for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) {
947 String str = col.getValue(m).toString().toLowerCase(Locale.getDefault());
948 if (str.indexOf(s) >= 0)
960 private class MotorRowFilterAll extends MotorRowFilter {
962 public boolean filterByDiameter(ThrustCurveMotorSet m) {
968 * Show motors smaller than the mount.
970 private class MotorRowFilterSmaller extends MotorRowFilter {
972 public boolean filterByDiameter(ThrustCurveMotorSet m) {
973 return (m.getDiameter() <= diameter + 0.0004);
978 * Show motors that fit the mount.
980 private class MotorRowFilterExact extends MotorRowFilter {
982 public boolean filterByDiameter(ThrustCurveMotorSet m) {
983 return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015));
989 * Custom layered pane that sets the bounds of the components on every layout.
991 public class CustomLayeredPane extends JLayeredPane {
993 public void doLayout() {
994 synchronized (getTreeLock()) {
997 chartPanel.setBounds(0, 0, w, h);
998 zoomIcon.setBounds(w - ZOOM_ICON_POSITION_NEGATIVE_X, ZOOM_ICON_POSITION_POSITIVE_Y, 50, 50);