]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/save/FileSaver.java
Version 17, September 2014
[GpsPrune.git] / tim / prune / save / FileSaver.java
1 package tim.prune.save;
2
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;
11 import java.io.File;
12 import java.io.FileWriter;
13 import java.io.IOException;
14
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;
33
34 import tim.prune.App;
35 import tim.prune.I18nManager;
36 import tim.prune.UpdateMessageBroker;
37 import tim.prune.config.Config;
38 import tim.prune.data.Coordinate;
39 import tim.prune.data.DataPoint;
40 import tim.prune.data.Field;
41 import tim.prune.data.FieldList;
42 import tim.prune.data.RecentFile;
43 import tim.prune.data.Timestamp;
44 import tim.prune.data.Track;
45 import tim.prune.data.Unit;
46 import tim.prune.data.UnitSetLibrary;
47 import tim.prune.load.GenericFileFilter;
48 import tim.prune.load.OneCharDocument;
49
50 /**
51  * Class to manage the saving of track data
52  * as text into a user-specified file
53  */
54 public class FileSaver
55 {
56         private App _app = null;
57         private JFrame _parentFrame = null;
58         private JDialog _dialog = null;
59         private JFileChooser _fileChooser = null;
60         private JPanel _cards = null;
61         private JButton _nextButton = null, _backButton = null;
62         private JTable _table = null;
63         private FieldSelectionTableModel _model = null;
64         private JButton _moveUpButton = null, _moveDownButton = null;
65         private UpDownToggler _toggler = null;
66         private JRadioButton[] _delimiterRadios = null;
67         private JTextField _otherDelimiterText = null;
68         private JCheckBox _headerRowCheckbox = null;
69         private PointTypeSelector _pointTypeSelector = null;
70         private JRadioButton[] _coordUnitsRadios = null;
71         private JRadioButton[] _altitudeUnitsRadios = null;
72         private JRadioButton[] _timestampUnitsRadios = null;
73
74         private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC,
75                 Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG};
76         private static final Unit[] UNIT_ALTS = {null, UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_FEET};
77         private static final Timestamp.Format[] FORMAT_TIMES = {Timestamp.Format.ORIGINAL, Timestamp.Format.LOCALE, Timestamp.Format.ISO8601};
78
79
80         /**
81          * Constructor
82          * @param inApp application object to inform of success
83          * @param inParentFrame parent frame
84          */
85         public FileSaver(App inApp, JFrame inParentFrame)
86         {
87                 _app = inApp;
88                 _parentFrame = inParentFrame;
89         }
90
91
92         /**
93          * Show the save file dialog
94          * @param inDefaultDelimiter default delimiter to use
95          */
96         public void showDialog(char inDefaultDelimiter)
97         {
98                 if (_dialog == null)
99                 {
100                         _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.saveoptions.title"), true);
101                         _dialog.setLocationRelativeTo(_parentFrame);
102                         _dialog.getContentPane().add(makeDialogComponents());
103                         _dialog.pack();
104                 }
105                 // Check field list
106                 Track track = _app.getTrackInfo().getTrack();
107                 FieldList fieldList = track.getFieldList();
108                 int numFields = fieldList.getNumFields();
109                 _model = new FieldSelectionTableModel(numFields);
110                 for (int i=0; i<numFields; i++)
111                 {
112                         Field field = fieldList.getField(i);
113                         FieldInfo info = new FieldInfo(field, track.hasData(field));
114                         _model.addFieldInfo(info, i);
115                 }
116                 // Initialise dialog and show it
117                 initDialog(_model, inDefaultDelimiter);
118                 _dialog.setVisible(true);
119         }
120
121
122         /**
123          * Make the dialog components
124          * @return the GUI components for the save dialog
125          */
126         private Component makeDialogComponents()
127         {
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);
133
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);
145
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)
155                         {
156                                 int row = _table.getSelectedRow();
157                                 if (row > 0)
158                                 {
159                                         _model.swapItems(row, row - 1);
160                                         _table.setRowSelectionInterval(row - 1, row - 1);
161                                 }
162                         }
163                 });
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)
169                         {
170                                 int row = _table.getSelectedRow();
171                                 if (row > -1 && row < (_model.getRowCount() - 1))
172                                 {
173                                         _model.swapItems(row, row + 1);
174                                         _table.setRowSelectionInterval(row + 1, row + 1);
175                                 }
176                         }
177                 });
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);
184
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)));
192
193                 // delimiter panel
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);
200                 // radio buttons
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++)
219                 {
220                         delimGroup.add(_delimiterRadios[i]);
221                 }
222                 delimsPanel.add(otherPanel);
223                 firstCard.add(delimsPanel);
224
225                 // header checkbox
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");
230
231                 // Second card
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++)
254                 {
255                         coordGroup.add(_coordUnitsRadios[i]);
256                         coordsUnitsPanel.add(_coordUnitsRadios[i]);
257                         _coordUnitsRadios[i].setSelected(i==0);
258                 }
259                 coordsUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
260                 secondCardHolder.add(coordsUnitsPanel);
261                 secondCardHolder.add(Box.createRigidArea(new Dimension(0,7)));
262                 // altitude units
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++)
275                 {
276                         altGroup.add(_altitudeUnitsRadios[i]);
277                         altUnitsPanel.add(_altitudeUnitsRadios[i]);
278                         _altitudeUnitsRadios[i].setSelected(i==0);
279                 }
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++)
296                 {
297                         timeGroup.add(_timestampUnitsRadios[i]);
298                         timestampPanel.add(_timestampUnitsRadios[i]);
299                         _timestampUnitsRadios[i].setSelected(i==0);
300                 }
301                 timestampPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
302                 secondCardHolder.add(timestampPanel);
303                 secondCard.add(secondCardHolder, BorderLayout.NORTH);
304                 _cards.add(secondCard, "card2");
305
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)
312                         {
313                                 CardLayout cl = (CardLayout) _cards.getLayout();
314                                 cl.previous(_cards);
315                                 _backButton.setEnabled(false);
316                                 _nextButton.setEnabled(true);
317                         }
318                 });
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)
325                         {
326                                 CardLayout cl = (CardLayout) _cards.getLayout();
327                                 cl.next(_cards);
328                                 _backButton.setEnabled(true);
329                                 _nextButton.setEnabled(false);
330                         }
331                 });
332                 buttonPanel.add(_nextButton);
333                 JButton okButton = new JButton(I18nManager.getText("button.finish"));
334                 okButton.addActionListener(new ActionListener() {
335                         public void actionPerformed(ActionEvent e)
336                         {
337                                 if (saveToFile())
338                                 {
339                                         _dialog.dispose();
340                                 }
341                         }
342                 });
343                 buttonPanel.add(okButton);
344                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
345                 cancelButton.addActionListener(new ActionListener() {
346                         public void actionPerformed(ActionEvent e)
347                         {
348                                 _dialog.dispose();
349                         }
350                 });
351                 buttonPanel.add(cancelButton);
352                 panel.add(buttonPanel, BorderLayout.SOUTH);
353                 return panel;
354         }
355
356         /**
357          * Initialize the dialog with the given details
358          * @param inModel table model
359          * @param inDefaultDelimiter default delimiter character
360          */
361         private void initDialog(TableModel inModel, char inDefaultDelimiter)
362         {
363                 // set table model
364                 _table.setModel(inModel);
365                 // reset toggler
366                 _toggler.setListSize(inModel.getRowCount());
367                 // choose last-used delimiter as default
368                 switch (inDefaultDelimiter)
369                 {
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);
376                 }
377                 _pointTypeSelector.init(_app.getTrackInfo());
378                 // set card and enable buttons
379                 CardLayout cl = (CardLayout) _cards.getLayout();
380                 cl.first(_cards);
381                 _nextButton.setEnabled(true);
382                 _backButton.setEnabled(false);
383         }
384
385
386         /**
387          * Start the save process by choosing the file to save to
388          * @return true if successful or cancelled, false if failed
389          */
390         private boolean saveToFile()
391         {
392                 if (!_pointTypeSelector.getAnythingSelected()) {
393                         JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"),
394                                 I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE);
395                         return false;
396                 }
397                 if (_fileChooser == null)
398                 {
399                         _fileChooser = new JFileChooser();
400                         _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
401                         _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.txt", new String[] {"txt", "text"}));
402                         _fileChooser.setAcceptAllFileFilterUsed(true);
403                         // start from directory in config which should be set
404                         String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
405                         if (configDir == null) {configDir = Config.getConfigString(Config.KEY_PHOTO_DIR);}
406                         if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
407                 }
408                 if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
409                 {
410                         return saveToFile(_fileChooser.getSelectedFile());
411                 }
412                 return true; // cancelled
413         }
414
415
416         /**
417          * Save the track to the specified file using the chosen options
418          * @param inSaveFile file to save to
419          * @return true if save successful, false if failed
420          */
421         private boolean saveToFile(File inSaveFile)
422         {
423                 // TODO: Shorten method
424                 FileWriter writer = null;
425                 final String lineSeparator = System.getProperty("line.separator");
426                 boolean saveOK = true;
427                 // Get coordinate format and altitude format
428                 int coordFormat = Coordinate.FORMAT_NONE;
429                 for (int i=0; i<_coordUnitsRadios.length; i++)
430                         if (_coordUnitsRadios[i].isSelected())
431                                 coordFormat = FORMAT_COORDS[i];
432                 Unit altitudeUnit = null;
433                 for (int i=0; i<_altitudeUnitsRadios.length; i++)
434                 {
435                         if (_altitudeUnitsRadios[i].isSelected()) {
436                                 altitudeUnit = UNIT_ALTS[i];
437                         }
438                 }
439                 // Get timestamp format
440                 Timestamp.Format timestampFormat = Timestamp.Format.ORIGINAL;
441                 for (int i=0; i<_timestampUnitsRadios.length; i++)
442                 {
443                         if (_timestampUnitsRadios[i].isSelected()) {
444                                 timestampFormat = FORMAT_TIMES[i];
445                         }
446                 }
447
448                 // Correct chosen filename if necessary
449                 final File saveFile = (isFilenameOk(inSaveFile)?inSaveFile:new File(inSaveFile.getAbsolutePath() + ".txt"));
450
451                 // Check if file exists, and confirm overwrite if necessary
452                 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
453                 if (!saveFile.exists() || JOptionPane.showOptionDialog(_parentFrame,
454                                 I18nManager.getText("dialog.save.overwrite.text"),
455                                 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
456                                 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
457                         == JOptionPane.YES_OPTION)
458                 {
459                         try
460                         {
461                                 // Create output file
462                                 writer = new FileWriter(saveFile);
463                                 // Determine delimiter character to use
464                                 final char delimiter = getDelimiter();
465                                 FieldInfo info = null;
466
467                                 StringBuffer buffer = null;
468                                 int numFields = _model.getRowCount();
469                                 boolean firstField = true;
470                                 // Write header row if required
471                                 if (_headerRowCheckbox.isSelected())
472                                 {
473                                         buffer = new StringBuffer();
474                                         for (int f=0; f<numFields; f++)
475                                         {
476                                                 info = _model.getFieldInfo(f);
477                                                 if (info.isSelected())
478                                                 {
479                                                         // output field separator
480                                                         if (!firstField) {
481                                                                 buffer.append(delimiter);
482                                                         }
483                                                         buffer.append(info.getField().getName());
484                                                         firstField = false;
485                                                 }
486                                         }
487                                         writer.write(buffer.toString());
488                                         writer.write(lineSeparator);
489                                 }
490
491                                 // Examine selection
492                                 int selStart = -1, selEnd = -1;
493                                 if (_pointTypeSelector.getJustSelection()) {
494                                         selStart = _app.getTrackInfo().getSelection().getStart();
495                                         selEnd = _app.getTrackInfo().getSelection().getEnd();
496                                 }
497                                 // Loop over points outputting each in turn to buffer
498                                 Track track = _app.getTrackInfo().getTrack();
499                                 final int numPoints = track.getNumPoints();
500                                 int numSaved = 0;
501                                 for (int p=0; p<numPoints; p++)
502                                 {
503                                         DataPoint point = track.getPoint(p);
504                                         boolean savePoint = ((point.isWaypoint() && _pointTypeSelector.getWaypointsSelected())
505                                                 || (!point.isWaypoint() && !point.hasMedia() && _pointTypeSelector.getTrackpointsSelected())
506                                                 || (!point.isWaypoint() && point.getPhoto()!=null && _pointTypeSelector.getPhotopointsSelected())
507                                                 || (!point.isWaypoint() && point.getAudio()!=null && _pointTypeSelector.getAudiopointsSelected()))
508                                                 && (!_pointTypeSelector.getJustSelection() || (p>=selStart && p<=selEnd));
509                                         if (!savePoint) {continue;}
510                                         numSaved++;
511                                         firstField = true;
512                                         buffer = new StringBuffer();
513                                         for (int f=0; f<numFields; f++)
514                                         {
515                                                 info = _model.getFieldInfo(f);
516                                                 if (info.isSelected())
517                                                 {
518                                                         // output field separator
519                                                         if (!firstField) {
520                                                                 buffer.append(delimiter);
521                                                         }
522                                                         saveField(buffer, point, info.getField(), coordFormat, altitudeUnit, timestampFormat);
523                                                         firstField = false;
524                                                 }
525                                         }
526                                         // Output to file
527                                         writer.write(buffer.toString());
528                                         writer.write(lineSeparator);
529                                 }
530                                 // Store directory in config for later
531                                 Config.setConfigString(Config.KEY_TRACK_DIR, saveFile.getParentFile().getAbsolutePath());
532                                 // Add to recent file list
533                                 Config.getRecentFileList().addFile(new RecentFile(inSaveFile, true));
534                                 // Save successful
535                                 UpdateMessageBroker.informSubscribers();
536                                 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
537                                          + " " + numSaved + " " + I18nManager.getText("confirm.save.ok2")
538                                          + " " + saveFile.getAbsolutePath());
539                                 _app.informDataSaved();
540                         }
541                         catch (IOException ioe)
542                         {
543                                 saveOK = false;
544                                 _app.showErrorMessageNoLookup("error.save.dialogtitle",
545                                         I18nManager.getText("error.save.failed") + " : " + ioe.getMessage());
546                         }
547                         finally
548                         {
549                                 // try to close file if it's open
550                                 try {
551                                         writer.close();
552                                 }
553                                 catch (Exception e) {}
554                         }
555                 }
556                 else
557                 {
558                         // Overwrite file confirm cancelled
559                         saveOK = false;
560                 }
561                 return saveOK;
562         }
563
564
565         /**
566          * Format the given field and append to the given buffer for saving
567          * @param inBuffer buffer to append to
568          * @param inPoint point object
569          * @param inField field object
570          * @param inCoordFormat coordinate format
571          * @param inAltitudeUnit altitude unit
572          * @param inTimestampFormat timestamp format
573          */
574         private void saveField(StringBuffer inBuffer, DataPoint inPoint, Field inField,
575                 int inCoordFormat, Unit inAltitudeUnit, Timestamp.Format inTimestampFormat)
576         {
577                 // Output field according to type
578                 if (inField == Field.LATITUDE)
579                 {
580                         inBuffer.append(inPoint.getLatitude().output(inCoordFormat));
581                 }
582                 else if (inField == Field.LONGITUDE)
583                 {
584                         inBuffer.append(inPoint.getLongitude().output(inCoordFormat));
585                 }
586                 else if (inField == Field.ALTITUDE)
587                 {
588                         try
589                         {
590                                 inBuffer.append(inPoint.getAltitude().getStringValue(inAltitudeUnit));
591                         }
592                         catch (NullPointerException npe) {}
593                 }
594                 else if (inField == Field.TIMESTAMP)
595                 {
596                         if (inPoint.hasTimestamp())
597                         {
598                                 // format value accordingly
599                                 inBuffer.append(inPoint.getTimestamp().getText(inTimestampFormat));
600                         }
601                 }
602                 else
603                 {
604                         String value = inPoint.getFieldValue(inField);
605                         if (value != null)
606                         {
607                                 inBuffer.append(value);
608                         }
609                 }
610         }
611
612
613         /**
614          * @return the selected delimiter character
615          */
616         private char getDelimiter()
617         {
618                 // Check the preset 4 delimiters
619                 final char[] delimiters = {',', '\t', ';', ' '};
620                 for (int i=0; i<4; i++)
621                 {
622                         if (_delimiterRadios[i].isSelected())
623                         {
624                                 return delimiters[i];
625                         }
626                 }
627                 // Wasn't any of those so must be 'other'
628                 return _otherDelimiterText.getText().charAt(0);
629         }
630
631
632         /**
633          * Check the selected filename to see if it is acceptable
634          * @param inFile chosen file to save
635          * @return true if filename is ok
636          */
637         private static boolean isFilenameOk(File inFile)
638         {
639                 String filename = inFile.getName().toLowerCase();
640                 return (filename.length() <4 || (!filename.endsWith(".gpx")
641                         && !filename.endsWith(".kml") && !filename.endsWith(".kmz") && !filename.endsWith(".zip")));
642         }
643 }