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