]> gitweb.fperrin.net Git - GpsPrune.git/blobdiff - src/tim/prune/load/TextFileLoader.java
Moved source into separate src directory due to popular request
[GpsPrune.git] / src / tim / prune / load / TextFileLoader.java
diff --git a/src/tim/prune/load/TextFileLoader.java b/src/tim/prune/load/TextFileLoader.java
new file mode 100644 (file)
index 0000000..b4a0ca8
--- /dev/null
@@ -0,0 +1,711 @@
+package tim.prune.load;
+
+import java.awt.BorderLayout;
+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.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellEditor;
+
+import java.io.File;
+
+import tim.prune.App;
+import tim.prune.I18nManager;
+import tim.prune.data.Field;
+import tim.prune.data.PointCreateOptions;
+import tim.prune.data.SourceInfo;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.GuiGridLayout;
+import tim.prune.gui.WizardLayout;
+
+
+/**
+ * Class to handle loading of text files including GUI options,
+ * and passing loaded data back to App object
+ */
+public class TextFileLoader
+{
+       private File _file = null;
+       private App _app = null;
+       private JFrame _parentFrame = null;
+       private JDialog _dialog = null;
+       private WizardLayout _wizard = null;
+       private JButton _backButton = null, _nextButton = null;
+       private JButton _finishButton = null;
+       private JButton _moveUpButton = null, _moveDownButton = null;
+       private JRadioButton[] _delimiterRadios = null;
+       private JTextField _otherDelimiterText = null;
+       private JLabel _statusLabel = null;
+       private DelimiterInfo[] _delimiterInfos = null;
+       private FileCacher _fileCacher = null;
+       private JList<String> _snippetBox = null;
+       private FileExtractTableModel _fileExtractTableModel = null;
+       private JTable _fieldTable;
+       private FieldSelectionTableModel _fieldTableModel = null;
+       private JComboBox<String> _altitudeUnitsDropdown = null;
+       private JComboBox<String> _hSpeedUnitsDropdown = null;
+       private JComboBox<String> _vSpeedUnitsDropdown = null;
+       private JRadioButton _vSpeedUpwardsRadio = null;
+       private ComponentHider _componentHider = null;
+       private int _selectedField = -1;
+       private char _currentDelimiter = ',';
+
+       // previously selected values
+       private char _lastUsedDelimiter = ',';
+       private Field[] _lastSelectedFields = null;
+       private Unit _lastAltitudeUnit = null;
+
+       // constants
+       private static final int SNIPPET_SIZE = 6;
+       private static final int MAX_SNIPPET_WIDTH = 80;
+       private static final char[] DELIMITERS = {',', '\t', ';', ' '};
+
+
+       /**
+        * Inner class to listen for delimiter change operations
+        */
+       private class DelimListener implements ActionListener, DocumentListener
+       {
+               public void actionPerformed(ActionEvent e)
+               {
+                       informDelimiterSelected();
+               }
+               public void changedUpdate(DocumentEvent e)
+               {
+                       informDelimiterSelected();
+               }
+               public void insertUpdate(DocumentEvent e)
+               {
+                       informDelimiterSelected();
+               }
+               public void removeUpdate(DocumentEvent e)
+               {
+                       informDelimiterSelected();
+               }
+       }
+
+
+       /**
+        * Constructor
+        * @param inApp Application object to inform of track load
+        * @param inParentFrame parent frame to reference for dialogs
+        */
+       public TextFileLoader(App inApp, JFrame inParentFrame)
+       {
+               _app = inApp;
+               _parentFrame = inParentFrame;
+       }
+
+
+       /**
+        * Open the selected file and show the GUI dialog to select load options
+        * @param inFile file to open
+        */
+       public void openFile(File inFile)
+       {
+               _file = inFile;
+               if (preCheckFile(_file))
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.openoptions.title"), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+                       // add closing listener
+                       _dialog.addWindowListener(new WindowAdapter() {
+                               public void windowClosing(WindowEvent e) {
+                                       _dialog.dispose();
+                                       _app.informNoDataLoaded();
+                               }
+                       });
+                       _dialog.getContentPane().add(makeDialogComponents());
+
+                       // select best separator according to row counts (more is better)
+                       int bestDelim = getBestOption(_delimiterInfos[0].getNumWinningRecords(),
+                               _delimiterInfos[1].getNumWinningRecords(), _delimiterInfos[2].getNumWinningRecords(),
+                               _delimiterInfos[3].getNumWinningRecords());
+                       if (bestDelim >= 0)
+                               _delimiterRadios[bestDelim].setSelected(true);
+                       else
+                               _delimiterRadios[_delimiterRadios.length-1].setSelected(true);
+                       informDelimiterSelected();
+                       _dialog.pack();
+                       _dialog.setVisible(true);
+               }
+               else
+               {
+                       // Didn't pass pre-check
+                       _app.showErrorMessageNoLookup("error.load.dialogtitle",
+                               I18nManager.getText("error.load.noread") + ": " + inFile.getName());
+                       _app.informNoDataLoaded();
+               }
+       }
+
+
+       /**
+        * Check the given file for readability and funny characters,
+        * and count the fields for the various separators
+        * @param inFile file to check
+        */
+       private boolean preCheckFile(File inFile)
+       {
+               // Check file exists and is readable
+               if (inFile == null || !inFile.exists() || !inFile.canRead())
+               {
+                       return false;
+               }
+               // Use a FileCacher to read the file into an array
+               _fileCacher = new FileCacher(inFile);
+
+               // Check each line of the file
+               String[] fileContents = _fileCacher.getContents();
+               if (fileContents == null) {
+                       return false; // nothing cached, might be binary
+               }
+               boolean fileOK = true;
+               _delimiterInfos = new DelimiterInfo[5];
+               for (int i=0; i<4; i++) _delimiterInfos[i] = new DelimiterInfo(DELIMITERS[i]);
+
+               String currLine = null;
+               String[] splitFields = null;
+               int commaFields = 0, semicolonFields = 0, tabFields = 0, spaceFields = 0;
+               for (int lineNum=0; lineNum<fileContents.length && fileOK; lineNum++)
+               {
+                       currLine = fileContents[lineNum];
+                       // check for invalid characters
+                       if (currLine.indexOf('\0') >= 0) {fileOK = false;}
+                       // check for commas
+                       splitFields = currLine.split(",");
+                       commaFields = splitFields.length;
+                       if (commaFields > 1) _delimiterInfos[0].incrementNumRecords();
+                       _delimiterInfos[0].updateMaxFields(commaFields);
+                       // check for tabs
+                       splitFields = currLine.split("\t");
+                       tabFields = splitFields.length;
+                       if (tabFields > 1) _delimiterInfos[1].incrementNumRecords();
+                       _delimiterInfos[1].updateMaxFields(tabFields);
+                       // check for semicolons
+                       splitFields = currLine.split(";");
+                       semicolonFields = splitFields.length;
+                       if (semicolonFields > 1) _delimiterInfos[2].incrementNumRecords();
+                       _delimiterInfos[2].updateMaxFields(semicolonFields);
+                       // check for spaces
+                       splitFields = currLine.split(" ");
+                       spaceFields = splitFields.length;
+                       if (spaceFields > 1) _delimiterInfos[3].incrementNumRecords();
+                       _delimiterInfos[3].updateMaxFields(spaceFields);
+                       // increment counters
+                       int bestScorer = getBestOption(commaFields, tabFields, semicolonFields, spaceFields);
+                       if (bestScorer >= 0)
+                               _delimiterInfos[bestScorer].incrementNumWinningRecords();
+               }
+               return fileOK;
+       }
+
+
+       /**
+        * Get the index of the best one in the list
+        * @return the index of the maximum of the four given values
+        */
+       private static int getBestOption(int inOpt0, int inOpt1, int inOpt2, int inOpt3)
+       {
+               int bestIndex = -1;
+               int maxScore = 1;
+               if (inOpt0 > maxScore) {bestIndex = 0; maxScore = inOpt0;}
+               if (inOpt1 > maxScore) {bestIndex = 1; maxScore = inOpt1;}
+               if (inOpt2 > maxScore) {bestIndex = 2; maxScore = inOpt2;}
+               if (inOpt3 > maxScore) {bestIndex = 3; maxScore = inOpt3;}
+               return bestIndex;
+       }
+
+
+       /**
+        * Make the components for the open options dialog
+        * @return Component for all options
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel wholePanel = new JPanel();
+               wholePanel.setLayout(new BorderLayout());
+
+               // add buttons to south
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+               _backButton = new JButton(I18nManager.getText("button.back"));
+               _backButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _wizard.showPreviousCard();
+                               _nextButton.setEnabled(!_wizard.isLastCard());
+                               _backButton.setEnabled(!_wizard.isFirstCard());
+                               _finishButton.setEnabled(false);
+                       }
+               });
+               _backButton.setEnabled(false);
+               buttonPanel.add(_backButton);
+               _nextButton = new JButton(I18nManager.getText("button.next"));
+               _nextButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               prepareNextPanel(); // Maybe it needs to be initialized based on previous panels
+                               _wizard.showNextCard();
+                               _nextButton.setEnabled(!_wizard.isLastCard() && isCurrentCardValid());
+                               _backButton.setEnabled(!_wizard.isFirstCard());
+                               _finishButton.setEnabled(_wizard.isLastCard() && isCurrentCardValid());
+                       }
+               });
+               buttonPanel.add(_nextButton);
+               _finishButton = new JButton(I18nManager.getText("button.finish"));
+               _finishButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               finished();
+                       }
+               });
+               _finishButton.setEnabled(false);
+               buttonPanel.add(_finishButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _dialog.dispose();
+                               _app.informNoDataLoaded();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               wholePanel.add(buttonPanel, BorderLayout.SOUTH);
+
+               // Make the card panel in the centre
+               JPanel cardPanel = new JPanel();
+               _wizard = new WizardLayout(cardPanel);
+               JPanel firstCard = new JPanel();
+               firstCard.setLayout(new BorderLayout());
+               firstCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+
+               JPanel delimsPanel = new JPanel();
+               delimsPanel.setLayout(new GridLayout(0, 2));
+               delimsPanel.add(new JLabel(I18nManager.getText("dialog.delimiter.label")));
+               delimsPanel.add(new JLabel("")); // blank label to go to next grid row
+               // 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();
+               DelimListener delimListener = new DelimListener();
+               for (int i=0; i<_delimiterRadios.length; i++)
+               {
+                       delimGroup.add(_delimiterRadios[i]);
+                       _delimiterRadios[i].addActionListener(delimListener);
+               }
+               _otherDelimiterText.getDocument().addDocumentListener(delimListener);
+               delimsPanel.add(new JLabel(""));
+               delimsPanel.add(otherPanel);
+               _statusLabel = new JLabel("");
+               delimsPanel.add(_statusLabel);
+               firstCard.add(delimsPanel, BorderLayout.SOUTH);
+               // load snippet to show first few lines
+               _snippetBox = new JList<String>(_fileCacher.getSnippet(SNIPPET_SIZE, MAX_SNIPPET_WIDTH));
+               _snippetBox.setEnabled(false);
+               firstCard.add(makeLabelledPanel("dialog.openoptions.filesnippet", _snippetBox), BorderLayout.CENTER);
+
+               // Second screen, for field order selection
+               JPanel secondCard = new JPanel();
+               secondCard.setLayout(new BorderLayout());
+               secondCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+               // table for file contents
+               _fileExtractTableModel = new FileExtractTableModel();
+               JTable extractTable = new JTable(_fileExtractTableModel);
+               JScrollPane tableScrollPane = new JScrollPane(extractTable);
+               extractTable.setPreferredScrollableViewportSize(new Dimension(350, 80));
+               extractTable.getTableHeader().setReorderingAllowed(false);
+               secondCard.add(makeLabelledPanel("dialog.openoptions.filesnippet", tableScrollPane), BorderLayout.NORTH);
+               JPanel innerPanel2 = new JPanel();
+               innerPanel2.setLayout(new BorderLayout());
+               innerPanel2.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+               _fieldTable = new JTable(new FieldSelectionTableModel());
+               _fieldTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               // add listener for selected table row
+               _fieldTable.getSelectionModel().addListSelectionListener(
+                       new ListSelectionListener() {
+                               public void valueChanged(ListSelectionEvent e) {
+                                       ListSelectionModel lsm = (ListSelectionModel) e.getSource();
+                                       if (lsm.isSelectionEmpty()) {
+                                               //no rows are selected
+                                               selectField(-1);
+                                       } else {
+                                               selectField(lsm.getMinSelectionIndex());
+                                       }
+                               }
+                       });
+               JScrollPane lowerTablePane = new JScrollPane(_fieldTable);
+               lowerTablePane.setPreferredSize(new Dimension(300, 100));
+               innerPanel2.add(lowerTablePane, BorderLayout.CENTER);
+
+               JPanel innerPanel3 = new JPanel();
+               innerPanel3.setLayout(new BoxLayout(innerPanel3, BoxLayout.Y_AXIS));
+               _moveUpButton = new JButton(I18nManager.getText("button.moveup"));
+               _moveUpButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               int currRow = _fieldTable.getSelectedRow();
+                               closeTableComboBox(currRow);
+                               _fieldTableModel.moveUp(currRow);
+                               _fieldTable.setRowSelectionInterval(currRow-1, currRow-1);
+                       }
+               });
+               innerPanel3.add(_moveUpButton);
+               _moveDownButton = new JButton(I18nManager.getText("button.movedown"));
+               _moveDownButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               int currRow = _fieldTable.getSelectedRow();
+                               closeTableComboBox(currRow);
+                               _fieldTableModel.moveDown(currRow);
+                               _fieldTable.setRowSelectionInterval(currRow+1, currRow+1);
+                       }
+               });
+               innerPanel3.add(_moveDownButton);
+               innerPanel3.add(Box.createVerticalStrut(60));
+               JButton guessButton = new JButton(I18nManager.getText("button.guessfields"));
+               guessButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _lastSelectedFields = null;
+                               prepareSecondPanel();
+                       }
+               });
+               innerPanel3.add(guessButton);
+
+               innerPanel2.add(innerPanel3, BorderLayout.EAST);
+               secondCard.add(innerPanel2, BorderLayout.CENTER);
+
+               // Third card, for units selection of altitude and speeds
+               JPanel thirdCard = new JPanel();
+               thirdCard.setLayout(new BorderLayout(10, 10));
+               JPanel holderPanel = new JPanel();
+               holderPanel.setLayout(new BoxLayout(holderPanel, BoxLayout.Y_AXIS));
+               // Altitude
+               JPanel altUnitsPanel = new JPanel();
+               GuiGridLayout altGrid = new GuiGridLayout(altUnitsPanel);
+               altUnitsPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.altitude")));
+               JLabel altLabel = new JLabel(I18nManager.getText("dialog.openoptions.altitudeunits") + ": ");
+               altGrid.add(altLabel);
+               String[] altUnits = {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")};
+               _altitudeUnitsDropdown = new JComboBox<String>(altUnits);
+               altGrid.add(_altitudeUnitsDropdown);
+               holderPanel.add(altUnitsPanel);
+               // Horizontal speed
+               JPanel speedPanel = new JPanel();
+               GuiGridLayout speedGrid = new GuiGridLayout(speedPanel);
+               speedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.speed")));
+               JLabel speedLabel = new JLabel(I18nManager.getText("dialog.openoptions.speedunits") + ": ");
+               speedGrid.add(speedLabel);
+               _hSpeedUnitsDropdown = new JComboBox<String>();
+               for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
+                       _hSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
+               }
+               speedGrid.add(_hSpeedUnitsDropdown);
+               holderPanel.add(speedPanel);
+               // Vertical speed
+               JPanel vSpeedPanel = new JPanel();
+               GuiGridLayout vSpeedGrid = new GuiGridLayout(vSpeedPanel);
+               vSpeedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.verticalspeed")));
+               JLabel vSpeedLabel = new JLabel(I18nManager.getText("dialog.openoptions.vertspeedunits") + ": ");
+               vSpeedGrid.add(vSpeedLabel);
+               _vSpeedUnitsDropdown = new JComboBox<String>();
+               for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
+                       _vSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
+               }
+               vSpeedGrid.add(_vSpeedUnitsDropdown);
+               final String vSpeedLabelText = I18nManager.getText("dialog.openoptions.vspeed.intro");
+               if (!vSpeedLabelText.isEmpty()) {
+                       vSpeedGrid.add(new JLabel(vSpeedLabelText));
+                       vSpeedGrid.add(new JLabel(""));
+               }
+               _vSpeedUpwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positiveup"));
+               JRadioButton vSpeedDownwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positivedown"));
+               ButtonGroup vSpeedDirGroup = new ButtonGroup();
+               vSpeedDirGroup.add(_vSpeedUpwardsRadio); vSpeedDirGroup.add(vSpeedDownwardsRadio);
+               vSpeedGrid.add(_vSpeedUpwardsRadio);     vSpeedGrid.add(vSpeedDownwardsRadio);
+               _vSpeedUpwardsRadio.setSelected(true);
+               holderPanel.add(vSpeedPanel);
+               thirdCard.add(holderPanel, BorderLayout.NORTH);
+
+               // Make a hider to show and hide the components according to the selected fields
+               _componentHider = new ComponentHider();
+               _componentHider.addComponent(altLabel, Field.ALTITUDE);
+               _componentHider.addComponent(_altitudeUnitsDropdown, Field.ALTITUDE);
+               _componentHider.addComponent(speedLabel, Field.SPEED);
+               _componentHider.addComponent(_hSpeedUnitsDropdown, Field.SPEED);
+               _componentHider.addComponent(vSpeedLabel, Field.VERTICAL_SPEED);
+               _componentHider.addComponent(_vSpeedUnitsDropdown, Field.VERTICAL_SPEED);
+               _componentHider.addComponent(_vSpeedUpwardsRadio, Field.VERTICAL_SPEED);
+               _componentHider.addComponent(vSpeedDownwardsRadio, Field.VERTICAL_SPEED);
+
+               // Add cards to the wizard
+               _wizard.addCard(firstCard);
+               _wizard.addCard(secondCard);
+               _wizard.addCard(thirdCard);
+
+               wholePanel.add(cardPanel, BorderLayout.CENTER);
+               return wholePanel;
+       }
+
+
+       /**
+        * Close the combo box on the selected row of the field table
+        * @param inRow currently selected row number
+        */
+       private void closeTableComboBox(int inRow)
+       {
+               TableCellEditor editor = _fieldTable.getCellEditor(inRow, 1);
+               if (editor != null)
+               {
+                       editor.stopCellEditing();
+               }
+       }
+
+
+       /**
+        * change the status based on selection of a delimiter
+        */
+       protected void informDelimiterSelected()
+       {
+               int fields = 0;
+               // Loop through radios to see which one is selected
+               for (int i=0; i<(_delimiterRadios.length-1); i++)
+               {
+                       if (_delimiterRadios[i].isSelected())
+                       {
+                               // Set label text to describe records and fields
+                               int numRecords = _delimiterInfos[i].getNumRecords();
+                               if (numRecords == 0)
+                               {
+                                       _statusLabel.setText(I18nManager.getText("dialog.openoptions.deliminfo.norecords"));
+                               }
+                               else
+                               {
+                                       fields = _delimiterInfos[i].getMaxFields();
+                                       _statusLabel.setText("" + numRecords + " " + I18nManager.getText("dialog.openoptions.deliminfo.records")
+                                               + " " + fields + " " + I18nManager.getText("dialog.openoptions.deliminfo.fields"));
+                               }
+                       }
+               }
+               // Don't show label if "other" delimiter is chosen (as records, fields are unknown)
+               if (_delimiterRadios[_delimiterRadios.length-1].isSelected())
+               {
+                       _statusLabel.setText("");
+               }
+               // enable/disable next button
+               _nextButton.setEnabled((_delimiterRadios[4].isSelected() == false && fields > 1)
+                       || _otherDelimiterText.getText().length() == 1);
+       }
+
+
+       /**
+        * Get the delimiter info from the first step
+        * @return delimiter information object for the selected delimiter
+        */
+       public DelimiterInfo getSelectedDelimiterInfo()
+       {
+               for (int i=0; i<4; i++)
+                       if (_delimiterRadios[i].isSelected()) return _delimiterInfos[i];
+               // must be "other" - build info if necessary
+               if (_delimiterInfos[4] == null)
+                       _delimiterInfos[4] = new DelimiterInfo(_otherDelimiterText.getText().charAt(0));
+               return _delimiterInfos[4];
+       }
+
+
+       /**
+        * Prepare the next panel to be shown, if necessary
+        */
+       private void prepareNextPanel()
+       {
+               int currPanel = _wizard.getCurrentCardIndex();
+               if (currPanel == 0) {
+                       prepareSecondPanel();
+               }
+               else if (currPanel == 1)
+               {
+                       Field[] selectedFields = _fieldTableModel.getFieldArray();
+                       // Enable / disable controls based on whether altitude / speed / vspeed fields were chosen on second panel
+                       _componentHider.enableComponents(Field.ALTITUDE, doesFieldArrayContain(selectedFields, Field.ALTITUDE));
+                       _componentHider.enableComponents(Field.SPEED, doesFieldArrayContain(selectedFields, Field.SPEED));
+                       _componentHider.enableComponents(Field.VERTICAL_SPEED, doesFieldArrayContain(selectedFields, Field.VERTICAL_SPEED));
+                       // TODO: Also check ranges of altitudes, speeds, vert speeds to show them in the third panel
+               }
+       }
+
+       /**
+        * Use the delimiter selected to determine the fields in the file
+        * and prepare the second panel accordingly
+        */
+       private void prepareSecondPanel()
+       {
+               DelimiterInfo info = getSelectedDelimiterInfo();
+               FileSplitter splitter = new FileSplitter(_fileCacher);
+               // Check info makes sense - num fields > 0, num records > 0
+               // set "Finished" button to disabled if not ok
+               // Add data to GUI elements
+               String[][] tableData = splitter.splitFieldData(info.getDelimiter());
+               // possible to ignore blank columns here
+               _currentDelimiter = info.getDelimiter();
+               _fileExtractTableModel.updateData(tableData);
+               _fieldTableModel = new FieldSelectionTableModel();
+
+               // Check number of fields and use last ones if count matches
+               Field[] startFieldArray = null;
+               if (_lastSelectedFields != null && splitter.getNumColumns() == _lastSelectedFields.length)
+               {
+                       startFieldArray = _lastSelectedFields;
+               }
+               else
+               {
+                       // Take first full row of file and use it to guess fields
+                       startFieldArray = FieldGuesser.guessFields(splitter.getFirstFullRow());
+               }
+
+               _fieldTableModel.updateData(startFieldArray);
+               _fieldTable.setModel(_fieldTableModel);
+               // add dropdowns to second column
+               JComboBox<String> fieldTypesBox = new JComboBox<String>();
+               String[] fieldNames = Field.getFieldNames();
+               for (int i=0; i<fieldNames.length; i++)
+               {
+                       fieldTypesBox.addItem(fieldNames[i]);
+               }
+               _fieldTable.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(fieldTypesBox));
+
+               // Set altitude format to same as last time if available
+               if (_lastAltitudeUnit == UnitSetLibrary.UNITS_METRES)
+                       _altitudeUnitsDropdown.setSelectedIndex(0);
+               else if (_lastAltitudeUnit == UnitSetLibrary.UNITS_FEET)
+                       _altitudeUnitsDropdown.setSelectedIndex(1);
+               // no selection on field list
+               selectField(-1);
+       }
+
+       /**
+        * See if the given array of selected fields contains the specified one
+        * @param inFields array of fields selected by user in the second panel
+        * @param inCheck field to check
+        * @return true if the field is present in the array
+        */
+       private boolean doesFieldArrayContain(Field[] inFields, Field inCheck)
+       {
+               if (inFields != null) {
+                       for (int i=0; i<inFields.length; i++) {
+                               if (inFields[i] == inCheck) { // == check ok here because it only checks for built-in fields
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * All options have been selected, so load file
+        */
+       private void finished()
+       {
+               // Save delimiter, field array and altitude format for later use
+               _lastUsedDelimiter = _currentDelimiter;
+               _lastSelectedFields = _fieldTableModel.getFieldArray();
+               // TODO: Remember all the units selections for next load?
+               // Get the selected units for altitudes and speeds
+               SourceInfo sourceInfo = new SourceInfo(_file, SourceInfo.FILE_TYPE.TEXT);
+               PointCreateOptions options = new PointCreateOptions();
+               options.setAltitudeUnits(_altitudeUnitsDropdown.getSelectedIndex() == 0 ? UnitSetLibrary.UNITS_METRES : UnitSetLibrary.UNITS_FEET);
+               Unit hSpeedUnit = UnitSetLibrary.ALL_SPEED_UNITS[_hSpeedUnitsDropdown.getSelectedIndex()];
+               options.setSpeedUnits(hSpeedUnit);
+               Unit vSpeedUnit = UnitSetLibrary.ALL_SPEED_UNITS[_vSpeedUnitsDropdown.getSelectedIndex()];
+               options.setVerticalSpeedUnits(vSpeedUnit, _vSpeedUpwardsRadio.isSelected());
+
+               // give data to App
+               _app.informDataLoaded(_fieldTableModel.getFieldArray(),
+                       _fileExtractTableModel.getData(), options, sourceInfo, null);
+               // clear up file cacher
+               _fileCacher.clear();
+               // dispose of dialog
+               _dialog.dispose();
+       }
+
+       /**
+        * @return true if the inputs on the current tab are valid, user is allowed to proceed
+        */
+       private boolean isCurrentCardValid()
+       {
+               int cardIndex = _wizard.getCurrentCardIndex();
+               if (cardIndex == 1)
+               {
+                       // validate second panel
+                       return _fieldTableModel.getRowCount() > 1;
+               }
+               // all other panels are always valid
+               return true;
+       }
+
+       /**
+        * Make a panel with a label and a component
+        * @param inLabelKey label key to use
+        * @param inComponent component for main area of panel
+        * @return labelled Panel
+        */
+       private static JPanel makeLabelledPanel(String inLabelKey, JComponent inComponent)
+       {
+               JPanel panel = new JPanel();
+               panel.setLayout(new BorderLayout());
+               panel.add(new JLabel(I18nManager.getText(inLabelKey)), BorderLayout.NORTH);
+               panel.add(inComponent, BorderLayout.CENTER);
+               return panel;
+       }
+
+
+       /**
+        * An entry in the field list has been selected
+        * @param inFieldNum index of field, starting with 0
+        */
+       private void selectField(int inFieldNum)
+       {
+               if (inFieldNum == -1 || inFieldNum != _selectedField)
+               {
+                       _selectedField = inFieldNum;
+                       _moveUpButton.setEnabled(inFieldNum > 0);
+                       _moveDownButton.setEnabled(inFieldNum >= 0
+                               && inFieldNum < (_fieldTableModel.getRowCount()-1));
+               }
+       }
+
+
+       /**
+        * @return the last delimiter character used for a load
+        */
+       public char getLastUsedDelimiter()
+       {
+               return _lastUsedDelimiter;
+       }
+}