]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/save/FileSaver.java
Version 14, October 2012
[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.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.RecentFile;
44 import tim.prune.data.Timestamp;
45 import tim.prune.data.Track;
46 import tim.prune.load.GenericFileFilter;
47 import tim.prune.load.OneCharDocument;
48
49 /**
50  * Class to manage the saving of track data
51  * as text into a user-specified file
52  */
53 public class FileSaver
54 {
55         private App _app = null;
56         private JFrame _parentFrame = 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;
72
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};
77
78
79         /**
80          * Constructor
81          * @param inApp application object to inform of success
82          * @param inParentFrame parent frame
83          */
84         public FileSaver(App inApp, JFrame inParentFrame)
85         {
86                 _app = inApp;
87                 _parentFrame = inParentFrame;
88         }
89
90
91         /**
92          * Show the save file dialog
93          * @param inDefaultDelimiter default delimiter to use
94          */
95         public void showDialog(char inDefaultDelimiter)
96         {
97                 if (_dialog == null)
98                 {
99                         _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.saveoptions.title"), true);
100                         _dialog.setLocationRelativeTo(_parentFrame);
101                         _dialog.getContentPane().add(makeDialogComponents());
102                         _dialog.pack();
103                 }
104                 // Check field list
105                 Track track = _app.getTrackInfo().getTrack();
106                 FieldList fieldList = track.getFieldList();
107                 int numFields = fieldList.getNumFields();
108                 _model = new FieldSelectionTableModel(numFields);
109                 for (int i=0; i<numFields; i++)
110                 {
111                         Field field = fieldList.getField(i);
112                         FieldInfo info = new FieldInfo(field, track.hasData(field));
113                         _model.addFieldInfo(info, i);
114                 }
115                 // Initialise dialog and show it
116                 initDialog(_model, inDefaultDelimiter);
117                 _dialog.setVisible(true);
118         }
119
120
121         /**
122          * Make the dialog components
123          * @return the GUI components for the save dialog
124          */
125         private Component makeDialogComponents()
126         {
127                 JPanel panel = new JPanel();
128                 panel.setLayout(new BorderLayout());
129                 _cards = new JPanel();
130                 _cards.setLayout(new CardLayout());
131                 panel.add(_cards, BorderLayout.CENTER);
132
133                 // Make first card for field selection and delimiter
134                 JPanel firstCard = new JPanel();
135                 firstCard.setLayout(new BoxLayout(firstCard, BoxLayout.Y_AXIS));
136                 JPanel tablePanel = new JPanel();
137                 tablePanel.setLayout(new BorderLayout());
138                 _table = new JTable();
139                 _table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
140                 // Enclose table in a scrollpane to prevent other components getting lost
141                 JScrollPane scrollPane = new JScrollPane(_table);
142                 _table.setPreferredScrollableViewportSize(new Dimension(300, 150));
143                 tablePanel.add(scrollPane, BorderLayout.CENTER);
144
145                 // Make a panel to hold the table and up/down buttons
146                 JPanel fieldsPanel = new JPanel();
147                 fieldsPanel.setLayout(new BorderLayout());
148                 fieldsPanel.add(tablePanel, BorderLayout.CENTER);
149                 JPanel updownPanel = new JPanel();
150                 updownPanel.setLayout(new BoxLayout(updownPanel, BoxLayout.Y_AXIS));
151                 _moveUpButton = new JButton(I18nManager.getText("button.moveup"));
152                 _moveUpButton.addActionListener(new ActionListener() {
153                         public void actionPerformed(ActionEvent e)
154                         {
155                                 int row = _table.getSelectedRow();
156                                 if (row > 0)
157                                 {
158                                         _model.swapItems(row, row - 1);
159                                         _table.setRowSelectionInterval(row - 1, row - 1);
160                                 }
161                         }
162                 });
163                 _moveUpButton.setEnabled(false);
164                 updownPanel.add(_moveUpButton);
165                 _moveDownButton = new JButton(I18nManager.getText("button.movedown"));
166                 _moveDownButton.addActionListener(new ActionListener() {
167                         public void actionPerformed(ActionEvent e)
168                         {
169                                 int row = _table.getSelectedRow();
170                                 if (row > -1 && row < (_model.getRowCount() - 1))
171                                 {
172                                         _model.swapItems(row, row + 1);
173                                         _table.setRowSelectionInterval(row + 1, row + 1);
174                                 }
175                         }
176                 });
177                 _moveDownButton.setEnabled(false);
178                 updownPanel.add(_moveDownButton);
179                 fieldsPanel.add(updownPanel, BorderLayout.EAST);
180                 // enable/disable buttons based on table row selection
181                 _toggler = new UpDownToggler(_moveUpButton, _moveDownButton);
182                 _table.getSelectionModel().addListSelectionListener(_toggler);
183
184                 // Add fields panel and the delimiter panel to first card in pack
185                 JLabel saveOptionsLabel = new JLabel(I18nManager.getText("dialog.save.fieldstosave"));
186                 saveOptionsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
187                 firstCard.add(saveOptionsLabel);
188                 fieldsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
189                 firstCard.add(fieldsPanel);
190                 firstCard.add(Box.createRigidArea(new Dimension(0,10)));
191
192                 // delimiter panel
193                 JLabel delimLabel = new JLabel(I18nManager.getText("dialog.delimiter.label"));
194                 delimLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
195                 firstCard.add(delimLabel);
196                 JPanel delimsPanel = new JPanel();
197                 delimsPanel.setLayout(new GridLayout(0, 2));
198                 delimsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
199                 // radio buttons
200                 _delimiterRadios = new JRadioButton[5];
201                 _delimiterRadios[0] = new JRadioButton(I18nManager.getText("dialog.delimiter.comma"));
202                 delimsPanel.add(_delimiterRadios[0]);
203                 _delimiterRadios[1] = new JRadioButton(I18nManager.getText("dialog.delimiter.tab"));
204                 delimsPanel.add(_delimiterRadios[1]);
205                 _delimiterRadios[2] = new JRadioButton(I18nManager.getText("dialog.delimiter.semicolon"));
206                 delimsPanel.add(_delimiterRadios[2]);
207                 _delimiterRadios[3] = new JRadioButton(I18nManager.getText("dialog.delimiter.space"));
208                 delimsPanel.add(_delimiterRadios[3]);
209                 JPanel otherPanel = new JPanel();
210                 otherPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
211                 _delimiterRadios[4] = new JRadioButton(I18nManager.getText("dialog.delimiter.other"));
212                 otherPanel.add(_delimiterRadios[4]);
213                 _otherDelimiterText = new JTextField(new OneCharDocument(), null, 2);
214                 otherPanel.add(_otherDelimiterText);
215                 // Group radio buttons
216                 ButtonGroup delimGroup = new ButtonGroup();
217                 for (int i=0; i<_delimiterRadios.length; i++)
218                 {
219                         delimGroup.add(_delimiterRadios[i]);
220                 }
221                 delimsPanel.add(otherPanel);
222                 firstCard.add(delimsPanel);
223
224                 // header checkbox
225                 firstCard.add(Box.createRigidArea(new Dimension(0,10)));
226                 _headerRowCheckbox = new JCheckBox(I18nManager.getText("dialog.save.headerrow"), true);
227                 firstCard.add(_headerRowCheckbox);
228                 _cards.add(firstCard, "card1");
229
230                 // Second card
231                 JPanel secondCard = new JPanel();
232                 secondCard.setLayout(new BorderLayout());
233                 JPanel secondCardHolder = new JPanel();
234                 secondCardHolder.setLayout(new BoxLayout(secondCardHolder, BoxLayout.Y_AXIS));
235                 // point type selector
236                 secondCardHolder.add(Box.createRigidArea(new Dimension(0,10)));
237                 _pointTypeSelector = new PointTypeSelector();
238                 _pointTypeSelector.setAlignmentX(JLabel.LEFT_ALIGNMENT);
239                 secondCardHolder.add(_pointTypeSelector);
240                 JLabel coordLabel = new JLabel(I18nManager.getText("dialog.save.coordinateunits"));
241                 coordLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
242                 secondCardHolder.add(coordLabel);
243                 JPanel coordsUnitsPanel = new JPanel();
244                 coordsUnitsPanel.setBorder(BorderFactory.createEtchedBorder());
245                 coordsUnitsPanel.setLayout(new GridLayout(0, 2));
246                 _coordUnitsRadios = new JRadioButton[4];
247                 _coordUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original"));
248                 _coordUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.degminsec"));
249                 _coordUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.degmin"));
250                 _coordUnitsRadios[3] = new JRadioButton(I18nManager.getText("units.deg"));
251                 ButtonGroup coordGroup = new ButtonGroup();
252                 for (int i=0; i<4; i++)
253                 {
254                         coordGroup.add(_coordUnitsRadios[i]);
255                         coordsUnitsPanel.add(_coordUnitsRadios[i]);
256                         _coordUnitsRadios[i].setSelected(i==0);
257                 }
258                 coordsUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
259                 secondCardHolder.add(coordsUnitsPanel);
260                 secondCardHolder.add(Box.createRigidArea(new Dimension(0,7)));
261                 // altitude units
262                 JLabel altUnitsLabel = new JLabel(I18nManager.getText("dialog.save.altitudeunits"));
263                 altUnitsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
264                 secondCardHolder.add(altUnitsLabel);
265                 JPanel altUnitsPanel = new JPanel();
266                 altUnitsPanel.setBorder(BorderFactory.createEtchedBorder());
267                 altUnitsPanel.setLayout(new GridLayout(0, 2));
268                 _altitudeUnitsRadios = new JRadioButton[3];
269                 _altitudeUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original"));
270                 _altitudeUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.metres"));
271                 _altitudeUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.feet"));
272                 ButtonGroup altGroup = new ButtonGroup();
273                 for (int i=0; i<3; i++)
274                 {
275                         altGroup.add(_altitudeUnitsRadios[i]);
276                         altUnitsPanel.add(_altitudeUnitsRadios[i]);
277                         _altitudeUnitsRadios[i].setSelected(i==0);
278                 }
279                 altUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
280                 secondCardHolder.add(altUnitsPanel);
281                 secondCardHolder.add(Box.createRigidArea(new Dimension(0,7)));
282                 // Selection of format of timestamps
283                 JLabel timestampLabel = new JLabel(I18nManager.getText("dialog.save.timestampformat"));
284                 timestampLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
285                 secondCardHolder.add(timestampLabel);
286                 JPanel timestampPanel = new JPanel();
287                 timestampPanel.setBorder(BorderFactory.createEtchedBorder());
288                 timestampPanel.setLayout(new GridLayout(0, 2));
289                 _timestampUnitsRadios = new JRadioButton[3];
290                 _timestampUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original"));
291                 _timestampUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.default"));
292                 _timestampUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.iso8601"));
293                 ButtonGroup timeGroup = new ButtonGroup();
294                 for (int i=0; i<3; i++)
295                 {
296                         timeGroup.add(_timestampUnitsRadios[i]);
297                         timestampPanel.add(_timestampUnitsRadios[i]);
298                         _timestampUnitsRadios[i].setSelected(i==0);
299                 }
300                 timestampPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
301                 secondCardHolder.add(timestampPanel);
302                 secondCard.add(secondCardHolder, BorderLayout.NORTH);
303                 _cards.add(secondCard, "card2");
304
305                 // Put together with ok/cancel buttons on the bottom
306                 JPanel buttonPanel = new JPanel();
307                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
308                 _backButton = new JButton(I18nManager.getText("button.back"));
309                 _backButton.addActionListener(new ActionListener() {
310                         public void actionPerformed(ActionEvent e)
311                         {
312                                 CardLayout cl = (CardLayout) _cards.getLayout();
313                                 cl.previous(_cards);
314                                 _backButton.setEnabled(false);
315                                 _nextButton.setEnabled(true);
316                         }
317                 });
318                 _backButton.setEnabled(false);
319                 buttonPanel.add(_backButton);
320                 _nextButton = new JButton(I18nManager.getText("button.next"));
321                 _nextButton.setEnabled(true);
322                 _nextButton.addActionListener(new ActionListener() {
323                         public void actionPerformed(ActionEvent e)
324                         {
325                                 CardLayout cl = (CardLayout) _cards.getLayout();
326                                 cl.next(_cards);
327                                 _backButton.setEnabled(true);
328                                 _nextButton.setEnabled(false);
329                         }
330                 });
331                 buttonPanel.add(_nextButton);
332                 JButton okButton = new JButton(I18nManager.getText("button.finish"));
333                 okButton.addActionListener(new ActionListener() {
334                         public void actionPerformed(ActionEvent e)
335                         {
336                                 if (saveToFile())
337                                 {
338                                         _dialog.dispose();
339                                 }
340                         }
341                 });
342                 buttonPanel.add(okButton);
343                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
344                 cancelButton.addActionListener(new ActionListener() {
345                         public void actionPerformed(ActionEvent e)
346                         {
347                                 _dialog.dispose();
348                         }
349                 });
350                 buttonPanel.add(cancelButton);
351                 panel.add(buttonPanel, BorderLayout.SOUTH);
352                 return panel;
353         }
354
355         /**
356          * Initialize the dialog with the given details
357          * @param inModel table model
358          * @param inDefaultDelimiter default delimiter character
359          */
360         private void initDialog(TableModel inModel, char inDefaultDelimiter)
361         {
362                 // set table model
363                 _table.setModel(inModel);
364                 // reset toggler
365                 _toggler.setListSize(inModel.getRowCount());
366                 // choose last-used delimiter as default
367                 switch (inDefaultDelimiter)
368                 {
369                         case ','  : _delimiterRadios[0].setSelected(true); break;
370                         case '\t' : _delimiterRadios[1].setSelected(true); break;
371                         case ';'  : _delimiterRadios[2].setSelected(true); break;
372                         case ' '  : _delimiterRadios[3].setSelected(true); break;
373                         default   : _delimiterRadios[4].setSelected(true);
374                                                 _otherDelimiterText.setText("" + inDefaultDelimiter);
375                 }
376                 _pointTypeSelector.init(_app.getTrackInfo());
377                 // set card and enable buttons
378                 CardLayout cl = (CardLayout) _cards.getLayout();
379                 cl.first(_cards);
380                 _nextButton.setEnabled(true);
381                 _backButton.setEnabled(false);
382         }
383
384
385         /**
386          * Start the save process by choosing the file to save to
387          * @return true if successful or cancelled, false if failed
388          */
389         private boolean saveToFile()
390         {
391                 if (!_pointTypeSelector.getAnythingSelected()) {
392                         JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"),
393                                 I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE);
394                         return false;
395                 }
396                 if (_fileChooser == null)
397                 {
398                         _fileChooser = new JFileChooser();
399                         _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
400                         _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.txt", new String[] {"txt", "text"}));
401                         _fileChooser.setAcceptAllFileFilterUsed(true);
402                         // start from directory in config which should be set
403                         String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
404                         if (configDir == null) {configDir = Config.getConfigString(Config.KEY_PHOTO_DIR);}
405                         if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
406                 }
407                 if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
408                 {
409                         return saveToFile(_fileChooser.getSelectedFile());
410                 }
411                 return true; // cancelled
412         }
413
414
415         /**
416          * Save the track to the specified file using the chosen options
417          * @param inSaveFile file to save to
418          * @return true if save successful, false if failed
419          */
420         private boolean saveToFile(File inSaveFile)
421         {
422                 // TODO: Shorten method
423                 FileWriter writer = null;
424                 final String lineSeparator = System.getProperty("line.separator");
425                 boolean saveOK = true;
426                 // Get coordinate format and altitude format
427                 int coordFormat = Coordinate.FORMAT_NONE;
428                 for (int i=0; i<_coordUnitsRadios.length; i++)
429                         if (_coordUnitsRadios[i].isSelected())
430                                 coordFormat = FORMAT_COORDS[i];
431                 Altitude.Format altitudeFormat = Altitude.Format.NO_FORMAT;
432                 for (int i=0; i<_altitudeUnitsRadios.length; i++)
433                 {
434                         if (_altitudeUnitsRadios[i].isSelected()) {
435                                 altitudeFormat = FORMAT_ALTS[i];
436                         }
437                 }
438                 // Get timestamp format
439                 int timestampFormat = Timestamp.FORMAT_ORIGINAL;
440                 for (int i=0; i<_timestampUnitsRadios.length; i++)
441                 {
442                         if (_timestampUnitsRadios[i].isSelected()) {
443                                 timestampFormat = FORMAT_TIMES[i];
444                         }
445                 }
446
447                 // Correct chosen filename if necessary
448                 final File saveFile = (isFilenameOk(inSaveFile)?inSaveFile:new File(inSaveFile.getAbsolutePath() + ".txt"));
449
450                 // Check if file exists, and confirm overwrite if necessary
451                 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
452                 if (!saveFile.exists() || JOptionPane.showOptionDialog(_parentFrame,
453                                 I18nManager.getText("dialog.save.overwrite.text"),
454                                 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
455                                 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
456                         == JOptionPane.YES_OPTION)
457                 {
458                         try
459                         {
460                                 // Create output file
461                                 writer = new FileWriter(saveFile);
462                                 // Determine delimiter character to use
463                                 final char delimiter = getDelimiter();
464                                 FieldInfo info = null;
465
466                                 StringBuffer buffer = null;
467                                 int numFields = _model.getRowCount();
468                                 boolean firstField = true;
469                                 // Write header row if required
470                                 if (_headerRowCheckbox.isSelected())
471                                 {
472                                         buffer = new StringBuffer();
473                                         for (int f=0; f<numFields; f++)
474                                         {
475                                                 info = _model.getFieldInfo(f);
476                                                 if (info.isSelected())
477                                                 {
478                                                         // output field separator
479                                                         if (!firstField) {
480                                                                 buffer.append(delimiter);
481                                                         }
482                                                         buffer.append(info.getField().getName());
483                                                         firstField = false;
484                                                 }
485                                         }
486                                         writer.write(buffer.toString());
487                                         writer.write(lineSeparator);
488                                 }
489
490                                 // Examine selection
491                                 int selStart = -1, selEnd = -1;
492                                 if (_pointTypeSelector.getJustSelection()) {
493                                         selStart = _app.getTrackInfo().getSelection().getStart();
494                                         selEnd = _app.getTrackInfo().getSelection().getEnd();
495                                 }
496                                 // Loop over points outputting each in turn to buffer
497                                 Track track = _app.getTrackInfo().getTrack();
498                                 final int numPoints = track.getNumPoints();
499                                 int numSaved = 0;
500                                 for (int p=0; p<numPoints; p++)
501                                 {
502                                         DataPoint point = track.getPoint(p);
503                                         boolean savePoint = ((point.isWaypoint() && _pointTypeSelector.getWaypointsSelected())
504                                                 || (!point.isWaypoint() && !point.hasMedia() && _pointTypeSelector.getTrackpointsSelected())
505                                                 || (!point.isWaypoint() && point.getPhoto()!=null && _pointTypeSelector.getPhotopointsSelected())
506                                                 || (!point.isWaypoint() && point.getAudio()!=null && _pointTypeSelector.getAudiopointsSelected()))
507                                                 && (!_pointTypeSelector.getJustSelection() || (p>=selStart && p<=selEnd));
508                                         if (!savePoint) {continue;}
509                                         numSaved++;
510                                         firstField = true;
511                                         buffer = new StringBuffer();
512                                         for (int f=0; f<numFields; f++)
513                                         {
514                                                 info = _model.getFieldInfo(f);
515                                                 if (info.isSelected())
516                                                 {
517                                                         // output field separator
518                                                         if (!firstField) {
519                                                                 buffer.append(delimiter);
520                                                         }
521                                                         saveField(buffer, point, info.getField(), coordFormat, altitudeFormat, timestampFormat);
522                                                         firstField = false;
523                                                 }
524                                         }
525                                         // Output to file
526                                         writer.write(buffer.toString());
527                                         writer.write(lineSeparator);
528                                 }
529                                 // Store directory in config for later
530                                 Config.setConfigString(Config.KEY_TRACK_DIR, saveFile.getParentFile().getAbsolutePath());
531                                 // Add to recent file list
532                                 Config.getRecentFileList().addFile(new RecentFile(inSaveFile, true));
533                                 // Save successful
534                                 UpdateMessageBroker.informSubscribers();
535                                 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
536                                          + " " + numSaved + " " + I18nManager.getText("confirm.save.ok2")
537                                          + " " + saveFile.getAbsolutePath());
538                                 _app.informDataSaved();
539                         }
540                         catch (IOException ioe)
541                         {
542                                 saveOK = false;
543                                 _app.showErrorMessageNoLookup("error.save.dialogtitle",
544                                         I18nManager.getText("error.save.failed") + " : " + ioe.getMessage());
545                         }
546                         finally
547                         {
548                                 // try to close file if it's open
549                                 try {
550                                         writer.close();
551                                 }
552                                 catch (Exception e) {}
553                         }
554                 }
555                 else
556                 {
557                         // Overwrite file confirm cancelled
558                         saveOK = false;
559                 }
560                 return saveOK;
561         }
562
563
564         /**
565          * Format the given field and append to the given buffer for saving
566          * @param inBuffer buffer to append to
567          * @param inPoint point object
568          * @param inField field object
569          * @param inCoordFormat coordinate format
570          * @param inAltitudeFormat altitude format
571          * @param inTimestampFormat timestamp format
572          */
573         private void saveField(StringBuffer inBuffer, DataPoint inPoint, Field inField,
574                 int inCoordFormat, Altitude.Format inAltitudeFormat, int inTimestampFormat)
575         {
576                 // Output field according to type
577                 if (inField == Field.LATITUDE)
578                 {
579                         inBuffer.append(inPoint.getLatitude().output(inCoordFormat));
580                 }
581                 else if (inField == Field.LONGITUDE)
582                 {
583                         inBuffer.append(inPoint.getLongitude().output(inCoordFormat));
584                 }
585                 else if (inField == Field.ALTITUDE)
586                 {
587                         try
588                         {
589                                 inBuffer.append(inPoint.getAltitude().getStringValue(inAltitudeFormat));
590                         }
591                         catch (NullPointerException npe) {}
592                 }
593                 else if (inField == Field.TIMESTAMP)
594                 {
595                         if (inPoint.hasTimestamp())
596                         {
597                                 if (inTimestampFormat == Timestamp.FORMAT_ORIGINAL) {
598                                         // output original string
599                                         inBuffer.append(inPoint.getTimestamp().getText(Timestamp.FORMAT_ORIGINAL));
600                                 }
601                                 else {
602                                         // format value accordingly
603                                         inBuffer.append(inPoint.getTimestamp().getText(inTimestampFormat));
604                                 }
605                         }
606                 }
607                 else
608                 {
609                         String value = inPoint.getFieldValue(inField);
610                         if (value != null)
611                         {
612                                 inBuffer.append(value);
613                         }
614                 }
615         }
616
617
618         /**
619          * @return the selected delimiter character
620          */
621         private char getDelimiter()
622         {
623                 // Check the preset 4 delimiters
624                 final char[] delimiters = {',', '\t', ';', ' '};
625                 for (int i=0; i<4; i++)
626                 {
627                         if (_delimiterRadios[i].isSelected())
628                         {
629                                 return delimiters[i];
630                         }
631                 }
632                 // Wasn't any of those so must be 'other'
633                 return _otherDelimiterText.getText().charAt(0);
634         }
635
636
637         /**
638          * Check the selected filename to see if it is acceptable
639          * @param inFile chosen file to save
640          * @return true if filename is ok
641          */
642         private static boolean isFilenameOk(File inFile)
643         {
644                 String filename = inFile.getName().toLowerCase();
645                 return (filename.length() <4 || (!filename.endsWith(".gpx")
646                         && !filename.endsWith(".kml") && !filename.endsWith(".kmz") && !filename.endsWith(".zip")));
647         }
648 }