Numerous bug fixes and updates
[debian/openrocket] / src / net / sf / openrocket / gui / components / SimulationExportPanel.java
1 package net.sf.openrocket.gui.components;
2
3 import java.awt.Component;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
6 import java.io.File;
7 import java.util.Arrays;
8
9 import javax.swing.BorderFactory;
10 import javax.swing.JButton;
11 import javax.swing.JCheckBox;
12 import javax.swing.JComboBox;
13 import javax.swing.JFileChooser;
14 import javax.swing.JLabel;
15 import javax.swing.JOptionPane;
16 import javax.swing.JPanel;
17 import javax.swing.JScrollPane;
18 import javax.swing.JTable;
19 import javax.swing.SwingUtilities;
20 import javax.swing.filechooser.FileFilter;
21 import javax.swing.table.AbstractTableModel;
22 import javax.swing.table.TableCellRenderer;
23 import javax.swing.table.TableColumn;
24 import javax.swing.table.TableColumnModel;
25
26 import net.miginfocom.swing.MigLayout;
27 import net.sf.openrocket.document.Simulation;
28 import net.sf.openrocket.simulation.FlightData;
29 import net.sf.openrocket.simulation.FlightDataBranch;
30 import net.sf.openrocket.unit.Unit;
31 import net.sf.openrocket.unit.UnitGroup;
32 import net.sf.openrocket.util.Prefs;
33 import net.sf.openrocket.util.SaveCSVWorker;
34
35 public class SimulationExportPanel extends JPanel {
36
37         private static final String SPACE = "SPACE";
38         private static final String TAB = "TAB";
39         
40         private static final FileFilter CSV_FILE_FILTER = new FileFilter() {
41                 @Override
42                 public String getDescription() {
43                         return "Comma Separated Files (*.csv)";
44                 }
45                 @Override
46                 public boolean accept(File f) {
47                         if (f.isDirectory())
48                                 return true;
49                         String name = f.getName().toLowerCase();
50                         return name.endsWith(".csv");
51                 }
52     };
53
54         
55         
56         private final JTable table;
57         private final SelectionTableModel tableModel;
58         private final JLabel selectedCountLabel;
59         
60         private final Simulation simulation;
61         private final FlightDataBranch branch;
62         
63         private final boolean[] selected;
64         private final FlightDataBranch.Type[] types;
65         private final Unit[] units;
66         
67         private final JComboBox fieldSeparator;
68         private final JCheckBox simulationComments;
69         private final JCheckBox fieldNameComments;
70         private final JCheckBox eventComments;
71         private final JComboBox commentCharacter;
72         
73         
74         public SimulationExportPanel(Simulation sim) {
75                 super(new MigLayout("fill, flowy"));
76
77                 JLabel label;
78                 JPanel panel;
79                 JButton button;
80                 String tip;
81                 
82                 
83                 this.simulation = sim;
84                 
85                 // TODO: MEDIUM: Only exports primary branch
86                 
87                 final FlightData data = simulation.getSimulatedData();
88
89                 // Check that data exists
90                 if (data == null  || data.getBranchCount() == 0 ||
91                                 data.getBranch(0).getTypes().length == 0) {
92                         throw new IllegalArgumentException("No data for panel");
93                 }
94                 
95                 
96                 // Create the data model
97                 branch = data.getBranch(0);
98
99                 types = branch.getTypes();
100                 Arrays.sort(types);
101                 
102                 selected = new boolean[types.length];
103                 units = new Unit[types.length];
104                 for (int i = 0; i < types.length; i++) {
105                         selected[i] = Prefs.isExportSelected(types[i]);
106                         units[i] = types[i].getUnitGroup().getDefaultUnit();
107                 }
108                 
109                 
110                 //// Create the panel
111                 
112                 
113                 // Set up the variable selection table
114                 tableModel = new SelectionTableModel();
115                 table = new JTable(tableModel);
116                 table.setDefaultRenderer(Object.class, 
117                                 new SelectionBackgroundCellRenderer(table.getDefaultRenderer(Object.class)));
118                 table.setDefaultRenderer(Boolean.class, 
119                                 new SelectionBackgroundCellRenderer(table.getDefaultRenderer(Boolean.class)));
120                 table.setRowSelectionAllowed(false);
121                 table.setColumnSelectionAllowed(false);
122                 
123                 table.setDefaultEditor(Unit.class, new UnitCellEditor() {
124                         @Override
125                         protected UnitGroup getUnitGroup(Unit value, int row, int column) {
126                                 return types[row].getUnitGroup();
127                         }
128                 });
129                 
130                 // Set column widths
131                 TableColumnModel columnModel = table.getColumnModel();
132                 TableColumn col = columnModel.getColumn(0);
133                 int w = table.getRowHeight();
134                 col.setMinWidth(w);
135                 col.setPreferredWidth(w);
136                 col.setMaxWidth(w);
137                 
138                 col = columnModel.getColumn(1);
139                 col.setPreferredWidth(200);
140                 
141                 col = columnModel.getColumn(2);
142                 col.setPreferredWidth(100);
143
144                 
145                 // Add table
146                 panel = new JPanel(new MigLayout("fill"));
147                 panel.setBorder(BorderFactory.createTitledBorder("Variables to export"));
148                 
149                 panel.add(new JScrollPane(table), "wmin 300lp, width 300lp, height 1, grow 100, wrap");
150                 
151                 // Select all/none buttons
152                 button = new JButton("Select all");
153                 button.addActionListener(new ActionListener() {
154                         @Override
155                         public void actionPerformed(ActionEvent e) {
156                                 tableModel.selectAll();
157                         }
158                 });
159                 panel.add(button, "split 2, growx 1, sizegroup selectbutton");
160                 
161                 button = new JButton("Select none");
162                 button.addActionListener(new ActionListener() {
163                         @Override
164                         public void actionPerformed(ActionEvent e) {
165                                 tableModel.selectNone();
166                         }
167                 });
168                 panel.add(button, "growx 1, sizegroup selectbutton, wrap");
169                 
170                 
171                 selectedCountLabel = new JLabel();
172                 updateSelectedCount();
173                 panel.add(selectedCountLabel);
174                 
175                 this.add(panel, "grow 100, wrap");
176                 
177                 
178                 
179                 // Field separator panel
180                 panel = new JPanel(new MigLayout("fill"));
181                 panel.setBorder(BorderFactory.createTitledBorder("Field separator"));
182                 
183                 label = new JLabel("Field separator string:");
184                 tip = "<html>The string used to separate the fields in the exported file.<br>" +
185                                 "Use ',' for a Comma Separated Values (CSV) file.";
186                 label.setToolTipText(tip);
187                 panel.add(label);
188                 
189                 fieldSeparator = new JComboBox(new String[] { ",", ";", SPACE, TAB });
190                 fieldSeparator.setEditable(true);
191                 fieldSeparator.setSelectedItem(Prefs.getString(Prefs.EXPORT_FIELD_SEPARATOR, 
192                                 ","));
193                 fieldSeparator.setToolTipText(tip);
194                 panel.add(fieldSeparator);
195                 
196                 this.add(panel, "spany, split, growx 1");
197                 
198                 
199                 
200                 
201                 // Comments separator panel
202                 panel = new JPanel(new MigLayout("fill"));
203                 panel.setBorder(BorderFactory.createTitledBorder("Comments"));
204                 
205                 simulationComments = new JCheckBox("Include simulation description");
206                 simulationComments.setToolTipText("Include a comment at the beginning of the file " +
207                                 "describing the simulation.");
208                 simulationComments.setSelected(Prefs.getBoolean(Prefs.EXPORT_SIMULATION_COMMENT, 
209                                 true));
210                 panel.add(simulationComments, "wrap");
211                 
212                 
213                 fieldNameComments = new JCheckBox("Include field descriptions");
214                 fieldNameComments.setToolTipText("Include a comment line with the descriptions of " +
215                                 "the exported variables.");
216                 fieldNameComments.setSelected(Prefs.getBoolean(Prefs.EXPORT_FIELD_NAME_COMMENT, true));
217                 panel.add(fieldNameComments, "wrap");
218                 
219                 
220                 eventComments = new JCheckBox("Include flight events");
221                 eventComments.setToolTipText("Include a comment line for every flight event.");
222                 eventComments.setSelected(Prefs.getBoolean(Prefs.EXPORT_EVENT_COMMENTS, true));
223                 panel.add(eventComments, "wrap");
224                 
225                 
226                 label = new JLabel("Comment character:");
227                 tip = "The character(s) that mark a comment line.";
228                 label.setToolTipText(tip);
229                 panel.add(label, "split 2");
230                 
231                 commentCharacter = new JComboBox(new String[] { "#", "%", ";" });
232                 commentCharacter.setEditable(true);
233                 commentCharacter.setSelectedItem(Prefs.getString(Prefs.EXPORT_COMMENT_CHARACTER, "#"));
234                 commentCharacter.setToolTipText(tip);
235                 panel.add(commentCharacter);
236                 
237                 this.add(panel, "growx 1");
238
239                 
240                 // Space-filling panel
241                 panel = new JPanel();
242                 this.add(panel, "width 1, height 1, grow 1");
243                 
244                 
245                 // Export button
246                 button = new JButton("Export to file...");
247                 button.addActionListener(new ActionListener() {
248                         @Override
249                         public void actionPerformed(ActionEvent e) {
250                                 doExport();
251                         }
252                 });
253                 this.add(button, "gapbottom para, gapright para, right");
254                 
255         }
256         
257         
258         private void doExport() {
259                 JFileChooser chooser = new JFileChooser();
260                 chooser.setFileFilter(CSV_FILE_FILTER);
261                 chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
262                 
263                 if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
264                         return;
265                 
266                 File file = chooser.getSelectedFile();
267                 if (file == null)
268                         return;
269                 
270                 if (file.getName().indexOf('.') < 0) {
271                         String name = file.getAbsolutePath();
272                         name = name + ".csv";
273                         file = new File(name);
274                 }
275
276                 if (file.exists()) {
277                         int ret = JOptionPane.showConfirmDialog(this, 
278                                         "File \"" + file.getName() + "\" exists.  Overwrite?", 
279                                         "File exists", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
280                         if (ret != JOptionPane.YES_OPTION)
281                                 return;
282                 }
283
284                 String commentChar = commentCharacter.getSelectedItem().toString();
285                 String fieldSep = fieldSeparator.getSelectedItem().toString();
286                 boolean simulationComment = simulationComments.isSelected();
287                 boolean fieldComment = fieldNameComments.isSelected();
288                 boolean eventComment = eventComments.isSelected();
289                 
290                 // Store preferences and export
291                 int n = 0;
292                 Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
293                 for (int i=0; i < selected.length; i++) {
294                         Prefs.setExportSelected(types[i], selected[i]);
295                         if (selected[i])
296                                 n++;
297                 }
298                 Prefs.putString(Prefs.EXPORT_FIELD_SEPARATOR, fieldSep);
299                 Prefs.putString(Prefs.EXPORT_COMMENT_CHARACTER, commentChar);
300                 Prefs.putBoolean(Prefs.EXPORT_EVENT_COMMENTS, eventComment);
301                 Prefs.putBoolean(Prefs.EXPORT_FIELD_NAME_COMMENT, fieldComment);
302                 Prefs.putBoolean(Prefs.EXPORT_SIMULATION_COMMENT, simulationComment);
303                 
304                 
305                 FlightDataBranch.Type[] fieldTypes = new FlightDataBranch.Type[n];
306                 Unit[] fieldUnits = new Unit[n];
307                 int pos = 0;
308                 for (int i=0; i < selected.length; i++) {
309                         if (selected[i]) {
310                                 fieldTypes[pos] = types[i];
311                                 fieldUnits[pos] = units[i];
312                                 pos++;
313                         }
314                 }
315                 
316                 if (fieldSep.equals(SPACE)) {
317                         fieldSep = " ";
318                 } else if (fieldSep.equals(TAB)) {
319                         fieldSep = "\t";
320                 }
321                 
322                 
323                 SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, 
324                                 commentChar, simulationComment, fieldComment, eventComment, 
325                                 SwingUtilities.getWindowAncestor(this));
326         }
327         
328         
329         private void updateSelectedCount() {
330                 int total = selected.length;
331                 int n = 0;
332                 String str;
333                 
334                 for (int i=0; i < selected.length; i++) {
335                         if (selected[i])
336                                 n++;
337                 }
338                 
339                 if (n == 1) {
340                         str = "Exporting 1 variable out of " + total + ".";
341                 } else {
342                         str = "Exporting "+n+" variables out of " + total + ".";
343                 }
344
345                 selectedCountLabel.setText(str);
346         }
347         
348         
349         
350         /**
351          * A table cell renderer that uses another renderer and sets the background and
352          * foreground of the returned component based on the selection of the variable.
353          */
354         private class SelectionBackgroundCellRenderer implements TableCellRenderer {
355
356                 private final TableCellRenderer renderer;
357                 
358                 public SelectionBackgroundCellRenderer(TableCellRenderer renderer) {
359                         this.renderer = renderer;
360                 }
361                 
362                 @Override
363                 public Component getTableCellRendererComponent(JTable table, Object value,
364                                 boolean isSelected, boolean hasFocus, int row, int column) {
365                         
366                         Component component = renderer.getTableCellRendererComponent(table, 
367                                         value, isSelected, hasFocus, row, column);
368                         
369                         if (selected[row]) {
370                                 component.setBackground(table.getSelectionBackground());
371                                 component.setForeground(table.getSelectionForeground());
372                         } else {
373                                 component.setBackground(table.getBackground());
374                                 component.setForeground(table.getForeground());
375                         }
376                         
377                         return component;
378                 }
379                 
380         }
381         
382         
383         /**
384          * The table model for the variable selection.
385          */
386         private class SelectionTableModel extends AbstractTableModel {
387                 private static final int SELECTED = 0;
388                 private static final int NAME = 1;
389                 private static final int UNIT = 2;
390
391                 @Override
392                 public int getColumnCount() {
393                         return 3;
394                 }
395
396                 @Override
397                 public int getRowCount() {
398                         return types.length;
399                 }
400                 
401                 @Override
402                 public String getColumnName(int column) {
403                         switch (column) {
404                         case SELECTED:
405                                 return "";
406                         case NAME:
407                                 return "Variable";
408                         case UNIT:
409                                 return "Unit";
410                         default:
411                                 throw new IndexOutOfBoundsException("column=" + column);
412                         }
413                         
414                 }
415                 
416                 @Override
417                 public Class<?> getColumnClass(int column) {
418                         switch (column) {
419                         case SELECTED:
420                                 return Boolean.class;
421                         case NAME:
422                                 return FlightDataBranch.Type.class;
423                         case UNIT:
424                                 return Unit.class;
425                         default:
426                                 throw new IndexOutOfBoundsException("column=" + column);
427                         }
428                 }
429
430                 @Override
431                 public Object getValueAt(int row, int column) {
432
433                         switch (column) {
434                         case SELECTED:
435                                 return selected[row];
436                                 
437                         case NAME:
438                                 return types[row];
439                                 
440                         case UNIT:
441                                 return units[row];
442                                 
443                         default:
444                                 throw new IndexOutOfBoundsException("column="+column);
445                         }
446                         
447                 }
448
449                 @Override
450                 public void setValueAt(Object value, int row, int column) {
451                         
452                         switch (column) {
453                         case SELECTED:
454                                 selected[row] = (Boolean)value;
455                                 this.fireTableRowsUpdated(row, row);
456                                 updateSelectedCount();
457                                 break;
458                                 
459                         case NAME:
460                                 break;
461                                 
462                         case UNIT:
463                                 units[row] = (Unit)value;
464                                 break;
465                                 
466                         default:
467                                 throw new IndexOutOfBoundsException("column="+column);
468                         }
469                         
470                 }
471
472                 @Override
473                 public boolean isCellEditable(int row, int column) {
474                         switch (column) {
475                         case SELECTED:
476                                 return true;
477                                 
478                         case NAME:
479                                 return false;
480                                 
481                         case UNIT:
482                                 return types[row].getUnitGroup().getUnitCount() > 1;
483                                 
484                         default:
485                                 throw new IndexOutOfBoundsException("column="+column);
486                         }
487                 }
488                 
489                 public void selectAll() {
490                         Arrays.fill(selected, true);
491                         updateSelectedCount();
492                         this.fireTableDataChanged();
493                 }
494                 
495                 public void selectNone() {
496                         Arrays.fill(selected, false);
497                         updateSelectedCount();
498                         this.fireTableDataChanged();
499                 }
500                 
501         }
502         
503 }