Print

Print


Author: [log in to unmask]
Date: Sat Mar 14 12:37:17 2015
New Revision: 2453

Log:
Updates and fixes to monitoring app on dev branch.

Added:
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/EventDashboard.java
      - copied, changed from r2434, java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/RunPanel.java
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusEventsTable.java
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusPanel.java
Removed:
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/RunPanel.java
Modified:
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/Commands.java
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/EventProcessing.java
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/MonitoringApplication.java
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/MonitoringApplicationFrame.java
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/PlotInfoPanel.java
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/PlotPanel.java
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusTable.java
    java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/util/RunnableEtStation.java

Modified: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/Commands.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/Commands.java	(original)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/Commands.java	Sat Mar 14 12:37:17 2015
@@ -34,9 +34,10 @@
     // Save a screenshot
     static final String SAVE_SCREENSHOT = "saveScreenshot";
     
-    // Save the plots
+    // Plotting actions
     static final String SAVE_PLOTS = "savePlots";
     static final String CLEAR_PLOTS = "resetPlots";
+    static final String SAVE_SELECTED_PLOTS = "saveSelectedPlots";
     
     // Exit the application.
     static final String EXIT = "exit";

Copied: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/EventDashboard.java (from r2434, java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/RunPanel.java)
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/RunPanel.java	(original)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/EventDashboard.java	Sat Mar 14 12:37:17 2015
@@ -23,7 +23,7 @@
  * Dashboard for displaying information about the current run.
  * @author Jeremy McCormick <[log in to unmask]>
  */
