package tim.prune.save; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FileWriter; import java.io.IOException; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.table.TableModel; import tim.prune.App; import tim.prune.I18nManager; import tim.prune.UpdateMessageBroker; import tim.prune.config.Config; import tim.prune.data.Coordinate; import tim.prune.data.DataPoint; import tim.prune.data.Field; import tim.prune.data.FieldList; import tim.prune.data.RecentFile; import tim.prune.data.Timestamp; import tim.prune.data.Track; import tim.prune.data.Unit; import tim.prune.data.UnitSetLibrary; import tim.prune.load.GenericFileFilter; import tim.prune.load.OneCharDocument; /** * Class to manage the saving of track data * as text into a user-specified file */ public class FileSaver { private App _app = null; private JFrame _parentFrame = null; private JDialog _dialog = null; private JFileChooser _fileChooser = null; private JPanel _cards = null; private JButton _nextButton = null, _backButton = null; private JTable _table = null; private FieldSelectionTableModel _model = null; private JButton _moveUpButton = null, _moveDownButton = null; private UpDownToggler _toggler = null; private JRadioButton[] _delimiterRadios = null; private JTextField _otherDelimiterText = null; private JCheckBox _headerRowCheckbox = null; private PointTypeSelector _pointTypeSelector = null; private JRadioButton[] _coordUnitsRadios = null; private JRadioButton[] _altitudeUnitsRadios = null; private JRadioButton[] _timestampUnitsRadios = null; private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC, Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG}; private static final Unit[] UNIT_ALTS = {null, UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_FEET}; private static final Timestamp.Format[] FORMAT_TIMES = {Timestamp.Format.ORIGINAL, Timestamp.Format.LOCALE, Timestamp.Format.ISO8601}; /** * Constructor * @param inApp application object to inform of success * @param inParentFrame parent frame */ public FileSaver(App inApp, JFrame inParentFrame) { _app = inApp; _parentFrame = inParentFrame; } /** * Show the save file dialog * @param inDefaultDelimiter default delimiter to use */ public void showDialog(char inDefaultDelimiter) { if (_dialog == null) { _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.saveoptions.title"), true); _dialog.setLocationRelativeTo(_parentFrame); _dialog.getContentPane().add(makeDialogComponents()); _dialog.pack(); } // Has the track got media? final boolean hasMedia = _app.getTrackInfo().getPhotoList().hasCorrelatedPhotos() || _app.getTrackInfo().getAudioList().hasCorrelatedAudios(); // Check field list Track track = _app.getTrackInfo().getTrack(); FieldList fieldList = track.getFieldList(); int numFields = fieldList.getNumFields(); _model = new FieldSelectionTableModel(numFields + (hasMedia ? 1 : 0)); for (int i=0; i 0) { _model.swapItems(row, row - 1); _table.setRowSelectionInterval(row - 1, row - 1); } } }); _moveUpButton.setEnabled(false); updownPanel.add(_moveUpButton); _moveDownButton = new JButton(I18nManager.getText("button.movedown")); _moveDownButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int row = _table.getSelectedRow(); if (row > -1 && row < (_model.getRowCount() - 1)) { _model.swapItems(row, row + 1); _table.setRowSelectionInterval(row + 1, row + 1); } } }); _moveDownButton.setEnabled(false); updownPanel.add(_moveDownButton); fieldsPanel.add(updownPanel, BorderLayout.EAST); // enable/disable buttons based on table row selection _toggler = new UpDownToggler(_moveUpButton, _moveDownButton); _table.getSelectionModel().addListSelectionListener(_toggler); // Add fields panel and the delimiter panel to first card in pack JLabel saveOptionsLabel = new JLabel(I18nManager.getText("dialog.save.fieldstosave")); saveOptionsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT); firstCard.add(saveOptionsLabel); fieldsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT); firstCard.add(fieldsPanel); firstCard.add(Box.createRigidArea(new Dimension(0,10))); // delimiter panel JLabel delimLabel = new JLabel(I18nManager.getText("dialog.delimiter.label")); delimLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT); firstCard.add(delimLabel); JPanel delimsPanel = new JPanel(); delimsPanel.setLayout(new GridLayout(0, 2)); delimsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT); // radio buttons _delimiterRadios = new JRadioButton[5]; _delimiterRadios[0] = new JRadioButton(I18nManager.getText("dialog.delimiter.comma")); delimsPanel.add(_delimiterRadios[0]); _delimiterRadios[1] = new JRadioButton(I18nManager.getText("dialog.delimiter.tab")); delimsPanel.add(_delimiterRadios[1]); _delimiterRadios[2] = new JRadioButton(I18nManager.getText("dialog.delimiter.semicolon")); delimsPanel.add(_delimiterRadios[2]); _delimiterRadios[3] = new JRadioButton(I18nManager.getText("dialog.delimiter.space")); delimsPanel.add(_delimiterRadios[3]); JPanel otherPanel = new JPanel(); otherPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); _delimiterRadios[4] = new JRadioButton(I18nManager.getText("dialog.delimiter.other")); otherPanel.add(_delimiterRadios[4]); _otherDelimiterText = new JTextField(new OneCharDocument(), null, 2); otherPanel.add(_otherDelimiterText); // Group radio buttons ButtonGroup delimGroup = new ButtonGroup(); for (int i=0; i<_delimiterRadios.length; i++) { delimGroup.add(_delimiterRadios[i]); } delimsPanel.add(otherPanel); firstCard.add(delimsPanel); // header checkbox firstCard.add(Box.createRigidArea(new Dimension(0,10))); _headerRowCheckbox = new JCheckBox(I18nManager.getText("dialog.save.headerrow"), true); firstCard.add(_headerRowCheckbox); _cards.add(firstCard, "card1"); // Second card JPanel secondCard = new JPanel(); secondCard.setLayout(new BorderLayout()); JPanel secondCardHolder = new JPanel(); secondCardHolder.setLayout(new BoxLayout(secondCardHolder, BoxLayout.Y_AXIS)); // point type selector secondCardHolder.add(Box.createRigidArea(new Dimension(0,10))); _pointTypeSelector = new PointTypeSelector(); _pointTypeSelector.setAlignmentX(JLabel.LEFT_ALIGNMENT); secondCardHolder.add(_pointTypeSelector); JLabel coordLabel = new JLabel(I18nManager.getText("dialog.save.coordinateunits")); coordLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT); secondCardHolder.add(coordLabel); JPanel coordsUnitsPanel = new JPanel(); coordsUnitsPanel.setBorder(BorderFactory.createEtchedBorder()); coordsUnitsPanel.setLayout(new GridLayout(0, 2)); _coordUnitsRadios = new JRadioButton[4]; _coordUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original")); _coordUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.degminsec")); _coordUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.degmin")); _coordUnitsRadios[3] = new JRadioButton(I18nManager.getText("units.deg")); ButtonGroup coordGroup = new ButtonGroup(); for (int i=0; i<4; i++) { coordGroup.add(_coordUnitsRadios[i]); coordsUnitsPanel.add(_coordUnitsRadios[i]); _coordUnitsRadios[i].setSelected(i==0); } coordsUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT); secondCardHolder.add(coordsUnitsPanel); secondCardHolder.add(Box.createRigidArea(new Dimension(0,7))); // altitude units JLabel altUnitsLabel = new JLabel(I18nManager.getText("dialog.save.altitudeunits")); altUnitsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT); secondCardHolder.add(altUnitsLabel); JPanel altUnitsPanel = new JPanel(); altUnitsPanel.setBorder(BorderFactory.createEtchedBorder()); altUnitsPanel.setLayout(new GridLayout(0, 2)); _altitudeUnitsRadios = new JRadioButton[3]; _altitudeUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original")); _altitudeUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.metres")); _altitudeUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.feet")); ButtonGroup altGroup = new ButtonGroup(); for (int i=0; i<3; i++) { altGroup.add(_altitudeUnitsRadios[i]); altUnitsPanel.add(_altitudeUnitsRadios[i]); _altitudeUnitsRadios[i].setSelected(i==0); } altUnitsPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT); secondCardHolder.add(altUnitsPanel); secondCardHolder.add(Box.createRigidArea(new Dimension(0,7))); // Selection of format of timestamps JLabel timestampLabel = new JLabel(I18nManager.getText("dialog.save.timestampformat")); timestampLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT); secondCardHolder.add(timestampLabel); JPanel timestampPanel = new JPanel(); timestampPanel.setBorder(BorderFactory.createEtchedBorder()); timestampPanel.setLayout(new GridLayout(0, 2)); _timestampUnitsRadios = new JRadioButton[3]; _timestampUnitsRadios[0] = new JRadioButton(I18nManager.getText("units.original")); _timestampUnitsRadios[1] = new JRadioButton(I18nManager.getText("units.default")); _timestampUnitsRadios[2] = new JRadioButton(I18nManager.getText("units.iso8601")); ButtonGroup timeGroup = new ButtonGroup(); for (int i=0; i<3; i++) { timeGroup.add(_timestampUnitsRadios[i]); timestampPanel.add(_timestampUnitsRadios[i]); _timestampUnitsRadios[i].setSelected(i==0); } timestampPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT); secondCardHolder.add(timestampPanel); secondCard.add(secondCardHolder, BorderLayout.NORTH); _cards.add(secondCard, "card2"); // Put together with ok/cancel buttons on the bottom JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); _backButton = new JButton(I18nManager.getText("button.back")); _backButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { CardLayout cl = (CardLayout) _cards.getLayout(); cl.previous(_cards); _backButton.setEnabled(false); _nextButton.setEnabled(true); } }); _backButton.setEnabled(false); buttonPanel.add(_backButton); _nextButton = new JButton(I18nManager.getText("button.next")); _nextButton.setEnabled(true); _nextButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { CardLayout cl = (CardLayout) _cards.getLayout(); cl.next(_cards); _backButton.setEnabled(true); _nextButton.setEnabled(false); } }); buttonPanel.add(_nextButton); JButton okButton = new JButton(I18nManager.getText("button.finish")); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (saveToFile()) { _dialog.dispose(); } } }); buttonPanel.add(okButton); JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { _dialog.dispose(); } }); buttonPanel.add(cancelButton); panel.add(buttonPanel, BorderLayout.SOUTH); return panel; } /** * Initialize the dialog with the given details * @param inModel table model * @param inDefaultDelimiter default delimiter character */ private void initDialog(TableModel inModel, char inDefaultDelimiter) { // set table model _table.setModel(inModel); // reset toggler _toggler.setListSize(inModel.getRowCount()); // choose last-used delimiter as default switch (inDefaultDelimiter) { case ',' : _delimiterRadios[0].setSelected(true); break; case '\t' : _delimiterRadios[1].setSelected(true); break; case ';' : _delimiterRadios[2].setSelected(true); break; case ' ' : _delimiterRadios[3].setSelected(true); break; default : _delimiterRadios[4].setSelected(true); _otherDelimiterText.setText("" + inDefaultDelimiter); } _pointTypeSelector.init(_app.getTrackInfo()); // set card and enable buttons CardLayout cl = (CardLayout) _cards.getLayout(); cl.first(_cards); _nextButton.setEnabled(true); _backButton.setEnabled(false); } /** * Start the save process by choosing the file to save to * @return true if successful or cancelled, false if failed */ private boolean saveToFile() { if (!_pointTypeSelector.getAnythingSelected()) { JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.notypesselected"), I18nManager.getText("dialog.saveoptions.title"), JOptionPane.WARNING_MESSAGE); return false; } if (_fileChooser == null) { _fileChooser = new JFileChooser(); _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); _fileChooser.addChoosableFileFilter(new GenericFileFilter("filetype.txt", new String[] {"txt", "text"})); _fileChooser.setAcceptAllFileFilterUsed(true); // start from directory in config which should be set String configDir = Config.getConfigString(Config.KEY_TRACK_DIR); if (configDir == null) {configDir = Config.getConfigString(Config.KEY_PHOTO_DIR);} if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));} } if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) { return saveToFile(_fileChooser.getSelectedFile()); } return true; // cancelled } /** * Save the track to the specified file using the chosen options * @param inSaveFile file to save to * @return true if save successful, false if failed */ private boolean saveToFile(File inSaveFile) { // TODO: Shorten method FileWriter writer = null; final String lineSeparator = System.getProperty("line.separator"); boolean saveOK = true; // Get coordinate format and altitude format int coordFormat = Coordinate.FORMAT_NONE; for (int i=0; i<_coordUnitsRadios.length; i++) if (_coordUnitsRadios[i].isSelected()) coordFormat = FORMAT_COORDS[i]; Unit altitudeUnit = null; for (int i=0; i<_altitudeUnitsRadios.length; i++) { if (_altitudeUnitsRadios[i].isSelected()) { altitudeUnit = UNIT_ALTS[i]; } } // Get timestamp format Timestamp.Format timestampFormat = Timestamp.Format.ORIGINAL; for (int i=0; i<_timestampUnitsRadios.length; i++) { if (_timestampUnitsRadios[i].isSelected()) { timestampFormat = FORMAT_TIMES[i]; } } // Correct chosen filename if necessary final File saveFile = (isFilenameOk(inSaveFile)?inSaveFile:new File(inSaveFile.getAbsolutePath() + ".txt")); // Check if file exists, and confirm overwrite if necessary Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")}; if (!saveFile.exists() || JOptionPane.showOptionDialog(_parentFrame, I18nManager.getText("dialog.save.overwrite.text"), I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]) == JOptionPane.YES_OPTION) { try { // Create output file writer = new FileWriter(saveFile); // Determine delimiter character to use final char delimiter = getDelimiter(); FieldInfo info = null; StringBuffer buffer = null; int numFields = _model.getRowCount(); boolean firstField = true; // Write header row if required if (_headerRowCheckbox.isSelected()) { buffer = new StringBuffer(); for (int f=0; f=selStart && p<=selEnd)); if (!savePoint) {continue;} numSaved++; firstField = true; buffer = new StringBuffer(); for (int f=0; f