package rescuecore2.log; import static rescuecore2.misc.java.JavaTools.instantiate; import rescuecore2.worldmodel.WorldModel; import rescuecore2.worldmodel.Entity; import rescuecore2.messages.Command; import rescuecore2.misc.CommandLineOptions; import rescuecore2.misc.gui.ListModelList; import rescuecore2.misc.java.LoadableTypeProcessor; import rescuecore2.config.Config; import rescuecore2.config.ConfigException; import rescuecore2.view.ViewComponent; import rescuecore2.registry.Registry; import rescuecore2.view.ViewListener; import rescuecore2.view.RenderedObject; import rescuecore2.view.EntityInspector; import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.Dimension; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JScrollPane; import javax.swing.JOptionPane; import javax.swing.JButton; import javax.swing.JTabbedPane; import javax.swing.JSplitPane; import javax.swing.BorderFactory; import javax.swing.JToggleButton; import javax.swing.Box; import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; import java.io.IOException; import java.util.Dictionary; import java.util.Hashtable; import java.util.List; import java.util.ArrayList; /** A class for viewing log files. */ public class LogViewer extends JPanel { private static final String VIEWERS_KEY = "log.viewers"; private static final int TICK_STEP_SIZE = 10; private static final int VIEWER_SIZE = 500; private static final int FRAME_DELAY = 200; private LogReader log; private JLabel timestep; private EntityInspector inspector; private JSlider slider; private JList commandsList; private JList updatesList; private ListModelList<Command> commands; private ListModelList<Entity> updates; private List<ViewComponent> viewers; private JButton down; private JButton up; private int maxTime; /** Construct a LogViewer. @param reader The LogReader to read. @param config The system configuration. @throws LogException If there is a problem reading the log. */ public LogViewer(LogReader reader, Config config) throws LogException { super(new BorderLayout()); this.log = reader; inspector = new EntityInspector(); registerViewers(config); maxTime = log.getMaxTimestep(); slider = new JSlider(0, maxTime); down = new JButton("<-"); up = new JButton("->"); slider.setSnapToTicks(true); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.setMinorTickSpacing(1); Dictionary<Integer, JComponent> labels = new Hashtable<Integer, JComponent>(); labels.put(maxTime, new JLabel(String.valueOf(maxTime))); for (int i = 0; i < maxTime; i += TICK_STEP_SIZE) { labels.put(i, new JLabel(String.valueOf(i))); } slider.setLabelTable(labels); slider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { showTimestep(slider.getValue()); } }); down.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int value = slider.getValue() - 1; if (value >= 0) { slider.setValue(value); } } }); up.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int value = slider.getValue() + 1; if (value <= maxTime) { slider.setValue(value); } } }); final JToggleButton play = new JToggleButton(">>"); play.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Thread t = new Thread(new Runnable() { @Override public void run() { while (play.isSelected()) { int value = slider.getValue() + 1; if (value <= maxTime) { slider.setValue(value); } else { play.setSelected(false); } try { Thread.sleep(FRAME_DELAY); } catch (InterruptedException e1) { Logger.warn("Player interrupted", e1); } } } }); t.start(); } }); JPanel lists = new JPanel(new GridLayout(0, 1)); commands = new ListModelList<Command>(); commandsList = new JList(commands); updates = new ListModelList<Entity>(); updatesList = new JList(updates); JScrollPane s = new JScrollPane(commandsList); s.setBorder(BorderFactory.createTitledBorder("Commands")); s.setPreferredSize(commandsList.getPreferredScrollableViewportSize()); lists.add(s); s = new JScrollPane(updatesList); s.setBorder(BorderFactory.createTitledBorder("Updates")); s.setPreferredSize(updatesList.getPreferredScrollableViewportSize()); lists.add(s); timestep = new JLabel("Timestep: 0"); JTabbedPane tabs = new JTabbedPane(); for (ViewComponent next : viewers) { tabs.addTab(next.getViewerName(), next); next.addViewListener(new ViewListener() { @Override public void objectsClicked(ViewComponent view, List<RenderedObject> objects) { for (RenderedObject next : objects) { if (next.getObject() instanceof Entity) { inspector.inspect((Entity)next.getObject()); return; } } } @Override public void objectsRollover(ViewComponent view, List<RenderedObject> objects) { } }); } JSplitPane split1 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inspector, tabs); JSplitPane split2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, split1, lists); add(split2, BorderLayout.CENTER); Box bottom = Box.createHorizontalBox(); bottom.add(down); bottom.add(slider); bottom.add(up); bottom.add(play); add(bottom, BorderLayout.SOUTH); add(timestep, BorderLayout.NORTH); slider.setValue(0); } /** Show a particular timestep in the viewer. @param time The timestep to show. If this value is out of range then this method will silently return. */ public void showTimestep(int time) { try { if (time < 0 || time > maxTime) { return; } timestep.setText("Timestep: " + time); commands.clear(); updates.clear(); CommandsRecord commandsRecord = log.getCommands(time); if (commandsRecord != null) { commands.addAll(commandsRecord.getCommands()); } UpdatesRecord updatesRecord = log.getUpdates(time); /* if (updatesRecord != null) { updates.addAll(updatesRecord.getChangeSet()); } */ WorldModel<? extends Entity> model = log.getWorldModel(time); for (ViewComponent next : viewers) { next.view(model, commandsRecord == null ? null : commandsRecord.getCommands(), updatesRecord == null ? null : updatesRecord.getChangeSet()); next.repaint(); } down.setEnabled(time != 0); up.setEnabled(time != maxTime); } catch (LogException e) { JOptionPane.showMessageDialog(this, e, "Error", JOptionPane.ERROR_MESSAGE); } } private void registerViewers(Config config) { viewers = new ArrayList<ViewComponent>(); for (String next : config.getArrayValue(VIEWERS_KEY, "")) { ViewComponent viewer = instantiate(next, ViewComponent.class); if (viewer != null) { viewer.initialise(config); viewers.add(viewer); } } } /** Launch a new LogViewer. @param args Command line arguments. Accepts only one argument: the name of a log file. */ public static void main(String[] args) { Config config = new Config(); try { args = CommandLineOptions.processArgs(args, config); if (args.length != 1) { printUsage(); return; } String name = args[0]; processJarFiles(config); LogReader reader = new FileLogReader(name, Registry.SYSTEM_REGISTRY); LogViewer viewer = new LogViewer(reader, config); viewer.setPreferredSize(new Dimension(VIEWER_SIZE, VIEWER_SIZE)); JFrame frame = new JFrame("Log viewer: " + name); frame.getContentPane().add(viewer, BorderLayout.CENTER); frame.pack(); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.setVisible(true); } catch (IOException e) { Logger.error("Error reading log", e); } catch (ConfigException e) { Logger.error("Configuration error", e); } catch (LogException e) { Logger.error("Error reading log", e); } } private static void printUsage() { System.out.println("Usage: LogViewer <filename>"); } private static void processJarFiles(Config config) throws IOException { LoadableTypeProcessor processor = new LoadableTypeProcessor(config); processor.addFactoryRegisterCallbacks(Registry.SYSTEM_REGISTRY); processor.process(); } }