package lcm.logging;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.io.*;
import java.util.*;
import java.net.*;
import java.util.concurrent.*;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import lcm.lcm.*;
import lcm.util.*;
import static java.awt.GridBagConstraints.*;
/** A GUI implementation of a log player allowing seeking. **/
public class LogPlayer extends JComponent
{
static
{
System.setProperty("java.net.preferIPv4Stack", "true");
System.out.println("LC: Disabling IPV6 support");
}
Log log;
JButton playButton = new JButton("Play ");
JButton stepButton = new JButton("Step");
JButton fasterButton;
// JButton fasterButton = new JButton(">>");
JButton slowerButton; // = new JButton("<<");
JLabel speedLabel = new JLabel("1.0", JLabel.CENTER);
double speed = 1.0;
static final int POS_MAX = 10000;
JLabel posLabel = new JLabel("Event 0");
JLabel timeLabel = new JLabel("Time 0.0s");
JLabel actualSpeedLabel = new JLabel("1.0x");
JLabel logName = new JLabel("---");
PlayerThread player = null;
LCM lcm;
/** The time of the first event in the current log **/
long timeOffset = 0;
JFileChooser jfc = new JFileChooser();
String currentLogPath;
double total_seconds; // an estimate of how many seconds there are in the file
BlockingQueue<QueuedEvent> events = new LinkedBlockingQueue<QueuedEvent>();
Object sync = new Object();
interface QueuedEvent
{
public void execute(LogPlayer lp);
}
class QueueThread extends Thread
{
public void run()
{
while (true) {
try {
QueuedEvent qe = events.take();
qe.execute(LogPlayer.this);
} catch (InterruptedException ex) {
}
}
}
}
/** We have events coming from all over the place: the UI, UDP
events, callbacks from the scrubbers. To keep things sanely
thread-safe, all of these things simply queue events which are
processed in-order. doStop, doPlay, doStep, do(Anything) can
only be called from the queue thread.
**/
class PlayPauseEvent implements QueuedEvent
{
boolean toggle = false;
boolean playstate;
public PlayPauseEvent()
{
this.toggle = true;
}
public PlayPauseEvent(boolean playstate)
{
this.playstate = playstate;
}
public void execute(LogPlayer lp)
{
if (toggle)
{
if (player!=null)
doStop();
else
doPlay();
}
else
{
if (playstate)
doPlay();
else
doStop();
}
}
}
// seek, preserving the current play/pause state
class SeekEvent implements QueuedEvent
{
double pos;
public SeekEvent(double pos)
{
this.pos = pos;
}
public void execute(LogPlayer lp)
{
boolean player_was_running = (player != null);
if (player_was_running)
doStop();
doSeek(pos);
if (player_was_running)
doPlay();
}
}
class StepEvent implements QueuedEvent
{
public void execute(LogPlayer lp)
{
doStep();
}
}
class Filter implements Comparable<Filter>
{
String inchannel;
String outchannel;
boolean enabled = true;
public int compareTo(Filter f)
{
return inchannel.compareTo(f.inchannel);
}
}
Pattern filteredPattern;
boolean invertFilteredPattern;
FilterTableModel filterTableModel = new FilterTableModel();
ArrayList<Filter> filters = new ArrayList<Filter>();
// JTable calls upon filterTableModel which calls upon filters...
// which needs to exist before that!
JTable filterTable = new JTable(filterTableModel);
HashMap<String,Filter> filterMap = new HashMap<String,Filter>();
JTextField inchannel = new JTextField();
JTextField outchannel = new JTextField();
JScrubber js = new JScrubber();
boolean show_absolute_time = false;
JTextField stepChannelField = new JTextField("");
// faster/slower would be better as semi-log.
static final double slowerSpeed(double v)
{
return v/2;
}
static final double fasterSpeed(double v)
{
return v*2;
}
void setSpeed(double v)
{
v = Math.max(1.0/1024, v); // minimum supported speed (0.000977x)
speedLabel.setText(String.format("%.3f", v));
speed = v;
}
void setChannelFilter(String channelFilterRegex)
{
filteredPattern = Pattern.compile(channelFilterRegex);
}
void invertChannelFilter(){
invertFilteredPattern = true;
}
public LogPlayer(String lcmurl) throws IOException
{
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
filteredPattern = null;
invertFilteredPattern = false;
Insets insets = new Insets(0,0,0,0);
int row = 0;
logName.setText("No log loaded");
logName.setFont(new Font("SansSerif", Font.PLAIN, 10));
timeLabel.setFont(new Font("SansSerif", Font.PLAIN, 10));
posLabel.setFont(new Font("SansSerif", Font.PLAIN, 10));
actualSpeedLabel.setFont(new Font("SansSerif", Font.PLAIN, 10));
fasterButton = new JButton(new ImageIcon(makeArrowImage(Color.blue, getBackground(), false)));
fasterButton.setRolloverIcon(new ImageIcon(makeArrowImage(Color.magenta, getBackground(), false)));
fasterButton.setPressedIcon(new ImageIcon(makeArrowImage(Color.red, getBackground(), false)));
fasterButton.setBorderPainted(false);
fasterButton.setContentAreaFilled(false);
// Borders keep appearing when the buttons are pressed. Not sure why.
//fasterButton.setBorder(null); //new javax.swing.border.EmptyBorder(0,0,0,0));
slowerButton = new JButton(new ImageIcon(makeArrowImage(Color.blue, getBackground(), true)));
slowerButton.setRolloverIcon(new ImageIcon(makeArrowImage(Color.magenta, getBackground(), true)));
slowerButton.setPressedIcon(new ImageIcon(makeArrowImage(Color.red, getBackground(), true)));
slowerButton.setBorderPainted(false);
slowerButton.setContentAreaFilled(false);
Font buttonFont = new Font("SansSerif", Font.PLAIN, 10);
fasterButton.setFont(buttonFont);
slowerButton.setFont(buttonFont);
playButton.setFont(buttonFont);
stepButton.setFont(buttonFont);
JPanel p = new JPanel();
p.setLayout(new GridLayout(1,3,0,0));
p.add(slowerButton);
p.add(speedLabel);
p.add(fasterButton);
// x y w h fillx filly anchor fill insets, ix, iy
add(logName,
new GridBagConstraints(0, row, 1, 1, 0.0, 0.0, WEST, NONE, insets, 0, 0));
add(playButton,
new GridBagConstraints(1, row, 1, 1, 0.0, 0.0, CENTER, NONE, insets, 0, 0));
add(stepButton,
new GridBagConstraints(2, row, 1, 1, 0.0, 0.0, CENTER, NONE, insets, 0, 0));
add(p,
new GridBagConstraints(3, row, REMAINDER, 1, 0.0, 0.0, EAST, NONE, insets, 0, 0));
row++;
add(js,
new GridBagConstraints(0, row, REMAINDER, 1, 1.0, 0.0, CENTER, HORIZONTAL, new Insets(0, 5, 0, 5), 0, 0));
row++;
add(timeLabel,
new GridBagConstraints(0, row, 1, 1, 0.0, 0.0, WEST, NONE, new Insets(0, 10, 0, 0), 0, 0));
add(actualSpeedLabel,
new GridBagConstraints(1, row, 1, 1, 0.0, 0.0, WEST, NONE, new Insets(0, 10, 0, 0), 0, 0));
add(posLabel,
new GridBagConstraints(3, row, 1, 1, 0.0, 0.0, EAST, NONE, new Insets(0,0, 0, 10), 0, 0));
row++;
add(new JScrollPane(filterTable),
new GridBagConstraints(0, row, REMAINDER, 1, 1.0, 1.0, CENTER, BOTH, new Insets(0,0, 0, 0), 0, 0));
row++;
/// spacers
add(Box.createHorizontalStrut(90),
new GridBagConstraints(0, row, 1, 1, 0.0, 0.0, WEST, NONE, insets, 0, 0));
add(Box.createHorizontalStrut(100),
new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, WEST, NONE, insets, 0, 0));
///////////////////////////
row++;
JPanel stepPanel = new JPanel(new BorderLayout());
stepPanel.add(new JLabel("Channel Prefix: "), BorderLayout.WEST);
stepPanel.add(stepChannelField, BorderLayout.CENTER);
JButton toggleAllButton = new JButton("Toggle Selected");
toggleAllButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int[] rowIndices = filterTable.getSelectedRows();
for (int i = 0; i < rowIndices.length; ++i) {
Filter f = filters.get(rowIndices[i]);
f.enabled = !f.enabled;
}
filterTableModel.fireTableDataChanged();
for (int i = 0; i < rowIndices.length; ++i) {
filterTable.addRowSelectionInterval(rowIndices[i],
rowIndices[i]);
}
}});
add(toggleAllButton,
new GridBagConstraints(0, row, 2, 1, 0.0, 0.0, CENTER, NONE,
insets, 0, 0));
add(stepPanel,
new GridBagConstraints(2, row, REMAINDER, 1, 1.0, 0.0, CENTER, HORIZONTAL, new Insets(0, 5, 0, 5), 0, 0));
// position.addChangeListener(new MyChangeListener());
setPlaying(false);
fasterButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setSpeed(fasterSpeed(speed));
}});
slowerButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setSpeed(slowerSpeed(speed));
}});
playButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
events.offer(new PlayPauseEvent());
}});
stepButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
events.offer(new StepEvent());
}
});
if(null == lcmurl)
lcm = new LCM();
else
lcm = new LCM(lcmurl);
logName.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount()==2)
openDialog();
}
});
timeLabel.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
show_absolute_time = ! show_absolute_time;
}
});
js.set(0);
js.addScrubberListener(new MyScrubberListener());
filterTable.getColumnModel().getColumn(2).setMaxWidth(50);
playButton.setEnabled(false);
new UDPThread().start();
new QueueThread().start();
}
class MyScrubberListener implements JScrubberListener
{
public void scrubberMovedByUser(JScrubber js, double x)
{
events.offer(new SeekEvent(x));
}
public void scrubberPassedRepeat(JScrubber js, double from_pos, double to_pos)
{
events.offer(new SeekEvent(to_pos));
}
public void scrubberExportRegion(JScrubber js, double p0, double p1)
{
System.out.printf("Export %15f %15f\n", p0, p1);
String outpath = getOutputFileFromDialog();
if (outpath == null)
return;
System.out.println("Exporting to "+outpath);
try {
Log inlog = new Log(log.getPath(), "r");
Log outlog = new Log(outpath, "rw");
inlog.seekPositionFraction(p0);
while (inlog.getPositionFraction() < p1) {
Log.Event e = inlog.readNext();
Filter f = filterMap.get(e.channel);
if (f != null && f.enabled)
outlog.write(e);
}
inlog.close();
outlog.close();
System.out.printf("Done!\n");
} catch (IOException ex) {
System.out.println("Exception: "+ex);
}
}
}
// remote control via UDP packets
class UDPThread extends Thread
{
public void run()
{
DatagramSocket sock;
DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
try {
sock = new DatagramSocket(53261, Inet4Address.getByName("127.0.0.1"));
} catch (SocketException ex) {
System.out.println("Exception: "+ex);
return;
} catch (UnknownHostException ex) {
System.out.println("Exception: "+ex);
return;
}
while (true)
{
try {
sock.receive(packet);
String cmd = new String(packet.getData(), 0, packet.getLength());
cmd = cmd.trim();
if (cmd.equals("PLAYPAUSETOGGLE")) {
events.offer(new PlayPauseEvent());
} else if (cmd.equals("PLAY")) {
events.offer(new PlayPauseEvent(true));
} else if (cmd.equals("PAUSE")) {
events.offer(new PlayPauseEvent(false));
} else if (cmd.equals("STEP")) {
events.offer(new StepEvent());
} else if (cmd.equals("FASTER")) {
setSpeed(fasterSpeed(speed));
} else if (cmd.equals("SLOWER")) {
setSpeed(slowerSpeed(speed));
} else if (cmd.startsWith("BACK")) {
double seconds = Double.parseDouble(cmd.substring(4));
double pos = log.getPositionFraction() - seconds/total_seconds;
events.offer(new SeekEvent(pos));
} else if (cmd.startsWith("FORWARD")) {
double seconds = Double.parseDouble(cmd.substring(7));
double pos = log.getPositionFraction() + seconds/total_seconds;
events.offer(new SeekEvent(pos));
} else {
System.out.println("Unknown remote command: "+cmd);
}
} catch (IOException ex) {
}
}
}
}
String getOutputFileFromDialog()
{
JFileChooser chooser = new JFileChooser();
int res = chooser.showSaveDialog(this);
if (res != JFileChooser.APPROVE_OPTION)
return null;
return chooser.getSelectedFile().getPath();
}
void openDialog()
{
doStop();
int res = jfc.showOpenDialog(this);
if (res != JFileChooser.APPROVE_OPTION)
return;
try {
setLog(jfc.getSelectedFile().getPath(), true);
} catch (IOException ex) {
System.out.println("Exception: "+ex);
}
}
void savePreferences() throws IOException
{
if (currentLogPath == null)
return;
String path = currentLogPath+".jlp";
FileWriter fouts = new FileWriter(path);
BufferedWriter outs = new BufferedWriter(fouts);
ArrayList<JScrubber.Bookmark> bookmarks = js.getBookmarks();
for (JScrubber.Bookmark b : bookmarks) {
String type = "PLAIN";
if (b.type == JScrubber.BOOKMARK_LREPEAT)
type = "LREPEAT";
if (b.type == JScrubber.BOOKMARK_RREPEAT)
type = "RREPEAT";
outs.write("BOOKMARK "+type+" "+b.position+"\n");
}
outs.write("ZOOMFRAC "+js.getZoomFraction()+"\n");
for (Filter f : filters) {
outs.write("CHANNEL " + f.inchannel+" "+f.outchannel+" "+f.enabled+"\n");
}
outs.close();
fouts.close();
}
Filter addChannelFilter(String channel, boolean enabledByDefault){
Filter f = new Filter();
f.inchannel = channel;
f.outchannel = channel;
if (filteredPattern== null)
f.enabled = enabledByDefault;
else
f.enabled = !(invertFilteredPattern ^ filteredPattern.matcher(channel).matches());
filterMap.put(f.inchannel, f);
filters.add(f);
Collections.sort(filters);
filterTableModel.fireTableDataChanged();
return f;
}
void loadPreferences(String path) throws IOException
{
BufferedReader ins;
js.clearBookmarks();
filterMap.clear();
filters.clear();
try {
ins = new BufferedReader(new FileReader(path));
} catch (FileNotFoundException ex) {
// no error; just a no-op
return;
}
String line;
while ((line = ins.readLine()) != null) {
String toks[] = line.split("\\s+");
if (toks[0].equals("BOOKMARK") && toks.length==3) {
int type = JScrubber.BOOKMARK_PLAIN;
if (toks[1].equals("RREPEAT"))
type = JScrubber.BOOKMARK_RREPEAT;
if (toks[1].equals("LREPEAT"))
type = JScrubber.BOOKMARK_LREPEAT;
js.addBookmark(type, Double.parseDouble(toks[2]));
}
if (toks[0].equals("CHANNEL") && toks.length==4) {
Filter f = filterMap.get(toks[1]);
if (f == null) {
f = new Filter();
f.inchannel = toks[1];
f.outchannel = toks[1];
filterMap.put(toks[1], f);
filters.add(f);
}
f.outchannel = toks[2];
f.enabled = Boolean.parseBoolean(toks[3]);
if (filteredPattern != null) //disable if either the saved value, or the filter say it should be disabled
f.enabled = f.enabled && !(invertFilteredPattern ^ filteredPattern.matcher(f.inchannel).matches());
}
if (toks[0].equals("ZOOMFRAC"))
js.setZoomFraction(Double.parseDouble(toks[1]));
}
filterTableModel.fireTableDataChanged();
}
void populateChannelFilters()
{
try {
long logStartUTime = -1;
while (true)
{
Log.Event e = log.readNext();
if (logStartUTime<0)
logStartUTime = e.utime;
if (e.utime-logStartUTime >30*1e6 ){ //only scan through the first 30sec of the log
break;
}
Filter f = filterMap.get(e.channel);
if (f == null) {
addChannelFilter(e.channel, !invertFilteredPattern);
}
}
} catch (EOFException ex) {
//System.err.println("Breaking at end of log");
} catch (IOException ex) {
System.err.println("Exception: "+ex);
}
try{
//rewind to beginning of the log
log.seekPositionFraction(0);
} catch (IOException ex) {
System.err.println("Exception: "+ex);
}
}
void setLog(String path, boolean startPlaying) throws IOException
{
if (currentLogPath != null)
savePreferences();
currentLogPath = path;
log = new Log(path, "r");
logName.setText(new File(path).getName());
try {
Log.Event e = log.readNext();
timeOffset = e.utime;
playButton.setEnabled(true);
log.seekPositionFraction(.10);
Log.Event e10 = log.readNext();
log.seekPositionFraction(.90);
Log.Event e90 = log.readNext();
total_seconds = (e90.utime - e10.utime)/1000000.0 / 0.8;
System.out.printf("Total seconds: %f\n", total_seconds);
log.seekPositionFraction(0);
} catch (IOException ex) {
System.out.println("exception: "+ex);
}
loadPreferences(path+".jlp");
if (startPlaying)
doPlay();
else{
populateChannelFilters();
}
}
void setPlaying(boolean t)
{
playButton.setText(t ? "Pause" : "Play");
stepButton.setEnabled(!t);
}
// the player can stop automatically on error or EOF; we thus have
// a potential race condition between auto-stops and requested
// stops.
//
// We protect these two with 'sync'.
void doStop()
{
PlayerThread pptr;
synchronized(sync) {
if (player == null)
return;
pptr = player;
pptr.requestStop();
}
try {
pptr.join();
} catch (InterruptedException ex) {
System.out.println("Exception: "+ex);
}
}
void doPlay()
{
if (player != null)
return;
player = new PlayerThread();
player.start();
}
void doStep()
{
if (player != null)
return;
player = new PlayerThread(stepChannelField.getText());
player.start();
}
void doSeek(double ratio)
{
assert (player == null);
if (ratio < 0)
ratio = 0;
if (ratio > 1)
ratio = 1;
try {
log.seekPositionFraction(ratio);
Log.Event e = log.readNext();
log.seekPositionFraction(ratio);
js.set(log.getPositionFraction());
lastSystemTime = 0; // reset log-play statistics.
updateDisplay(e);
} catch (IOException ex) {
System.out.println("exception: "+ex);
}
}
long lastEventTime;
long lastSystemTime;
void updateDisplay(Log.Event e)
{
if (show_absolute_time) {
java.text.SimpleDateFormat df =
new java.text.SimpleDateFormat("yyyy.MM.dd HH:mm:ss.S z");
Date timestamp = new Date(e.utime / 1000);
timeLabel.setText(df.format(timestamp));
} else {
timeLabel.setText(String.format("%.3f s", (e.utime - timeOffset)/1000000.0));
}
posLabel.setText(""+e.eventNumber);
long systemTime = System.currentTimeMillis();
double dt = (systemTime - lastSystemTime)/1000.0;
if (dt > 0.5) {
double actualSpeed = (e.utime - lastEventTime) / 1000000.0 / dt;
lastEventTime = e.utime;
lastSystemTime = systemTime;
actualSpeedLabel.setText(String.format("%.2f x", actualSpeed));
}
}
class PlayerThread extends Thread
{
boolean stopflag = false;
String stopOnChannel;
public PlayerThread()
{
}
public PlayerThread(String stopOnChannel)
{
this.stopOnChannel = stopOnChannel;
}
public void requestStop()
{
stopflag = true;
}
public void run()
{
long lastTime = 0;
long lastDisplayTime = 0;
long localOffset = 0;
long logOffset = 0;
long last_e_utime = 0;
double lastspeed = 0;
synchronized (sync) {
setPlaying(true);
}
try {
while (!stopflag)
{
Log.Event e = log.readNext();
if (speed != lastspeed) {
//System.out.printf("Speed changed. Old %12.6f new %12.6f\n",
// lastspeed, speed);
logOffset = e.utime;
localOffset = System.nanoTime()/1000;
lastspeed = speed;
}
long logRelativeTime = (long) (e.utime - logOffset);
long now = System.nanoTime();
long clockRelativeTime = now/1000 - localOffset;
// we don't support playback below a rate of 1/1024x
long speed_scale = (long) Math.max(1, (speed*1024.0));
long waitTime = (1024*logRelativeTime/speed_scale - clockRelativeTime);
long waitms = waitTime / 1000;
waitms = Math.max(0, waitms);
/*System.out.printf("Now 0x%016X ns, %12.6fx playback, %8d/1024 playback, %10dus rel log time, %10dus, %10dus rel clock, "+
"wait %10dus (%10dms)\n",
now,
speed,
speed_scale,
logRelativeTime,
1024*logRelativeTime/speed_scale,
clockRelativeTime,
waitTime,
waitms);*/
last_e_utime = e.utime;
try {
// We might have a very long wait, but
// only sleep for relatively short amounts
// of time so that we remain responsive to
// seek/speed changes.
while (waitms > 0 && !stopflag) {
if (waitms > 50) {
Thread.sleep(50);
waitms -= 50;
}
else {
Thread.sleep(waitms);
waitms = 0;
}
}
} catch (InterruptedException ex) {
System.out.println("Interrupted");
}
// During the sleep, other threads might have
// run that have asked us to stop (a
// jscrubber.userset in particular); recheck
// the stop flag before we blindly proceed.
// (This ameliorates but does not solve an
// intrinsic race condition)
if (stopflag)
break;
Filter f = filterMap.get(e.channel);
if (f == null) {
f = addChannelFilter(e.channel, !invertFilteredPattern);
}
if (f.enabled && f.outchannel.length() > 0)
lcm.publish(f.outchannel, e.data, 0, e.data.length);
js.set(log.getPositionFraction());
// redraw labels no faster than 10 Hz
long curTime = System.currentTimeMillis();
if (curTime - lastDisplayTime > 100) {
updateDisplay(e);
lastDisplayTime = curTime;
}
if (stopOnChannel != null && e.channel.startsWith(stopOnChannel)) {
stopflag = true;
break;
}
}
} catch (EOFException ex) {
stopflag = true;
} catch (IOException ex) {
System.out.println("Exception: "+ex);
stopflag = true;
}
synchronized (sync) {
setPlaying(false);
player = null;
}
}
}
class FilterTableModel extends AbstractTableModel
{
public int getRowCount()
{
return filters.size();
}
public int getColumnCount()
{
return 3;
}
public String getColumnName(int column)
{
switch(column)
{
case 0:
return "Log channel";
case 1:
return "Playback channel";
case 2:
return "Enable";
}
return "??";
}
public Class getColumnClass(int column)
{
switch (column)
{
case 0:
case 1:
return String.class;
case 2:
return Boolean.class;
}
return null;
}
public Object getValueAt(int row, int column)
{
Filter f = filters.get(row);
switch(column)
{
case 0:
return f.inchannel;
case 1:
return f.outchannel;
case 2:
return f.enabled;
}
return "??";
}
public boolean isCellEditable(int row, int column)
{
return (column==1) || (column==2);
}
public void setValueAt(Object v, int row, int column)
{
Filter f = filters.get(row);
if (column == 1)
f.outchannel = (String) v;
if (column == 2)
f.enabled = (Boolean) v;
}
}
static LogPlayer p;
public static void usage()
{
System.err.println("usage: lcm-logplayer-gui [options] [log-name]");
System.err.println("");
System.err.println("lcm-logplayer-gui is the Lightweight Communications and Marshalling");
System.err.println("log playback tool. It provides a graphical user interface for playing logfiles");
System.err.println("recorded with lcm-logger. Features include random access, different playback ");
System.err.println("speeds, channel suppression and remapping, and more.");
System.err.println("");
System.err.println("Options:");
System.err.println(" -l, --lcm-url=URL Use the specified LCM URL");
System.err.println(" -p, --paused Start with the log paused.");
System.err.println(" -f, --filter=CHAN Disable channels that match the regex in CHAN.");
System.err.println(" (default: \"\")");
System.err.println(" -v, --invert-filter Invert the filtering regex. Only enable channels");
System.err.println(" matching CHAN.");
System.err.println(" -h, --help Shows this help text and exits");
System.err.println("");
System.exit(1);
}
public static void main(String args[])
{
JFrame f;
// check if the JRE is supplied by gcj, and warn the user if it is.
if(System.getProperty("java.vendor").indexOf("Free Software Foundation") >= 0) {
System.err.println("WARNING: Detected gcj. The LCM log player is not known to work well with gcj.");
System.err.println(" The Sun JRE is recommended.");
}
String lcmurl = null;
String logFile = null;
boolean startPaused = false;
int optind;
String channelFilterRegex = null;
boolean invertChannelFilter = false;
for(optind=0; optind<args.length; optind++) {
String c = args[optind];
if(c.equals("-h") || c.equals("--help")) {
usage();
} else if(c.equals("-l") || c.equals("--lcm-url") || c.startsWith("--lcm-url=")) {
String optarg = null;
if(c.startsWith("--lcm-url=")) {
optarg=c.split("=")[1];
} else if(optind < args.length) {
optind++;
optarg = args[optind];
}
if(null == optarg) {
usage();
} else {
lcmurl = optarg;
}
} else if (c.equals("-p") || c.equals("--paused")){
startPaused = true;
}else if(c.equals("-f") || c.equals("--filter") || c.startsWith("--filter=")) {
String optarg = null;
if(c.startsWith("--filter=")) {
optarg=c.split("=")[1];
} else if(optind < args.length) {
optind++;
optarg = args[optind];
}
if(null == optarg) {
usage();
} else {
channelFilterRegex = optarg;
}
} else if (c.equals("-v") || c.equals("--invert-filter")){
invertChannelFilter = true;
} else if (c.startsWith("-")){
usage();
} else if (logFile!=null) //there should only be 1 non-flag argument
usage();
else {
logFile = c;
}
}
try {
p = new LogPlayer(lcmurl);
f = new JFrame("LogPlayer");
f.setLayout(new BorderLayout());
f.add(p, BorderLayout.CENTER);
f.pack();
f.setSize(f.getWidth(),300);
f.setVisible(true);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
try {
p.savePreferences();
} catch (IOException ex) {
System.out.println("Couldn't save preferences: "+ex);
}
System.exit(0);
}});
if (channelFilterRegex!=null)
p.setChannelFilter(channelFilterRegex);
if (invertChannelFilter)
p.invertChannelFilter();
if (logFile !=null)
p.setLog(logFile, !startPaused);
else
p.openDialog();
} catch (IOException ex) {
System.out.println("Exception: "+ex);
}
}
static BufferedImage makeArrowImage(Color fillColor, Color backgroundColor, boolean flip)
{
int height = 18, width = 18;
BufferedImage im = new BufferedImage(width,height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = im.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// g.setColor(backgroundColor);
g.setColor(new Color(0,0,0,0));
// g.setColor(new Color(0,0,255,128));
g.fillRect(0,0,width,height);
if (flip) {
g.translate(width-1, height/2);
g.scale(-height/2, height/2);
} else {
g.translate(0, height/2);
g.scale(height/2, height/2);
}
g.setStroke(new BasicStroke(0f));
GeneralPath gp = new GeneralPath();
gp.moveTo(0,-1);
gp.lineTo(1,0);
gp.lineTo(0,1);
gp.lineTo(0,-1);
g.setColor(fillColor);
g.fill(gp);
g.setColor(Color.black);
// g.draw(gp);
g.translate(.75, 0);
g.setColor(fillColor);
g.fill(gp);
g.setColor(Color.black);
// g.draw(gp);
return im;
}
}