package me.drton.flightplot.export;
import me.drton.flightplot.PreferencesUtil;
import me.drton.jmavlib.log.FormatErrorException;
import me.drton.jmavlib.log.LogReader;
import org.jfree.data.Range;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.prefs.Preferences;
public class TrackExportDialog extends JDialog {
private static final String DIALOG_SETTING = "TrackExportDialog";
private static final String EXPORTER_CONFIGURATION_SETTING = "ExporterConfiguration";
private static final String READER_CONFIGURATION_SETTING = "ReaderConfiguration";
private static final String LAST_EXPORT_DIRECTORY_SETTING = "LastExportDirectory";
private JPanel contentPane;
private JButton buttonExport;
private JCheckBox splitTrackByFlightCheckBox;
private JComboBox exportFormatComboBox;
private JSlider samplesPerSecond;
private JLabel samplesPerSecondValue;
private JLabel logEndTimeValue;
private JTextField timeEndField;
private JTextField timeStartField;
private JLabel timeStartLabel;
private JLabel timeEndLabel;
private JTextField altOffsetField;
private JLabel statusLabel;
private JButton buttonClose;
private JCheckBox exportDataInChartCheckBox;
private File lastExportDirectory;
private Map<String, TrackExporter> exporters;
private TrackExporterConfiguration exporterConfiguration = new TrackExporterConfiguration();
private TrackReaderConfiguration readerConfiguration = new TrackReaderConfiguration();
private LogReader logReader;
private Range chartRange;
private class ExportFormatItem {
String description;
String formatName;
ExportFormatItem(String description, String formatName) {
this.description = description;
this.formatName = formatName;
}
@Override
public String toString() {
return description;
}
}
public TrackExportDialog(Map<String, TrackExporter> exporters) {
this.exporters = exporters;
setContentPane(contentPane);
setModal(true);
getRootPane().setDefaultButton(buttonExport);
setTitle("Export settings");
initExportersList(exporters);
initSampleSlider();
buttonExport.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
export();
}
});
buttonClose.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onClose();
}
});
// call onClose() when cross is clicked
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
onClose();
}
});
// call onClose() on ESCAPE
contentPane.registerKeyboardAction(new ActionListener() {
public void actionPerformed(ActionEvent e) {
onClose();
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
samplesPerSecond.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent changeEvent) {
updateForSamplesPerSecond();
}
});
exportDataInChartCheckBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent itemEvent) {
validateTimeRange(null);
}
});
logEndTimeValue.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent mouseEvent) {
if (!exportDataInChartCheckBox.isSelected()) {
timeEndField.setText(stringFromMicroseconds(logReader.getSizeMicroseconds()));
}
}
});
DocumentListener timeChangedListener = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
validateTimeRange(null);
}
@Override
public void removeUpdate(DocumentEvent e) {
validateTimeRange(null);
}
@Override
public void changedUpdate(DocumentEvent e) {
validateTimeRange(null);
}
};
timeStartField.getDocument().addDocumentListener(timeChangedListener);
timeEndField.getDocument().addDocumentListener(timeChangedListener);
pack();
}
private void initExportersList(Map<String, TrackExporter> exporters) {
for (TrackExporter exporter : exporters.values()) {
exportFormatComboBox.addItem(new ExportFormatItem(exporter.getDescription(), exporter.getName()));
}
}
private void initSampleSlider() {
Dictionary<Integer, JLabel> labels = new Hashtable<Integer, JLabel>();
labels.put(1, new JLabel("0.1"));
labels.put(10, new JLabel("1"));
labels.put(19, new JLabel("10"));
samplesPerSecond.setLabelTable(labels);
}
private String stringFromMicroseconds(long us) {
return String.format(Locale.ROOT, "%.3f", us / 1000000.0);
}
private long getTimeInterval() {
int value = samplesPerSecond.getValue();
if (value <= 10) {
return 10000000 / value;
} else if (value == 20) {
return 0;
} else {
return 1000000 / (value - 9);
}
}
private void setTimeInterval(long interval) {
if (interval == 0) {
samplesPerSecond.setValue(20);
} else if (interval <= 1000000) {
samplesPerSecond.setValue((int) (1000000 / interval + 9));
} else {
samplesPerSecond.setValue((int) (10000000 / interval));
}
updateForSamplesPerSecond();
}
private void updateForSamplesPerSecond() {
if (getTimeInterval() == 0) {
samplesPerSecondValue.setText("max");
} else {
samplesPerSecondValue.setText(String.format(Locale.ROOT, "%.1f", 1000000.0 / getTimeInterval()));
}
}
private String formatTime(long time) {
long s = time / 1000000;
long ms = (time / 1000) % 1000;
return String.format(Locale.ROOT, "%02d:%02d:%02d.%03d",
(int) (s / 3600), s / 60 % 60, s % 60, ms);
}
public void display(LogReader logReader, Range chartRange) {
if (logReader == null) {
throw new RuntimeException("Log not opened");
}
this.logReader = logReader;
this.chartRange = chartRange;
readerConfiguration.setTimeStart(logReader.getStartMicroseconds());
readerConfiguration.setTimeEnd(logReader.getStartMicroseconds() + logReader.getSizeMicroseconds());
updateDialogFromConfiguration();
setVisible(true);
}
private double getLogSizeInSeconds() {
return logReader.getSizeMicroseconds() / 1000000.0;
}
private File getDestinationFile(String extension, String description) {
JFileChooser fc = new JFileChooser();
if (lastExportDirectory != null) {
fc.setCurrentDirectory(lastExportDirectory);
}
FileNameExtensionFilter extensionFilter = new FileNameExtensionFilter(description, extension);
fc.setFileFilter(extensionFilter);
fc.setDialogTitle("Export Track");
int returnVal = fc.showDialog(null, "Export");
if (returnVal == JFileChooser.APPROVE_OPTION) {
lastExportDirectory = fc.getCurrentDirectory();
String exportFileName = fc.getSelectedFile().toString();
String exportFileExtension = extensionFilter.getExtensions()[0];
if (extensionFilter == fc.getFileFilter() && !exportFileName.toLowerCase().endsWith(exportFileExtension)) {
exportFileName += ("." + exportFileExtension);
}
File exportFile = new File(exportFileName);
if (!exportFile.exists()) {
return exportFile;
} else {
int result = JOptionPane.showConfirmDialog(null,
"Do you want to overwrite the existing file?" + "\n" + exportFile.getAbsoluteFile(),
"File already exists", JOptionPane.YES_NO_OPTION);
if (JOptionPane.YES_OPTION == result) {
return exportFile;
}
}
}
return null;
}
private void export() {
updateConfiguration();
final TrackExporter exporter = exporters.get(exporterConfiguration.getExportFormat());
if (exporter != null) {
final File file = getDestinationFile(exporter.getFileExtension(), exporter.getDescription());
if (null != file) {
setStatus("Exporting...", false);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
TrackReader trackReader = TrackReaderFactory.getTrackReader(logReader, readerConfiguration);
String trackTitle = "Track";
exporter.export(trackReader, exporterConfiguration, file, trackTitle);
setStatus(String.format("Exported to \"%s\"", file), false);
} catch (Exception e) {
setStatus(String.format("Export failed: %s", e.getMessage()), true);
e.printStackTrace();
}
}
});
}
}
}
private void setStatus(String status, boolean error) {
statusLabel.setText(status);
if (error) {
statusLabel.setForeground(Color.RED);
} else {
statusLabel.setForeground(Color.BLACK);
}
}
private Long parseExportTime(JTextField field, JLabel label) {
try {
long time = (long) (Double.parseDouble(field.getText()) * 1000000);
label.setText(formatTime(time));
return time;
} catch (NumberFormatException e) {
label.setText("-");
return null;
}
}
private boolean validateTimeRange(TrackReaderConfiguration configuration) {
String errorMsg = null;
if (exportDataInChartCheckBox.isSelected()) {
timeStartField.setEnabled(false);
timeEndField.setEnabled(false);
timeStartLabel.setText(formatTime((long) (chartRange.getLowerBound() * 1000000) - logReader.getStartMicroseconds()));
timeEndLabel.setText(formatTime((long) (chartRange.getUpperBound() * 1000000) - logReader.getStartMicroseconds()));
} else {
timeStartField.setEnabled(true);
timeEndField.setEnabled(true);
Long timeStart;
Long timeEnd;
timeStart = parseExportTime(timeStartField, timeStartLabel);
timeEnd = parseExportTime(timeEndField, timeEndLabel);
if (timeStart == null || timeEnd == null) {
errorMsg = "Invalid export time format";
} else {
if (timeStart < 0 || timeEnd <= timeStart) {
errorMsg = "Invalid export time range";
} else if (configuration != null) {
configuration.setTimeStart(timeStart + logReader.getStartMicroseconds());
configuration.setTimeEnd(timeEnd + logReader.getStartMicroseconds());
}
}
}
if (errorMsg != null) {
buttonExport.setEnabled(false);
setStatus(errorMsg, true);
return false;
} else {
buttonExport.setEnabled(true);
setStatus("Ready to export", false);
return true;
}
}
private boolean updateConfiguration() {
String errorMsg = null;
exporterConfiguration.setSplitTracksByFlightMode(splitTrackByFlightCheckBox.isSelected());
ExportFormatItem item = (ExportFormatItem) exportFormatComboBox.getSelectedItem();
exporterConfiguration.setExportFormat(item.formatName);
readerConfiguration.setTimeInterval(getTimeInterval());
if (exportDataInChartCheckBox.isSelected()) {
readerConfiguration.setTimeStart((long) (chartRange.getLowerBound() * 1000000));
readerConfiguration.setTimeEnd((long) (chartRange.getUpperBound() * 1000000));
} else {
if (!validateTimeRange(readerConfiguration)) {
return false;
}
}
double altOffset;
try {
altOffset = Double.parseDouble(altOffsetField.getText());
readerConfiguration.setAltitudeOffset(altOffset);
} catch (NumberFormatException e) {
errorMsg = "Invalid altitude offset format";
}
if (errorMsg != null) {
buttonExport.setEnabled(false);
setStatus(errorMsg, true);
return false;
} else {
buttonExport.setEnabled(true);
setStatus("Ready to export", false);
return true;
}
}
private void updateDialogFromConfiguration() {
splitTrackByFlightCheckBox.setSelected(exporterConfiguration.isSplitTracksByFlightMode());
if (exporterConfiguration.getExportFormat() != null) {
for (int index = 0; index < exportFormatComboBox.getItemCount(); index++) {
ExportFormatItem item = (ExportFormatItem) exportFormatComboBox.getItemAt(index);
if (exporterConfiguration.getExportFormat().equals(item.formatName)) {
exportFormatComboBox.setSelectedIndex(index);
break;
}
}
}
timeStartField.setText(stringFromMicroseconds(readerConfiguration.getTimeStart() - logReader.getStartMicroseconds()));
timeEndField.setText(stringFromMicroseconds(readerConfiguration.getTimeEnd() - logReader.getStartMicroseconds()));
altOffsetField.setText(String.valueOf(readerConfiguration.getAltitudeOffset()));
setTimeInterval(readerConfiguration.getTimeInterval());
logEndTimeValue.setText(String.format(" (log end: %s)", stringFromMicroseconds(logReader.getSizeMicroseconds())));
validateTimeRange(null);
}
private void onClose() {
dispose();
}
public void savePreferences(Preferences preferences) {
PreferencesUtil.saveWindowPreferences(this, preferences.node(DIALOG_SETTING));
exporterConfiguration.saveConfiguration(preferences.node(EXPORTER_CONFIGURATION_SETTING));
readerConfiguration.saveConfiguration(preferences.node(READER_CONFIGURATION_SETTING));
if (lastExportDirectory != null) {
preferences.put(LAST_EXPORT_DIRECTORY_SETTING, lastExportDirectory.getAbsolutePath());
}
}
public void loadPreferences(Preferences preferences) {
PreferencesUtil.loadWindowPreferences(this, preferences.node(DIALOG_SETTING), -1, -1);
exporterConfiguration.loadConfiguration(preferences.node(EXPORTER_CONFIGURATION_SETTING));
readerConfiguration.loadConfiguration(preferences.node(READER_CONFIGURATION_SETTING));
String lastExportDirectoryPath = preferences.get(LAST_EXPORT_DIRECTORY_SETTING, null);
if (null != lastExportDirectoryPath) {
lastExportDirectory = new File(lastExportDirectoryPath);
}
}
}