1 package tim.prune.save;
3 import java.awt.BorderLayout;
4 import java.awt.CardLayout;
5 import java.awt.Component;
6 import java.awt.Dimension;
7 import java.awt.FlowLayout;
8 import java.awt.GridLayout;
9 import java.awt.event.ActionEvent;
10 import java.awt.event.ActionListener;
12 import java.io.FileWriter;
13 import java.io.IOException;
15 import javax.swing.BorderFactory;
16 import javax.swing.Box;
17 import javax.swing.BoxLayout;
18 import javax.swing.ButtonGroup;
19 import javax.swing.JButton;
20 import javax.swing.JCheckBox;
21 import javax.swing.JDialog;
22 import javax.swing.JFileChooser;
23 import javax.swing.JFrame;
24 import javax.swing.JLabel;
25 import javax.swing.JOptionPane;
26 import javax.swing.JPanel;
27 import javax.swing.JRadioButton;
28 import javax.swing.JScrollPane;
29 import javax.swing.JTable;
30 import javax.swing.JTextField;
31 import javax.swing.ListSelectionModel;
32 import javax.swing.table.TableModel;
35 import tim.prune.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;
50 * Class to manage the saving of track data
51 * as text into a user-specified file
53 public class FileSaver
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;
73 private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC,
74 Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG};
75 private static final Altitude.Format[] FORMAT_ALTS = {Altitude.Format.NO_FORMAT, Altitude.Format.METRES, Altitude.Format.FEET};
76 private static final int[] FORMAT_TIMES = {Timestamp.FORMAT_ORIGINAL, Timestamp.FORMAT_LOCALE, Timestamp.FORMAT_ISO_8601};
81 * @param inApp application object to inform of success
82 * @param inParentFrame parent frame
84 public FileSaver(App inApp, JFrame inParentFrame)
87 _parentFrame = inParentFrame;
92 * Show the save file dialog
93 * @param inDefaultDelimiter default delimiter to use
95 public void showDialog(char inDefaultDelimiter)
99 _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.saveoptions.title"), true);
100 _dialog.setLocationRelativeTo(_parentFrame);
101 _dialog.getContentPane().add(makeDialogComponents());
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++)
111 Field field = fieldList.getField(i);
112 FieldInfo info = new FieldInfo(field, track.hasData(field));
113 _model.addFieldInfo(info, i);
115 // Initialise dialog and show it
116 initDialog(_model, inDefaultDelimiter);
117 _dialog.setVisible(true);
122 * Make the dialog components
123 * @return the GUI components for the save dialog
125 private Component makeDialogComponents()
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);
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);
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)
155 int row = _table.getSelectedRow();
158 _model.swapItems(row, row - 1);
159 _table.setRowSelectionInterval(row - 1, row - 1);
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)
169 int row = _table.getSelectedRow();
170 if (row > -1 && row < (_model.getRowCount() - 1))
172 _model.swapItems(row, row + 1);
173 _table.setRowSelectionInterval(row + 1, row + 1);
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);
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)));
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);
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++)
219 delimGroup.add(_delimiterRadios[i]);
221 delimsPanel.add(otherPanel);
222 firstCard.add(delimsPanel);
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");
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++)
254 coordGroup.add(_coordUnitsRadios[i]);
255 coordsUnitsPanel.add(_coordUnitsRadios[i]);
256 _coordUnitsRadios[i].setSelected(i==0);
258 coordsUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
259 secondCardHolder.add(coordsUnitsPanel);
260 secondCardHolder.add(Box.createRigidArea(new Dimension(0,7)));
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++)
275 altGroup.add(_altitudeUnitsRadios[i]);
276 altUnitsPanel.add(_altitudeUnitsRadios[i]);
277 _altitudeUnitsRadios[i].setSelected(i==0);
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++)
296 timeGroup.add(_timestampUnitsRadios[i]);
297 timestampPanel.add(_timestampUnitsRadios[i]);
298 _timestampUnitsRadios[i].setSelected(i==0);
300 timestampPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
301 secondCardHolder.add(timestampPanel);
302 secondCard.add(secondCardHolder, BorderLayout.NORTH);
303 _cards.add(secondCard, "card2");
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)
312 CardLayout cl = (CardLayout) _cards.getLayout();
314 _backButton.setEnabled(false);
315 _nextButton.setEnabled(true);
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)
325 CardLayout cl = (CardLayout) _cards.getLayout();
327 _backButton.setEnabled(true);
328 _nextButton.setEnabled(false);
331 buttonPanel.add(_nextButton);
332 JButton okButton = new JButton(I18nManager.getText("button.finish"));
333 okButton.addActionListener(new ActionListener() {
334 public void actionPerformed(ActionEvent e)
342 buttonPanel.add(okButton);
343 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
344 cancelButton.addActionListener(new ActionListener() {
345 public void actionPerformed(ActionEvent e)
350 buttonPanel.add(cancelButton);
351 panel.add(buttonPanel, BorderLayout.SOUTH);
356 * Initialize the dialog with the given details
357 * @param inModel table model
358 * @param inDefaultDelimiter default delimiter character
360 private void initDialog(TableModel inModel, char inDefaultDelimiter)
363 _table.setModel(inModel);
365 _toggler.setListSize(inModel.getRowCount());
366 // choose last-used delimiter as default
367 switch (inDefaultDelimiter)
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);
376 _pointTypeSelector.init(_app.getTrackInfo());
377 // set card and enable buttons
378 CardLayout cl = (CardLayout) _cards.getLayout();
380 _nextButton.setEnabled(true);
381 _backButton.setEnabled(false);
386 * Start the save process by choosing the file to save to
387 * @return true if successful or cancelled, false if failed
389 private boolean saveToFile()
391 if (!_pointTypeSelector.getAnythingSelected()) {
392 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"),
393 I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE);
396 if (_fileChooser == null)
398 _fileChooser = new JFileChooser();
399 _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
400 _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.txt", new String[] {"txt", "text"}));
401 _fileChooser.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));}
407 if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
409 return saveToFile(_fileChooser.getSelectedFile());
411 return true; // cancelled
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
420 private boolean saveToFile(File inSaveFile)
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++)
434 if (_altitudeUnitsRadios[i].isSelected()) {
435 altitudeFormat = FORMAT_ALTS[i];
438 // Get timestamp format
439 int timestampFormat = Timestamp.FORMAT_ORIGINAL;
440 for (int i=0; i<_timestampUnitsRadios.length; i++)
442 if (_timestampUnitsRadios[i].isSelected()) {
443 timestampFormat = FORMAT_TIMES[i];
447 // Correct chosen filename if necessary
448 final File saveFile = (isFilenameOk(inSaveFile)?inSaveFile:new File(inSaveFile.getAbsolutePath() + ".txt"));
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)
460 // Create output file
461 writer = new FileWriter(saveFile);
462 // Determine delimiter character to use
463 final char delimiter = getDelimiter();
464 FieldInfo info = null;
466 StringBuffer buffer = null;
467 int numFields = _model.getRowCount();
468 boolean firstField = true;
469 // Write header row if required
470 if (_headerRowCheckbox.isSelected())
472 buffer = new StringBuffer();
473 for (int f=0; f<numFields; f++)
475 info = _model.getFieldInfo(f);
476 if (info.isSelected())
478 // output field separator
480 buffer.append(delimiter);
482 buffer.append(info.getField().getName());
486 writer.write(buffer.toString());
487 writer.write(lineSeparator);
491 int selStart = -1, selEnd = -1;
492 if (_pointTypeSelector.getJustSelection()) {
493 selStart = _app.getTrackInfo().getSelection().getStart();
494 selEnd = _app.getTrackInfo().getSelection().getEnd();
496 // Loop over points outputting each in turn to buffer
497 Track track = _app.getTrackInfo().getTrack();
498 final int numPoints = track.getNumPoints();
500 for (int p=0; p<numPoints; p++)
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;}
511 buffer = new StringBuffer();
512 for (int f=0; f<numFields; f++)
514 info = _model.getFieldInfo(f);
515 if (info.isSelected())
517 // output field separator
519 buffer.append(delimiter);
521 saveField(buffer, point, info.getField(), coordFormat, altitudeFormat, timestampFormat);
526 writer.write(buffer.toString());
527 writer.write(lineSeparator);
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));
534 UpdateMessageBroker.informSubscribers();
535 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
536 + " " + numSaved + " " + I18nManager.getText("confirm.save.ok2")
537 + " " + saveFile.getAbsolutePath());
538 _app.informDataSaved();
540 catch (IOException ioe)
543 _app.showErrorMessageNoLookup("error.save.dialogtitle",
544 I18nManager.getText("error.save.failed") + " : " + ioe.getMessage());
548 // try to close file if it's open
552 catch (Exception e) {}
557 // Overwrite file confirm cancelled
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
573 private void saveField(StringBuffer inBuffer, DataPoint inPoint, Field inField,
574 int inCoordFormat, Altitude.Format inAltitudeFormat, int inTimestampFormat)
576 // Output field according to type
577 if (inField == Field.LATITUDE)
579 inBuffer.append(inPoint.getLatitude().output(inCoordFormat));
581 else if (inField == Field.LONGITUDE)
583 inBuffer.append(inPoint.getLongitude().output(inCoordFormat));
585 else if (inField == Field.ALTITUDE)
589 inBuffer.append(inPoint.getAltitude().getStringValue(inAltitudeFormat));
591 catch (NullPointerException npe) {}
593 else if (inField == Field.TIMESTAMP)
595 if (inPoint.hasTimestamp())
597 if (inTimestampFormat == Timestamp.FORMAT_ORIGINAL) {
598 // output original string
599 inBuffer.append(inPoint.getFieldValue(Field.TIMESTAMP));
602 // format value accordingly
603 inBuffer.append(inPoint.getTimestamp().getText(inTimestampFormat));
609 String value = inPoint.getFieldValue(inField);
612 inBuffer.append(value);
619 * @return the selected delimiter character
621 private char getDelimiter()
623 // Check the preset 4 delimiters
624 final char[] delimiters = {',', '\t', ';', ' '};
625 for (int i=0; i<4; i++)
627 if (_delimiterRadios[i].isSelected())
629 return delimiters[i];
632 // Wasn't any of those so must be 'other'
633 return _otherDelimiterText.getText().charAt(0);
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
642 private static boolean isFilenameOk(File inFile)
644 String filename = inFile.getName().toLowerCase();
645 return (filename.length() <4 || (!filename.endsWith(".gpx")
646 && !filename.endsWith(".kml") && !filename.endsWith(".kmz") && !filename.endsWith(".zip")));