/*
openaltimeter -- an open-source altimeter for RC aircraft
Copyright (C) 2010-2011 Jony Hudson, Jan Steidl, mycarda
http://openaltimeter.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openaltimeter.desktopapp;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.prefs.Preferences;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
import org.openaltimeter.data.HeightUnits;
import org.openaltimeter.desktopapp.Controller.ConnectionState;
import org.openaltimeter.desktopapp.Controller.OS;
public class MainWindow {
private JFrame frmOpenaltimeter;
public Controller controller;
JMenu mnSerialPort;
ButtonGroup serialMenuGroup;
JMenuItem mntmConnect;
JMenuItem mntmDisconnect;
JTextPane logTextPane;
JScrollPane scrollPane;
private StyledDocument logDocument;
private JMenuItem mntmDownloadData;
private JMenuItem mntmEraseLogger;
private JCheckBoxMenuItem chckbxmntmTemperature;
private JCheckBoxMenuItem chckbxmntmVoltage;
private JCheckBoxMenuItem chckbxmntmServo;
private JRadioButtonMenuItem rdbtnmntmFeet;
private JRadioButtonMenuItem rdbtnmntmMetres;
private final JProgressBar progressBar = new JProgressBar();
private JMenuItem mntmSaveData;
private JMenuItem mntmSaveSelectionData;
AltimeterChart altimeterChart;
public enum DataState {NO_DATA, HAVE_DATA};
private Preferences prefs;
private static final String PREF_SHOW_TEMPERATURE = "PREF_SHOW_TEMPERATURE";
private static final String PREF_SHOW_VOLTAGE = "PREF_SHOW_VOLTAGE";
private static final String PREF_SHOW_SERVO = "PREF_SHOW_SERVO";
private static final String PREF_WINDOW_X = "PREF_WINDOW_X";
private static final String PREF_WINDOW_Y = "PREF_WINDOW_Y";
private static final String PREF_WINDOW_WIDTH = "PREF_WINDOW_WIDTH";
private static final String PREF_WINDOW_HEIGHT = "PREF_WINDOW_HEIGHT";
private static final String PREF_FILE_PATH = "PREF_FILE_PATH";
private File filePath;
private JMenuItem mntmSettings;
private JMenuItem mntmFlashFirmware;
private JMenu mnAdvanced;
private JMenuItem mntmUploadSelectionerases;
private JMenuItem mntmClearAnnotations;
private JMenu mnAnalysis;
private JMenuItem mntmMarkDlgLaunches;
private JMenuItem mntmClearDlgLaunch;
public void show() {
frmOpenaltimeter.setVisible(true);
}
/**
* @wbp.parser.entryPoint
*/
public void initialise() {
prefs = Preferences.userNodeForPackage(this.getClass());
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
System.err.println("Unable to switch to native look and feel.");
}
try {
filePath = new File(prefs.get(PREF_FILE_PATH, "."));
} catch (Exception e) {
filePath = null;
}
frmOpenaltimeter = new JFrame();
frmOpenaltimeter.setIconImage(Toolkit.getDefaultToolkit().getImage(MainWindow.class.getResource("/logo_short_64.png")));
frmOpenaltimeter.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent arg0) {
controller.exit();
}
});
frmOpenaltimeter.setTitle("openaltimeter");
frmOpenaltimeter.setBounds(prefs.getInt(PREF_WINDOW_X, 70),
prefs.getInt(PREF_WINDOW_Y, 70),
prefs.getInt(PREF_WINDOW_WIDTH, 1100),
prefs.getInt(PREF_WINDOW_HEIGHT, 700));
frmOpenaltimeter.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
JMenuBar menuBar = new JMenuBar();
frmOpenaltimeter.setJMenuBar(menuBar);
JMenu mnFile = new JMenu("File");
menuBar.add(mnFile);
mntmSaveData = new JMenuItem("Save data ...");
mntmSaveData.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.saveRaw();
}
});
JMenuItem mntmLoadRawData = new JMenuItem("Load data ...");
mntmLoadRawData.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.loadRawData();
}
});
mnFile.add(mntmLoadRawData);
mnFile.addSeparator();
mntmSaveData.setEnabled(false);
mnFile.add(mntmSaveData);
mntmSaveSelectionData = new JMenuItem("Save selection data...");
mntmSaveSelectionData.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.saveRawSelection(altimeterChart.getVisibleDomainLowerBound(),
altimeterChart.getVisibleDomainUpperBound());
}
});
mntmSaveSelectionData.setEnabled(false);
mnFile.add(mntmSaveSelectionData);
JMenuItem mntmExit = new JMenuItem("Exit");
mntmExit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.exit();
}
});
mnFile.addSeparator();
mnFile.add(mntmExit);
JMenu mnView = new JMenu("View");
menuBar.add(mnView);
final JCheckBoxMenuItem chckbxmntmAltitude = new JCheckBoxMenuItem("Altitude");
chckbxmntmAltitude.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.altitudePlotSelectedChange(chckbxmntmAltitude.isSelected());
}
});
chckbxmntmAltitude.setSelected(true);
mnView.add(chckbxmntmAltitude);
chckbxmntmVoltage = new JCheckBoxMenuItem("Voltage");
chckbxmntmVoltage.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.voltagePlotSelectedChange(chckbxmntmVoltage.isSelected());
}
});
chckbxmntmVoltage.setSelected(prefs.getBoolean(PREF_SHOW_VOLTAGE, false));
mnView.add(chckbxmntmVoltage);
chckbxmntmServo = new JCheckBoxMenuItem("Servo");
chckbxmntmServo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.servoPlotSelectedChange(chckbxmntmServo.isSelected());
}
});
chckbxmntmServo.setSelected(prefs.getBoolean(PREF_SHOW_SERVO, false));
mnView.add(chckbxmntmServo);
chckbxmntmTemperature = new JCheckBoxMenuItem("Temperature");
chckbxmntmTemperature.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.temperaturePlotSelectedChange(chckbxmntmTemperature.isSelected());
}
});
chckbxmntmTemperature.setSelected(prefs.getBoolean(PREF_SHOW_TEMPERATURE, false));
mnView.add(chckbxmntmTemperature);
mnView.addSeparator();
ButtonGroup heightUnitsGroup = new ButtonGroup();
// TODO: this currently limits us to two unit systems.
HeightUnits units = controller.getHeightUnits();
rdbtnmntmFeet = new JRadioButtonMenuItem("Feet");
rdbtnmntmFeet.setEnabled(true);
rdbtnmntmFeet.setSelected(units == HeightUnits.FT);
rdbtnmntmFeet.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
HeightUnits hu;
if (rdbtnmntmFeet.isSelected()) hu = HeightUnits.FT;
else hu = HeightUnits.METERS;
controller.unitSelectedChange(hu);
}
});
heightUnitsGroup.add(rdbtnmntmFeet);
mnView.add(rdbtnmntmFeet);
rdbtnmntmMetres = new JRadioButtonMenuItem("Metres");
rdbtnmntmMetres.setEnabled(true);
rdbtnmntmMetres.setSelected(units == HeightUnits.METERS);
rdbtnmntmMetres.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
HeightUnits hu;
if (rdbtnmntmFeet.isSelected()) hu = HeightUnits.FT;
else hu = HeightUnits.METERS;
controller.unitSelectedChange(hu); }
});
heightUnitsGroup.add(rdbtnmntmMetres);
mnView.add(rdbtnmntmMetres);
mnAnalysis = new JMenu("Analysis");
menuBar.add(mnAnalysis);
mntmMarkDlgLaunches = new JMenuItem("Analyse DLG flights ...");
mntmMarkDlgLaunches.setEnabled(false);
mntmMarkDlgLaunches.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.analyseDLGFlights();
}
});
mnAnalysis.add(mntmMarkDlgLaunches);
mntmClearDlgLaunch = new JMenuItem("Clear DLG analysis");
mntmClearDlgLaunch.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
controller.clearDLGAnalysis();
}
});
mnAnalysis.add(mntmClearDlgLaunch);
mnAnalysis.addSeparator();
mntmClearAnnotations = new JMenuItem("Clear height and vario annotations");
mnAnalysis.add(mntmClearAnnotations);
mntmClearAnnotations.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
altimeterChart.clearHeightAndVarioAnnotations();
}
});
JMenu mnConnection = new JMenu("Connection");
menuBar.add(mnConnection);
mnSerialPort = new JMenu("Serial port");
mnConnection.add(mnSerialPort);
serialMenuGroup = new ButtonGroup();
mntmConnect = new JMenuItem("Connect");
mntmConnect.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.connect();
}
});
mnConnection.add(mntmConnect);
mntmDisconnect = new JMenuItem("Disconnect");
mntmDisconnect.setEnabled(false);
mntmDisconnect.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.disconnect();
}
});
mnConnection.add(mntmDisconnect);
JMenu mnLogger = new JMenu("Altimeter");
menuBar.add(mnLogger);
mntmDownloadData = new JMenuItem("Download data");
mntmDownloadData.setEnabled(false);
mntmDownloadData.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.downloadData();
}
});
mnLogger.add(mntmDownloadData);
mntmEraseLogger = new JMenuItem("Erase altimeter memory ...");
mntmEraseLogger.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.erase();
}
});
mntmEraseLogger.setEnabled(false);
mnLogger.add(mntmEraseLogger);
mntmSettings = new JMenuItem("Settings ...");
mntmSettings.setEnabled(false);
mntmSettings.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.runSettingsInterface();
}
});
mnLogger.add(mntmSettings);
mnLogger.addSeparator();
mntmFlashFirmware = new JMenuItem("Flash firmware ...");
mntmFlashFirmware.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.flashFirmware();
}
});
mnLogger.add(mntmFlashFirmware);
mnAdvanced = new JMenu("Advanced");
mnAdvanced.setVisible(false);
menuBar.add(mnAdvanced);
mntmUploadSelectionerases = new JMenuItem("Upload selection (erases OA)");
mntmUploadSelectionerases.setEnabled(false);
mntmUploadSelectionerases.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
controller.uploadSelection(altimeterChart.getVisibleDomainLowerBound(),
altimeterChart.getVisibleDomainUpperBound());
}
});
mnAdvanced.add(mntmUploadSelectionerases);
// os specific function
if (controller.os != OS.OTHER)
mntmFlashFirmware.setEnabled(true);
else mntmFlashFirmware.setEnabled(false);
JSplitPane splitPane = new JSplitPane();
splitPane.setContinuousLayout(true);
splitPane.setResizeWeight(0.6);
splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
splitPane.setDividerLocation(-1);
frmOpenaltimeter.getContentPane().add(splitPane, BorderLayout.CENTER);
altimeterChart = new AltimeterChart();
altimeterChart.setHeightUnits(units);
splitPane.setTopComponent(altimeterChart.getChartPanel());
altimeterChart.setVoltagePlotVisible(prefs.getBoolean(PREF_SHOW_VOLTAGE, false));
altimeterChart.setTemperaturePlotVisible(prefs.getBoolean(PREF_SHOW_TEMPERATURE, false));
altimeterChart.setServoPlotVisible(prefs.getBoolean(PREF_SHOW_SERVO, false));
// create the text pane, a document for it to view, and some styles
logTextPane = new JTextPane();
logTextPane.setPreferredSize(new Dimension(6, 100));
logDocument = logTextPane.getStyledDocument();
Style defaultStyle = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
Style messageStyle = logDocument.addStyle("message", defaultStyle);
StyleConstants.setFontFamily(messageStyle, "Monospaced");
StyleConstants.setFontSize(messageStyle, 12);
StyleConstants.setForeground(messageStyle, Color.BLACK);
Style altimeterStyle = logDocument.addStyle("altimeter", messageStyle);
StyleConstants.setForeground(altimeterStyle, Color.BLUE);
Style errorStyle = logDocument.addStyle("error", messageStyle);
StyleConstants.setForeground(errorStyle, Color.RED);
Style helpStyle = logDocument.addStyle("help", messageStyle);
StyleConstants.setForeground(helpStyle, Color.DARK_GRAY);
scrollPane = new JScrollPane(logTextPane);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
splitPane.setRightComponent(scrollPane);
frmOpenaltimeter.getContentPane().add(progressBar, BorderLayout.PAGE_END);
}
public void addCOMMenuItem(String portName) {
JRadioButtonMenuItem radioButtonMenuItem = new JRadioButtonMenuItem(
portName);
serialMenuGroup.add(radioButtonMenuItem);
mnSerialPort.add(radioButtonMenuItem);
}
public void selectFirstCOMItem() {
JMenuItem item = mnSerialPort.getItem(0);
if (item != null)
item.setSelected(true);
}
public String getSelectedCOMPort() {
int itemCount = mnSerialPort.getItemCount();
String selectedPort = "";
for (int i = 0; i < itemCount; i++)
if (mnSerialPort.getItem(i).isSelected())
selectedPort = mnSerialPort.getItem(i).getText();
return selectedPort;
}
public void setConnectedState(final ConnectionState state) {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
switch (state) {
case CONNECTED:
mntmConnect.setEnabled(false);
mntmDisconnect.setEnabled(true);
mntmDownloadData.setEnabled(true);
mntmEraseLogger.setEnabled(true);
mntmSettings.setEnabled(true);
mnSerialPort.setEnabled(false);
mntmUploadSelectionerases.setEnabled(true);
// os specific function
if (controller.os != OS.OTHER)
mntmFlashFirmware.setEnabled(true);
break;
case DISCONNECTED:
mntmConnect.setEnabled(true);
mntmDisconnect.setEnabled(false);
mntmDownloadData.setEnabled(false);
mntmEraseLogger.setEnabled(false);
mntmSettings.setEnabled(false);
mnSerialPort.setEnabled(true);
mntmUploadSelectionerases.setEnabled(false);
// os specific function
if (controller.os != OS.OTHER)
mntmFlashFirmware.setEnabled(true);
break;
case BUSY:
mntmConnect.setEnabled(false);
mntmDisconnect.setEnabled(false);
mntmDownloadData.setEnabled(false);
mntmEraseLogger.setEnabled(false);
mntmSettings.setEnabled(false);
mnSerialPort.setEnabled(false);
mntmUploadSelectionerases.setEnabled(false);
// os specific function
if (controller.os != OS.OTHER)
mntmFlashFirmware.setEnabled(false);
break;
}
}
});
}
public void setDataState(final DataState state) {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
switch (state) {
case NO_DATA:
mntmSaveData.setEnabled(false);
mntmSaveSelectionData.setEnabled(false);
mntmMarkDlgLaunches.setEnabled(false);
break;
case HAVE_DATA:
mntmSaveData.setEnabled(true);
mntmSaveSelectionData.setEnabled(true);
mntmMarkDlgLaunches.setEnabled(true);
break;
}
}
});
}
public void close()
{
setPreferences();
controller.appStopping();
frmOpenaltimeter.dispose();
}
private void setPreferences() {
prefs.putBoolean(PREF_SHOW_TEMPERATURE,chckbxmntmTemperature.isSelected());
prefs.putBoolean(PREF_SHOW_VOLTAGE,chckbxmntmVoltage.isSelected());
prefs.putBoolean(PREF_SHOW_SERVO, chckbxmntmServo.isSelected());
prefs.putInt(PREF_WINDOW_X, frmOpenaltimeter.getX());
prefs.putInt(PREF_WINDOW_Y, frmOpenaltimeter.getY());
prefs.putInt(PREF_WINDOW_WIDTH, frmOpenaltimeter.getWidth());
prefs.putInt(PREF_WINDOW_HEIGHT, frmOpenaltimeter.getHeight());
prefs.put(PREF_FILE_PATH, filePath.getAbsolutePath());
}
public void log(final String message, final String style) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
logDocument.insertString(logDocument.getLength(), message + "\n", logDocument.getStyle(style));
} catch (BadLocationException e) {
System.err.println("Error writing to log.");
}
}
});
}
public void updateProgress(final int progress)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressBar.setValue(progress);
}
});
}
public File showRawSaveDialog()
{
JFileChooser fc = new JFileChooser();
if (filePath != null)
fc.setCurrentDirectory(filePath);
FileFilter filter= new FileNameExtensionFilter("Text file", "txt");
fc.addChoosableFileFilter(filter);
if (fc.showSaveDialog(this.frmOpenaltimeter) == JFileChooser.APPROVE_OPTION)
{
File selectedFile = fc.getSelectedFile();
// if the text file filter is selected we ensure there's a .txt extension
if (fc.getFileFilter() == filter) {
if (!selectedFile.getAbsolutePath().endsWith(".txt"))
selectedFile = new File(selectedFile.getAbsolutePath() + ".txt");
}
filePath = selectedFile.getParentFile();
return selectedFile;
}
else return null;
}
public File showOpenDialog(FileFilter filter)
{
JFileChooser fc = new JFileChooser();
if (filePath != null)
fc.setCurrentDirectory(filePath);
fc.addChoosableFileFilter(filter);
if (fc.showOpenDialog(this.frmOpenaltimeter) == JFileChooser.APPROVE_OPTION)
{
File selectedFile = fc.getSelectedFile();
filePath = selectedFile.getParentFile();
return selectedFile;
}
else return null;
}
public boolean showConfirmDialog(String message, String title)
{
int response = JOptionPane.showConfirmDialog(this.frmOpenaltimeter, message, title, JOptionPane.OK_CANCEL_OPTION);
return (response == JOptionPane.OK_OPTION);
}
public void setTitle(String string) {
frmOpenaltimeter.setTitle(string);
}
}