1 package tim.prune.save;
3 import java.awt.BorderLayout;
4 import java.awt.CardLayout;
5 import java.awt.Component;
6 import java.awt.Dimension;
7 import java.awt.FlowLayout;
8 import java.awt.GridLayout;
9 import java.awt.event.ActionEvent;
10 import java.awt.event.ActionListener;
12 import java.io.FileWriter;
13 import java.io.IOException;
15 import javax.swing.BorderFactory;
16 import javax.swing.Box;
17 import javax.swing.BoxLayout;
18 import javax.swing.ButtonGroup;
19 import javax.swing.JButton;
20 import javax.swing.JCheckBox;
21 import javax.swing.JDialog;
22 import javax.swing.JFileChooser;
23 import javax.swing.JFrame;
24 import javax.swing.JLabel;
25 import javax.swing.JOptionPane;
26 import javax.swing.JPanel;
27 import javax.swing.JRadioButton;
28 import javax.swing.JScrollPane;
29 import javax.swing.JTable;
30 import javax.swing.JTextField;
31 import javax.swing.ListSelectionModel;
32 import javax.swing.table.TableModel;
35 import tim.prune.I18nManager;
36 import tim.prune.data.Altitude;
37 import tim.prune.data.Coordinate;
38 import tim.prune.data.DataPoint;
39 import tim.prune.data.Field;
40 import tim.prune.data.FieldList;
41 import tim.prune.data.Timestamp;
42 import tim.prune.data.Track;
43 import tim.prune.load.OneCharDocument;
46 * Class to manage the saving of track data
47 * into a user-specified file
49 public class FileSaver
51 private App _app = null;
52 private JFrame _parentFrame = null;
53 private Track _track = null;
54 private JDialog _dialog = null;
55 private JFileChooser _fileChooser = null;
56 private JPanel _cards = null;
57 private JButton _nextButton = null, _backButton = null;
58 private JTable _table = null;
59 private FieldSelectionTableModel _model = null;
60 private JButton _moveUpButton = null, _moveDownButton = null;
61 private UpDownToggler _toggler = null;
62 private JRadioButton[] _delimiterRadios = null;
63 private JTextField _otherDelimiterText = null;
64 private JCheckBox _headerRowCheckbox = null;
65 private JRadioButton[] _coordUnitsRadios = null;
66 private JRadioButton[] _altitudeUnitsRadios = null;
67 private JRadioButton[] _timestampUnitsRadios = null;
68 private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC,
69 Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG};
70 private static final int[] FORMAT_ALTS = {Altitude.FORMAT_NONE, Altitude.FORMAT_METRES, Altitude.FORMAT_FEET};
71 private static final int[] FORMAT_TIMES = {Timestamp.FORMAT_ORIGINAL, Timestamp.FORMAT_LOCALE, Timestamp.FORMAT_ISO_8601};
76 * @param inApp application object to inform of success
77 * @param inParentFrame parent frame
78 * @param inTrack track object to save
80 public FileSaver(App inApp, JFrame inParentFrame, Track inTrack)
83 _parentFrame = inParentFrame;
89 * Show the save file dialog
90 * @param inDefaultDelimiter default delimiter to use
92 public void showDialog(char inDefaultDelimiter)
96 _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.saveoptions.title"), true);
97 _dialog.setLocationRelativeTo(_parentFrame);
98 _dialog.getContentPane().add(makeDialogComponents());
102 FieldList fieldList = _track.getFieldList();
103 int numFields = fieldList.getNumFields();
104 _model = new FieldSelectionTableModel(numFields);
105 for (int i=0; i<numFields; i++)
107 Field field = fieldList.getField(i);
108 FieldInfo info = new FieldInfo(field, _track.hasData(field));
109 _model.addFieldInfo(info, i);
111 // Initialise dialog and show it
112 initDialog(_model, inDefaultDelimiter);
118 * Make the dialog components
119 * @return the GUI components for the save dialog
121 private Component makeDialogComponents()
123 JPanel panel = new JPanel();
124 panel.setLayout(new BorderLayout());
125 _cards = new JPanel();
126 _cards.setLayout(new CardLayout());
127 panel.add(_cards, BorderLayout.CENTER);
129 // Make first card for field selection and delimiter
130 JPanel firstCard = new JPanel();
131 firstCard.setLayout(new BoxLayout(firstCard, BoxLayout.Y_AXIS));
132 JPanel tablePanel = new JPanel();
133 tablePanel.setLayout(new BorderLayout());
134 _table = new JTable();
135 _table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
136 // Enclose table in a scrollpane to prevent other components getting lost
137 JScrollPane scrollPane = new JScrollPane(_table);
138 _table.setPreferredScrollableViewportSize(new Dimension(300, 150));
139 tablePanel.add(scrollPane, BorderLayout.CENTER);
141 // Make a panel to hold the table and up/down buttons
142 JPanel fieldsPanel = new JPanel();
143 fieldsPanel.setLayout(new BorderLayout());
144 fieldsPanel.add(tablePanel, BorderLayout.CENTER);
145 JPanel updownPanel = new JPanel();
146 updownPanel.setLayout(new BoxLayout(updownPanel, BoxLayout.Y_AXIS));
147 _moveUpButton = new JButton(I18nManager.getText("button.moveup"));
148 _moveUpButton.addActionListener(new ActionListener() {
149 public void actionPerformed(ActionEvent e)
151 int row = _table.getSelectedRow();
154 _model.swapItems(row, row - 1);
155 _table.setRowSelectionInterval(row - 1, row - 1);
159 _moveUpButton.setEnabled(false);
160 updownPanel.add(_moveUpButton);
161 _moveDownButton = new JButton(I18nManager.getText("button.movedown"));
162 _moveDownButton.addActionListener(new ActionListener() {
163 public void actionPerformed(ActionEvent e)
165 int row = _table.getSelectedRow();
166 if (row > -1 && row < (_model.getRowCount() - 1))
168 _model.swapItems(row, row + 1);
169 _table.setRowSelectionInterval(row + 1, row + 1);
173 _moveDownButton.setEnabled(false);
174 updownPanel.add(_moveDownButton);
175 fieldsPanel.add(updownPanel, BorderLayout.EAST);
176 // enable/disable buttons based on table row selection
177 _toggler = new UpDownToggler(_moveUpButton, _moveDownButton);
178 _table.getSelectionModel().addListSelectionListener(_toggler);
180 // Add fields panel and the delimiter panel to first card in pack
181 JLabel saveOptionsLabel = new JLabel(I18nManager.getText("dialog.save.fieldstosave"));
182 saveOptionsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
183 firstCard.add(saveOptionsLabel);
184 fieldsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
185 firstCard.add(fieldsPanel);
186 firstCard.add(Box.createRigidArea(new Dimension(0,10)));
189 JLabel delimLabel = new JLabel(I18nManager.getText("dialog.delimiter.label"));
190 delimLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
191 firstCard.add(delimLabel);
192 JPanel delimsPanel = new JPanel();
193 delimsPanel.setLayout(new GridLayout(0, 2));
194 delimsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
196 _delimiterRadios = new JRadioButton[5];
197 _delimiterRadios[0] = new JRadioButton(I18nManager.getText("dialog.delimiter.comma"));
198 delimsPanel.add(_delimiterRadios[0]);
199 _delimiterRadios[1] = new JRadioButton(I18nManager.getText("dialog.delimiter.tab"));
200 delimsPanel.add(_delimiterRadios[1]);
201 _delimiterRadios[2] = new JRadioButton(I18nManager.getText("dialog.delimiter.semicolon"));
202 delimsPanel.add(_delimiterRadios[2]);
203 _delimiterRadios[3] = new JRadioButton(I18nManager.getText("dialog.delimiter.space"));
204 delimsPanel.add(_delimiterRadios[3]);
205 JPanel otherPanel = new JPanel();
206 otherPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
207 _delimiterRadios[4] = new JRadioButton(I18nManager.getText("dialog.delimiter.other"));
208 otherPanel.add(_delimiterRadios[4]);
209 _otherDelimiterText = new JTextField(new OneCharDocument(), null, 2);
210 otherPanel.add(_otherDelimiterText);
211 // Group radio buttons
212 ButtonGroup delimGroup = new ButtonGroup();
213 for (int i=0; i<_delimiterRadios.length; i++)
215 delimGroup.add(_delimiterRadios[i]);
217 delimsPanel.add(otherPanel);
218 firstCard.add(delimsPanel);
221 firstCard.add(Box.createRigidArea(new Dimension(0,10)));
222 _headerRowCheckbox = new JCheckBox(I18nManager.getText("dialog.save.headerrow"));
223 firstCard.add(_headerRowCheckbox);
225 _cards.add(firstCard, "card1");
227 JPanel secondCard = new JPanel();
228 secondCard.setLayout(new BorderLayout());
229 JPanel secondCardHolder = new JPanel();
230 secondCardHolder.setLayout(new BoxLayout(secondCardHolder, BoxLayout.Y_AXIS));
231 JLabel coordLabel = new JLabel(I18nManager.getText("dialog.save.coordinateunits"));
232 coordLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
233 secondCardHolder.add(coordLabel);
234 JPanel coordsUnitsPanel = new JPanel();
235 coordsUnitsPanel.setBorder(BorderFactory.createEtchedBorder());
236 coordsUnitsPanel.setLayout(new GridLayout(0, 2));
237 _coordUnitsRadios = new JRadioButton[4];
238 _coordUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original"));
239 _coordUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.degminsec"));
240 _coordUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.degmin"));
241 _coordUnitsRadios[3] = new JRadioButton(I18nManager.getText("units.deg"));
242 ButtonGroup coordGroup = new ButtonGroup();
243 for (int i=0; i<4; i++)
245 coordGroup.add(_coordUnitsRadios[i]);
246 coordsUnitsPanel.add(_coordUnitsRadios[i]);
247 _coordUnitsRadios[i].setSelected(i==0);
249 coordsUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
250 secondCardHolder.add(coordsUnitsPanel);
251 secondCardHolder.add(Box.createRigidArea(new Dimension(0,10)));
253 JLabel altUnitsLabel = new JLabel(I18nManager.getText("dialog.save.altitudeunits"));
254 altUnitsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
255 secondCardHolder.add(altUnitsLabel);
256 JPanel altUnitsPanel = new JPanel();
257 altUnitsPanel.setBorder(BorderFactory.createEtchedBorder());
258 altUnitsPanel.setLayout(new GridLayout(0, 2));
259 _altitudeUnitsRadios = new JRadioButton[3];
260 _altitudeUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original"));
261 _altitudeUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.metres"));
262 _altitudeUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.feet"));
263 ButtonGroup altGroup = new ButtonGroup();
264 for (int i=0; i<3; i++)
266 altGroup.add(_altitudeUnitsRadios[i]);
267 altUnitsPanel.add(_altitudeUnitsRadios[i]);
268 _altitudeUnitsRadios[i].setSelected(i==0);
270 altUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
271 secondCardHolder.add(altUnitsPanel);
272 secondCardHolder.add(Box.createRigidArea(new Dimension(0,10)));
273 // Selection of format of timestamps
274 JLabel timestampLabel = new JLabel(I18nManager.getText("dialog.save.timestampformat"));
275 timestampLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
276 secondCardHolder.add(timestampLabel);
277 JPanel timestampPanel = new JPanel();
278 timestampPanel.setBorder(BorderFactory.createEtchedBorder());
279 timestampPanel.setLayout(new GridLayout(0, 2));
280 _timestampUnitsRadios = new JRadioButton[3];
281 _timestampUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original"));
282 _timestampUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.default"));
283 _timestampUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.iso8601"));
284 ButtonGroup timeGroup = new ButtonGroup();
285 for (int i=0; i<3; i++)
287 timeGroup.add(_timestampUnitsRadios[i]);
288 timestampPanel.add(_timestampUnitsRadios[i]);
289 _timestampUnitsRadios[i].setSelected(i==0);
291 timestampPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
292 secondCardHolder.add(timestampPanel);
293 secondCard.add(secondCardHolder, BorderLayout.NORTH);
294 _cards.add(secondCard, "card2");
296 // Put together with ok/cancel buttons on the bottom
297 JPanel buttonPanel = new JPanel();
298 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
299 _backButton = new JButton(I18nManager.getText("button.back"));
300 _backButton.addActionListener(new ActionListener() {
301 public void actionPerformed(ActionEvent e)
303 CardLayout cl = (CardLayout) _cards.getLayout();
305 _backButton.setEnabled(false);
306 _nextButton.setEnabled(true);
309 _backButton.setEnabled(false);
310 buttonPanel.add(_backButton);
311 _nextButton = new JButton(I18nManager.getText("button.next"));
312 _nextButton.setEnabled(true);
313 _nextButton.addActionListener(new ActionListener() {
314 public void actionPerformed(ActionEvent e)
316 CardLayout cl = (CardLayout) _cards.getLayout();
318 _backButton.setEnabled(true);
319 _nextButton.setEnabled(false);
322 buttonPanel.add(_nextButton);
323 JButton okButton = new JButton(I18nManager.getText("button.finish"));
324 okButton.addActionListener(new ActionListener() {
325 public void actionPerformed(ActionEvent e)
333 buttonPanel.add(okButton);
334 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
335 cancelButton.addActionListener(new ActionListener() {
336 public void actionPerformed(ActionEvent e)
341 buttonPanel.add(cancelButton);
342 panel.add(buttonPanel, BorderLayout.SOUTH);
347 * Initialize the dialog with the given details
348 * @param inModel table model
349 * @param inDefaultDelimiter default delimiter character
351 private void initDialog(TableModel inModel, char inDefaultDelimiter)
354 _table.setModel(inModel);
356 _toggler.setListSize(inModel.getRowCount());
357 // choose last-used delimiter as default
358 switch (inDefaultDelimiter)
360 case ',' : _delimiterRadios[0].setSelected(true); break;
361 case '\t' : _delimiterRadios[1].setSelected(true); break;
362 case ';' : _delimiterRadios[2].setSelected(true); break;
363 case ' ' : _delimiterRadios[3].setSelected(true); break;
364 default : _delimiterRadios[4].setSelected(true);
365 _otherDelimiterText.setText("" + inDefaultDelimiter);
367 // set card and enable buttons
368 CardLayout cl = (CardLayout) _cards.getLayout();
370 _nextButton.setEnabled(true);
371 _backButton.setEnabled(false);
376 * Save the track to file with the chosen options
377 * @return true if successful or cancelled, false if failed
379 private boolean saveToFile()
381 boolean saveOK = true;
382 FileWriter writer = null;
383 if (_fileChooser == null)
384 _fileChooser = new JFileChooser();
385 _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
386 if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
388 File saveFile = _fileChooser.getSelectedFile();
389 String lineSeparator = System.getProperty("line.separator");
390 // Get coordinate format and altitude format
391 int coordFormat = Coordinate.FORMAT_NONE;
392 for (int i=0; i<_coordUnitsRadios.length; i++)
393 if (_coordUnitsRadios[i].isSelected())
394 coordFormat = FORMAT_COORDS[i];
395 int altitudeFormat = Altitude.FORMAT_NONE;
396 for (int i=0; i<_altitudeUnitsRadios.length; i++)
398 if (_altitudeUnitsRadios[i].isSelected())
400 altitudeFormat = FORMAT_ALTS[i];
403 // Get timestamp formats
404 int timestampFormat = Timestamp.FORMAT_ORIGINAL;
405 for (int i=0; i<_timestampUnitsRadios.length; i++)
407 if (_timestampUnitsRadios[i].isSelected())
409 timestampFormat = FORMAT_TIMES[i];
413 // Check if file exists, and confirm overwrite if necessary
414 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
415 if (!saveFile.exists() || JOptionPane.showOptionDialog(_parentFrame,
416 I18nManager.getText("dialog.save.overwrite.text"),
417 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
418 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
419 == JOptionPane.YES_OPTION)
423 // Create output file
424 writer = new FileWriter(saveFile);
425 // Determine delimiter character to use
426 char delimiter = getDelimiter();
427 FieldInfo info = null;
430 StringBuffer buffer = null;
431 int numFields = _model.getRowCount();
432 boolean firstField = true;
433 // Write header row if required
434 if (_headerRowCheckbox.isSelected())
436 buffer = new StringBuffer();
437 for (int f=0; f<numFields; f++)
439 info = _model.getFieldInfo(f);
440 if (info.isSelected())
444 // output field separator
445 buffer.append(delimiter);
447 field = info.getField();
448 buffer.append(field.getName());
452 writer.write(buffer.toString());
453 writer.write(lineSeparator);
456 // Loop over points outputting each in turn to buffer
457 int numPoints = _track.getNumPoints();
458 for (int p=0; p<numPoints; p++)
460 DataPoint point = _track.getPoint(p);
462 buffer = new StringBuffer();
463 for (int f=0; f<numFields; f++)
465 info = _model.getFieldInfo(f);
466 if (info.isSelected())
470 // output field separator
471 buffer.append(delimiter);
473 field = info.getField();
474 // Output field according to type
475 if (field == Field.LATITUDE)
477 buffer.append(point.getLatitude().output(coordFormat));
479 else if (field == Field.LONGITUDE)
481 buffer.append(point.getLongitude().output(coordFormat));
483 else if (field == Field.ALTITUDE)
487 buffer.append(point.getAltitude().getValue(altitudeFormat));
489 catch (NullPointerException npe) {}
491 else if (field == Field.TIMESTAMP)
495 if (timestampFormat == Timestamp.FORMAT_ORIGINAL) {
496 // output original string
497 buffer.append(point.getFieldValue(Field.TIMESTAMP));
500 // format value accordingly
501 buffer.append(point.getTimestamp().getText(timestampFormat));
504 catch (NullPointerException npe) {}
508 String value = point.getFieldValue(field);
511 buffer.append(value);
518 writer.write(buffer.toString());
519 writer.write(lineSeparator);
522 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.ok1")
523 + " " + numPoints + " " + I18nManager.getText("dialog.save.ok2")
524 + " " + saveFile.getAbsolutePath(),
525 I18nManager.getText("dialog.save.oktitle"), JOptionPane.INFORMATION_MESSAGE);
526 _app.informDataSaved();
528 catch (IOException ioe)
531 JOptionPane.showMessageDialog(_parentFrame,
532 I18nManager.getText("error.save.failed") + ioe.getMessage(),
533 I18nManager.getText("error.save.dialogtitle"),
534 JOptionPane.ERROR_MESSAGE);
538 // try to close file if it's open
546 catch (Exception e) {}
551 // Overwrite file confirm cancelled
560 * @return the selected delimiter character
562 private char getDelimiter()
564 // Check the preset 4 delimiters
565 final char[] delimiters = {',', '\t', ';', ' '};
566 for (int i=0; i<4; i++)
568 if (_delimiterRadios[i].isSelected())
570 return delimiters[i];
573 // Wasn't any of those so must be 'other'
574 return _otherDelimiterText.getText().charAt(0);