]> gitweb.fperrin.net Git - GpsPrune.git/blob - src/tim/prune/load/TextFileLoader.java
b4a0ca829efc6c75fe64b7c8478ff9b6f5f50c8f
[GpsPrune.git] / src / tim / prune / load / TextFileLoader.java
1 package tim.prune.load;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Dimension;
6 import java.awt.FlowLayout;
7 import java.awt.GridLayout;
8 import java.awt.event.ActionEvent;
9 import java.awt.event.ActionListener;
10 import java.awt.event.WindowAdapter;
11 import java.awt.event.WindowEvent;
12
13 import javax.swing.*;
14 import javax.swing.event.DocumentEvent;
15 import javax.swing.event.DocumentListener;
16 import javax.swing.event.ListSelectionEvent;
17 import javax.swing.event.ListSelectionListener;
18 import javax.swing.table.TableCellEditor;
19
20 import java.io.File;
21
22 import tim.prune.App;
23 import tim.prune.I18nManager;
24 import tim.prune.data.Field;
25 import tim.prune.data.PointCreateOptions;
26 import tim.prune.data.SourceInfo;
27 import tim.prune.data.Unit;
28 import tim.prune.data.UnitSetLibrary;
29 import tim.prune.gui.GuiGridLayout;
30 import tim.prune.gui.WizardLayout;
31
32
33 /**
34  * Class to handle loading of text files including GUI options,
35  * and passing loaded data back to App object
36  */
37 public class TextFileLoader
38 {
39         private File _file = null;
40         private App _app = null;
41         private JFrame _parentFrame = null;
42         private JDialog _dialog = null;
43         private WizardLayout _wizard = null;
44         private JButton _backButton = null, _nextButton = null;
45         private JButton _finishButton = null;
46         private JButton _moveUpButton = null, _moveDownButton = null;
47         private JRadioButton[] _delimiterRadios = null;
48         private JTextField _otherDelimiterText = null;
49         private JLabel _statusLabel = null;
50         private DelimiterInfo[] _delimiterInfos = null;
51         private FileCacher _fileCacher = null;
52         private JList<String> _snippetBox = null;
53         private FileExtractTableModel _fileExtractTableModel = null;
54         private JTable _fieldTable;
55         private FieldSelectionTableModel _fieldTableModel = null;
56         private JComboBox<String> _altitudeUnitsDropdown = null;
57         private JComboBox<String> _hSpeedUnitsDropdown = null;
58         private JComboBox<String> _vSpeedUnitsDropdown = null;
59         private JRadioButton _vSpeedUpwardsRadio = null;
60         private ComponentHider _componentHider = null;
61         private int _selectedField = -1;
62         private char _currentDelimiter = ',';
63
64         // previously selected values
65         private char _lastUsedDelimiter = ',';
66         private Field[] _lastSelectedFields = null;
67         private Unit _lastAltitudeUnit = null;
68
69         // constants
70         private static final int SNIPPET_SIZE = 6;
71         private static final int MAX_SNIPPET_WIDTH = 80;
72         private static final char[] DELIMITERS = {',', '\t', ';', ' '};
73
74
75         /**
76          * Inner class to listen for delimiter change operations
77          */
78         private class DelimListener implements ActionListener, DocumentListener
79         {
80                 public void actionPerformed(ActionEvent e)
81                 {
82                         informDelimiterSelected();
83                 }
84                 public void changedUpdate(DocumentEvent e)
85                 {
86                         informDelimiterSelected();
87                 }
88                 public void insertUpdate(DocumentEvent e)
89                 {
90                         informDelimiterSelected();
91                 }
92                 public void removeUpdate(DocumentEvent e)
93                 {
94                         informDelimiterSelected();
95                 }
96         }
97
98
99         /**
100          * Constructor
101          * @param inApp Application object to inform of track load
102          * @param inParentFrame parent frame to reference for dialogs
103          */
104         public TextFileLoader(App inApp, JFrame inParentFrame)
105         {
106                 _app = inApp;
107                 _parentFrame = inParentFrame;
108         }
109
110
111         /**
112          * Open the selected file and show the GUI dialog to select load options
113          * @param inFile file to open
114          */
115         public void openFile(File inFile)
116         {
117                 _file = inFile;
118                 if (preCheckFile(_file))
119                 {
120                         _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.openoptions.title"), true);
121                         _dialog.setLocationRelativeTo(_parentFrame);
122                         _dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
123                         // add closing listener
124                         _dialog.addWindowListener(new WindowAdapter() {
125                                 public void windowClosing(WindowEvent e) {
126                                         _dialog.dispose();
127                                         _app.informNoDataLoaded();
128                                 }
129                         });
130                         _dialog.getContentPane().add(makeDialogComponents());
131
132                         // select best separator according to row counts (more is better)
133                         int bestDelim = getBestOption(_delimiterInfos[0].getNumWinningRecords(),
134                                 _delimiterInfos[1].getNumWinningRecords(), _delimiterInfos[2].getNumWinningRecords(),
135                                 _delimiterInfos[3].getNumWinningRecords());
136                         if (bestDelim >= 0)
137                                 _delimiterRadios[bestDelim].setSelected(true);
138                         else
139                                 _delimiterRadios[_delimiterRadios.length-1].setSelected(true);
140                         informDelimiterSelected();
141                         _dialog.pack();
142                         _dialog.setVisible(true);
143                 }
144                 else
145                 {
146                         // Didn't pass pre-check
147                         _app.showErrorMessageNoLookup("error.load.dialogtitle",
148                                 I18nManager.getText("error.load.noread") + ": " + inFile.getName());
149                         _app.informNoDataLoaded();
150                 }
151         }
152
153
154         /**
155          * Check the given file for readability and funny characters,
156          * and count the fields for the various separators
157          * @param inFile file to check
158          */
159         private boolean preCheckFile(File inFile)
160         {
161                 // Check file exists and is readable
162                 if (inFile == null || !inFile.exists() || !inFile.canRead())
163                 {
164                         return false;
165                 }
166                 // Use a FileCacher to read the file into an array
167                 _fileCacher = new FileCacher(inFile);
168
169                 // Check each line of the file
170                 String[] fileContents = _fileCacher.getContents();
171                 if (fileContents == null) {
172                         return false; // nothing cached, might be binary
173                 }
174                 boolean fileOK = true;
175                 _delimiterInfos = new DelimiterInfo[5];
176                 for (int i=0; i<4; i++) _delimiterInfos[i] = new DelimiterInfo(DELIMITERS[i]);
177
178                 String currLine = null;
179                 String[] splitFields = null;
180                 int commaFields = 0, semicolonFields = 0, tabFields = 0, spaceFields = 0;
181                 for (int lineNum=0; lineNum<fileContents.length && fileOK; lineNum++)
182                 {
183                         currLine = fileContents[lineNum];
184                         // check for invalid characters
185                         if (currLine.indexOf('\0') >= 0) {fileOK = false;}
186                         // check for commas
187                         splitFields = currLine.split(",");
188                         commaFields = splitFields.length;
189                         if (commaFields > 1) _delimiterInfos[0].incrementNumRecords();
190                         _delimiterInfos[0].updateMaxFields(commaFields);
191                         // check for tabs
192                         splitFields = currLine.split("\t");
193                         tabFields = splitFields.length;
194                         if (tabFields > 1) _delimiterInfos[1].incrementNumRecords();
195                         _delimiterInfos[1].updateMaxFields(tabFields);
196                         // check for semicolons
197                         splitFields = currLine.split(";");
198                         semicolonFields = splitFields.length;
199                         if (semicolonFields > 1) _delimiterInfos[2].incrementNumRecords();
200                         _delimiterInfos[2].updateMaxFields(semicolonFields);
201                         // check for spaces
202                         splitFields = currLine.split(" ");
203                         spaceFields = splitFields.length;
204                         if (spaceFields > 1) _delimiterInfos[3].incrementNumRecords();
205                         _delimiterInfos[3].updateMaxFields(spaceFields);
206                         // increment counters
207                         int bestScorer = getBestOption(commaFields, tabFields, semicolonFields, spaceFields);
208                         if (bestScorer >= 0)
209                                 _delimiterInfos[bestScorer].incrementNumWinningRecords();
210                 }
211                 return fileOK;
212         }
213
214
215         /**
216          * Get the index of the best one in the list
217          * @return the index of the maximum of the four given values
218          */
219         private static int getBestOption(int inOpt0, int inOpt1, int inOpt2, int inOpt3)
220         {
221                 int bestIndex = -1;
222                 int maxScore = 1;
223                 if (inOpt0 > maxScore) {bestIndex = 0; maxScore = inOpt0;}
224                 if (inOpt1 > maxScore) {bestIndex = 1; maxScore = inOpt1;}
225                 if (inOpt2 > maxScore) {bestIndex = 2; maxScore = inOpt2;}
226                 if (inOpt3 > maxScore) {bestIndex = 3; maxScore = inOpt3;}
227                 return bestIndex;
228         }
229
230
231         /**
232          * Make the components for the open options dialog
233          * @return Component for all options
234          */
235         private Component makeDialogComponents()
236         {
237                 JPanel wholePanel = new JPanel();
238                 wholePanel.setLayout(new BorderLayout());
239
240                 // add buttons to south
241                 JPanel buttonPanel = new JPanel();
242                 buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
243                 _backButton = new JButton(I18nManager.getText("button.back"));
244                 _backButton.addActionListener(new ActionListener() {
245                         public void actionPerformed(ActionEvent e)
246                         {
247                                 _wizard.showPreviousCard();
248                                 _nextButton.setEnabled(!_wizard.isLastCard());
249                                 _backButton.setEnabled(!_wizard.isFirstCard());
250                                 _finishButton.setEnabled(false);
251                         }
252                 });
253                 _backButton.setEnabled(false);
254                 buttonPanel.add(_backButton);
255                 _nextButton = new JButton(I18nManager.getText("button.next"));
256                 _nextButton.addActionListener(new ActionListener() {
257                         public void actionPerformed(ActionEvent e)
258                         {
259                                 prepareNextPanel(); // Maybe it needs to be initialized based on previous panels
260                                 _wizard.showNextCard();
261                                 _nextButton.setEnabled(!_wizard.isLastCard() && isCurrentCardValid());
262                                 _backButton.setEnabled(!_wizard.isFirstCard());
263                                 _finishButton.setEnabled(_wizard.isLastCard() && isCurrentCardValid());
264                         }
265                 });
266                 buttonPanel.add(_nextButton);
267                 _finishButton = new JButton(I18nManager.getText("button.finish"));
268                 _finishButton.addActionListener(new ActionListener() {
269                         public void actionPerformed(ActionEvent e)
270                         {
271                                 finished();
272                         }
273                 });
274                 _finishButton.setEnabled(false);
275                 buttonPanel.add(_finishButton);
276                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
277                 cancelButton.addActionListener(new ActionListener() {
278                         public void actionPerformed(ActionEvent e)
279                         {
280                                 _dialog.dispose();
281                                 _app.informNoDataLoaded();
282                         }
283                 });
284                 buttonPanel.add(cancelButton);
285                 wholePanel.add(buttonPanel, BorderLayout.SOUTH);
286
287                 // Make the card panel in the centre
288                 JPanel cardPanel = new JPanel();
289                 _wizard = new WizardLayout(cardPanel);
290                 JPanel firstCard = new JPanel();
291                 firstCard.setLayout(new BorderLayout());
292                 firstCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
293
294                 JPanel delimsPanel = new JPanel();
295                 delimsPanel.setLayout(new GridLayout(0, 2));
296                 delimsPanel.add(new JLabel(I18nManager.getText("dialog.delimiter.label")));
297                 delimsPanel.add(new JLabel("")); // blank label to go to next grid row
298                 // radio buttons
299                 _delimiterRadios = new JRadioButton[5];
300                 _delimiterRadios[0] = new JRadioButton(I18nManager.getText("dialog.delimiter.comma"));
301                 delimsPanel.add(_delimiterRadios[0]);
302                 _delimiterRadios[1] = new JRadioButton(I18nManager.getText("dialog.delimiter.tab"));
303                 delimsPanel.add(_delimiterRadios[1]);
304                 _delimiterRadios[2] = new JRadioButton(I18nManager.getText("dialog.delimiter.semicolon"));
305                 delimsPanel.add(_delimiterRadios[2]);
306                 _delimiterRadios[3] = new JRadioButton(I18nManager.getText("dialog.delimiter.space"));
307                 delimsPanel.add(_delimiterRadios[3]);
308                 JPanel otherPanel = new JPanel();
309                 otherPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
310                 _delimiterRadios[4] = new JRadioButton(I18nManager.getText("dialog.delimiter.other"));
311                 otherPanel.add(_delimiterRadios[4]);
312                 _otherDelimiterText = new JTextField(new OneCharDocument(), null, 2);
313                 otherPanel.add(_otherDelimiterText);
314                 // Group radio buttons
315                 ButtonGroup delimGroup = new ButtonGroup();
316                 DelimListener delimListener = new DelimListener();
317                 for (int i=0; i<_delimiterRadios.length; i++)
318                 {
319                         delimGroup.add(_delimiterRadios[i]);
320                         _delimiterRadios[i].addActionListener(delimListener);
321                 }
322                 _otherDelimiterText.getDocument().addDocumentListener(delimListener);
323                 delimsPanel.add(new JLabel(""));
324                 delimsPanel.add(otherPanel);
325                 _statusLabel = new JLabel("");
326                 delimsPanel.add(_statusLabel);
327                 firstCard.add(delimsPanel, BorderLayout.SOUTH);
328                 // load snippet to show first few lines
329                 _snippetBox = new JList<String>(_fileCacher.getSnippet(SNIPPET_SIZE, MAX_SNIPPET_WIDTH));
330                 _snippetBox.setEnabled(false);
331                 firstCard.add(makeLabelledPanel("dialog.openoptions.filesnippet", _snippetBox), BorderLayout.CENTER);
332
333                 // Second screen, for field order selection
334                 JPanel secondCard = new JPanel();
335                 secondCard.setLayout(new BorderLayout());
336                 secondCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
337                 // table for file contents
338                 _fileExtractTableModel = new FileExtractTableModel();
339                 JTable extractTable = new JTable(_fileExtractTableModel);
340                 JScrollPane tableScrollPane = new JScrollPane(extractTable);
341                 extractTable.setPreferredScrollableViewportSize(new Dimension(350, 80));
342                 extractTable.getTableHeader().setReorderingAllowed(false);
343                 secondCard.add(makeLabelledPanel("dialog.openoptions.filesnippet", tableScrollPane), BorderLayout.NORTH);
344                 JPanel innerPanel2 = new JPanel();
345                 innerPanel2.setLayout(new BorderLayout());
346                 innerPanel2.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
347
348                 _fieldTable = new JTable(new FieldSelectionTableModel());
349                 _fieldTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
350                 // add listener for selected table row
351                 _fieldTable.getSelectionModel().addListSelectionListener(
352                         new ListSelectionListener() {
353                                 public void valueChanged(ListSelectionEvent e) {
354                                         ListSelectionModel lsm = (ListSelectionModel) e.getSource();
355                                         if (lsm.isSelectionEmpty()) {
356                                                 //no rows are selected
357                                                 selectField(-1);
358                                         } else {
359                                                 selectField(lsm.getMinSelectionIndex());
360                                         }
361                                 }
362                         });
363                 JScrollPane lowerTablePane = new JScrollPane(_fieldTable);
364                 lowerTablePane.setPreferredSize(new Dimension(300, 100));
365                 innerPanel2.add(lowerTablePane, BorderLayout.CENTER);
366
367                 JPanel innerPanel3 = new JPanel();
368                 innerPanel3.setLayout(new BoxLayout(innerPanel3, BoxLayout.Y_AXIS));
369                 _moveUpButton = new JButton(I18nManager.getText("button.moveup"));
370                 _moveUpButton.addActionListener(new ActionListener() {
371                         public void actionPerformed(ActionEvent e)
372                         {
373                                 int currRow = _fieldTable.getSelectedRow();
374                                 closeTableComboBox(currRow);
375                                 _fieldTableModel.moveUp(currRow);
376                                 _fieldTable.setRowSelectionInterval(currRow-1, currRow-1);
377                         }
378                 });
379                 innerPanel3.add(_moveUpButton);
380                 _moveDownButton = new JButton(I18nManager.getText("button.movedown"));
381                 _moveDownButton.addActionListener(new ActionListener() {
382                         public void actionPerformed(ActionEvent e)
383                         {
384                                 int currRow = _fieldTable.getSelectedRow();
385                                 closeTableComboBox(currRow);
386                                 _fieldTableModel.moveDown(currRow);
387                                 _fieldTable.setRowSelectionInterval(currRow+1, currRow+1);
388                         }
389                 });
390                 innerPanel3.add(_moveDownButton);
391                 innerPanel3.add(Box.createVerticalStrut(60));
392                 JButton guessButton = new JButton(I18nManager.getText("button.guessfields"));
393                 guessButton.addActionListener(new ActionListener() {
394                         public void actionPerformed(ActionEvent e)
395                         {
396                                 _lastSelectedFields = null;
397                                 prepareSecondPanel();
398                         }
399                 });
400                 innerPanel3.add(guessButton);
401
402                 innerPanel2.add(innerPanel3, BorderLayout.EAST);
403                 secondCard.add(innerPanel2, BorderLayout.CENTER);
404
405                 // Third card, for units selection of altitude and speeds
406                 JPanel thirdCard = new JPanel();
407                 thirdCard.setLayout(new BorderLayout(10, 10));
408                 JPanel holderPanel = new JPanel();
409                 holderPanel.setLayout(new BoxLayout(holderPanel, BoxLayout.Y_AXIS));
410                 // Altitude
411                 JPanel altUnitsPanel = new JPanel();
412                 GuiGridLayout altGrid = new GuiGridLayout(altUnitsPanel);
413                 altUnitsPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.altitude")));
414                 JLabel altLabel = new JLabel(I18nManager.getText("dialog.openoptions.altitudeunits") + ": ");
415                 altGrid.add(altLabel);
416                 String[] altUnits = {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")};
417                 _altitudeUnitsDropdown = new JComboBox<String>(altUnits);
418                 altGrid.add(_altitudeUnitsDropdown);
419                 holderPanel.add(altUnitsPanel);
420                 // Horizontal speed
421                 JPanel speedPanel = new JPanel();
422                 GuiGridLayout speedGrid = new GuiGridLayout(speedPanel);
423                 speedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.speed")));
424                 JLabel speedLabel = new JLabel(I18nManager.getText("dialog.openoptions.speedunits") + ": ");
425                 speedGrid.add(speedLabel);
426                 _hSpeedUnitsDropdown = new JComboBox<String>();
427                 for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
428                         _hSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
429                 }
430                 speedGrid.add(_hSpeedUnitsDropdown);
431                 holderPanel.add(speedPanel);
432                 // Vertical speed
433                 JPanel vSpeedPanel = new JPanel();
434                 GuiGridLayout vSpeedGrid = new GuiGridLayout(vSpeedPanel);
435                 vSpeedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.verticalspeed")));
436                 JLabel vSpeedLabel = new JLabel(I18nManager.getText("dialog.openoptions.vertspeedunits") + ": ");
437                 vSpeedGrid.add(vSpeedLabel);
438                 _vSpeedUnitsDropdown = new JComboBox<String>();
439                 for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
440                         _vSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
441                 }
442                 vSpeedGrid.add(_vSpeedUnitsDropdown);
443                 final String vSpeedLabelText = I18nManager.getText("dialog.openoptions.vspeed.intro");
444                 if (!vSpeedLabelText.isEmpty()) {
445                         vSpeedGrid.add(new JLabel(vSpeedLabelText));
446                         vSpeedGrid.add(new JLabel(""));
447                 }
448                 _vSpeedUpwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positiveup"));
449                 JRadioButton vSpeedDownwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positivedown"));
450                 ButtonGroup vSpeedDirGroup = new ButtonGroup();
451                 vSpeedDirGroup.add(_vSpeedUpwardsRadio); vSpeedDirGroup.add(vSpeedDownwardsRadio);
452                 vSpeedGrid.add(_vSpeedUpwardsRadio);     vSpeedGrid.add(vSpeedDownwardsRadio);
453                 _vSpeedUpwardsRadio.setSelected(true);
454                 holderPanel.add(vSpeedPanel);
455                 thirdCard.add(holderPanel, BorderLayout.NORTH);
456
457                 // Make a hider to show and hide the components according to the selected fields
458                 _componentHider = new ComponentHider();
459                 _componentHider.addComponent(altLabel, Field.ALTITUDE);
460                 _componentHider.addComponent(_altitudeUnitsDropdown, Field.ALTITUDE);
461                 _componentHider.addComponent(speedLabel, Field.SPEED);
462                 _componentHider.addComponent(_hSpeedUnitsDropdown, Field.SPEED);
463                 _componentHider.addComponent(vSpeedLabel, Field.VERTICAL_SPEED);
464                 _componentHider.addComponent(_vSpeedUnitsDropdown, Field.VERTICAL_SPEED);
465                 _componentHider.addComponent(_vSpeedUpwardsRadio, Field.VERTICAL_SPEED);
466                 _componentHider.addComponent(vSpeedDownwardsRadio, Field.VERTICAL_SPEED);
467
468                 // Add cards to the wizard
469                 _wizard.addCard(firstCard);
470                 _wizard.addCard(secondCard);
471                 _wizard.addCard(thirdCard);
472
473                 wholePanel.add(cardPanel, BorderLayout.CENTER);
474                 return wholePanel;
475         }
476
477
478         /**
479          * Close the combo box on the selected row of the field table
480          * @param inRow currently selected row number
481          */
482         private void closeTableComboBox(int inRow)
483         {
484                 TableCellEditor editor = _fieldTable.getCellEditor(inRow, 1);
485                 if (editor != null)
486                 {
487                         editor.stopCellEditing();
488                 }
489         }
490
491
492         /**
493          * change the status based on selection of a delimiter
494          */
495         protected void informDelimiterSelected()
496         {
497                 int fields = 0;
498                 // Loop through radios to see which one is selected
499                 for (int i=0; i<(_delimiterRadios.length-1); i++)
500                 {
501                         if (_delimiterRadios[i].isSelected())
502                         {
503                                 // Set label text to describe records and fields
504                                 int numRecords = _delimiterInfos[i].getNumRecords();
505                                 if (numRecords == 0)
506                                 {
507                                         _statusLabel.setText(I18nManager.getText("dialog.openoptions.deliminfo.norecords"));
508                                 }
509                                 else
510                                 {
511                                         fields = _delimiterInfos[i].getMaxFields();
512                                         _statusLabel.setText("" + numRecords + " " + I18nManager.getText("dialog.openoptions.deliminfo.records")
513                                                 + " " + fields + " " + I18nManager.getText("dialog.openoptions.deliminfo.fields"));
514                                 }
515                         }
516                 }
517                 // Don't show label if "other" delimiter is chosen (as records, fields are unknown)
518                 if (_delimiterRadios[_delimiterRadios.length-1].isSelected())
519                 {
520                         _statusLabel.setText("");
521                 }
522                 // enable/disable next button
523                 _nextButton.setEnabled((_delimiterRadios[4].isSelected() == false && fields > 1)
524                         || _otherDelimiterText.getText().length() == 1);
525         }
526
527
528         /**
529          * Get the delimiter info from the first step
530          * @return delimiter information object for the selected delimiter
531          */
532         public DelimiterInfo getSelectedDelimiterInfo()
533         {
534                 for (int i=0; i<4; i++)
535                         if (_delimiterRadios[i].isSelected()) return _delimiterInfos[i];
536                 // must be "other" - build info if necessary
537                 if (_delimiterInfos[4] == null)
538                         _delimiterInfos[4] = new DelimiterInfo(_otherDelimiterText.getText().charAt(0));
539                 return _delimiterInfos[4];
540         }
541
542
543         /**
544          * Prepare the next panel to be shown, if necessary
545          */
546         private void prepareNextPanel()
547         {
548                 int currPanel = _wizard.getCurrentCardIndex();
549                 if (currPanel == 0) {
550                         prepareSecondPanel();
551                 }
552                 else if (currPanel == 1)
553                 {
554                         Field[] selectedFields = _fieldTableModel.getFieldArray();
555                         // Enable / disable controls based on whether altitude / speed / vspeed fields were chosen on second panel
556                         _componentHider.enableComponents(Field.ALTITUDE, doesFieldArrayContain(selectedFields, Field.ALTITUDE));
557                         _componentHider.enableComponents(Field.SPEED, doesFieldArrayContain(selectedFields, Field.SPEED));
558                         _componentHider.enableComponents(Field.VERTICAL_SPEED, doesFieldArrayContain(selectedFields, Field.VERTICAL_SPEED));
559                         // TODO: Also check ranges of altitudes, speeds, vert speeds to show them in the third panel
560                 }
561         }
562
563         /**
564          * Use the delimiter selected to determine the fields in the file
565          * and prepare the second panel accordingly
566          */
567         private void prepareSecondPanel()
568         {
569                 DelimiterInfo info = getSelectedDelimiterInfo();
570                 FileSplitter splitter = new FileSplitter(_fileCacher);
571                 // Check info makes sense - num fields > 0, num records > 0
572                 // set "Finished" button to disabled if not ok
573                 // Add data to GUI elements
574                 String[][] tableData = splitter.splitFieldData(info.getDelimiter());
575                 // possible to ignore blank columns here
576                 _currentDelimiter = info.getDelimiter();
577                 _fileExtractTableModel.updateData(tableData);
578                 _fieldTableModel = new FieldSelectionTableModel();
579
580                 // Check number of fields and use last ones if count matches
581                 Field[] startFieldArray = null;
582                 if (_lastSelectedFields != null && splitter.getNumColumns() == _lastSelectedFields.length)
583                 {
584                         startFieldArray = _lastSelectedFields;
585                 }
586                 else
587                 {
588                         // Take first full row of file and use it to guess fields
589                         startFieldArray = FieldGuesser.guessFields(splitter.getFirstFullRow());
590                 }
591
592                 _fieldTableModel.updateData(startFieldArray);
593                 _fieldTable.setModel(_fieldTableModel);
594                 // add dropdowns to second column
595                 JComboBox<String> fieldTypesBox = new JComboBox<String>();
596                 String[] fieldNames = Field.getFieldNames();
597                 for (int i=0; i<fieldNames.length; i++)
598                 {
599                         fieldTypesBox.addItem(fieldNames[i]);
600                 }
601                 _fieldTable.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(fieldTypesBox));
602
603                 // Set altitude format to same as last time if available
604                 if (_lastAltitudeUnit == UnitSetLibrary.UNITS_METRES)
605                         _altitudeUnitsDropdown.setSelectedIndex(0);
606                 else if (_lastAltitudeUnit == UnitSetLibrary.UNITS_FEET)
607                         _altitudeUnitsDropdown.setSelectedIndex(1);
608                 // no selection on field list
609                 selectField(-1);
610         }
611
612         /**
613          * See if the given array of selected fields contains the specified one
614          * @param inFields array of fields selected by user in the second panel
615          * @param inCheck field to check
616          * @return true if the field is present in the array
617          */
618         private boolean doesFieldArrayContain(Field[] inFields, Field inCheck)
619         {
620                 if (inFields != null) {
621                         for (int i=0; i<inFields.length; i++) {
622                                 if (inFields[i] == inCheck) { // == check ok here because it only checks for built-in fields
623                                         return true;
624                                 }
625                         }
626                 }
627                 return false;
628         }
629
630         /**
631          * All options have been selected, so load file
632          */
633         private void finished()
634         {
635                 // Save delimiter, field array and altitude format for later use
636                 _lastUsedDelimiter = _currentDelimiter;
637                 _lastSelectedFields = _fieldTableModel.getFieldArray();
638                 // TODO: Remember all the units selections for next load?
639                 // Get the selected units for altitudes and speeds
640                 SourceInfo sourceInfo = new SourceInfo(_file, SourceInfo.FILE_TYPE.TEXT);
641                 PointCreateOptions options = new PointCreateOptions();
642                 options.setAltitudeUnits(_altitudeUnitsDropdown.getSelectedIndex() == 0 ? UnitSetLibrary.UNITS_METRES : UnitSetLibrary.UNITS_FEET);
643                 Unit hSpeedUnit = UnitSetLibrary.ALL_SPEED_UNITS[_hSpeedUnitsDropdown.getSelectedIndex()];
644                 options.setSpeedUnits(hSpeedUnit);
645                 Unit vSpeedUnit = UnitSetLibrary.ALL_SPEED_UNITS[_vSpeedUnitsDropdown.getSelectedIndex()];
646                 options.setVerticalSpeedUnits(vSpeedUnit, _vSpeedUpwardsRadio.isSelected());
647
648                 // give data to App
649                 _app.informDataLoaded(_fieldTableModel.getFieldArray(),
650                         _fileExtractTableModel.getData(), options, sourceInfo, null);
651                 // clear up file cacher
652                 _fileCacher.clear();
653                 // dispose of dialog
654                 _dialog.dispose();
655         }
656
657         /**
658          * @return true if the inputs on the current tab are valid, user is allowed to proceed
659          */
660         private boolean isCurrentCardValid()
661         {
662                 int cardIndex = _wizard.getCurrentCardIndex();
663                 if (cardIndex == 1)
664                 {
665                         // validate second panel
666                         return _fieldTableModel.getRowCount() > 1;
667                 }
668                 // all other panels are always valid
669                 return true;
670         }
671
672         /**
673          * Make a panel with a label and a component
674          * @param inLabelKey label key to use
675          * @param inComponent component for main area of panel
676          * @return labelled Panel
677          */
678         private static JPanel makeLabelledPanel(String inLabelKey, JComponent inComponent)
679         {
680                 JPanel panel = new JPanel();
681                 panel.setLayout(new BorderLayout());
682                 panel.add(new JLabel(I18nManager.getText(inLabelKey)), BorderLayout.NORTH);
683                 panel.add(inComponent, BorderLayout.CENTER);
684                 return panel;
685         }
686
687
688         /**
689          * An entry in the field list has been selected
690          * @param inFieldNum index of field, starting with 0
691          */
692         private void selectField(int inFieldNum)
693         {
694                 if (inFieldNum == -1 || inFieldNum != _selectedField)
695                 {
696                         _selectedField = inFieldNum;
697                         _moveUpButton.setEnabled(inFieldNum > 0);
698                         _moveDownButton.setEnabled(inFieldNum >= 0
699                                 && inFieldNum < (_fieldTableModel.getRowCount()-1));
700                 }
701         }
702
703
704         /**
705          * @return the last delimiter character used for a load
706          */
707         public char getLastUsedDelimiter()
708         {
709                 return _lastUsedDelimiter;
710         }
711 }