-class RunPanel extends JPanel implements PropertyChangeListener {
+class EventDashboard extends JPanel implements PropertyChangeListener {
 
     FieldPanel runNumberField = new FieldPanel("Run Number", "", 10, false);
     DatePanel startDateField = new DatePanel("Run Start", "", 16, false);
@@ -35,17 +35,17 @@
     FieldPanel dataReceivedField = new FieldPanel("Data Received [MB]", "", 14, false);
     FieldPanel eventNumberField = new FieldPanel("Event Number", "", 14, false);
     FieldPanel dataRateField = new FieldPanel("Data Rate [MB/s]", "", 12, false);
-    FieldPanel eventRateField = new FieldPanel("Event Rate [evt/s]", "", 14, false);
+    FieldPanel eventRateField = new FieldPanel("Event Rate [Hz]", "", 14, false);
 
     RunModel runModel;
     
     static final NumberFormat formatter = new DecimalFormat("#0.00"); 
 
-    public RunPanel() {
+    public EventDashboard() {
         build();
     }
     
-    public RunPanel(RunModel runModel) {
+    public EventDashboard(RunModel runModel) {
         this.runModel = runModel;
         this.runModel.addPropertyChangeListener(this);
         build();
@@ -73,7 +73,7 @@
         this.runModel = runModel;
     }
 
-    class RunPanelUpdater extends CompositeRecordProcessor {
+    class EventDashboardUpdater extends CompositeRecordProcessor {
 
         Timer timer;
         

Modified: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/EventProcessing.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/EventProcessing.java	(original)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/EventProcessing.java	Sat Mar 14 12:37:17 2015
@@ -94,6 +94,18 @@
      * @param configurationModel The global configuration.
      */
     void setup(ConfigurationModel configurationModel) {
+        
+        // Setup LCSim from the configuration.
+        setupLcsim(configurationModel);
+
+        // Now setup the CompositeLoop.
+        setupLoop(configurationModel);
+    }
+
+    /**
+     * @param configurationModel
+     */
+    private void setupLcsim(ConfigurationModel configurationModel) {
         MonitoringApplication.logger.info("setting up LCSim");
 
         // Get steering resource or file as a String parameter.
@@ -168,15 +180,12 @@
             // Catch all errors and rethrow them as RuntimeExceptions.
             application.errorHandler.setError(t).setMessage("Error setting up LCSim.").printStackTrace().raiseException();
         }
-
-        // Now setup the CompositeLoop.
-        setupLoop(configurationModel);
     }
 
     /**
      * Create the event builder for converting EVIO events to LCSim.
      */
-    void createEventBuilder(ConfigurationModel configurationModel) {
+    private void createEventBuilder(ConfigurationModel configurationModel) {
 
         // Get the class for the event builder.
         String eventBuilderClassName = configurationModel.getEventBuilderClassName();
@@ -196,7 +205,7 @@
      * Setup the loop from the global configuration.
      * @param configurationModel The global configuration.
      */
-    void setupLoop(ConfigurationModel configurationModel) {
+    private void setupLoop(ConfigurationModel configurationModel) {
 
         CompositeLoopConfiguration loopConfig = new CompositeLoopConfiguration().setStopOnEndRun(configurationModel.getDisconnectOnEndRun()).setStopOnErrors(configurationModel.getDisconnectOnError()).setDataSourceType(configurationModel.getDataSourceType()).setProcessingStage(configurationModel.getProcessingStage()).setEtConnection(sessionState.connection).setFilePath(configurationModel.getDataSourcePath()).setLCSimEventBuilder(sessionState.eventBuilder);
 
@@ -238,9 +247,11 @@
             logger.config("added extra Driver " + driver.getName() + " to job");
         }
 
-        // Enable conditions system activation from EVIO event information.
-        logger.config("added EvioDetectorConditionsProcessor to job with detector " + configurationModel.getDetectorName());
-        loopConfig.add(new EvioDetectorConditionsProcessor(configurationModel.getDetectorName()));
+        // Enable conditions system activation from EVIO event data if using an EVIO file source.
+        if (configurationModel.getDataSourceType().equals(DataSourceType.EVIO_FILE)) {            
+            logger.config("added EvioDetectorConditionsProcessor to job with detector " + configurationModel.getDetectorName());
+            loopConfig.add(new EvioDetectorConditionsProcessor(configurationModel.getDetectorName()));
+        }
 
         // Create the CompositeLoop with the configuration.
         sessionState.loop = new CompositeLoop(loopConfig);
@@ -250,7 +261,7 @@
      * Setup a steering file on disk.
      * @param steering The steering file.
      */
-    void setupSteeringFile(String steering) {
+    private void setupSteeringFile(String steering) {
         sessionState.jobManager.setup(new File(steering));
     }
 
@@ -259,7 +270,7 @@
      * @param steering The steering resource.
      * @throws IOException if there is a problem setting up or accessing the resource.
      */
-    void setupSteeringResource(String steering) throws IOException {
+    private void setupSteeringResource(String steering) throws IOException {
         InputStream is = this.getClass().getClassLoader().getResourceAsStream(steering);
         if (is == null)
             throw new IOException("Steering resource is not accessible or does not exist.");
@@ -437,7 +448,7 @@
                 // Add an attachment that listens for DAQ configuration changes via physics SYNC events.
                 createPhysicsSyncStation();
                 
-                // Add ann attachment that listens for PRESTART events.
+                // Add an attachment that listens for PRESTART events.
                 createPreStartStation();
                 
             } catch (Exception e) {
@@ -478,7 +489,7 @@
     /**
      * Create the ET that listens for DAQ configuration change via SYNC events.
      */
-    void createPhysicsSyncStation() {
+    private void createPhysicsSyncStation() {
         logger.fine("creating physics SYNC station ...");       
         PhysicsSyncEventStation configStation = new PhysicsSyncEventStation(
                 this.sessionState.connection.getEtSystem(),
@@ -492,7 +503,7 @@
     /**
      * Create the ET station that listens for GO events in order to initialize the conditions system.
      */
-    void createPreStartStation() {
+    private void createPreStartStation() {
         logger.fine("creating PRESTART station ...");
         String detectorName = this.application.configurationModel.getDetectorName();
         EtSystem system = this.sessionState.connection.getEtSystem();

Modified: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/MonitoringApplication.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/MonitoringApplication.java	(original)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/MonitoringApplication.java	Sat Mar 14 12:37:17 2015
@@ -5,10 +5,6 @@
 import hep.aida.jfree.plotter.PlotterRegionListener;
 import hep.aida.ref.remote.rmi.client.RmiStoreFactory;
 
-import java.awt.Dimension;
-import java.awt.Rectangle;
-import java.awt.Robot;
-import java.awt.Toolkit;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.image.BufferedImage;
@@ -50,7 +46,6 @@
 import org.hps.monitoring.application.util.TableExporter;
 import org.hps.monitoring.plotting.MonitoringAnalysisFactory;
 import org.hps.monitoring.plotting.MonitoringPlotFactory;
-import org.hps.monitoring.subsys.StatusCode;
 import org.hps.monitoring.subsys.SystemStatus;
 import org.hps.monitoring.subsys.SystemStatusListener;
 import org.hps.monitoring.subsys.SystemStatusRegistry;
@@ -69,7 +64,7 @@
  * 
  * @author Jeremy McCormick <[log in to unmask]>
  */
-final class MonitoringApplication implements ActionListener, PropertyChangeListener, SystemStatusListener {
+final class MonitoringApplication implements ActionListener, PropertyChangeListener {
 
     // Statically initialize logging, which will be fully setup later.
     static final Logger logger;
@@ -344,11 +339,14 @@
      * Reset the plots and clear the tabs in the plot window.
      */
     void resetPlots() {
-
+        
+        // Clear global list of registered plotters.
+        MonitoringPlotFactory.getPlotterRegistry().clear();  
+        
         // Clear the static AIDA tree in case plots are hanging around from previous sessions.
         AIDA.defaultInstance().clearAll();
 
-        // Reset plot panel which removes all tabs.
+        // Reset plot panel which removes all its tabs.
         frame.plotPanel.reset();
         
         logger.info("plots were cleared");
@@ -358,43 +356,20 @@
      * Configure the system status monitor panel for a new job.
      */
     void setupSystemStatusMonitor() {
+        
         // Clear the system status monitor table.
-        frame.systemStatusTable.getTableModel().clear();
+        frame.systemStatusPanel.clear();
 
         // Get the global registry of SystemStatus objects.
         SystemStatusRegistry registry = SystemStatusRegistry.getSystemStatusRegistery();
 
         // Process the SystemStatus objects.
         for (SystemStatus systemStatus : registry.getSystemStatuses()) {
-            // Add a row to the table for every SystemStatus.
-            frame.systemStatusTable.getTableModel().addSystemStatus(systemStatus);
-
-            // Add this class as a listener so all status changes can be logged.
-            systemStatus.addListener(this);
+            // This will add the status to the two tables.
+            frame.systemStatusPanel.addSystemStatus(systemStatus);
         }
         
         logger.info("system status monitor initialized successfully");
-    }
-    
-    /**
-     * Hook for logging all status changes from the system status monitor.
-     */
-    @Override
-    public void statusChanged(SystemStatus status) {
-
-        // Choose the appropriate log level.
-        Level level = Level.FINE;
-        if (status.getStatusCode().equals(Level.WARNING)) {
-            level = Level.WARNING;
-        } else if (status.getStatusCode().ordinal() >= StatusCode.ERROR.ordinal()) {
-            level = Level.SEVERE;
-        }
-        
-        // Log all status changes.
-        logger.log(level, "STATUS, " + "subsys: " + status.getSubsystem() + ", " 
-                + "code: " + status.getStatusCode().name() 
-                + ", " + "descr: " + status.getDescription() 
-                + ", " + "mesg: " + status.getMessage());
     }
     
     /**
@@ -416,7 +391,7 @@
 
             // List of extra composite record processors including the updater for the RunPanel.
             List<CompositeRecordProcessor> processors = new ArrayList<CompositeRecordProcessor>();
-            processors.add(frame.runPanel.new RunPanelUpdater());
+            processors.add(frame.dashboardPanel.new EventDashboardUpdater());
             
             // Add Driver to update the trigger diagnostics tables.
             List<Driver> drivers = new ArrayList<Driver>();
@@ -623,6 +598,7 @@
     /**
      * Save a screenshot to a file using a file chooser.
      */
+    // FIXME: This might need to be on a new thread to allow the GUI to redraw w/o chooser visible.
     void saveScreenshot() {
         JFileChooser fc = new JFileChooser();
         fc.setAcceptAllFileFilterUsed(false);
@@ -637,8 +613,17 @@
             if (!fileName.endsWith("." + format)) {
                 fileName += "." + format;
             }
+            frame.repaint();
+            Object lock = new Object();
+            synchronized (lock) {
+                try {
+                    lock.wait(500);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
             writeScreenshot(fileName, format);
-            DialogUtil.showInfoDialog(frame, "Screenshot Saved", "Screenshot was saved to file.");
+            DialogUtil.showInfoDialog(frame, "Screenshot Saved", "Screenshot was saved to file" + '\n' + fileName);
             logger.info("saved screenshot to " + fileName);
         }
     }
@@ -648,15 +633,13 @@
      * @param fileName The name of the output file.
      */
     void writeScreenshot(String fileName, String format) {
-        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
-        Rectangle screenRectangle = new Rectangle(screenSize);
+        BufferedImage image = new BufferedImage(frame.getWidth(), frame.getHeight(), BufferedImage.TYPE_INT_RGB);
+        frame.paint(image.getGraphics()); 
         try {
-            Robot robot = new Robot();
-            BufferedImage image = robot.createScreenCapture(screenRectangle);
             ImageIO.write(image, format, new File(fileName));
-        } catch (Exception e) {
-            errorHandler.setError(e).setMessage("Failed to take screenshot.").printStackTrace().log().showErrorDialog();
-        }
+        } catch (IOException e) {
+            errorHandler.setError(e).setMessage("Failed to save screenshot.").printStackTrace().log().showErrorDialog();
+        }        
     }            
     
     /**

Modified: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/MonitoringApplicationFrame.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/MonitoringApplicationFrame.java	(original)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/MonitoringApplicationFrame.java	Sat Mar 14 12:37:17 2015
@@ -8,7 +8,6 @@
 
 import javax.swing.JFrame;
 import javax.swing.JPanel;
-import javax.swing.JScrollPane;
 import javax.swing.JSeparator;
 import javax.swing.JSplitPane;
 import javax.swing.JTabbedPane;
@@ -21,14 +20,14 @@
  */
 class MonitoringApplicationFrame extends JFrame {
             
-    RunPanel runPanel;    
+    EventDashboard dashboardPanel;    
     PlotPanel plotPanel;
     PlotInfoPanel plotInfoPanel;
     LogPanel logPanel;
-    SystemStatusTable systemStatusTable;
     JPanel buttonsPanel;
     TriggerDiagnosticsPanel triggerPanel;
     ConditionsPanel conditionsPanel;
+    SystemStatusPanel systemStatusPanel;
     MenuBar menu; 
     
     JSplitPane mainSplitPane;
@@ -39,9 +38,9 @@
     
     SettingsDialog settingsDialog;
        
-    static Rectangle bounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
-    static int pixWidthMax = (int) bounds.getWidth();
-    static int pixHeightMax = (int) bounds.getHeight();
+    static final Rectangle BOUNDS = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
+    static final int PIXEL_WIDTH_MAX = (int) BOUNDS.getWidth();
+    static final int PIXEL_HEIGHT_MAX = (int) BOUNDS.getHeight();
     
     /**
      * 
@@ -58,7 +57,7 @@
         setContentPane(contentPanel);
         contentPanel.setLayout(new BorderLayout());
         contentPanel.setOpaque(true);
-        contentPanel.setPreferredSize(new Dimension(pixWidthMax, pixHeightMax));
+        contentPanel.setPreferredSize(new Dimension(PIXEL_WIDTH_MAX, PIXEL_HEIGHT_MAX));
                 
         // Create the top panel.
         JPanel topPanel = new JPanel();
@@ -96,7 +95,7 @@
         leftPanel.setLayout(new BorderLayout());
                             
         // Create the run dashboard.
-        runPanel = new RunPanel(application.runModel);
+        dashboardPanel = new EventDashboard(application.runModel);
 
         // Create the tabbed pane for content in bottom of left panel such as log table and system monitor.
         JTabbedPane tableTabbedPane = new JTabbedPane();
@@ -106,8 +105,9 @@
         tableTabbedPane.addTab("Log Messages", logPanel);
         
         // Create the system monitor.
-        systemStatusTable = new SystemStatusTable();
-        tableTabbedPane.addTab("System Status Monitor", new JScrollPane(systemStatusTable));
+        //systemStatusTable = new SystemStatusTable();
+        systemStatusPanel = new SystemStatusPanel();
+        tableTabbedPane.addTab("System Status Monitor", systemStatusPanel);
         
         // Add the trigger diagnostics tables.
         triggerPanel = new TriggerDiagnosticsPanel();
@@ -118,19 +118,20 @@
         tableTabbedPane.addTab("Detector Conditions", conditionsPanel);
         
         // Vertical split pane in left panel.
-        leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, runPanel, tableTabbedPane);
-        leftSplitPane.setResizeWeight(1.0);
+        leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, dashboardPanel, tableTabbedPane);
+        leftSplitPane.setDividerLocation(250);
         leftPanel.add(leftSplitPane, BorderLayout.CENTER);
                                 
         // Create the right panel.
         JPanel rightPanel = new JPanel();
         rightPanel.setLayout(new BorderLayout());
-        
+                
         // Create the plot info panel.
         plotInfoPanel = new PlotInfoPanel();
                 
         // Create the plot panel.
-        plotPanel = new PlotPanel();
+        plotPanel = new PlotPanel();        
+        plotInfoPanel.saveButton.addActionListener(plotPanel);
         
         // Create the right panel vertical split pane for displaying plots and their information and statistics.
         rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, plotPanel, plotInfoPanel);
@@ -153,6 +154,9 @@
         // Setup the frame now that all components have been added.        
         pack();
         setExtendedState(JFrame.MAXIMIZED_BOTH);
+        
+        System.out.println("dashboard pref size: " + dashboardPanel.getPreferredSize());
+        
         setVisible(true); 
     }
              

Modified: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/PlotInfoPanel.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/PlotInfoPanel.java	(original)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/PlotInfoPanel.java	Sat Mar 14 12:37:17 2015
@@ -15,10 +15,10 @@
 import hep.aida.ref.function.FunctionDispatcher;
 import hep.aida.ref.function.FunctionListener;
 
+import java.awt.Color;
 import java.awt.Component;
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-import java.awt.Insets;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.EventObject;
@@ -27,6 +27,8 @@
 import java.util.TimerTask;
 
 import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
 import javax.swing.JComboBox;
 import javax.swing.JList;
 import javax.swing.JPanel;
@@ -47,6 +49,8 @@
     JComboBox<Object> plotComboBox;
     JTable infoTable = new JTable();
     DefaultTableModel model;
+    JButton saveButton;
+    
     PlotterRegion currentRegion;
     Object currentObject;
     static final int INSET_SIZE = 5;
@@ -63,11 +67,18 @@
      */
     @SuppressWarnings("unchecked")
     PlotInfoPanel() {
-
-        setLayout(new GridBagLayout());
-        setBorder(BorderFactory.createEmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
-
-        GridBagConstraints c;
+                
+        setLayout(new FlowLayout(FlowLayout.LEFT));
+
+        JPanel leftPanel = new JPanel();
+        leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.PAGE_AXIS));
+           
+        JPanel buttonPanel = new JPanel();
+        saveButton = new JButton("Save Plots ...");
+        saveButton.setActionCommand(Commands.SAVE_SELECTED_PLOTS);
+        buttonPanel.add(saveButton);       
+        //c.anchor = GridBagConstraints.NORTHWEST;
+        leftPanel.add(buttonPanel);
 
         plotComboBox = new JComboBox<Object>();
         plotComboBox.setActionCommand(PLOT_SELECTED);
@@ -85,26 +96,17 @@
             }
         });
         plotComboBox.addActionListener(this);
-        c = new GridBagConstraints();
-        c.gridx = 0;
-        c.gridy = 0;
-        c.fill = GridBagConstraints.HORIZONTAL;
-        c.insets = new Insets(0, 0, INSET_SIZE, 0);
-        add(plotComboBox, c);
-
+        leftPanel.add(plotComboBox);
+        
         String data[][] = new String[0][0];
         model = new DefaultTableModel(data, COLUMN_NAMES);
         infoTable.setModel(model);
-
-        // FIXME: Are these adequate column size settings? Could prob be bigger...
         infoTable.getColumn("Field").setMinWidth(25);
         infoTable.getColumn("Value").setMinWidth(20);
-
-        c = new GridBagConstraints();
-        c.gridx = 0;
-        c.gridy = 1;
-        c.fill = GridBagConstraints.BOTH;
-        add(infoTable, c);
+        infoTable.setMinimumSize(new Dimension(100, 200));
+        leftPanel.add(infoTable);
+        
+        add(leftPanel);
     }
 
     /**

Modified: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/PlotPanel.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/PlotPanel.java	(original)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/PlotPanel.java	Sat Mar 14 12:37:17 2015
@@ -1,15 +1,26 @@
 package org.hps.monitoring.application;
 
+import hep.aida.IPlotter;
+
 import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.IOException;
 
+import javax.swing.JFileChooser;
 import javax.swing.JPanel;
 import javax.swing.JTabbedPane;
+
+import org.hps.monitoring.application.util.DialogUtil;
+import org.hps.monitoring.plotting.MonitoringPlotFactory;
 
 /**
  * This is the panel containing the tabs with the monitoring plots.
  * @author Jeremy McCormick <[log in to unmask]>
  */
-class PlotPanel extends JPanel {
+class PlotPanel extends JPanel implements ActionListener {
     
     private JTabbedPane plotPane;    
     
@@ -23,8 +34,55 @@
     JTabbedPane getPlotPane() {
         return plotPane;
     }
+
+    /**
+     * Get the indices of the current selected tabs.
+     * @return The indices of the current tabs.
+     */
+    int[] getSelectedTabs() {
+        int[] indices = new int[2];
+        indices[0] = plotPane.getSelectedIndex();
+        Component component = plotPane.getSelectedComponent();
+        if (component instanceof JTabbedPane) {
+            indices[1] = ((JTabbedPane)component).getSelectedIndex();
+        } 
+        return indices;
+    }
+    
+    public void actionPerformed(ActionEvent event) {
+        if (event.getActionCommand().equals(Commands.SAVE_SELECTED_PLOTS)) {
+            int[] indices = getSelectedTabs();
+            IPlotter plotter = MonitoringPlotFactory.getPlotterRegistry().find(indices[0], indices[1]);
+            if (plotter != null) {
+                savePlotter(plotter);
+            } else {
+                DialogUtil.showErrorDialog(this, "Error Finding Plots", "No plots found in selected tab.");
+            }
+        }
+    }
+            
+    static final String DEFAULT_FORMAT = "png";
+    void savePlotter(IPlotter plotter) {
+        JFileChooser fc = new JFileChooser();
+        fc.setAcceptAllFileFilterUsed(false);
+        fc.setDialogTitle("Save Plots - " + plotter.title());
+        fc.setCurrentDirectory(new File("."));
+        int r = fc.showSaveDialog(this);
+        if (r == JFileChooser.APPROVE_OPTION) {                        
+            String path = fc.getSelectedFile().getPath();
+            if (path.lastIndexOf(".") == -1) {
+                path += "." + DEFAULT_FORMAT;
+            }
+            try {
+                plotter.writeToFile(path);
+            } catch (IOException e) {
+                e.printStackTrace();
+                DialogUtil.showErrorDialog(this, "Error Saving Plots", "There was an error saving the plots.");
+            }
+        }        
+    }
     
     void reset() {
-        plotPane.removeAll();
-    }
+        plotPane.removeAll();        
+    }    
 }

Added: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusEventsTable.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusEventsTable.java	(added)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusEventsTable.java	Sat Mar 14 12:37:17 2015
@@ -0,0 +1,147 @@
+/**
+ * 
+ */
+package org.hps.monitoring.application;
+
+import java.awt.Component;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.DefaultTableModel;
+
+import org.hps.monitoring.application.model.SystemStatusTableModel;
+import org.hps.monitoring.subsys.StatusCode;
+import org.hps.monitoring.subsys.Subsystem;
+import org.hps.monitoring.subsys.SystemStatus;
+import org.hps.monitoring.subsys.SystemStatusImpl;
+import org.hps.monitoring.subsys.SystemStatusListener;
+
+/**
+ * This is a table that shows every system status change in a different row.
+ * 
+ * @author Jeremy McCormick <[log in to unmask]>
+ */
+public class SystemStatusEventsTable extends JTable {
+    
+    SystemStatusEventsTableModel tableModel = new SystemStatusEventsTableModel();
+    
+    SystemStatusEventsTable() {
+        setModel(tableModel);
+        
+        // Date formatting.
+        getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
+
+            final SimpleDateFormat dateFormat = new SimpleDateFormat("MMMM-dd-yyyy HH:mm:ss.SSS");
+
+            @Override
+            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+                if (value instanceof Date) {
+                    value = dateFormat.format(value);
+                }
+                return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+            }
+        });
+        
+        // Rendering of system status cells using different background colors.
+        getColumnModel().getColumn(2).setCellRenderer(new DefaultTableCellRenderer() {
+
+            @Override
+            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
+
+                // Cells are by default rendered as a JLabel.
+                JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
+
+                // Color code the cell by its status.
+                StatusCode statusCode = StatusCode.valueOf((String) value);
+                label.setBackground(statusCode.getColor());
+                return label;
+            }
+        });
+    }
+    
+    void registerListener() {
+    }
+    
+    static class SystemStatusEventsTableModel extends DefaultTableModel implements SystemStatusListener {
+        
+        List<SystemStatus> statuses = new ArrayList<SystemStatus>();
+        
+        String[] columnNames = { "Date", "Subsystem", "Status Code", "Description", "Message" };
+        Class<?>[] columnClasses = { Date.class, Subsystem.class, SystemStatus.class, String.class, String.class };
+
+        SystemStatusEventsTableModel() {
+        }
+        
+        @Override
+        public String getColumnName(int column) {
+            return columnNames[column];
+        }
+        
+        @Override
+        public Class<?> getColumnClass(int column) {
+            return columnClasses[column];
+        }
+
+        /**
+         * Update the table with status changes.
+         * @param status The system status.
+         */
+        @Override
+        public void statusChanged(SystemStatus status) {
+            SystemStatus newStatus = new SystemStatusImpl(status);
+            statuses.add(newStatus);
+            fireTableDataChanged();
+        }
+        
+        /**
+         * Register the listener on this status.
+         * @param status The system status.
+         */
+        void addSystemStatus(SystemStatus status) {
+            status.addListener(this);
+        }
+        
+        @Override
+        public int getColumnCount() {
+            return columnNames.length;
+        }
+        
+        @Override
+        public int getRowCount() {
+            if (statuses != null) {
+                return statuses.size();
+            } else {
+                return 0;
+            }
+        }
+        
+        @Override
+        public Object getValueAt(int row, int column) {
+            SystemStatus status = statuses.get(row);
+            switch (column) {
+                case 0:
+                    return new Date(status.getLastChangedMillis());
+                case 1:
+                    return status.getSubsystem();
+                case 2:
+                    return status.getStatusCode();
+                case 3:
+                    return status.getDescription();
+                case 4: 
+                    return status.getMessage();
+                default:
+                    return null;
+            }
+        }
+        
+        public void clear() {
+            this.statuses.clear();
+            this.setRowCount(0);
+        }
+    }    
+}

Added: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusPanel.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusPanel.java	(added)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusPanel.java	Sat Mar 14 12:37:17 2015
@@ -0,0 +1,51 @@
+/**
+ * 
+ */
+package org.hps.monitoring.application;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+
+import org.hps.monitoring.application.SystemStatusEventsTable.SystemStatusEventsTableModel;
+import org.hps.monitoring.subsys.SystemStatus;
+
+/**
+ * This is a panel showing the two tables for viewing the system statuses,
+ * one showing the current state of all system status monitors and the other
+ * all system status change events.
+ * 
+ * @author Jeremy McCormick <[log in to unmask]>
+ */
+public class SystemStatusPanel extends JPanel {
+    
+    SystemStatusTable statusTable = new SystemStatusTable();
+    SystemStatusEventsTable eventsTable = new SystemStatusEventsTable();
+        
+    SystemStatusPanel() {         
+        super(new BorderLayout());
+        JSplitPane splitPane = new JSplitPane(
+                JSplitPane.VERTICAL_SPLIT, 
+                new JScrollPane(statusTable), 
+                new JScrollPane(eventsTable));
+        splitPane.setDividerLocation(50);
+        add(splitPane,
+            BorderLayout.CENTER);
+    }   
+    
+    void addSystemStatus(SystemStatus status) {
+        // Register listeners of table models on this status.
+        statusTable.getTableModel().addSystemStatus(status);        
+        eventsTable.tableModel.addSystemStatus(status);
+    }
+    
+    void clear() {
+        // Clear the system status monitor table.
+        statusTable.getTableModel().clear();    
+
+        // Clear the system status events table.
+        ((SystemStatusEventsTableModel)eventsTable.getModel()).clear();
+    }
+}

Modified: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusTable.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusTable.java	(original)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/SystemStatusTable.java	Sat Mar 14 12:37:17 2015
@@ -1,6 +1,5 @@
 package org.hps.monitoring.application;
 
-import java.awt.Color;
 import java.awt.Component;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
@@ -17,8 +16,7 @@
 import org.hps.monitoring.subsys.StatusCode;
 
 /**
- * A GUI window for showing changes to {@link org.hps.monitoring.subsys.SystemStatus} objects using
- * a <code>JTable</code>.
+ * This table shows the current state of {@link org.hps.monitoring.subsys.SystemStatus} objects.
  */
 class SystemStatusTable extends JTable {
 
@@ -37,28 +35,7 @@
 
                 // Color code the cell by its status.
                 StatusCode statusCode = StatusCode.valueOf((String) value);
-                if (statusCode.ordinal() >= StatusCode.ERROR.ordinal()) {
-                    // Any type of error is red.
-                    label.setBackground(Color.RED);
-                } else if (statusCode.equals(StatusCode.WARNING)) {
-                    // Warnings are yellow.
-                    label.setBackground(Color.YELLOW);
-                } else if (statusCode.equals(StatusCode.OKAY)) {
-                    // Okay is green.
-                    label.setBackground(Color.GREEN);
-                } else if (statusCode.equals(StatusCode.OFFLINE)) {
-                    // Offline is orange.
-                    label.setBackground(Color.ORANGE);
-                } else if (statusCode.equals(StatusCode.UNKNOWN)) {
-                    // Unknown is gray.
-                    label.setBackground(Color.GRAY);
-                } else if (statusCode.equals(StatusCode.CLEARED)) {
-                    // Cleared is light gray.
-                    label.setBackground(Color.LIGHT_GRAY);
-                } else {
-                    // Default is white, though this shouldn't happen!
-                    label.setBackground(Color.WHITE);
-                }
+                label.setBackground(statusCode.getColor());
                 return label;
             }
         });

Modified: java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/util/RunnableEtStation.java
 =============================================================================
--- java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/util/RunnableEtStation.java	(original)
+++ java/branches/monitoring-app-dev/src/main/java/org/hps/monitoring/application/util/RunnableEtStation.java	Sat Mar 14 12:37:17 2015
@@ -26,7 +26,6 @@
  */
 public abstract class RunnableEtStation implements Runnable {
     
-    protected EtSystem system;
     protected EtStation station;
     protected EtAttachment attachment;
     protected boolean disconnect = false;    
@@ -76,7 +75,7 @@
         try {
             for (;;) {
                 // Get the next array of events from the server.
-                EtEvent[] events = system.getEvents(
+                EtEvent[] events = config.system.getEvents(
                         attachment, Mode.SLEEP, Modify.NOTHING, 0, config.readEvents);
                 
                 // Process the events.
@@ -96,7 +95,7 @@
             System.err.println("Error running " + this.config.name + " ...");
             e.printStackTrace();
         } finally {
-            if (system.alive()) {
+            if (config.system.alive()) {
                 System.out.println(this.config.name + " is disconnecting ...");
                 disconnect();
                 System.out.println("disconnected " + this.config.name);
@@ -123,21 +122,23 @@
             }
 
             // Create station and attach to the ET system.
-            EtStation station = system.createStation(stationConfig, config.name, config.order);
-            attachment = system.attach(station);
+            EtStation station = config.system.createStation(stationConfig, config.name, config.order);
+            attachment = config.system.attach(station);
 
         } catch (Exception e) {
+            System.err.println("Error while running setup of " + config.name + " ...");
+            e.printStackTrace();
             // Any errors during setup are rethrown.
             throw new RuntimeException(e);
         }
     }
 
     public synchronized void disconnect() {
-        if (system.alive()) {
+        if (config.system.alive()) {
             if (attachment.isUsable()) {
                 try {
-                    system.wakeUpAll(station);
-                    system.detach(attachment);
+                    config.system.wakeUpAll(station);
+                    config.system.detach(attachment);
                 } catch (IOException | EtException | EtClosedException | EtDeadException e) {
                     e.printStackTrace();
                 }