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.JTable;
29 import javax.swing.JTextField;
30 import javax.swing.ListSelectionModel;
33 import tim.prune.I18nManager;
34 import tim.prune.data.Altitude;
35 import tim.prune.data.Coordinate;
36 import tim.prune.data.DataPoint;
37 import tim.prune.data.Field;
38 import tim.prune.data.FieldList;
39 import tim.prune.data.Track;
40 import tim.prune.load.OneCharDocument;
43 * Class to manage the saving of track data
44 * into a user-specified file
46 public class FileSaver
48 private App _app = null;
49 private JFrame _parentFrame = null;
50 private Track _track = null;
51 private JDialog _dialog = null;
52 private JFileChooser _fileChooser = null;
53 private JPanel _cards = null;
54 private JButton _nextButton = null, _backButton = null;
55 private JTable _table = null;
56 private FieldSelectionTableModel _model = null;
57 private JButton _moveUpButton = null, _moveDownButton = null;
58 private JRadioButton[] _delimiterRadios = null;
59 private JTextField _otherDelimiterText = null;
60 private JCheckBox _headerRowCheckbox = null;
61 private JRadioButton[] _coordUnitsRadios = null;
62 private JRadioButton[] _altitudeUnitsRadios = null;
63 private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC,
64 Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG};
65 private static final int[] FORMAT_ALTS = {Altitude.FORMAT_NONE, Altitude.FORMAT_METRES, Altitude.FORMAT_FEET};
70 * @param inApp application object to inform of success
71 * @param inParentFrame parent frame
72 * @param inTrack track object to save
74 public FileSaver(App inApp, JFrame inParentFrame, Track inTrack)
77 _parentFrame = inParentFrame;
83 * Show the save file dialog
84 * @param inDefaultDelimiter default delimiter to use
86 public void showDialog(char inDefaultDelimiter)
88 _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.saveoptions.title"), true);
89 _dialog.setLocationRelativeTo(_parentFrame);
91 FieldList fieldList = _track.getFieldList();
92 int numFields = fieldList.getNumFields();
93 _model = new FieldSelectionTableModel(numFields);
94 for (int i=0; i<numFields; i++)
96 Field field = fieldList.getField(i);
97 FieldInfo info = new FieldInfo(field, _track.hasData(field));
98 _model.addFieldInfo(info, i);
100 _dialog.getContentPane().add(makeDialogComponents(_model, inDefaultDelimiter));
107 * Make the dialog components
108 * @param inTableModel table model for fields
109 * @param inDelimiter default delimiter character
110 * @return the GUI components for the save dialog
112 private Component makeDialogComponents(FieldSelectionTableModel inTableModel, char inDelimiter)
114 JPanel panel = new JPanel();
115 panel.setLayout(new BorderLayout());
116 _cards = new JPanel();
117 _cards.setLayout(new CardLayout());
118 panel.add(_cards, BorderLayout.CENTER);
120 // Make first card for field selection and delimiter
121 JPanel firstCard = new JPanel();
122 firstCard.setLayout(new BoxLayout(firstCard, BoxLayout.Y_AXIS));
123 JPanel tablePanel = new JPanel();
124 tablePanel.setLayout(new BorderLayout());
125 _table = new JTable(inTableModel);
126 _table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
127 tablePanel.add(_table.getTableHeader(), BorderLayout.NORTH);
128 tablePanel.add(_table, BorderLayout.CENTER);
130 // Make a panel to hold the table and up/down buttons
131 JPanel fieldsPanel = new JPanel();
132 fieldsPanel.setLayout(new BorderLayout());
133 fieldsPanel.add(tablePanel, BorderLayout.CENTER);
134 JPanel updownPanel = new JPanel();
135 updownPanel.setLayout(new BoxLayout(updownPanel, BoxLayout.Y_AXIS));
136 _moveUpButton = new JButton(I18nManager.getText("button.moveup"));
137 _moveUpButton.addActionListener(new ActionListener() {
138 public void actionPerformed(ActionEvent e)
140 int row = _table.getSelectedRow();
143 _model.swapItems(row, row - 1);
144 _table.setRowSelectionInterval(row - 1, row - 1);
148 _moveUpButton.setEnabled(false);
149 updownPanel.add(_moveUpButton);
150 _moveDownButton = new JButton(I18nManager.getText("button.movedown"));
151 _moveDownButton.addActionListener(new ActionListener() {
152 public void actionPerformed(ActionEvent e)
154 int row = _table.getSelectedRow();
155 if (row > -1 && row < (_model.getRowCount() - 1))
157 _model.swapItems(row, row + 1);
158 _table.setRowSelectionInterval(row + 1, row + 1);
162 _moveDownButton.setEnabled(false);
163 updownPanel.add(_moveDownButton);
164 fieldsPanel.add(updownPanel, BorderLayout.EAST);
165 // enable/disable buttons based on table row selection
166 _table.getSelectionModel().addListSelectionListener(
167 new UpDownToggler(_moveUpButton, _moveDownButton, inTableModel.getRowCount())
170 // Add fields panel and the delimiter panel to first card in pack
171 JLabel saveOptionsLabel = new JLabel(I18nManager.getText("dialog.save.fieldstosave"));
172 saveOptionsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
173 firstCard.add(saveOptionsLabel);
174 fieldsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
175 firstCard.add(fieldsPanel);
176 firstCard.add(Box.createRigidArea(new Dimension(0,10)));
179 JLabel delimLabel = new JLabel(I18nManager.getText("dialog.delimiter.label"));
180 delimLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
181 firstCard.add(delimLabel);
182 JPanel delimsPanel = new JPanel();
183 delimsPanel.setLayout(new GridLayout(0, 2));
184 delimsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
186 _delimiterRadios = new JRadioButton[5];
187 _delimiterRadios[0] = new JRadioButton(I18nManager.getText("dialog.delimiter.comma"));
188 delimsPanel.add(_delimiterRadios[0]);
189 _delimiterRadios[1] = new JRadioButton(I18nManager.getText("dialog.delimiter.tab"));
190 delimsPanel.add(_delimiterRadios[1]);
191 _delimiterRadios[2] = new JRadioButton(I18nManager.getText("dialog.delimiter.semicolon"));
192 delimsPanel.add(_delimiterRadios[2]);
193 _delimiterRadios[3] = new JRadioButton(I18nManager.getText("dialog.delimiter.space"));
194 delimsPanel.add(_delimiterRadios[3]);
195 JPanel otherPanel = new JPanel();
196 otherPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
197 _delimiterRadios[4] = new JRadioButton(I18nManager.getText("dialog.delimiter.other"));
198 otherPanel.add(_delimiterRadios[4]);
199 _otherDelimiterText = new JTextField(new OneCharDocument(), null, 2);
200 otherPanel.add(_otherDelimiterText);
201 // Group radio buttons
202 ButtonGroup delimGroup = new ButtonGroup();
203 for (int i=0; i<_delimiterRadios.length; i++)
205 delimGroup.add(_delimiterRadios[i]);
207 // choose last-used delimiter as default
210 case ',' : _delimiterRadios[0].setSelected(true); break;
211 case '\t' : _delimiterRadios[1].setSelected(true); break;
212 case ';' : _delimiterRadios[2].setSelected(true); break;
213 case ' ' : _delimiterRadios[3].setSelected(true); break;
214 default : _delimiterRadios[4].setSelected(true);
215 _otherDelimiterText.setText("" + inDelimiter);
217 delimsPanel.add(otherPanel);
218 firstCard.add(delimsPanel);
221 firstCard.add(Box.createRigidArea(new Dimension(0,10)));
222 _headerRowCheckbox = new JCheckBox(I18nManager.getText("dialog.save.headerrow"));
223 firstCard.add(_headerRowCheckbox);
225 _cards.add(firstCard, "card1");
227 JPanel secondCard = new JPanel();
228 secondCard.setLayout(new BorderLayout());
229 JPanel secondCardHolder = new JPanel();
230 secondCardHolder.setLayout(new BoxLayout(secondCardHolder, BoxLayout.Y_AXIS));
231 JLabel coordLabel = new JLabel(I18nManager.getText("dialog.save.coordinateunits"));
232 coordLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
233 secondCardHolder.add(coordLabel);
234 JPanel coordsUnitsPanel = new JPanel();
235 coordsUnitsPanel.setBorder(BorderFactory.createEtchedBorder());
236 coordsUnitsPanel.setLayout(new GridLayout(0, 2));
237 _coordUnitsRadios = new JRadioButton[4];
238 _coordUnitsRadios[0] = new JRadioButton(I18nManager.getText("dialog.save.units.original"));
239 _coordUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.degminsec"));
240 _coordUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.degmin"));
241 _coordUnitsRadios[3] = new JRadioButton(I18nManager.getText("units.deg"));
242 ButtonGroup coordGroup = new ButtonGroup();
243 for (int i=0; i<4; i++)
245 coordGroup.add(_coordUnitsRadios[i]);
246 coordsUnitsPanel.add(_coordUnitsRadios[i]);
247 _coordUnitsRadios[i].setSelected(i==0);
249 coordsUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
250 secondCardHolder.add(coordsUnitsPanel);
251 secondCardHolder.add(Box.createRigidArea(new Dimension(0,10)));
252 JLabel altUnitsLabel = new JLabel(I18nManager.getText("dialog.save.altitudeunits"));
253 altUnitsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
254 secondCardHolder.add(altUnitsLabel);
255 JPanel altUnitsPanel = new JPanel();
256 altUnitsPanel.setBorder(BorderFactory.createEtchedBorder());
257 altUnitsPanel.setLayout(new GridLayout(0, 2));
258 _altitudeUnitsRadios = new JRadioButton[3];
259 _altitudeUnitsRadios[0] = new JRadioButton(I18nManager.getText("dialog.save.units.original"));
260 _altitudeUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.metres"));
261 _altitudeUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.feet"));
262 ButtonGroup altGroup = new ButtonGroup();
263 for (int i=0; i<3; i++)
265 altGroup.add(_altitudeUnitsRadios[i]);
266 altUnitsPanel.add(_altitudeUnitsRadios[i]);
267 _altitudeUnitsRadios[i].setSelected(i==0);
269 altUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
270 secondCardHolder.add(altUnitsPanel);
271 // TODO: selection of format of timestamps
272 secondCard.add(secondCardHolder, BorderLayout.NORTH);
273 _cards.add(secondCard, "card2");
275 // Put together with ok/cancel buttons on the bottom
276 JPanel buttonPanel = new JPanel();
277 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
278 _backButton = new JButton(I18nManager.getText("button.back"));
279 _backButton.addActionListener(new ActionListener() {
280 public void actionPerformed(ActionEvent e)
282 CardLayout cl = (CardLayout)(_cards.getLayout());
284 _backButton.setEnabled(false);
285 _nextButton.setEnabled(true);
288 _backButton.setEnabled(false);
289 buttonPanel.add(_backButton);
290 _nextButton = new JButton(I18nManager.getText("button.next"));
291 _nextButton.setEnabled(true);
292 _nextButton.addActionListener(new ActionListener() {
293 public void actionPerformed(ActionEvent e)
295 CardLayout cl = (CardLayout)(_cards.getLayout());
297 _backButton.setEnabled(true);
298 _nextButton.setEnabled(false);
301 buttonPanel.add(_nextButton);
302 JButton okButton = new JButton(I18nManager.getText("button.finish"));
303 okButton.addActionListener(new ActionListener() {
304 public void actionPerformed(ActionEvent e)
312 buttonPanel.add(okButton);
313 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
314 cancelButton.addActionListener(new ActionListener() {
315 public void actionPerformed(ActionEvent e)
320 buttonPanel.add(cancelButton);
321 panel.add(buttonPanel, BorderLayout.SOUTH);
327 * Save the track to file with the chosen options
328 * @return true if successful or cancelled, false if failed
330 private boolean saveToFile()
332 boolean saveOK = true;
333 FileWriter writer = null;
334 if (_fileChooser == null)
335 _fileChooser = new JFileChooser();
336 _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
337 if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
339 File saveFile = _fileChooser.getSelectedFile();
340 String lineSeparator = System.getProperty("line.separator");
341 // Get coordinate format and altitude format
342 int coordFormat = Coordinate.FORMAT_NONE;
343 for (int i=0; i<_coordUnitsRadios.length; i++)
344 if (_coordUnitsRadios[i].isSelected())
345 coordFormat = FORMAT_COORDS[i];
346 int altitudeFormat = Altitude.FORMAT_NONE;
347 for (int i=0; i<_altitudeUnitsRadios.length; i++)
349 if (_altitudeUnitsRadios[i].isSelected())
351 altitudeFormat = FORMAT_ALTS[i];
355 // Check if file exists, and confirm overwrite if necessary
356 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
357 if (!saveFile.exists() || JOptionPane.showOptionDialog(_parentFrame,
358 I18nManager.getText("dialog.save.overwrite.text"),
359 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
360 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
361 == JOptionPane.YES_OPTION)
365 // Create output file
366 writer = new FileWriter(saveFile);
367 // Determine delimiter character to use
368 char delimiter = getDelimiter();
369 FieldInfo info = null;
372 StringBuffer buffer = null;
373 int numFields = _model.getRowCount();
374 boolean firstField = true;
375 // Write header row if required
376 if (_headerRowCheckbox.isSelected())
378 buffer = new StringBuffer();
379 for (int f=0; f<numFields; f++)
381 info = _model.getFieldInfo(f);
382 if (info.isSelected())
386 // output field separator
387 buffer.append(delimiter);
389 field = info.getField();
390 buffer.append(field.getName());
394 writer.write(buffer.toString());
395 writer.write(lineSeparator);
398 // Loop over points outputting each in turn to buffer
399 int numPoints = _track.getNumPoints();
400 for (int p=0; p<numPoints; p++)
402 DataPoint point = _track.getPoint(p);
404 buffer = new StringBuffer();
405 for (int f=0; f<numFields; f++)
407 info = _model.getFieldInfo(f);
408 if (info.isSelected())
412 // output field separator
413 buffer.append(delimiter);
415 field = info.getField();
416 // Output field according to type
417 if (field == Field.LATITUDE)
419 buffer.append(point.getLatitude().output(coordFormat));
421 else if (field == Field.LONGITUDE)
423 buffer.append(point.getLongitude().output(coordFormat));
425 else if (field == Field.ALTITUDE)
429 buffer.append(point.getAltitude().getValue(altitudeFormat));
431 catch (NullPointerException npe) {}
433 else if (field == Field.TIMESTAMP)
437 buffer.append(point.getTimestamp().getText());
439 catch (NullPointerException npe) {}
443 String value = point.getFieldValue(field);
446 buffer.append(value);
453 writer.write(buffer.toString());
454 writer.write(lineSeparator);
457 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.ok1")
458 + " " + numPoints + " " + I18nManager.getText("dialog.save.ok2")
459 + " " + saveFile.getAbsolutePath(),
460 I18nManager.getText("dialog.save.oktitle"), JOptionPane.INFORMATION_MESSAGE);
461 _app.informDataSaved();
463 catch (IOException ioe)
466 JOptionPane.showMessageDialog(_parentFrame,
467 I18nManager.getText("error.save.failed") + ioe.getMessage(),
468 I18nManager.getText("error.save.dialogtitle"),
469 JOptionPane.ERROR_MESSAGE);
473 // try to close file if it's open
481 catch (Exception e) {}
486 // Overwrite file confirm cancelled
495 * @return the selected delimiter character
497 private char getDelimiter()
499 // Check the preset 4 delimiters
500 final char[] delimiters = {',', '\t', ';', ' '};
501 for (int i=0; i<4; i++)
503 if (_delimiterRadios[i].isSelected())
505 return delimiters[i];
508 // Wasn't any of those so must be 'other'
509 return _otherDelimiterText.getText().charAt(0);