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.Config;
36 import tim.prune.I18nManager;
37 import tim.prune.UpdateMessageBroker;
38 import tim.prune.data.Altitude;
39 import tim.prune.data.Coordinate;
40 import tim.prune.data.DataPoint;
41 import tim.prune.data.Field;
42 import tim.prune.data.FieldList;
43 import tim.prune.data.Timestamp;
44 import tim.prune.data.Track;
45 import tim.prune.load.GenericFileFilter;
46 import tim.prune.load.OneCharDocument;
49 * Class to manage the saving of track data
50 * into a user-specified file
52 public class FileSaver
54 private App _app = null;
55 private JFrame _parentFrame = null;
56 private Track _track = null;
57 private JDialog _dialog = null;
58 private JFileChooser _fileChooser = null;
59 private JPanel _cards = null;
60 private JButton _nextButton = null, _backButton = null;
61 private JTable _table = null;
62 private FieldSelectionTableModel _model = null;
63 private JButton _moveUpButton = null, _moveDownButton = null;
64 private UpDownToggler _toggler = null;
65 private JRadioButton[] _delimiterRadios = null;
66 private JTextField _otherDelimiterText = null;
67 private JCheckBox _headerRowCheckbox = null;
68 private PointTypeSelector _pointTypeSelector = null;
69 private JRadioButton[] _coordUnitsRadios = null;
70 private JRadioButton[] _altitudeUnitsRadios = null;
71 private JRadioButton[] _timestampUnitsRadios = null;
73 private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC,
74 Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG};
75 private static final Altitude.Format[] FORMAT_ALTS = {Altitude.Format.NO_FORMAT, Altitude.Format.METRES, Altitude.Format.FEET};
76 private static final int[] FORMAT_TIMES = {Timestamp.FORMAT_ORIGINAL, Timestamp.FORMAT_LOCALE, Timestamp.FORMAT_ISO_8601};
81 * @param inApp application object to inform of success
82 * @param inParentFrame parent frame
83 * @param inTrack track object to save
85 public FileSaver(App inApp, JFrame inParentFrame, Track inTrack)
88 _parentFrame = inParentFrame;
94 * Show the save file dialog
95 * @param inDefaultDelimiter default delimiter to use
97 public void showDialog(char inDefaultDelimiter)
101 _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.saveoptions.title"), true);
102 _dialog.setLocationRelativeTo(_parentFrame);
103 _dialog.getContentPane().add(makeDialogComponents());
107 FieldList fieldList = _track.getFieldList();
108 int numFields = fieldList.getNumFields();
109 _model = new FieldSelectionTableModel(numFields);
110 for (int i=0; i<numFields; i++)
112 Field field = fieldList.getField(i);
113 FieldInfo info = new FieldInfo(field, _track.hasData(field));
114 _model.addFieldInfo(info, i);
116 // Initialise dialog and show it
117 initDialog(_model, inDefaultDelimiter);
118 _dialog.setVisible(true);
123 * Make the dialog components
124 * @return the GUI components for the save dialog
126 private Component makeDialogComponents()
128 JPanel panel = new JPanel();
129 panel.setLayout(new BorderLayout());
130 _cards = new JPanel();
131 _cards.setLayout(new CardLayout());
132 panel.add(_cards, BorderLayout.CENTER);
134 // Make first card for field selection and delimiter
135 JPanel firstCard = new JPanel();
136 firstCard.setLayout(new BoxLayout(firstCard, BoxLayout.Y_AXIS));
137 JPanel tablePanel = new JPanel();
138 tablePanel.setLayout(new BorderLayout());
139 _table = new JTable();
140 _table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
141 // Enclose table in a scrollpane to prevent other components getting lost
142 JScrollPane scrollPane = new JScrollPane(_table);
143 _table.setPreferredScrollableViewportSize(new Dimension(300, 150));
144 tablePanel.add(scrollPane, BorderLayout.CENTER);
146 // Make a panel to hold the table and up/down buttons
147 JPanel fieldsPanel = new JPanel();
148 fieldsPanel.setLayout(new BorderLayout());
149 fieldsPanel.add(tablePanel, BorderLayout.CENTER);
150 JPanel updownPanel = new JPanel();
151 updownPanel.setLayout(new BoxLayout(updownPanel, BoxLayout.Y_AXIS));
152 _moveUpButton = new JButton(I18nManager.getText("button.moveup"));
153 _moveUpButton.addActionListener(new ActionListener() {
154 public void actionPerformed(ActionEvent e)
156 int row = _table.getSelectedRow();
159 _model.swapItems(row, row - 1);
160 _table.setRowSelectionInterval(row - 1, row - 1);
164 _moveUpButton.setEnabled(false);
165 updownPanel.add(_moveUpButton);
166 _moveDownButton = new JButton(I18nManager.getText("button.movedown"));
167 _moveDownButton.addActionListener(new ActionListener() {
168 public void actionPerformed(ActionEvent e)
170 int row = _table.getSelectedRow();
171 if (row > -1 && row < (_model.getRowCount() - 1))
173 _model.swapItems(row, row + 1);
174 _table.setRowSelectionInterval(row + 1, row + 1);
178 _moveDownButton.setEnabled(false);
179 updownPanel.add(_moveDownButton);
180 fieldsPanel.add(updownPanel, BorderLayout.EAST);
181 // enable/disable buttons based on table row selection
182 _toggler = new UpDownToggler(_moveUpButton, _moveDownButton);
183 _table.getSelectionModel().addListSelectionListener(_toggler);
185 // Add fields panel and the delimiter panel to first card in pack
186 JLabel saveOptionsLabel = new JLabel(I18nManager.getText("dialog.save.fieldstosave"));
187 saveOptionsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
188 firstCard.add(saveOptionsLabel);
189 fieldsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
190 firstCard.add(fieldsPanel);
191 firstCard.add(Box.createRigidArea(new Dimension(0,10)));
194 JLabel delimLabel = new JLabel(I18nManager.getText("dialog.delimiter.label"));
195 delimLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
196 firstCard.add(delimLabel);
197 JPanel delimsPanel = new JPanel();
198 delimsPanel.setLayout(new GridLayout(0, 2));
199 delimsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
201 _delimiterRadios = new JRadioButton[5];
202 _delimiterRadios[0] = new JRadioButton(I18nManager.getText("dialog.delimiter.comma"));
203 delimsPanel.add(_delimiterRadios[0]);
204 _delimiterRadios[1] = new JRadioButton(I18nManager.getText("dialog.delimiter.tab"));
205 delimsPanel.add(_delimiterRadios[1]);
206 _delimiterRadios[2] = new JRadioButton(I18nManager.getText("dialog.delimiter.semicolon"));
207 delimsPanel.add(_delimiterRadios[2]);
208 _delimiterRadios[3] = new JRadioButton(I18nManager.getText("dialog.delimiter.space"));
209 delimsPanel.add(_delimiterRadios[3]);
210 JPanel otherPanel = new JPanel();
211 otherPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
212 _delimiterRadios[4] = new JRadioButton(I18nManager.getText("dialog.delimiter.other"));
213 otherPanel.add(_delimiterRadios[4]);
214 _otherDelimiterText = new JTextField(new OneCharDocument(), null, 2);
215 otherPanel.add(_otherDelimiterText);
216 // Group radio buttons
217 ButtonGroup delimGroup = new ButtonGroup();
218 for (int i=0; i<_delimiterRadios.length; i++)
220 delimGroup.add(_delimiterRadios[i]);
222 delimsPanel.add(otherPanel);
223 firstCard.add(delimsPanel);
226 firstCard.add(Box.createRigidArea(new Dimension(0,10)));
227 _headerRowCheckbox = new JCheckBox(I18nManager.getText("dialog.save.headerrow"), true);
228 firstCard.add(_headerRowCheckbox);
229 _cards.add(firstCard, "card1");
232 JPanel secondCard = new JPanel();
233 secondCard.setLayout(new BorderLayout());
234 JPanel secondCardHolder = new JPanel();
235 secondCardHolder.setLayout(new BoxLayout(secondCardHolder, BoxLayout.Y_AXIS));
236 // point type selector
237 secondCardHolder.add(Box.createRigidArea(new Dimension(0,10)));
238 _pointTypeSelector = new PointTypeSelector();
239 _pointTypeSelector.setAlignmentX(JLabel.LEFT_ALIGNMENT);
240 secondCardHolder.add(_pointTypeSelector);
241 JLabel coordLabel = new JLabel(I18nManager.getText("dialog.save.coordinateunits"));
242 coordLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
243 secondCardHolder.add(coordLabel);
244 JPanel coordsUnitsPanel = new JPanel();
245 coordsUnitsPanel.setBorder(BorderFactory.createEtchedBorder());
246 coordsUnitsPanel.setLayout(new GridLayout(0, 2));
247 _coordUnitsRadios = new JRadioButton[4];
248 _coordUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original"));
249 _coordUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.degminsec"));
250 _coordUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.degmin"));
251 _coordUnitsRadios[3] = new JRadioButton(I18nManager.getText("units.deg"));
252 ButtonGroup coordGroup = new ButtonGroup();
253 for (int i=0; i<4; i++)
255 coordGroup.add(_coordUnitsRadios[i]);
256 coordsUnitsPanel.add(_coordUnitsRadios[i]);
257 _coordUnitsRadios[i].setSelected(i==0);
259 coordsUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
260 secondCardHolder.add(coordsUnitsPanel);
261 secondCardHolder.add(Box.createRigidArea(new Dimension(0,7)));
263 JLabel altUnitsLabel = new JLabel(I18nManager.getText("dialog.save.altitudeunits"));
264 altUnitsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
265 secondCardHolder.add(altUnitsLabel);
266 JPanel altUnitsPanel = new JPanel();
267 altUnitsPanel.setBorder(BorderFactory.createEtchedBorder());
268 altUnitsPanel.setLayout(new GridLayout(0, 2));
269 _altitudeUnitsRadios = new JRadioButton[3];
270 _altitudeUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original"));
271 _altitudeUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.metres"));
272 _altitudeUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.feet"));
273 ButtonGroup altGroup = new ButtonGroup();
274 for (int i=0; i<3; i++)
276 altGroup.add(_altitudeUnitsRadios[i]);
277 altUnitsPanel.add(_altitudeUnitsRadios[i]);
278 _altitudeUnitsRadios[i].setSelected(i==0);
280 altUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
281 secondCardHolder.add(altUnitsPanel);
282 secondCardHolder.add(Box.createRigidArea(new Dimension(0,7)));
283 // Selection of format of timestamps
284 JLabel timestampLabel = new JLabel(I18nManager.getText("dialog.save.timestampformat"));
285 timestampLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
286 secondCardHolder.add(timestampLabel);
287 JPanel timestampPanel = new JPanel();
288 timestampPanel.setBorder(BorderFactory.createEtchedBorder());
289 timestampPanel.setLayout(new GridLayout(0, 2));
290 _timestampUnitsRadios = new JRadioButton[3];
291 _timestampUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original"));
292 _timestampUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.default"));
293 _timestampUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.iso8601"));
294 ButtonGroup timeGroup = new ButtonGroup();
295 for (int i=0; i<3; i++)
297 timeGroup.add(_timestampUnitsRadios[i]);
298 timestampPanel.add(_timestampUnitsRadios[i]);
299 _timestampUnitsRadios[i].setSelected(i==0);
301 timestampPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
302 secondCardHolder.add(timestampPanel);
303 secondCard.add(secondCardHolder, BorderLayout.NORTH);
304 _cards.add(secondCard, "card2");
306 // Put together with ok/cancel buttons on the bottom
307 JPanel buttonPanel = new JPanel();
308 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
309 _backButton = new JButton(I18nManager.getText("button.back"));
310 _backButton.addActionListener(new ActionListener() {
311 public void actionPerformed(ActionEvent e)
313 CardLayout cl = (CardLayout) _cards.getLayout();
315 _backButton.setEnabled(false);
316 _nextButton.setEnabled(true);
319 _backButton.setEnabled(false);
320 buttonPanel.add(_backButton);
321 _nextButton = new JButton(I18nManager.getText("button.next"));
322 _nextButton.setEnabled(true);
323 _nextButton.addActionListener(new ActionListener() {
324 public void actionPerformed(ActionEvent e)
326 CardLayout cl = (CardLayout) _cards.getLayout();
328 _backButton.setEnabled(true);
329 _nextButton.setEnabled(false);
332 buttonPanel.add(_nextButton);
333 JButton okButton = new JButton(I18nManager.getText("button.finish"));
334 okButton.addActionListener(new ActionListener() {
335 public void actionPerformed(ActionEvent e)
343 buttonPanel.add(okButton);
344 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
345 cancelButton.addActionListener(new ActionListener() {
346 public void actionPerformed(ActionEvent e)
351 buttonPanel.add(cancelButton);
352 panel.add(buttonPanel, BorderLayout.SOUTH);
357 * Initialize the dialog with the given details
358 * @param inModel table model
359 * @param inDefaultDelimiter default delimiter character
361 private void initDialog(TableModel inModel, char inDefaultDelimiter)
364 _table.setModel(inModel);
366 _toggler.setListSize(inModel.getRowCount());
367 // choose last-used delimiter as default
368 switch (inDefaultDelimiter)
370 case ',' : _delimiterRadios[0].setSelected(true); break;
371 case '\t' : _delimiterRadios[1].setSelected(true); break;
372 case ';' : _delimiterRadios[2].setSelected(true); break;
373 case ' ' : _delimiterRadios[3].setSelected(true); break;
374 default : _delimiterRadios[4].setSelected(true);
375 _otherDelimiterText.setText("" + inDefaultDelimiter);
377 _pointTypeSelector.init(_app.getTrackInfo());
378 // set card and enable buttons
379 CardLayout cl = (CardLayout) _cards.getLayout();
381 _nextButton.setEnabled(true);
382 _backButton.setEnabled(false);
387 * Save the track to file with the chosen options
388 * @return true if successful or cancelled, false if failed
390 private boolean saveToFile()
392 // TODO: Shorten method
393 if (!_pointTypeSelector.getAnythingSelected()) {return false;}
394 boolean saveOK = true;
395 FileWriter writer = null;
396 if (_fileChooser == null)
398 _fileChooser = new JFileChooser();
399 _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
400 _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.txt", new String[] {"txt", "text"}));
401 _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.gpx", new String[] {"gpx"}));
402 _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.kml", new String[] {"kml"}));
403 _fileChooser.setAcceptAllFileFilterUsed(true);
404 // start from directory in config which should be set
405 String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
406 if (configDir == null) {configDir = Config.getConfigString(Config.KEY_PHOTO_DIR);}
407 if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
409 if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
411 File saveFile = _fileChooser.getSelectedFile();
412 String lineSeparator = System.getProperty("line.separator");
413 // Get coordinate format and altitude format
414 int coordFormat = Coordinate.FORMAT_NONE;
415 for (int i=0; i<_coordUnitsRadios.length; i++)
416 if (_coordUnitsRadios[i].isSelected())
417 coordFormat = FORMAT_COORDS[i];
418 Altitude.Format altitudeFormat = Altitude.Format.NO_FORMAT;
419 for (int i=0; i<_altitudeUnitsRadios.length; i++)
421 if (_altitudeUnitsRadios[i].isSelected())
423 altitudeFormat = FORMAT_ALTS[i];
426 // Get timestamp formats
427 int timestampFormat = Timestamp.FORMAT_ORIGINAL;
428 for (int i=0; i<_timestampUnitsRadios.length; i++)
430 if (_timestampUnitsRadios[i].isSelected())
432 timestampFormat = FORMAT_TIMES[i];
436 // Check if file exists, and confirm overwrite if necessary
437 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
438 if (!saveFile.exists() || JOptionPane.showOptionDialog(_parentFrame,
439 I18nManager.getText("dialog.save.overwrite.text"),
440 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
441 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
442 == JOptionPane.YES_OPTION)
446 // Create output file
447 writer = new FileWriter(saveFile);
448 // Determine delimiter character to use
449 char delimiter = getDelimiter();
450 FieldInfo info = null;
453 StringBuffer buffer = null;
454 int numFields = _model.getRowCount();
455 boolean firstField = true;
456 // Write header row if required
457 if (_headerRowCheckbox.isSelected())
459 buffer = new StringBuffer();
460 for (int f=0; f<numFields; f++)
462 info = _model.getFieldInfo(f);
463 if (info.isSelected())
467 // output field separator
468 buffer.append(delimiter);
470 field = info.getField();
471 buffer.append(field.getName());
475 writer.write(buffer.toString());
476 writer.write(lineSeparator);
479 // Loop over points outputting each in turn to buffer
480 final int numPoints = _track.getNumPoints();
482 for (int p=0; p<numPoints; p++)
484 DataPoint point = _track.getPoint(p);
485 boolean savePoint = ((point.isWaypoint() && _pointTypeSelector.getWaypointsSelected())
486 || (!point.isWaypoint() && point.getPhoto()==null && _pointTypeSelector.getTrackpointsSelected())
487 || (!point.isWaypoint() && point.getPhoto()!=null && _pointTypeSelector.getPhotopointsSelected()));
488 if (!savePoint) {continue;}
491 buffer = new StringBuffer();
492 for (int f=0; f<numFields; f++)
494 info = _model.getFieldInfo(f);
495 if (info.isSelected())
499 // output field separator
500 buffer.append(delimiter);
502 field = info.getField();
503 // Output field according to type
504 if (field == Field.LATITUDE)
506 buffer.append(point.getLatitude().output(coordFormat));
508 else if (field == Field.LONGITUDE)
510 buffer.append(point.getLongitude().output(coordFormat));
512 else if (field == Field.ALTITUDE)
516 buffer.append(point.getAltitude().getStringValue(altitudeFormat));
518 catch (NullPointerException npe) {}
520 else if (field == Field.TIMESTAMP)
522 if (point.hasTimestamp())
524 if (timestampFormat == Timestamp.FORMAT_ORIGINAL) {
525 // output original string
526 buffer.append(point.getFieldValue(Field.TIMESTAMP));
529 // format value accordingly
530 buffer.append(point.getTimestamp().getText(timestampFormat));
536 String value = point.getFieldValue(field);
539 buffer.append(value);
546 writer.write(buffer.toString());
547 writer.write(lineSeparator);
549 // Store directory in config for later
550 Config.setConfigString(Config.KEY_TRACK_DIR, saveFile.getParentFile().getAbsolutePath());
552 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
553 + " " + numSaved + " " + I18nManager.getText("confirm.save.ok2")
554 + " " + saveFile.getAbsolutePath());
555 _app.informDataSaved();
557 catch (IOException ioe)
560 _app.showErrorMessageNoLookup("error.save.dialogtitle",
561 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage());
565 // try to close file if it's open
567 if (writer != null) {
571 catch (Exception e) {}
576 // Overwrite file confirm cancelled
585 * @return the selected delimiter character
587 private char getDelimiter()
589 // Check the preset 4 delimiters
590 final char[] delimiters = {',', '\t', ';', ' '};
591 for (int i=0; i<4; i++)
593 if (_delimiterRadios[i].isSelected())
595 return delimiters[i];
598 // Wasn't any of those so must be 'other'
599 return _otherDelimiterText.getText().charAt(0);