I18 changes
[debian/openrocket] / src / net / sf / openrocket / gui / dialogs / motor / thrustcurve / ThrustCurveMotorSelectionPanel.java
1 package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.Cursor;
7 import java.awt.Font;
8 import java.awt.Paint;
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;
18
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;
43
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;
62
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;
73
74 public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelector {
75         private static final LogHelper log = Application.getLogger();
76         private static final Translator trans = Application.getTranslator();
77
78         private static final double MOTOR_SIMILARITY_THRESHOLD = 0.95;
79         
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 = {
84                 //// Show all motors
85                 trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc1"),
86                         //// Show motors with diameter less than that of the motor mount
87                 trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc2"),
88                         //// Show motors with diameter equal to that of the motor mount
89                 trans.get("TCMotorSelPan.SHOW_DESCRIPTIONS.desc3")
90         };
91         private static final int SHOW_MAX = 2;
92         
93         private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50;
94         private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12;
95         
96         private static final Paint[] CURVE_COLORS = ChartColor.createDefaultPaintArray();
97         
98         private static final Color NO_COMMENT_COLOR = Color.GRAY;
99         private static final Color WITH_COMMENT_COLOR = Color.BLACK;
100         
101         private static final ThrustCurveMotorComparator MOTOR_COMPARATOR = new ThrustCurveMotorComparator();
102         
103
104
105         private final List<ThrustCurveMotorSet> database;
106         
107         private final double diameter;
108         private CloseableDialog dialog = null;
109         
110
111         private final ThrustCurveMotorDatabaseModel model;
112         private final JTable table;
113         private final TableRowSorter<TableModel> sorter;
114         
115         private final JCheckBox hideSimilarBox;
116         
117         private final JTextField searchField;
118         private String[] searchTerms = new String[0];
119         
120
121         private final JLabel curveSelectionLabel;
122         private final JComboBox curveSelectionBox;
123         private final DefaultComboBoxModel curveSelectionModel;
124         
125         private final JLabel totalImpulseLabel;
126         private final JLabel avgThrustLabel;
127         private final JLabel maxThrustLabel;
128         private final JLabel burnTimeLabel;
129         private final JLabel launchMassLabel;
130         private final JLabel emptyMassLabel;
131         private final JLabel dataPointsLabel;
132         private final JLabel digestLabel;
133         
134         private final JTextArea comment;
135         private final Font noCommentFont;
136         private final Font withCommentFont;
137         
138         private final JFreeChart chart;
139         private final ChartPanel chartPanel;
140         private final JLabel zoomIcon;
141         
142         private final JComboBox delayBox;
143         
144         private ThrustCurveMotor selectedMotor;
145         private ThrustCurveMotorSet selectedMotorSet;
146         private double selectedDelay;
147         
148         
149         /**
150          * Sole constructor.
151          * 
152          * @param current       the currently selected ThrustCurveMotor, or <code>null</code> for none.
153          * @param delay         the currently selected ejection charge delay.
154          * @param diameter      the diameter of the motor mount.
155          */
156         public ThrustCurveMotorSelectionPanel(ThrustCurveMotor current, double delay, double diameter) {
157                 super(new MigLayout("fill", "[grow][]"));
158                 
159                 this.diameter = diameter;
160                 
161
162                 // Construct the database (adding the current motor if not in the db already)
163                 List<ThrustCurveMotorSet> db;
164                 db = Application.getMotorSetDatabase().getMotorSets();
165                 
166                 // If current motor is not found in db, add a new ThrustCurveMotorSet containing it
167                 if (current != null) {
168                         selectedMotor = current;
169                         for (ThrustCurveMotorSet motorSet : db) {
170                                 if (motorSet.getMotors().contains(current)) {
171                                         selectedMotorSet = motorSet;
172                                         break;
173                                 }
174                         }
175                         if (selectedMotorSet == null) {
176                                 db = new ArrayList<ThrustCurveMotorSet>(db);
177                                 ThrustCurveMotorSet extra = new ThrustCurveMotorSet();
178                                 extra.addMotor(current);
179                                 selectedMotorSet = extra;
180                                 db.add(extra);
181                                 Collections.sort(db);
182                         }
183                 }
184                 database = db;
185                 
186
187
188                 ////  GUI
189                 
190                 JPanel panel;
191                 JLabel label;
192                 
193                 panel = new JPanel(new MigLayout("fill"));
194                 this.add(panel, "grow");
195                 
196
197
198                 // Selection label
199                 //// Select rocket motor:
200                 label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Selrocketmotor"), Style.BOLD);
201                 panel.add(label, "spanx, wrap para");
202                 
203                 // Diameter selection
204                 JComboBox filterComboBox = new JComboBox(SHOW_DESCRIPTIONS);
205                 filterComboBox.addActionListener(new ActionListener() {
206                         @Override
207                         public void actionPerformed(ActionEvent e) {
208                                 JComboBox cb = (JComboBox) e.getSource();
209                                 int sel = cb.getSelectedIndex();
210                                 if ((sel < 0) || (sel > SHOW_MAX))
211                                         sel = SHOW_ALL;
212                                 switch (sel) {
213                                 case SHOW_ALL:
214                                         sorter.setRowFilter(new MotorRowFilterAll());
215                                         break;
216                                 
217                                 case SHOW_SMALLER:
218                                         sorter.setRowFilter(new MotorRowFilterSmaller());
219                                         break;
220                                 
221                                 case SHOW_EXACT:
222                                         sorter.setRowFilter(new MotorRowFilterExact());
223                                         break;
224                                 
225                                 default:
226                                         throw new BugException("Invalid selection mode sel=" + sel);
227                                 }
228                                 Prefs.putChoise("MotorDiameterMatch", sel);
229                                 scrollSelectionVisible();
230                         }
231                 });
232                 panel.add(filterComboBox, "spanx, growx, wrap rel");
233                 
234                 //// Hide very similar thrust curves
235                 hideSimilarBox = new JCheckBox(trans.get("TCMotorSelPan.checkbox.hideSimilar"));
236                 GUIUtil.changeFontSize(hideSimilarBox, -1);
237                 hideSimilarBox.setSelected(Prefs.getBoolean(Prefs.MOTOR_HIDE_SIMILAR, true));
238                 hideSimilarBox.addActionListener(new ActionListener() {
239                         @Override
240                         public void actionPerformed(ActionEvent e) {
241                                 Prefs.putBoolean(Prefs.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected());
242                                 updateData();
243                         }
244                 });
245                 panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para");
246                 
247
248                 // Motor selection table
249                 model = new ThrustCurveMotorDatabaseModel(database);
250                 table = new JTable(model);
251                 
252
253                 // Set comparators and widths
254                 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
255                 sorter = new TableRowSorter<TableModel>(model);
256                 for (int i = 0; i < ThrustCurveMotorColumns.values().length; i++) {
257                         ThrustCurveMotorColumns column = ThrustCurveMotorColumns.values()[i];
258                         sorter.setComparator(i, column.getComparator());
259                         table.getColumnModel().getColumn(i).setPreferredWidth(column.getWidth());
260                 }
261                 table.setRowSorter(sorter);
262                 
263                 // Set selection and double-click listeners
264                 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
265                         @Override
266                         public void valueChanged(ListSelectionEvent e) {
267                                 int row = table.getSelectedRow();
268                                 if (row >= 0) {
269                                         row = table.convertRowIndexToModel(row);
270                                         ThrustCurveMotorSet motorSet = model.getMotorSet(row);
271                                         log.user("Selected table row " + row + ": " + motorSet);
272                                         if (motorSet != selectedMotorSet) {
273                                                 select(selectMotor(motorSet));
274                                         }
275                                 } else {
276                                         log.user("Selected table row " + row + ", nothing selected");
277                                 }
278                         }
279                 });
280                 table.addMouseListener(new MouseAdapter() {
281                         @Override
282                         public void mouseClicked(MouseEvent e) {
283                                 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
284                                         if (dialog != null) {
285                                                 dialog.close(true);
286                                         }
287                                 }
288                         }
289                 });
290                 
291
292                 JScrollPane scrollpane = new JScrollPane();
293                 scrollpane.setViewportView(table);
294                 panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para");
295                 
296
297
298
299                 // Motor mount diameter label
300                 //// Motor mount diameter: 
301                 label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Motormountdia")+ " " +
302                                 UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit(diameter));
303                 panel.add(label, "gapright 30lp, spanx, split");
304                 
305
306
307                 // Search field
308                 //// Search:
309                 label = new StyledLabel(trans.get("TCMotorSelPan.lbl.Search"));
310                 panel.add(label, "");
311                 
312                 searchField = new JTextField();
313                 searchField.getDocument().addDocumentListener(new DocumentListener() {
314                         @Override
315                         public void changedUpdate(DocumentEvent e) {
316                                 update();
317                         }
318                         
319                         @Override
320                         public void insertUpdate(DocumentEvent e) {
321                                 update();
322                         }
323                         
324                         @Override
325                         public void removeUpdate(DocumentEvent e) {
326                                 update();
327                         }
328                         
329                         private void update() {
330                                 String text = searchField.getText().trim();
331                                 String[] split = text.split("\\s+");
332                                 ArrayList<String> list = new ArrayList<String>();
333                                 for (String s : split) {
334                                         s = s.trim().toLowerCase();
335                                         if (s.length() > 0) {
336                                                 list.add(s);
337                                         }
338                                 }
339                                 searchTerms = list.toArray(new String[0]);
340                                 sorter.sort();
341                                 scrollSelectionVisible();
342                         }
343                 });
344                 panel.add(searchField, "growx, wrap");
345                 
346
347
348                 // Vertical split
349                 this.add(panel, "grow");
350                 this.add(new JSeparator(JSeparator.VERTICAL), "growy, gap para para");
351                 panel = new JPanel(new MigLayout("fill"));
352                 
353
354
355                 // Thrust curve selection
356                 //// Select thrust curve:
357                 curveSelectionLabel = new JLabel(trans.get("TCMotorSelPan.lbl.Selectthrustcurve"));
358                 panel.add(curveSelectionLabel);
359                 
360                 curveSelectionModel = new DefaultComboBoxModel();
361                 curveSelectionBox = new JComboBox(curveSelectionModel);
362                 curveSelectionBox.setRenderer(new CurveSelectionRenderer(curveSelectionBox.getRenderer()));
363                 curveSelectionBox.addActionListener(new ActionListener() {
364                         @Override
365                         public void actionPerformed(ActionEvent e) {
366                                 Object value = curveSelectionBox.getSelectedItem();
367                                 if (value != null) {
368                                         select(((MotorHolder) value).getMotor());
369                                 }
370                         }
371                 });
372                 panel.add(curveSelectionBox, "growx, wrap para");
373                 
374
375
376
377
378                 // Ejection charge delay:
379                 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Ejectionchargedelay")));
380                 
381                 delayBox = new JComboBox();
382                 delayBox.setEditable(true);
383                 delayBox.addActionListener(new ActionListener() {
384                         @Override
385                         public void actionPerformed(ActionEvent e) {
386                                 JComboBox cb = (JComboBox) e.getSource();
387                                 String sel = (String) cb.getSelectedItem();
388                                 //// None
389                                 if (sel.equalsIgnoreCase(trans.get("TCMotorSelPan.equalsIgnoreCase.None"))) {
390                                         selectedDelay = Motor.PLUGGED;
391                                 } else {
392                                         try {
393                                                 selectedDelay = Double.parseDouble(sel);
394                                         } catch (NumberFormatException ignore) {
395                                         }
396                                 }
397                                 setDelays(false);
398                         }
399                 });
400                 panel.add(delayBox, "growx, wrap rel");
401                 //// (Number of seconds or \"None\")
402                 panel.add(new StyledLabel(trans.get("TCMotorSelPan.lbl.NumberofsecondsorNone"), -3), "skip, wrap para");
403                 setDelays(false);
404                 
405
406                 panel.add(new JSeparator(), "spanx, growx, wrap para");
407                 
408
409
410                 // Thrust curve info
411                 //// Total impulse:
412                 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Totalimpulse")));
413                 totalImpulseLabel = new JLabel();
414                 panel.add(totalImpulseLabel, "wrap");
415                 
416                 //// Avg. thrust:
417                 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Avgthrust")));
418                 avgThrustLabel = new JLabel();
419                 panel.add(avgThrustLabel, "wrap");
420                 
421                 //// Max. thrust:
422                 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Maxthrust")));
423                 maxThrustLabel = new JLabel();
424                 panel.add(maxThrustLabel, "wrap");
425                 
426                 //// Burn time:
427                 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Burntime")));
428                 burnTimeLabel = new JLabel();
429                 panel.add(burnTimeLabel, "wrap");
430                 
431                 //// Launch mass:
432                 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Launchmass")));
433                 launchMassLabel = new JLabel();
434                 panel.add(launchMassLabel, "wrap");
435                 
436                 //// Empty mass:
437                 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Emptymass")));
438                 emptyMassLabel = new JLabel();
439                 panel.add(emptyMassLabel, "wrap");
440                 
441                 //// Data points:
442                 panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Datapoints")));
443                 dataPointsLabel = new JLabel();
444                 panel.add(dataPointsLabel, "wrap para");
445                 
446                 if (System.getProperty("openrocket.debug.motordigest") != null) {
447                         //// Digest:
448                         panel.add(new JLabel(trans.get("TCMotorSelPan.lbl.Digest")));
449                         digestLabel = new JLabel();
450                         panel.add(digestLabel, "w :300:, wrap para");
451                 } else {
452                         digestLabel = null;
453                 }
454                 
455
456                 comment = new JTextArea(5, 5);
457                 GUIUtil.changeFontSize(comment, -2);
458                 withCommentFont = comment.getFont();
459                 noCommentFont = withCommentFont.deriveFont(Font.ITALIC);
460                 comment.setLineWrap(true);
461                 comment.setWrapStyleWord(true);
462                 comment.setEditable(false);
463                 scrollpane = new JScrollPane(comment);
464                 panel.add(scrollpane, "spanx, growx, wrap para");
465                 
466
467
468
469                 // Thrust curve plot
470                 chart = ChartFactory.createXYLineChart(
471                                 null, // title
472                                 null, // xAxisLabel
473                                 null, // yAxisLabel
474                                 null, // dataset
475                                 PlotOrientation.VERTICAL,
476                                 false, // legend
477                                 false, // tooltips
478                                 false // urls
479                                 );
480                 
481
482                 // Add the data and formatting to the plot
483                 XYPlot plot = chart.getXYPlot();
484                 
485                 changeLabelFont(plot.getRangeAxis(), -2);
486                 changeLabelFont(plot.getDomainAxis(), -2);
487                 
488                 //// Thrust curve:
489                 chart.setTitle(new TextTitle(trans.get("TCMotorSelPan.title.Thrustcurve"), this.getFont()));
490                 chart.setBackgroundPaint(this.getBackground());
491                 plot.setBackgroundPaint(Color.WHITE);
492                 plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
493                 plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
494                 
495                 chartPanel = new ChartPanel(chart,
496                                 false, // properties
497                                 false, // save
498                                 false, // print
499                                 false, // zoom
500                                 false); // tooltips
501                 chartPanel.setMouseZoomable(false);
502                 chartPanel.setPopupMenu(null);
503                 chartPanel.setMouseWheelEnabled(false);
504                 chartPanel.setRangeZoomable(false);
505                 chartPanel.setDomainZoomable(false);
506                 
507                 chartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
508                 chartPanel.addMouseListener(new MouseAdapter() {
509                         @Override
510                         public void mouseClicked(MouseEvent e) {
511                                 if (selectedMotor == null || selectedMotorSet == null)
512                                         return;
513                                 if (e.getButton() == MouseEvent.BUTTON1) {
514                                         // Open plot dialog
515                                         List<ThrustCurveMotor> motors = getFilteredCurves();
516                                         ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(motors,
517                                                         motors.indexOf(selectedMotor),
518                                                         SwingUtilities.getWindowAncestor(ThrustCurveMotorSelectionPanel.this));
519                                         plotDialog.setVisible(true);
520                                 }
521                         }
522                 });
523                 
524                 JLayeredPane layer = new CustomLayeredPane();
525                 
526                 layer.setBorder(BorderFactory.createLineBorder(Color.BLUE));
527                 
528                 layer.add(chartPanel, (Integer) 0);
529                 
530                 zoomIcon = new JLabel(Icons.ZOOM_IN);
531                 zoomIcon.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
532                 layer.add(zoomIcon, (Integer) 1);
533                 
534
535                 panel.add(layer, "width 300:300:, height 180:180:, grow, spanx");
536                 
537
538
539                 this.add(panel, "grow");
540                 
541
542
543                 // Sets the filter:
544                 int showMode = Prefs.getChoise(Prefs.MOTOR_DIAMETER_FILTER, SHOW_MAX, SHOW_EXACT);
545                 filterComboBox.setSelectedIndex(showMode);
546                 
547
548                 // Update the panel data
549                 updateData();
550                 selectedDelay = delay;
551                 setDelays(false);
552                 
553         }
554         
555         
556         @Override
557         public Motor getSelectedMotor() {
558                 return selectedMotor;
559         }
560         
561         
562         @Override
563         public double getSelectedDelay() {
564                 return selectedDelay;
565         }
566         
567         
568         @Override
569         public JComponent getDefaultFocus() {
570                 return searchField;
571         }
572         
573         @Override
574         public void selectedMotor(Motor motorSelection) {
575                 if (!(motorSelection instanceof ThrustCurveMotor)) {
576                         log.error("Received argument that was not ThrustCurveMotor: " + motorSelection);
577                         return;
578                 }
579                 
580                 ThrustCurveMotor motor = (ThrustCurveMotor) motorSelection;
581                 ThrustCurveMotorSet set = findMotorSet(motor);
582                 if (set == null) {
583                         log.error("Could not find set for motor:" + motorSelection);
584                         return;
585                 }
586                 
587                 // Store selected motor in preferences node, set all others to false
588                 Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
589                 for (ThrustCurveMotor m : set.getMotors()) {
590                         String digest = MotorDigest.digestMotor(m);
591                         prefs.putBoolean(digest, m == motor);
592                 }
593         }
594         
595         public void setCloseableDialog(CloseableDialog dialog) {
596                 this.dialog = dialog;
597         }
598         
599         
600
601         private void changeLabelFont(ValueAxis axis, float size) {
602                 Font font = axis.getTickLabelFont();
603                 font = font.deriveFont(font.getSize2D() + size);
604                 axis.setTickLabelFont(font);
605         }
606         
607         
608         /**
609          * Called when a different motor is selected from within the panel.
610          */
611         private void select(ThrustCurveMotor motor) {
612                 if (selectedMotor == motor)
613                         return;
614                 
615                 ThrustCurveMotorSet set = findMotorSet(motor);
616                 if (set == null) {
617                         throw new BugException("Could not find motor from database, motor=" + motor);
618                 }
619                 
620                 boolean updateDelays = (selectedMotorSet != set);
621                 
622                 selectedMotor = motor;
623                 selectedMotorSet = set;
624                 updateData();
625                 if (updateDelays) {
626                         setDelays(true);
627                 }
628         }
629         
630         
631         private void updateData() {
632                 
633                 if (selectedMotorSet == null) {
634                         // No motor selected
635                         curveSelectionModel.removeAllElements();
636                         curveSelectionBox.setEnabled(false);
637                         curveSelectionLabel.setEnabled(false);
638                         totalImpulseLabel.setText("");
639                         avgThrustLabel.setText("");
640                         maxThrustLabel.setText("");
641                         burnTimeLabel.setText("");
642                         launchMassLabel.setText("");
643                         emptyMassLabel.setText("");
644                         dataPointsLabel.setText("");
645                         if (digestLabel != null) {
646                                 digestLabel.setText("");
647                         }
648                         setComment("");
649                         chart.getXYPlot().setDataset(new XYSeriesCollection());
650                         return;
651                 }
652                 
653
654                 // Check which thrust curves to display
655                 List<ThrustCurveMotor> motors = getFilteredCurves();
656                 final int index = motors.indexOf(selectedMotor);
657                 
658
659                 // Update the thrust curve selection box
660                 curveSelectionModel.removeAllElements();
661                 for (int i = 0; i < motors.size(); i++) {
662                         curveSelectionModel.addElement(new MotorHolder(motors.get(i), i));
663                 }
664                 curveSelectionBox.setSelectedIndex(index);
665                 
666                 if (motors.size() > 1) {
667                         curveSelectionBox.setEnabled(true);
668                         curveSelectionLabel.setEnabled(true);
669                 } else {
670                         curveSelectionBox.setEnabled(false);
671                         curveSelectionLabel.setEnabled(false);
672                 }
673                 
674
675                 // Update thrust curve data
676                 totalImpulseLabel.setText(UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
677                                 selectedMotor.getTotalImpulseEstimate()));
678                 avgThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
679                                 selectedMotor.getAverageThrustEstimate()));
680                 maxThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
681                                 selectedMotor.getMaxThrustEstimate()));
682                 burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
683                                 selectedMotor.getBurnTimeEstimate()));
684                 launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
685                                 selectedMotor.getLaunchCG().weight));
686                 emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
687                                 selectedMotor.getEmptyCG().weight));
688                 dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1));
689                 if (digestLabel != null) {
690                         digestLabel.setText(MotorDigest.digestMotor(selectedMotor));
691                 }
692                 
693                 setComment(selectedMotor.getDescription());
694                 
695
696                 // Update the plot
697                 XYPlot plot = chart.getXYPlot();
698                 
699                 XYSeriesCollection dataset = new XYSeriesCollection();
700                 for (int i = 0; i < motors.size(); i++) {
701                         ThrustCurveMotor m = motors.get(i);
702                         
703                         //// Thrust
704                         XYSeries series = new XYSeries(trans.get("TCMotorSelPan.title.Thrust"));
705                         double[] time = m.getTimePoints();
706                         double[] thrust = m.getThrustPoints();
707                         
708                         for (int j = 0; j < time.length; j++) {
709                                 series.add(time[j], thrust[j]);
710                         }
711                         
712                         dataset.addSeries(series);
713                         
714                         boolean selected = (i == index);
715                         plot.getRenderer().setSeriesStroke(i, new BasicStroke(selected ? 3 : 1));
716                         plot.getRenderer().setSeriesPaint(i, getColor(i));
717                 }
718                 
719                 plot.setDataset(dataset);
720         }
721         
722         
723         private List<ThrustCurveMotor> getFilteredCurves() {
724                 List<ThrustCurveMotor> motors = selectedMotorSet.getMotors();
725                 if (hideSimilarBox.isSelected()) {
726                         List<ThrustCurveMotor> filtered = new ArrayList<ThrustCurveMotor>(motors.size());
727                         for (int i = 0; i < motors.size(); i++) {
728                                 ThrustCurveMotor m = motors.get(i);
729                                 if (m.equals(selectedMotor)) {
730                                         filtered.add(m);
731                                         continue;
732                                 }
733                                 
734                                 double similarity = MotorCorrelation.similarity(selectedMotor, m);
735                                 log.debug("Motor similarity: " + similarity);
736                                 if (similarity < MOTOR_SIMILARITY_THRESHOLD) {
737                                         filtered.add(m);
738                                 }
739                         }
740                         motors = filtered;
741                 }
742                 
743                 Collections.sort(motors, MOTOR_COMPARATOR);
744                 
745                 return motors;
746         }
747         
748         
749         private void setComment(String s) {
750                 s = s.trim();
751                 if (s.length() == 0) {
752                         //// No description available.
753                         comment.setText("No description available.");
754                         comment.setFont(noCommentFont);
755                         comment.setForeground(NO_COMMENT_COLOR);
756                 } else {
757                         comment.setText(s);
758                         comment.setFont(withCommentFont);
759                         comment.setForeground(WITH_COMMENT_COLOR);
760                 }
761                 comment.setCaretPosition(0);
762         }
763         
764         private void scrollSelectionVisible() {
765                 if (selectedMotorSet != null) {
766                         int index = table.convertRowIndexToView(model.getIndex(selectedMotorSet));
767                         System.out.println("index=" + index);
768                         table.getSelectionModel().setSelectionInterval(index, index);
769                         Rectangle rect = table.getCellRect(index, 0, true);
770                         rect = new Rectangle(rect.x, rect.y - 100, rect.width, rect.height + 200);
771                         table.scrollRectToVisible(rect);
772                 }
773         }
774         
775         
776         public static Color getColor(int index) {
777                 return (Color) CURVE_COLORS[index % CURVE_COLORS.length];
778         }
779         
780         
781         /**
782          * Find the ThrustCurveMotorSet that contains a motor.
783          * 
784          * @param motor         the motor to look for.
785          * @return                      the ThrustCurveMotorSet, or null if not found.
786          */
787         private ThrustCurveMotorSet findMotorSet(ThrustCurveMotor motor) {
788                 for (ThrustCurveMotorSet set : database) {
789                         if (set.getMotors().contains(motor)) {
790                                 return set;
791                         }
792                 }
793                 
794                 return null;
795         }
796         
797         
798
799         /**
800          * Select the default motor from this ThrustCurveMotorSet.  This uses primarily motors
801          * that the user has previously used, and secondarily a heuristic method of selecting which
802          * thrust curve seems to be better or more reliable.
803          * 
804          * @param set   the motor set
805          * @return              the default motor in this set
806          */
807         private ThrustCurveMotor selectMotor(ThrustCurveMotorSet set) {
808                 if (set.getMotorCount() == 0) {
809                         throw new BugException("Attempting to select motor from empty ThrustCurveMotorSet: " + set);
810                 }
811                 if (set.getMotorCount() == 1) {
812                         return set.getMotors().get(0);
813                 }
814                 
815
816                 // Find which motor has been used the most recently
817                 List<ThrustCurveMotor> list = set.getMotors();
818                 Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
819                 for (ThrustCurveMotor m : list) {
820                         String digest = MotorDigest.digestMotor(m);
821                         if (prefs.getBoolean(digest, false)) {
822                                 return m;
823                         }
824                 }
825                 
826                 // No motor has been used
827                 Collections.sort(list, MOTOR_COMPARATOR);
828                 return list.get(0);
829         }
830         
831         
832         /**
833          * Set the values in the delay combo box.  If <code>reset</code> is <code>true</code>
834          * then sets the selected value as the value closest to selectedDelay, otherwise
835          * leaves selection alone.
836          */
837         private void setDelays(boolean reset) {
838                 if (selectedMotor == null) {
839                         
840                         //// None
841                         delayBox.setModel(new DefaultComboBoxModel(new String[] { trans.get("TCMotorSelPan.delayBox.None") }));
842                         delayBox.setSelectedIndex(0);
843                         
844                 } else {
845                         
846                         List<Double> delays = selectedMotorSet.getDelays();
847                         String[] delayStrings = new String[delays.size()];
848                         double currentDelay = selectedDelay; // Store current setting locally
849                         
850                         for (int i = 0; i < delays.size(); i++) {
851                                 //// None
852                                 delayStrings[i] = ThrustCurveMotor.getDelayString(delays.get(i), trans.get("TCMotorSelPan.delayBox.None"));
853                         }
854                         delayBox.setModel(new DefaultComboBoxModel(delayStrings));
855                         
856                         if (reset) {
857                                 
858                                 // Find and set the closest value
859                                 double closest = Double.NaN;
860                                 for (int i = 0; i < delays.size(); i++) {
861                                         // if-condition to always become true for NaN
862                                         if (!(Math.abs(delays.get(i) - currentDelay) > Math.abs(closest - currentDelay))) {
863                                                 closest = delays.get(i);
864                                         }
865                                 }
866                                 if (!Double.isNaN(closest)) {
867                                         selectedDelay = closest;
868                                 //// None
869                                         delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(closest, trans.get("TCMotorSelPan.delayBox.None")));
870                                 } else {
871                                         delayBox.setSelectedItem("None");
872                                 }
873                                 
874                         } else {
875                                 
876                                 selectedDelay = currentDelay;
877                         //// None
878                                 delayBox.setSelectedItem(ThrustCurveMotor.getDelayString(currentDelay, trans.get("TCMotorSelPan.delayBox.None")));
879                                 
880                         }
881                         
882                 }
883         }
884         
885         
886
887
888         //////////////////////
889         
890
891         private class CurveSelectionRenderer implements ListCellRenderer {
892                 
893                 private final ListCellRenderer renderer;
894                 
895                 public CurveSelectionRenderer(ListCellRenderer renderer) {
896                         this.renderer = renderer;
897                 }
898                 
899                 @Override
900                 public Component getListCellRendererComponent(JList list, Object value, int index,
901                                 boolean isSelected, boolean cellHasFocus) {
902                         
903                         Component c = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
904                         if (value instanceof MotorHolder) {
905                                 MotorHolder m = (MotorHolder) value;
906                                 c.setForeground(getColor(m.getIndex()));
907                         }
908                         
909                         return c;
910                 }
911                 
912         }
913         
914         
915         ////////  Row filters
916         
917         /**
918          * Abstract adapter class.
919          */
920         private abstract class MotorRowFilter extends RowFilter<TableModel, Integer> {
921                 @Override
922                 public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
923                         int index = entry.getIdentifier();
924                         ThrustCurveMotorSet m = model.getMotorSet(index);
925                         return filterByDiameter(m) && filterByString(m);
926                 }
927                 
928                 public abstract boolean filterByDiameter(ThrustCurveMotorSet m);
929                 
930                 
931                 public boolean filterByString(ThrustCurveMotorSet m) {
932                         main: for (String s : searchTerms) {
933                                 for (ThrustCurveMotorColumns col : ThrustCurveMotorColumns.values()) {
934                                         String str = col.getValue(m).toString().toLowerCase();
935                                         if (str.indexOf(s) >= 0)
936                                                 continue main;
937                                 }
938                                 return false;
939                         }
940                         return true;
941                 }
942         }
943         
944         /**
945          * Show all motors.
946          */
947         private class MotorRowFilterAll extends MotorRowFilter {
948                 @Override
949                 public boolean filterByDiameter(ThrustCurveMotorSet m) {
950                         return true;
951                 }
952         }
953         
954         /**
955          * Show motors smaller than the mount.
956          */
957         private class MotorRowFilterSmaller extends MotorRowFilter {
958                 @Override
959                 public boolean filterByDiameter(ThrustCurveMotorSet m) {
960                         return (m.getDiameter() <= diameter + 0.0004);
961                 }
962         }
963         
964         /**
965          * Show motors that fit the mount.
966          */
967         private class MotorRowFilterExact extends MotorRowFilter {
968                 @Override
969                 public boolean filterByDiameter(ThrustCurveMotorSet m) {
970                         return ((m.getDiameter() <= diameter + 0.0004) && (m.getDiameter() >= diameter - 0.0015));
971                 }
972         }
973         
974         
975         /**
976          * Custom layered pane that sets the bounds of the components on every layout.
977          */
978         public class CustomLayeredPane extends JLayeredPane {
979                 @Override
980                 public void doLayout() {
981                         synchronized (getTreeLock()) {
982                                 int w = getWidth();
983                                 int h = getHeight();
984                                 chartPanel.setBounds(0, 0, w, h);
985                                 zoomIcon.setBounds(w - ZOOM_ICON_POSITION_NEGATIVE_X, ZOOM_ICON_POSITION_POSITIVE_Y, 50, 50);
986                         }
987                 }
988         }
989 }