package com.aerodynelabs.habtk.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.Vector;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.ToolTipManager;
import javax.swing.event.TableModelEvent;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;
import org.noos.xing.mydoggy.AggregationPosition;
import org.noos.xing.mydoggy.MultiSplitConstraint;
import org.noos.xing.mydoggy.Content;
import org.noos.xing.mydoggy.ContentManager;
import org.noos.xing.mydoggy.ToolWindowManager;
//import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableCellRenderer;
import com.aerodynelabs.habtk.atmosphere.AtmosphereProfile;
import com.aerodynelabs.habtk.atmosphere.GSDParser;
import com.aerodynelabs.habtk.atmosphere.RUCGFS;
import com.aerodynelabs.habtk.charts.SoundingChart;
import com.aerodynelabs.habtk.prediction.Predictor;
import com.aerodynelabs.map.MapOverlay;
import com.aerodynelabs.map.MapPanel;
import com.aerodynelabs.map.MapPath;
import com.aerodynelabs.map.MapPoint;
/**
* A panel to display a series of predictions.
* @author Ethan Harstad
*
*/
@SuppressWarnings("serial")
public class FlightListPanel extends JPanel {
private JTable table;
private DataModel model;
private Vector<Flight> flights = new Vector<Flight>();
private JPopupMenu menu;
private ToolWindowManager wm;
private MapPanel map;
private int activeRow;
private int lastColor = 0;
private static final Color colors[] = {
new Color( 255, 0, 0),
new Color( 255, 116, 0),
new Color( 255, 170, 0),
new Color( 255, 211, 0),
new Color( 255, 255, 0),
new Color( 159, 238, 0),
new Color( 0, 204, 0),
new Color( 0, 153, 153),
new Color( 18, 64, 171),
new Color( 57, 20, 175),
new Color( 113, 9, 170),
new Color( 205, 0, 116)
};
/**
* Custom date and time renderer
*/
//static class DateTimeRenderer extends SubstanceDefaultTableCellRenderer {
static class DateTimeRenderer extends DefaultTableCellRenderer {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public DateTimeRenderer() {
super();
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
public void setValue(Object value) {
setText((value == null) ? "" : sdf.format(value));
}
}
/**
* Custom time renderer
*/
//static class ElapsedTimeRenderer extends SubstanceDefaultTableCellRenderer {
static class ElapsedTimeRenderer extends DefaultTableCellRenderer {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
public ElapsedTimeRenderer() {
super();
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
public void setValue(Object value) {
setText((value == null) ? "" : sdf.format(value));
}
}
//static class ColorRenderer extends SubstanceDefaultTableCellRenderer {
static class ColorRenderer extends DefaultTableCellRenderer {
public ColorRenderer() {
super();
setOpaque(true);
}
public Component getTableCellRendererComponent(JTable arg0,
Object color, boolean isSelected, boolean hasFocus, int r, int c) {
setBackground((Color)color);
return this;
}
}
/**
* Relates a predictor to a path and overlay
*/
class Flight {
protected Predictor flight;
protected MapPath path;
protected MapOverlay overlay;
public Flight(Predictor f, MapPath p, MapOverlay o) {
flight = f;
path = p;
overlay = o;
}
}
/**
* Custom table data model
*/
class DataModel extends AbstractTableModel {
private String headers[] = {
" ",
"Show",
"Launch Time (UTC)",
"Balloon Type",
"Lift (kg)",
"Time Aloft",
"Distance (km)",
"Altitude (km)"
};
@Override
public int getColumnCount() {
return headers.length;
}
@Override
public String getColumnName(int c) {
return headers[c];
}
@Override
public boolean isCellEditable(int r, int c){
if(c == 1) return true;
return false;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Class getColumnClass(int c) {
switch(c) {
case 0:
return Color.class;
case 1:
return Boolean.class;
case 2:
case 5:
return Date.class;
case 4:
case 6:
case 7:
return Double.class;
}
return String.class;
}
@Override
public int getRowCount() {
return flights.size();
}
@Override
public Object getValueAt(int row, int col) {
Flight flight = flights.get(row);
switch(col) {
case 0: // Color
return flight.overlay.getColor();
case 1: // Enabled
return flight.overlay.isEnabled();
case 2: // Launch Time
return flight.path.getStartTime() * 1000;
case 3: // Balloon
return flight.flight.getTypeName();
case 4: // Lift
return flight.flight.getLift();
case 5: // Time aloft
return flight.path.getElapsedTime() * 1000;
case 6: // Distance
return Math.floor(flight.path.getDistance()/10 + 0.5) / 100;
case 7: // Altitude
return Math.floor(flight.path.getMaxAlt()) / 1000;
}
return null;
}
@Override
public void setValueAt(Object o, int r, int c) {
Flight flight = flights.get(r);
if(c == 1) {
flight.overlay.setEnabled(o.equals(true));
map.updateNotify();
}
}
}
/**
* Construct a flight list that displays on the given map.
* @param map
*/
public FlightListPanel(final MapPanel map, final ToolWindowManager twm) {
super();
setLayout(new BorderLayout());
this.map = map;
this.wm = twm;
model = new DataModel();
table = new JTable(model);
// table.setAutoCreateRowSorter(true);
table.setRowSorter(new TableRowSorter<DataModel>(model));
JScrollPane scroller = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
add(scroller, BorderLayout.CENTER);
//DefaultTableCellRenderer defaultRenderer = new SubstanceDefaultTableCellRenderer();
DefaultTableCellRenderer defaultRenderer = new DefaultTableCellRenderer();
defaultRenderer.setHorizontalAlignment(SwingConstants.CENTER);
table.setDefaultRenderer(Object.class, defaultRenderer);
table.setDefaultRenderer(Double.class, defaultRenderer);
table.setDefaultRenderer(Color.class, new ColorRenderer());
table.getColumnModel().getColumn(2).setCellRenderer(new DateTimeRenderer());
table.getColumnModel().getColumn(5).setCellRenderer(new ElapsedTimeRenderer());
// table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
TableCellRenderer renderer = table.getTableHeader().getDefaultRenderer();
for(int i = 0; i < table.getColumnCount(); i++) {
if(i == 0) {
TableColumn col = table.getColumnModel().getColumn(i);
col.setMinWidth(5);
col.setMaxWidth(15);
col.setPreferredWidth(10);
} else {
table.getColumnModel().getColumn(i).setPreferredWidth(
(int) (renderer.getTableCellRendererComponent(table, table.getModel().getColumnName(i), false, false, 0, i).getPreferredSize().width * 1.1));
}
}
menu = new JPopupMenu();
JMenuItem saveFlight = new JMenuItem("Save Flight");
saveFlight.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
flights.get(activeRow).flight.save();
}
});
menu.add(saveFlight);
JMenuItem saveKml = new JMenuItem("Export KML");
saveKml.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(new File("flights/"));
FileFilter filter = new FileNameExtensionFilter("KML Files", "kml");
chooser.setFileFilter(filter);
int val = chooser.showSaveDialog(null);
if(val == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
String name = file.getName();
if(name.lastIndexOf('.') == -1) {
name += ".kml";
file = new File(file.getParentFile(), name);
}
flights.get(activeRow).path.exportKML(file);
}
}
});
menu.add(saveKml);
menu.add(new JSeparator());
JMenuItem viewSounding = new JMenuItem("View Sounding");
viewSounding.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ContentManager cm = wm.getContentManager();
Content content = cm.getContent("Sounding");
if(content == null) {
SoundingChart chart = new SoundingChart(SoundingChart.SKEWT);
Content mapAnchor = cm.getContent("Map");
if(mapAnchor != null) {
content = cm.addContent("Sounding", "Sounding", null, chart, "Sounding", new MultiSplitConstraint(mapAnchor, AggregationPosition.RIGHT));
} else {
content = cm.addContent("Sounding", "Sounding", null, chart, "Sounding");
}
}
SoundingChart chart = (SoundingChart) content.getComponent();
MapPoint p = flights.get(activeRow).flight.getStart();
RUCGFS source = new RUCGFS();
File file = source.getAtmosphere((int)(p.getTime()), p.getLatitude(), p.getLongitude());
GSDParser parser = new GSDParser();
AtmosphereProfile profile = parser.parseAtmosphere(file);
chart.setSounding(profile);
}
});
menu.add(viewSounding);
table.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
activeRow = table.getRowSorter().convertRowIndexToModel(table.rowAtPoint(e.getPoint()));
if(e.getButton() == MouseEvent.BUTTON3) {
menu.show(e.getComponent(), e.getX(), e.getY());
return;
}
if(e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
for(Flight f : flights) {
f.overlay.setEnabled(false);
}
flights.get(activeRow).overlay.setEnabled(true);
map.updateNotify();
table.tableChanged(new TableModelEvent(model));
return;
}
}
});
// Disable tooltips for performance increase
ToolTipManager.sharedInstance().unregisterComponent(table);
ToolTipManager.sharedInstance().unregisterComponent(table.getTableHeader());
}
/**
* Add a flight to the list.
* @param flight
* @param path
*/
public void addFlight(Predictor flight, MapPath path) {
MapOverlay overlay = new MapOverlay("Prediction " + flights.size()+1);
overlay.addPath("Prediction", path);
overlay.setColor(colors[lastColor++]);
if(lastColor >= colors.length) lastColor = 0;
map.addOverlay(overlay);
flights.add(0, new Flight(flight, path, overlay));
table.tableChanged(new TableModelEvent(model));
map.updateNotify();
}
}