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