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