Commit in java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal on MAIN
Cluster.java-136306 removed
ColorScale.java-138306 removed
Datum.java-73306 removed
EcalHit.java-41306 removed
EcalMonitorDriver.java-32306 removed
EcalPanel.java-638306 removed
EventManager.java-159306 removed
GradientScale.java-119306 removed
Main.java-87306 removed
MultiGradientScale.java-148306 removed
StatusPanel.java-170306 removed
Viewer.java-458306 removed
event/Association.java+61added 307
     /Cluster.java+171added 307
     /EcalHit.java+97added 307
     /Event.java+128added 307
exec/Main.java+84added 307
io/AdvancedReader.java+249added 307
  /EventManager.java+62added 307
  /TextManager.java+191added 307
lcsim/LCIOBridgeDriver.java+180added 307
ui/ActiveViewer.java+153added 307
  /CalorimeterPanel.java+1347added 307
  /ClusterViewer.java+448added 307
  /FileViewer.java+158added 307
  /OccupancyViewer.java+150added 307
  /PEventViewer.java+167added 307
  /POccupancyViewer.java+127added 307
  /PassiveViewer.java+163added 307
  /StatusPanel.java+196added 307
  /Viewer.java+445added 307
util/BooleanMap.java+217added 307
    /ColorMap.java+19added 307
    /ColorScale.java+156added 307
    /CrystalEvent.java+54added 307
    /CrystalListener.java+37added 307
    /GradientScale.java+121added 307
    /MultiGradientScale.java+150added 307
+5331-2199
26 added + 12 removed, total 38 files
Major event display update.

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
Cluster.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/Cluster.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/Cluster.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,136 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.awt.Point;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * The class <code>Cluster</code> represents a seed hit and a collection
- * of additional hits that together form a hit cluster.
- * 
- * @author Kyle McCarty
- */
-public class Cluster {
-	private final Point seed;
-	private ArrayList<Point> hitList = new ArrayList<Point>();
-	private ArrayList<Point> shareList = new ArrayList<Point>();
-	
-	/**
-	 * <b>Cluster</b><br/><br/>
-	 * <code>public <b>Cluster</b>(int ix, int iy)</code><br/><br/>
-	 * Creates a new cluster. All clusters are required to have a seed
-	 * hit.
-	 * @param ix - The seed hit's x-coordinate.
-	 * @param iy - The seed hit's y-coordinate.
-	 */
-	public Cluster(int ix, int iy) { this(new Point(ix, iy)); }
-	
-	/**
-	 * <b>Cluster</b><br/><br/>
-	 * <code>public <b>Cluster</b>(EcalHit seed)</code><br/><br/>
-	 * Creates a new cluster. All clusters are required to have a seed
-	 * hit.
-	 * @param seed - The <code>Point</code> object indicating in which
-	 * crystal the seed hit occurred.
-	 */
-	public Cluster(Point seed) { this.seed = seed; }
-	
-	/**
-	 * <b>addComponentHit</b><br/><br/>
-	 * <code>public void <b>addComponentHit</b>(int ix, int iy)</code><br/><br/>
-	 * Adds an <code>Point</code> to the list of this cluster's
-	 * component hits.
-	 * @param ix - The component hit's x-coordinate.
-	 * @param iy - The component hit's y-coordinate.
-	 */
-	public void addComponentHit(int ix, int iy) { hitList.add(new Point(ix, iy)); }
-	
-	/**
-	 * <b>addComponentHit</b><br/><br/>
-	 * <code>public void <b>addComponentHit</b>(Point eHit)</code><br/><br/>
-	 * Adds an <code>Point</code> to the list of this cluster's
-	 * component hits.
-	 * @param eHit - The <code>Point</code> object indicating in which
-	 * crystal the hit occurred.
-	 */
-	public void addComponentHit(Point eHit) { hitList.add(eHit); }
-	
-	/**
-	 * <b>addSharedHit</b><br/><br/>
-	 * <code>public void <b>addSharedHit</b>(int ix, int iy)</code><br/><br/>
-	 * Adds an <code>Point</code> to the list of this cluster's shared
-	 * hits.
-	 * @param ix - The shared hit's x-coordinate.
-	 * @param iy - The shared hit's y-coordinate.
-	 */
-	public void addSharedHit(int ix, int iy) { shareList.add(new Point(ix, iy)); }
-	
-	/**
-	 * <b>addSharedHit</b><br/><br/>
-	 * <code>public void <b>addSharedHit</b>(Point eHit)</code><br/><br/>
-	 * Adds an <code>Point</code> to the list of this cluster's shared
-	 * hits.
-	 * @param eHit - The <code>Point</code> object indicating in which
-	 * crystal the hit occurred.
-	 */
-	public void addSharedHit(Point eHit) { shareList.add(eHit); }
-	
-	/**
-	 * <b>getComponentHits</b><br/><br/>
-	 * <code>public List<Point> <b>getComponentHits</b>()</code><br/><br/>
-	 * Gets the list of hits that make up the cluster, exempting the
-	 * seed hit and shared hits.
-	 * @return Returns the cluster hits as a <code>List</code> object
-	 * composed of <code>Point</code> objects.
-	 */
-	public List<Point> getComponentHits() { return hitList; }
-	
-	/**
-	 * <b>getSharedHits</b><br/><br/>
-	 * <code>public List<Point> <b>getSharedHits</b>()</code><br/><br/>
-	 * Gets the list of hits that make up the cluster, exempting the
-	 * seed hit and component hits.
-	 * @return Returns the shared hits as a <code>List</code> object
-	 * composed of <code>Point</code> objects.
-	 */
-	public List<Point> getSharedHits() { return shareList; }
-	
-	/**
-	 * <b>getSeedHit</b><br/><br/>
-	 * <code>public EcalHit <b>getSeedHit</b>()</code><br/><br/>
-	 * Gets the hit representing the cluster center.
-	 * @return Returns the cluster center hit as an <code>EcalHit
-	 * </code>.
-	 */
-	public Point getSeedHit() { return seed; }
-	
-	/**
-	 * <b>getHitCount</b><br/><br/>
-	 * <code>public int <b>getHitCount</b>()</code><br/><br/>
-	 * Indicates how many total hits compose this cluster. This includes
-	 * component hits, shared hits, and the seed hit.
-	 * @return Returns the number of component hits in the cluster
-	 * as an <code>int</code>.
-	 */
-	public int getHitCount() { return hitList.size() + shareList.size() + 1; }
-	
-	/**
-	 * <b>getComponentHitCount</b><br/><br/>
-	 * <code>public int <b>getComponentHitCount</b>()</code><br/><br/>
-	 * Indicates how many component hits compose this cluster. Note
-	 * that this does not include the seed hit or shared hits.
-	 * @return Returns the number of component hits in the cluster
-	 * as an <code>int</code>.
-	 */
-	public int getComponentHitCount() { return shareList.size(); }
-	
-	/**
-	 * <b>getSharedHitCount</b><br/><br/>
-	 * <code>public int <b>getSharedHitCount</b>()</code><br/><br/>
-	 * Indicates how many shared hits compose this cluster. Note that
-	 * this does not include the seed hit or component hits.
-	 * @return Returns the number of shared hits in the cluster as an
-	 * <code>int</code>.
-	 */
-	public int getSharedHitCount() { return hitList.size() + 1; }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
ColorScale.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ColorScale.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ColorScale.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,138 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.awt.Color;
-
-/**
- * The abstract class <code>ColorScale</code> contains shared methods for all
- * types of color scalers.
- * 
- * @author Kyle McCarty
- **/
-public abstract class ColorScale {
-    // Indicates if linear or logairthmic scaling should be used.
-    protected boolean linear = true;
-    // The minimum value for color scaling.
-    protected double min = 0;
-    // The maximum value for color scaling.
-    protected double max = 1;
-    // A scale variable used for mapping logarithmic values.
-    protected double scale = 1.0;
-    // An efficiency variable used for logarithmic mapping.
-    protected double lMin = 0.0;
-    // An efficiency variable used for logarithmic mapping.
-    protected double lMax = 1.0;
-    
-    /**
-     * <b>setMinimum</b><br/><br/>
-     * <code>public void <b>setMinimum</b>(double minimum)</code><br/><br/>
-     * Sets the value under which no color scaling will be performed.
-     * @param minimum - The lowest value for which color scaling will be performed.
-     **/
-    public void setMinimum(double minimum) {
-        min = minimum;
-        revalidate();
-    }
-    
-    /**
-     * <b>setMaximum</b><br/><br/>
-     * <code>public void <b>setMaximum</b>(double maximum)</code><br/><br/>
-     * Sets the value over which no color scaling will be performed.
-     * @param maximum - The highest value for which color scaling will be performed.
-     **/
-    public void setMaximum(double maximum) {
-        max = maximum;
-        revalidate();
-    }
-    
-    /**
-     * <b>getMinimum</b><br/><br/>
-     * <code>public double <b>getMinimum</b>()</code><br/><br/>
-     * Gets the lowest value for which color scaling is performed.
-     * @return Returns the minimum value for color scaling as a <code>double</code>.
-     **/
-    public double getMinimum() { return min; }
-    
-    /**
-     * <b>getMaximum</b><br/><br/>
-     * <code>public double <b>getMaximum</b>()</code><br/><br/>
-     * Gets the highest value for which color scaling is performed.
-     * @return Returns the maximum value for color scaling as a <code>double</code>.
-     **/
-    public double getMaximum() { return max; }
-    
-    /**
-     * <b>setScalingLinear</b><br/><br/>
-     * <code>public void <b>setScalingLinear</b>()</code><br/><br/>
-     * Sets the scaling behavior to linear.
-     **/
-    public void setScalingLinear() {
-        linear = true;
-        revalidate();
-    }
-    
-    /**
-     * <b>isLinearScale</b><br/><br/>
-     * <code>public boolean <b>isLinearScale</b>()</code><br/><br/>
-     * Indicates whether this color mapping is linear or not.
-     * @return Returns <code>true</code> if this is a linear mapping and <code>false
-     * </code> otherwise.
-     **/
-    public boolean isLinearScale() { return linear; }
-    
-    /**
-     * <b>isLogarithmicScale</b><br/><br/>
-     * <code>public boolean <b>isLogarithmicScale</b>()</code><br/><br/>
-     * Indicates whether this color mapping is logarithmic or not.
-     * @return Returns <code>true</code> if this is a logarithmic mapping and <code>
-     * false</code> if it is not.
-     **/
-    public boolean isLogairthmicScale() { return !linear; }
-    
-    /**
-     * <b>setScalingLogarithmic</b><br/><br/>
-     * <code>public void <b>setScalingLogarithmic</b>()</code><br/><br/>
-     * Sets the scaling behavior to logarithmic.
-     **/
-    public void setScalingLogarithmic() {
-        linear = false;
-        revalidate();
-    }
-    
-    /**
-     * <b>getColor</b><br/><br/>
-     * <code>public Color <b>getColor</b>(double value)</code><br/><br/>
-     * Determines the color representing the indicated value.
-     * @param value - The value to relate to a color.
-     * @return Returns a <code>Color</code> object associated with the argument value.
-     **/
-    public abstract Color getColor(double value);
-    
-    /**
-     * <b>revalidate</b><br/><br/>
-     * <code>protected void <b>revalidate</b>()</code><br/><br/>
-     * Makes any necessary changes whenever a critical value is changed.
-     **/
-    protected void revalidate() {
-        // Ensure that the minimum is not zero in the case of log scaling.
-        if (!linear && min == 0) {
-            if (max < 0.01) { min = max / 100.0; }
-            else { min = 0.01; }
-        }
-        
-        // We only need to revalidate if we are using a logarithmic scale.
-        if (!linear) {
-            // Determine the scaling variable for logarithmic results.
-            double temp = min;
-            int steps = 0;
-            while (temp < 1) {
-                temp = temp * 10;
-                steps++;
-            }
-            scale = Math.pow(10, steps);
-            
-            // Revalidate the logarithmic variables.
-            lMax = Math.log10(scale * max);
-            lMin = Math.log10(scale * min);
-        }
-    }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
Datum.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/Datum.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/Datum.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,73 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.awt.Point;
-
-/**
- * The <code>Datum</code> class contains a point representing a crystal in the
- * calorimeter display panel. This is used for storing information when reading
- * from a file. HPS objects should be used when reading from LCIO.
- * 
- * @author Kyle McCarty
- **/
-public class Datum {
-    // The coordinate on the calorimeter panel.
-    protected Point loc;
-    
-    /**
-     * <b>Datum</b><br/><br/>
-     * <code>public <b>Datum</b>()</code><br/><br/>
-     * Initializes an empty <code>Datum</code>. Note that it will have an
-     * invalid coordinate.
-     **/
-    public Datum() { this(-1, -1); }
-
-    /**
-     * <b>Datum</b><br/><br/>
-     * <code>public <b>Datum</b>(int x, int y)</code><br/><br/>
-     * Initializes a new <code>Datum</code> at the indicated coordinate.
-     * @param x - The x-coordinate of the object.
-     * @param y - The y-coordinate of the object.
-     **/
-    public Datum(int x, int y) { loc = new Point(x, y); }
-    
-    /**
-     * <b>getX</b><br/><br/>
-     * <code>public int <b>getX</b>()</code><br/><br/>
-     * Indicates the x-coordinate of the object.
-     * @return Returns the x-coordinate as an <code>int</code>.
-     **/
-    public int getX() { return loc.x; }
-    
-    /**
-     * <b>getY</b><br/><br/>
-     * <code>public int <b>getY</b>()</code><br/><br/>
-     * Indicates the y-coordinate of the object.
-     * @return Returns the y-coordinate as an <code>int</code>.
-     **/
-    public int getY() { return loc.y; }
-    
-    /**
-     * <b>getLocation</b><br/><br/>
-     * <code>public Point <b>getLocation</b>()</code><br/><br/>
-     * Indicates the location of the object.
-     * @return Returns the object's location as a <code>Point
-     * </code> object.
-     **/
-    public Point getLocation() { return loc; }
-    
-    /**
-     * <b>setX</b><br/><br/>
-     * <code>public void <b>setX</b>(int x)</code><br/><br/>
-     * Sets the object's x-coordinate.
-     * @param x - The new x-coordinate.
-     **/
-    public void setX(int x) { loc.x = x; }
-    
-    /**
-     * <b>setY</b><br/><br/>
-     * <code>public void <b>setY</b>(int y)</code><br/><br/>
-     * Sets the obejct's y-coordinate.
-     * @param y - The new y-coordinate.
-     **/
-    public void setY(int y) { loc.y = y; }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
EcalHit.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/EcalHit.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/EcalHit.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,41 +0,0 @@
-package org.hps.monitoring.ecal;
-
-/**
- * The class <code>EcalHit</code> is an extension of <code>Datum</code>
- * that stores an energy. This is used for reading input from a text
- * file. <code>CalorimeterHit</code> should be used when reading from
- * an LCIO file.
- **/
-public final class EcalHit extends Datum {
-    // The (raw) energy of this hit.
-    private double energy = 0.0;
-    
-    /**
-     * <b>EcalHit</b><br/><br/>
-     * <code>public <b>EcalHit</b>(int x, int y, double energy)</code><br/><br/>
-     * Initializes a calorimeter hit object.
-     * @param x - The x-coordinate of the hit.
-     * @param y - The y-coordinate of the hit.
-     * @param energy - The raw energy of the hit.
-     **/
-    public EcalHit(int x, int y, double energy) {
-        super(x, y);
-        this.energy = energy;
-    }
-    
-    /**
-     * <b>getEnergy</b><br/><br/>
-     * <code>public double <b>getEnergy</b>()</code><br/><br/>
-     * Indicates the raw energy of this hit.
-     * @return Returns the raw energy as a <code>double</code>.
-     **/
-    public double getEnergy() { return energy; }
-    
-    /**
-     * <b>setEnergy</b><br/><br/>
-     * <code>public void <b>setEnergy</b>(double energy)</code><br/><br/>
-     * Sets the energy of the hit to the indicated value.
-     * @param energy - The new energy of the hit.
-     **/
-    public void setEnergy(double energy) { this.energy = energy; }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
EcalMonitorDriver.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/EcalMonitorDriver.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/EcalMonitorDriver.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,32 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import org.lcsim.event.EventHeader;
-import org.lcsim.util.Driver;
-
-public class EcalMonitorDriver extends Driver {
-    private Viewer viewer;
-    private String ecalCollectionName = "EcalHits";
-    private String clusterCollectionName = "EcalClusters";
-    
-    public void setEcalCollectionName(String ecalCollectionName) {
-        this.ecalCollectionName = ecalCollectionName;
-    }
-    
-    public void setClusterCollectionName(String clusterCollectionName) {
-        this.clusterCollectionName = clusterCollectionName;
-    }
-    
-    public void startOfData() {
-        viewer = new Viewer();
-        viewer.setVisible(true);
-    }
-    
-    public void process(EventHeader event) {
-        viewer.displayLCIOEvent(event, ecalCollectionName, clusterCollectionName);
-    }
-    
-    public void endOfData() {
-        viewer.setVisible(false);
-        viewer = null;
-    }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
EcalPanel.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/EcalPanel.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/EcalPanel.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,638 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.FontMetrics;
-import java.awt.Graphics;
-import java.awt.Point;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-
-import javax.swing.JPanel;
-
-/**
- * The class <code>EcalPanel</code> handles the rendering of the calorimeter
- * crystals as well as mapping colors to their values, rendering a color scale,
- * and marking cluster crystals.
- * 
- * @author Kyle McCarty
- **/
-public class EcalPanel extends JPanel {
-    // Java-suggested variable.
-    private static final long serialVersionUID = 6292751227464151897L;
-    // The color used for rendering seed hits.
-    private Color clusterColor = Color.GREEN;
-    // The default color of the calorimeter crystals.
-    private Color defaultColor = null;
-    // The color-mapping scale used by to color calorimeter crystals.
-    private MultiGradientScale scale = MultiGradientScale.makeRainbowScale(0.0, 1.0);
-    // The number of boxes in the x-direction.
-    private int xBoxes = 1;
-    // The number of boxes in the y-direction.
-    private int yBoxes = 1;
-    // The width of the scale.
-    private int scaleWidth = 75;
-    // Stores which calorimeter crystals are disabled.
-    private boolean[][] disabled;
-    // Stores the energies in each calorimeter crystal.
-    private double[][] hit;
-    // Stores whether a crystal is the location of a seed hit.
-    private boolean[][] cluster;
-    // Stores what color to highlight the crystal with.
-    private Color[][] highlight;
-    // The panel on which the scale is rendered.
-    private ScalePanel scalePanel = new ScalePanel();
-    // Store the size of the panel as of the last refresh.
-	private int[] lastSize = new int[2];
-    
-    // Efficiency variables for crystal placement.
-    private int[] widths;
-    private int[] heights;
-    private int[] xPosition;
-    private int[] yPosition;
-    private int[] clusterSpace = new int[3];
-    
-    /**
-     * <b>EcalPanel</b><br/><br/>
-     * Initializes the calorimeter panel.
-     * @param numXBoxes - The number of crystals in the x-direction.
-     * @param numYBoxes - The number of crystals in the y-direction.
-     **/
-    public EcalPanel(int numXBoxes, int numYBoxes) {
-        // Initialize the base component.
-        super();
-        
-        // Set the number of calorimeter crystals.
-        xBoxes = numXBoxes;
-        yBoxes = numYBoxes;
-        
-        // Initialize data the arrays.
-        disabled = new boolean[xBoxes][yBoxes];
-        hit = new double[xBoxes][yBoxes];
-        cluster = new boolean[xBoxes][yBoxes];
-        highlight = new Color[xBoxes][yBoxes];
-        
-        for(int x = 0; x < xBoxes; x++) {
-        	for(int y = 0; y < yBoxes; y++) {
-        		highlight[x][y] = null;
-        	}
-        }
-        
-        // Initialize the size arrays,
-        widths = new int[xBoxes];
-        heights = new int[yBoxes];
-        xPosition = new int [xBoxes + 1];
-        yPosition = new int[yBoxes + 1];
-        
-        // Add the scale panel.
-        setLayout(null);
-        add(scalePanel);
-    }
-    
-    /**
-     * <b>setCrystalEnabled</b><br/><br/>
-     * <code>public void <b>setCrystalEnabled</b>(int xIndex, int yIndex, boolean active)</code><br/><br/>
-     * Sets whether the indicated crystal is enabled or not. Invalid indices
-     * will be ignored.
-     * @param xIndex - The x-coordinate of the crystal.
-     * @param yIndex - The y-coordinate of the crystal.
-     * @param active - This should be <code>true</code> if the crystal is
-     * active and <code>false</code> if it is not.
-     * @throws IndexOutOfBoundsException Occurs when the given xy crystal
-     * coordinate does not point to a crystal.
-     **/
-    public void setCrystalEnabled(int xIndex, int yIndex, boolean active) throws IndexOutOfBoundsException {
-        if (xIndex >= 0 && xIndex < xBoxes && yIndex >= 0 && yIndex < yBoxes) {
-            disabled[xIndex][yIndex] = !active;
-        }
-        else {
-            throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", xIndex, yIndex));
-        }
-    }
-    
-    /**
-     * <b>addCrystalEnergy</b><br/><br/>
-     * <code>public void <b>addCrystalEnergy</b>(int xIndex, int yIndex, double energy)</code><br/><br/>
-     * Adds the indicated quantity of energy to the crystal at the given
-     * coordinates.
-     * @param xIndex - The x-coordinate of the crystal.
-     * @param yIndex - The y-coordinate of the crystal.
-     * @param energy - The energy to add.
-     * @throws IndexOutOfBoundsException Occurs when the given xy crystal
-     * coordinate does not point to a crystal.
-     **/
-    public void addCrystalEnergy(int xIndex, int yIndex, double energy) throws IndexOutOfBoundsException {
-        if (xIndex >= 0 && xIndex < xBoxes && yIndex >= 0 && yIndex < yBoxes) {
-            this.hit[xIndex][yIndex] += energy;
-        }
-        else {
-            throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", xIndex, yIndex));
-        }
-    }
-    
-    /**
-     * <b>setCrystalCluster</b><br/><br/>
-     * <code>public void <b>setCrystalCluster</b>(int xIndex, int yIndex, boolean cluster)</code><br/><br/>
-     * Sets whether a crystal is also the location of a seed hit.
-     * @param xIndex - The x-coordinate of the crystal.
-     * @param yIndex - The y-coordinate of the crystal.
-     * @param cluster - This should be <code>true</code> if there
-     * is a seed hit and <code>false</code> if there is not.
-     * @throws IndexOutOfBoundsException Occurs when the given xy
-     * crystal coordinate does not point to a crystal.
-     **/
-    public void setCrystalCluster(int xIndex, int yIndex, boolean cluster) throws IndexOutOfBoundsException {
-        if (xIndex >= 0 && xIndex < xBoxes && yIndex >= 0 && yIndex < yBoxes) {
-            this.cluster[xIndex][yIndex] = cluster;
-        }
-        else {
-            throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", xIndex, yIndex));
-        }
-    }
-    
-    /**
-     * <b>setCrystalHighlight</b><br/><br/>
-     * <code>public void <b>setCrystalHighlight</b>(int xIndex, int yIndex, Color highlight)</code><br/><br/>
-     * @param xIndex - The x-coordinate of the crystal.
-     * @param yIndex - The y-coordinate of the crystal.
-     * @param highlight - The color which the indicated crystal should
-     * be highlighted. A value of <code>null</code> indicates that no
-     * highlight should be used.
-     * @throws IndexOutOfBoundsException Occurs when the given xy
-     * crystal coordinate does not point to a crystal.
-     */
-    public void setCrystalHighlight(int xIndex, int yIndex, Color highlight) throws IndexOutOfBoundsException {
-        if (xIndex >= 0 && xIndex < xBoxes && yIndex >= 0 && yIndex < yBoxes) {
-            this.highlight[xIndex][yIndex] = highlight;
-        }
-        else {
-            throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", xIndex, yIndex));
-        }
-    }
-    
-    /**
-     * <b>clearCrystals</b><br/><br/>
-     * <code>public void <b>clearCrystals</b>()</code><br/><br/>
-     * Sets all crystal energies to zero and removes all clusters. This
-     * <b>does not</b> enable disabled crystals.
-     **/
-    public void clearCrystals() {
-        for (int x = 0; x < xBoxes; x++) {
-            for (int y = 0; y < yBoxes; y++) {
-                hit[x][y] = 0.0;
-                cluster[x][y] = false;
-            }
-        }
-    }
-    
-    /**
-     * <b>clearHighlight</b><br/><br/>
-     * <code>public void <b>clearHighlight</b>()</code><br/><br/>
-     * Clears any highlighting on the crystals.
-     */
-    public void clearHighlight() {
-        for (int x = 0; x < xBoxes; x++) {
-            for (int y = 0; y < yBoxes; y++) { highlight[x][y] = null; }
-        }
-    }
-    
-    /**
-     * <b>setClusterColor</b><br/><br/>
-     * <code>public void <b>setClusterColor</b>(Color c)</code><br/><br/>
-     * Sets the color of the seed hit marker.
-     * @param c - The color to be used for seed hit markers. A value of <code>null
-     * </code> will result in seed hit markers being the inverse color of the crystal
-     * in which they appear.
-     **/
-    public void setClusterColor(Color c) { clusterColor = c; }
-    
-    /**
-     * <b>setMinimum</b><br/><br>
-     * <code>public void <b>setMinimum</b>(double minimum)</code><br/><br/>
-     * Sets the minimum value of the color mapping scale. Energies below this
-     * value will all be the same minimum color.
-     * @param minimum - The minimum energy to be mapped.
-     **/
-    public void setMinimum(double minimum) {
-        scale.setMinimum(minimum);
-    }
-    
-    /**
-     * <b>setMaximum</b><br/><br/>
-     * <code>public void <b>setMaximum</b>(double maximum)</code><br/><br/>
-     * Sets the maximum value of the color mapping scale. Energies above this
-     * value will all be the same maximum color.
-     * @param maximum - The maximum energy to be mapped.
-     **/
-    public void setMaximum(double maximum) {
-        scale.setMaximum(maximum);
-    }
-    
-    /**
-     * <b>setScalingLinear</b><br/><br/>
-     * <code>public void <b>setScalingLinear</b>()<br/><br/>
-     * Sets the color mapping scale behavior to linear mapping.
-     **/
-    public void setScalingLinear() {
-        scale.setScalingLinear();
-    }
-    
-    /**
-     * <b>setScalingLogarithmic</b><br/><br/>
-     * <code>public void <b>setScalingLogarithmic</b>()</code><br/><br/>
-     * Sets the color mapping scale behavior to logarithmic mapping.
-     **/
-    public void setScalingLogarithmic() {
-        scale.setScalingLogarithmic();
-    }
-    
-    /**
-     * <b>isScalingLinear</b><br/><br/>
-     * <code>public void <b>isScalingLinear</b></code>()<br/><br/>
-     * Indicates whether the crystal colors are mapped linearly.
-     * @return Returns <code>true</code> if the mapping is linear
-     * and <code>false</code> otherwise.
-     */
-    public boolean isScalingLinear() { return scale.isLinearScale(); }
-    
-    /**
-     * <b>isScalingLogarithmic</b><br/><br/>
-     * <code>public void <b>isScalingLogarithmic</b></code>()<br/><br/>
-     * Indicates whether the crystal colors are mapped logarithmically.
-     * @return Returns <code>true</code> if the mapping is logarithmic
-     * and <code>false</code> otherwise.
-     */
-    public boolean isScalingLogarithmic() { return scale.isLogairthmicScale(); }
-    
-    /**
-     * <b>isCluster</b><br/><br/>
-     * <code>public boolean <b>isCluster</b></code>()<br/><br/>
-     * Determines if the crystal at the given coordinates is a cluster
-     * center or not.
-     * @param xCoor - The x-coordinate of the crystal.
-     * @param yCoor - The y-coordinate of the crystal.
-     * @return Returns <code>true</code> if the crystal is a cluster
-     * center and <code>false</code> if it is not or if the indices
-     * are invalid.
-     */
-    public boolean isCluster(int xCoor, int yCoor) {
-    	// If the coordinates are invalid, return false.
-    	if(!validateIndices(xCoor, yCoor)) { return false; }
-    	
-    	// Otherwise, check if it is a cluster.
-    	else { return cluster[xCoor][yCoor]; }
-    }
-    
-    /**
-     * <b>setScaleEnabled</b><br/><br/>
-     * <code>public void <b>setScaleEnabled</b>(boolean enabled)</code><br/><br/>
-     * Sets whether the scale should be visible or not.
-     * @param enabled - <code>true</code> indicates that the scale should
-     * be visible and <code>false</code> that it should be hidden.
-     **/
-    public void setScaleEnabled(boolean enabled) {
-        if (scalePanel.isVisible() != enabled) {
-            scalePanel.setVisible(enabled);
-        }
-    }
-    
-    /**
-     * <b>setCrystalDefaultColor</b><br/><br/>
-     * <code>public void <b>setCrystalDefaultColor</b>(Color c)</code><br/><br/>
-     * Sets the color that crystals with zero energy will display.
-     * @param c - The color to use for zero energy crystals. A value
-     * of <code>null</code> will use the appropriate energy color
-     * map value.
-     */
-    public void setCrystalDefaultColor(Color c) { defaultColor = c; }
-    
-    /**
-     * <b>getCrystalID</b><br/><br/>
-     * <code>public Point <b>getCrystalID</b>(int xCoor, int yCoor)</code><br/><br/>
-     * Determines the panel crystal index of the crystal at the given
-     * panel coordinates.
-     * @param xCoor - The x-coordinate on the panel.
-     * @param yCoor - The y-coordinate on the panel.
-     * @return Returns a <code>Point</code> object containing the panel
-     * crystal indices of the crystal at the given panel coordinates.
-     * Returns <code>null</code> if the coordinates do not map to a crystal.
-     */
-    public Point getCrystalID(int xCoor, int yCoor) {
-    	// If either coordinate is negative, return the null result.
-    	if(xCoor < 0 || yCoor < 0) { return null; }
-    	
-    	// If either coordinate is too large, return the nul result.
-    	if(xCoor > xPosition[xBoxes] || yCoor > yPosition[yBoxes]) {
-    		return null;
-    	}
-    	
-    	// Make a point to identify the crystal index.
-    	Point loc = new Point(-1, -1);
-    	
-    	// Determine which y index it is.
-    	for(int y = 0; y < yBoxes; y++) {
-    		if(yCoor <= yPosition[y + 1]) {
-    			loc.y = y;
-    			break;
-    		}
-    	}
-    	
-    	// Determine which x index it is.
-    	for(int x = 0; x < xBoxes; x++) {
-    		if(xCoor <= xPosition[x + 1]) {
-    			loc.x = x;
-    			break;
-    		}
-    	}
-    	
-    	// If either coordinate is not valid, return null.
-    	if(loc.x == -1 || loc.y == -1) { return null; }
-    	
-    	// Return the crystal identifier.
-    	return loc;
-    }
-    
-    /**
-     * <b>getCrystalEnergy</b><br/><br/>
-     * <code>public double <b>getCrystalEnergy</b>(int ix, int iy)</code><br/><br/>
-     * Provides the energy stored in the indicated crystal.
-     * @param ix - The crystal's x-index.
-     * @param iy - The crystal's y-index.
-     * @return Returns the energy as a <code>double</code>.
-     * @throws IndexOutOfBoundsException - Occurs when either of the
-     * given indices are invalid.
-     */
-    public double getCrystalEnergy(int ix, int iy) throws IndexOutOfBoundsException {
-    	if(!validateIndices(ix, iy)) {
-    		throw new IndexOutOfBoundsException("Invalid crystal index.");
-    	}
-    	else { return hit[ix][iy]; }
-    }
-    
-    public Color getCrystalHighlight(int ix, int iy) throws IndexOutOfBoundsException {
-    	if(!validateIndices(ix, iy)) {
-    		throw new IndexOutOfBoundsException("Invalid crystal index.");
-    	}
-    	else { return highlight[ix][iy]; }
-    }
-    
-    /**
-     * <b>redraw</b><br/><br/>
-     * <code>public void <b>redraw</b>()</code> Re-renders the calorimeter
-     * panel.
-     **/
-    public void redraw() { super.repaint(); }
-    
-    public void setSize(Dimension d) { setSize(d.width, d.height); }
-    
-    public void setSize(int width, int height) {
-        super.setSize(width, height);
-        scalePanel.setLocation(width - scaleWidth, 0);
-        scalePanel.setSize(scaleWidth, height);
-    }
-    
-    protected void paintComponent(Graphics g) {
-    	// Check to see if the panel has changed sizes since the last
-    	// time it was rendered.
-    	boolean sizeChanged = false;
-        if(getWidth() != lastSize[0] || getHeight() != lastSize[1]) {
-			lastSize[0] = getWidth();
-			lastSize[1] = getHeight();
-			sizeChanged = true;
-		}
-
-        // If the size of the panel has changed, we need to update
-        // the crystal locations.
-        if (sizeChanged) {
-            // Determine the width and heights of the calorimeter crystals.
-            int width;
-            if (scalePanel.isVisible()) { width = getWidth() - scaleWidth; }
-            else { width = getWidth(); }
-            int height = getHeight();
-            
-            int boxWidth = width / xBoxes;
-            int widthRem = width % xBoxes;
-            int boxHeight = height / yBoxes;
-            int heightRem = height % yBoxes;
-            
-            // Store the widths for each crystal.
-            for(int x = 0; x < xBoxes; x++) {
-            	widths[x] = boxWidth;
-            	if(widthRem > 0) {
-            		widths[x]++;
-            		widthRem--;
-            	}
-            	xPosition[x + 1] = xPosition[x] + widths[x];
-            }
-            
-            // Store the height for each crystal.
-            for(int y = 0; y < yBoxes; y++) {
-            	heights[y] = boxHeight;
-            	if(heightRem > 0) {
-            		heights[y]++;
-            		heightRem--;
-            	}
-            	yPosition[y + 1] = yPosition[y] + heights[y];
-            }
-            
-            // Calculate the cluster position variables.
-            double ltw = 0.25 * boxWidth;
-            double lth = 0.25 * boxHeight;
-            
-            if(ltw > lth) {
-            	clusterSpace[0] = (int)Math.round((boxWidth - lth - lth) / 2.0);
-            	clusterSpace[1] = (int)Math.round(lth);
-            	clusterSpace[2] = (int)Math.round(lth + lth);
-            	
-            }
-            else {
-            	clusterSpace[0] = (int)Math.round(ltw);
-            	clusterSpace[1] = (int)Math.round((boxHeight - ltw - ltw) / 2.0);
-            	clusterSpace[2] = (int)Math.round(ltw + ltw);
-            	
-            }
-        }
-        
-        // Render the crystals at the locations calculated in the size
-        // change block.
-        for (int x = 0; x < xBoxes; x++) {
-            for (int y = 0; y < yBoxes; y++) {
-                // Determine the appropriate color for the box.
-                Color crystalColor;
-                if (disabled[x][y]) { crystalColor = Color.BLACK; }
-                else if(defaultColor != null && hit[x][y] == 0) { crystalColor = defaultColor; }
-                else { crystalColor = scale.getColor(hit[x][y]); }
-                g.setColor(crystalColor);
-                
-                // Draw the crystal energy color.
-                g.fillRect(xPosition[x], yPosition[y], widths[x], heights[y]);
-                
-                // Draw the crystal border.
-                g.setColor(Color.BLACK);
-                g.drawRect(xPosition[x], yPosition[y], widths[x] - 1, heights[y] - 1);
-                
-                // Draw a highlight, if needed.
-                if(highlight[x][y] != null && !disabled[x][y]) {
-                	g.setColor(highlight[x][y]);
-                	g.drawRect(xPosition[x] + 1, yPosition[y] + 1, widths[x] - 3, heights[y] - 3);
-                }
-                
-                // If there is a cluster, draw a circle.
-                if (cluster[x][y]) {
-                    // Get the appropriate cluster color.
-                    Color c;
-                    if (clusterColor == null) {
-                    	int red = Math.abs(255 - crystalColor.getRed());
-                        int blue = Math.abs(255 - crystalColor.getBlue());
-                        int green = Math.abs(255 - crystalColor.getGreen());
-                        c = new Color(red, green, blue);
-                    }
-                    else { c = clusterColor; }
-                    
-                    // Draw an circle on the cluster crystal.
-                    g.setColor(c);
-                    g.fillOval(xPosition[x] + clusterSpace[0], yPosition[y] + clusterSpace[1],
-                    		clusterSpace[2], clusterSpace[2]);
-                }
-          	}
-        }
-    }
-    
-    /**
-     * <b>validateIndices</b><br/><br/>
-     * <code>private boolean <b>validateIndices</b>(int ix, int iy)</code><br/><br/>
-     * Indicates whether the given indices corresponds to a valid
-     * crystal or not.
-     * @param ix - The crystal's x index.
-     * @param iy - The crystal's y index.
-     * @return Returns <code>true</code> if the indices are valid
-     * and <code>false</code> if they are not.
-     */
-    private boolean validateIndices(int ix, int iy) {
-    	boolean lowX = (ix > -1);
-    	boolean highX = (ix < xBoxes);
-    	boolean lowY = (iy > -1);
-    	boolean highY = (iy < yBoxes);
-    	
-    	return (lowX && highX && lowY && highY);
-    }
-    
-    /**
-     * The local class <b>ScalePanel</b> renders the scale for the calorimeter
-     * color map.
-     **/
-    private class ScalePanel extends JPanel {
-		private static final long serialVersionUID = -2644562244208528609L;
-        
-        protected void paintComponent(Graphics g) {
-            // Set the text region width.
-            int textWidth = 45;
-            boolean useText;
-            
-            // Store height and width.
-            int height = getHeight();
-            int width;
-            if (getWidth() > textWidth) {
-                width = getWidth() - textWidth;
-                useText = true;
-            }
-            else {
-                width = getWidth();
-                useText = false;
-            }
-            
-            // Define the step size for the scale. This will differ depending
-            // on whether we employ a linear or logarithmic scale.
-            double step;
-            double curValue;
-            boolean linear = scale.isLinearScale();
-            if (linear) {
-                step = (scale.getMaximum() - scale.getMinimum()) / height;
-                curValue = scale.getMinimum();
-            }
-            else {
-                double max = Math.log10(scale.getMaximum());
-                double min = Math.log10(scale.getMinimum());
-                step = (max - min) / height;
-                curValue = min;
-            }
-            
-            // Color the text area.
-            g.setColor(Color.BLACK);
-            g.drawRect(0, 0, width, height);
-            g.drawRect(1, 1, width - 1, height - 1);
-            g.fillRect(width, 0, textWidth, height);
-            
-            // Render the scale.
-            int sy = height;
-            int[] sx = { 0, width };
-            for (int i = 0; i <= height; i++) {
-                // Get the appropriate value for the current pixel.
-                double scaledValue;
-                if (linear) { scaledValue = curValue; }
-                else { scaledValue = Math.pow(10, curValue); }
-                g.setColor(scale.getColor(scaledValue));
-                
-                // Draw a line.
-                g.drawLine(sx[0], sy, sx[1], sy);
-                
-                // Update the spacing variables.
-                curValue += step;
-                sy--;
-            }
-            
-            // Generate the scale text.
-            if (useText) {
-                // Determine the spacing of the text.
-                FontMetrics fm = g.getFontMetrics(g.getFont());
-                int fontHeight = fm.getHeight();
-                
-                // Populate the first and last values.
-                NumberFormat nf = new DecimalFormat("0.#E0");
-                g.setColor(Color.WHITE);
-                g.drawString(nf.format(scale.getMaximum()), width + 5, fontHeight);
-                g.drawString(nf.format(scale.getMinimum()), width + 5, height - 3);
-                
-                // Calculate text placement variables.
-                double heightAvailable = height - 2.0 * fontHeight;
-                double heightDefault = heightAvailable / (1.5 * fontHeight);
-                int num = (int) Math.floor(heightAvailable / heightDefault);
-                double heightRemainder = heightAvailable - (num * heightDefault);
-                double heightExtra = heightRemainder / num;
-                double lSpacing = heightDefault + heightExtra;
-                double lHalfSpacing = lSpacing / 2.0;
-                int lHeight = fontHeight + 3;
-                int[] lX = { width - 4, width, width + 5 };
-                int lShift = (int) (fontHeight * 0.25 + lHalfSpacing);
-                double lTemp = 0.0;
-                
-                // Calculate value conversion variables.
-                double lMin = scale.getMinimum();
-                double lScale;
-                if (linear) {
-                    lMin = scale.getMinimum();
-                    lScale = scale.getMaximum() - scale.getMinimum();
-                }
-                else {
-                    double min = Math.log10(scale.getMinimum());
-                    double max = Math.log10(scale.getMaximum());
-                    lMin = min;
-                    lScale = max - min;
-                }
-                
-                // Write the labels.
-                for (int i = 0; i < num; i++) {
-                    g.setColor(Color.BLACK);
-                    int h = (int) (lHeight + lHalfSpacing);
-                    g.drawLine(lX[0], h, lX[1], h);
-                    g.setColor(Color.WHITE);
-                    double lVal = lMin + (1.0 - ((double) h / height)) * lScale;
-                    if (!linear) { lVal = Math.pow(10, lVal); }
-                    g.drawString(nf.format(lVal), lX[2], lHeight + lShift);
-                    lTemp += lSpacing;
-                    lHeight = (int) (fontHeight + lTemp);
-                }
-            }
-        }
-    }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
EventManager.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/EventManager.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/EventManager.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,159 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.StringTokenizer;
-
-/**
- * The class <code>EventManager</code> handles loading hits and clusters from a
- * text file to populate the calorimeter panel. Input should be of the form
- * Event
- * Indicates the start of a new event.
- * 
- * EcalHit [X] [Y] [Energy]
- * Represents a calorimeter hit at coordinates ([X], [Y]) and with energy
- * [Energy]. Coordinates should be in calorimeter form (x = [-23, 23] and
- * y = [-5, 5]) and must integers. Energy can be a decimal value. Brackets
- * should not be included in the line.
- * 
- * Cluster [X] [Y]
- * Represents the location of a cluster at coordinates ([X], [Y]). Brackets
- * should not be included in the line.
- * 
- * @author Kyle McCarty
- **/
-public class EventManager {
-    // File readers for reading the input.
-    private FileReader fr;
-    private BufferedReader reader;
-    // List for storing the hits from the current event.
-    private ArrayList<EcalHit> hitList = new ArrayList<EcalHit>();
-    // List for storing the clusters from the current hit.
-    private ArrayList<Cluster> clusterList = new ArrayList<Cluster>();
-    // Whether the event manager has an open file.
-    private boolean open = true;
-    
-    /**
-     * <b>EventManager</b><br/><br/>
-     * <code>public <b>EventManager</b>(String filename)</code><br/><br/>
-     * Initializes an event manager that will read from the indicated file.
-     * @param filename - The path to the file containing hit information.
-     **/
-    public EventManager(String filename) throws IOException {
-        fr = new FileReader(filename);
-        reader = new BufferedReader(fr);
-    }
-    
-    /**
-     * <b>readEvent</b><br/><br/>
-     * <code>public boolean <b>readEvent</b>()</code><br/><br/>
-     * Populates the event manager with hits and clusters from the next event.
-     * @return Returns <code>true</code> if an event was read and <code>false
-     * </code> if it was not.
-     **/
-    public boolean readEvent() throws IOException {
-        // We can only read of the reader is open.
-        if (!open) { return false; }
-        
-        // Clear the data lists.
-        hitList.clear();
-        clusterList.clear();
-        
-        // Store the current line.
-        String curLine = reader.readLine();
-        
-        // Keep sorting until we hit a null or an event header.
-        while (curLine != null && curLine.compareTo("Event") != 0) {
-            curLine = reader.readLine();
-        }
-        
-        // If we hit a null, we are at the end of the file.
-        if (curLine == null) { return false; }
-        
-        // Otherwise, we have read an event header and must populate
-        // the data lists.
-        curLine = reader.readLine();
-        while (curLine != null && curLine.compareTo("Event") != 0) {
-            // Break apart the line.
-            StringTokenizer st = new StringTokenizer(curLine);
-            String name = st.nextToken();
-            int ix = Integer.parseInt(st.nextToken());
-            int iy = Integer.parseInt(st.nextToken());
-            
-            // If this is a cluster, add a new cluster object.
-            if (name.compareTo("Cluster") == 0) { clusterList.add(new Cluster(ix, iy)); }
-            
-            // If this is a calorimeter hit, add a new calorimeter hit object.
-            else if (name.compareTo("EcalHit") == 0) {
-                double energy = Double.parseDouble(st.nextToken());
-                hitList.add(new EcalHit(ix, iy, energy));
-            }
-            
-            // If this is a cluster component hit, add it to the last cluster.
-            else if(name.compareTo("CompHit") == 0) {
-            	// There must be a last cluster to process this hit type.
-            	if(clusterList.size() == 0) {
-            		System.err.println("File Format Error: A cluster component hit was read, but" +
-            				" no cluster has been declared. Terminating.");
-            		System.exit(1);
-            	}
-            	else { clusterList.get(clusterList.size() - 1).addComponentHit(ix, iy); }
-            }
-            
-            // If this is a cluster shared hit, add it to the last cluster.
-            else if(name.compareTo("SharHit") == 0) {
-            	// There must be a last cluster to process this hit type.
-            	if(clusterList.size() == 0) {
-            		System.err.println("File Format Error: A cluster shared hit was read, but" +
-            				" no cluster has been declared. Terminating.");
-            		System.exit(1);
-            	}
-            	else { clusterList.get(clusterList.size() - 1).addSharedHit(ix, iy); }
-            }
-            
-            // Get the next line.
-            curLine = reader.readLine();
-        }
-        
-        // Indicate that an event was processed.
-        return true;
-    }
-    
-    /**
-     * <b>close</b><br/><br/>
-     * <code>public void <b>close</b>()</code><br/><br/>
-     * Closes the event manager. Once this is performed, no additional events
-     * may be read.
-     * @throws IOException Occurs if there is an error closing the file stream.
-     **/
-    public void close() throws IOException {
-        reader.close();
-        fr.close();
-        open = false;
-    }
-    
-    /**
-     * <b>getHits</b><br/><br/>
-     * <code>public ArrayList<EcalHit> <b>getHits</b>()</code><br/><br/>
-     * Allows access to the current event's list of hits.
-     * @return Returns the current hits as an <code>ArrayList</code> object.
-     **/
-    public ArrayList<EcalHit> getHits() {
-        if (!open) { return null; }
-        else { return hitList; }
-    }
-    
-    /**
-     * <b>getClusters</b><br/><br/>
-     * <code>public ArrayList<Cluster> <b>getClusters</b></code><br/><br/>
-     * Allows access to the current event's list of clusters.
-     * @return Returns the current clusters as an <code>ArrayList
-     * </code> object.
-     **/
-    public ArrayList<Cluster> getClusters() {
-        if (!open) { return null; }
-        else { return clusterList; }
-    }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
GradientScale.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/GradientScale.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/GradientScale.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,119 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.awt.Color;
-
-/**
- * The class <code>GradientScale</code> is an implementation of the abstract
- * class <code>ColorScale</code> which represents a simple gradient from one
- * color to another. It will map any argument value that exceeds its maximum
- * value to the hot color and any argument value that is below its minimum value
- * to its cold color. All other argument values will be mapped somewhere between
- * the cold and hot colors using either a linear or logarithmic scale.
- * 
- * @author Kyle McCarty
- **/
-public final class GradientScale extends ColorScale {
-    // The color associated with the maximum value.
-    private Color hotColor = Color.WHITE;
-    // The color associated with the minimum value.
-    private Color coldColor = Color.BLACK;
-    // Efficiency variable holding the rgb difference between the two
-    // colors. This is used to prevent recalculation when mapping values.
-    private int[] drgb = { 255, 255, 255 };
-    
-    /**
-     * <b>setHotColor</b><br/><br/>
-     * <code>public void <b>setHotColor</b>(Color c)</code><br/><br/>
-     * Sets the color associated with the maximum value.
-     * @param c - The new color to use.
-     **/
-    public void setHotColor(Color c) {
-        hotColor = c;
-        revalidateColor();
-    }
-    
-    /**
-     * <b>setColdColor</b><br/><br/>
-     * <code>public void <b>setColdColor</b>(Color c)</code><br/><br/>
-     * Sets the color assocaited with the minimum value.
-     * @param c - The color to use.
-     **/
-    public void setColdColor(Color c) {
-        coldColor = c;
-        revalidateColor();
-    }
-    
-    public Color getColor(double value) {
-        // If the value is less than the minimum, return the cold color.
-        if (value < min) { return coldColor; }
-        
-        // If the value is greater than the maximum, return the hot color.
-        if (value > max) { return hotColor; }
-        
-        // Otherwise, calculate how far along the gradient the value is.
-        double percent;
-        if (linear) { percent = (value - min) / (max - min); }
-        else {
-            double lValue = Math.log10(scale * value);
-            percent = (lValue - lMin) / (lMax - lMin);
-        }
-        
-        // Scale the color.
-        int dr = (int) Math.round(percent * drgb[0]);
-        int dg = (int) Math.round(percent * drgb[1]);
-        int db = (int) Math.round(percent * drgb[2]);
-        
-        // Return the result.
-        return new Color(coldColor.getRed() + dr, coldColor.getGreen() + dg, coldColor.getBlue() + db);
-    }
-    
-    /**
-     * <b>revalidateColor</b><br/><br/>
-     * <code>private void <b>revalidateColor</b>()</code><br/><br/>
-     * Calculates the differences between the hot and cold colors and sets the
-     * class related class variables.
-     **/
-    private void revalidateColor() {
-        drgb[0] = hotColor.getRed() - coldColor.getRed();
-        drgb[1] = hotColor.getGreen() - coldColor.getGreen();
-        drgb[2] = hotColor.getBlue() - coldColor.getBlue();
-    }
-    
-    /**
-     * <b>makeGreyScale</b><br/><br/>
-     * <code>public static GradientScale <b>makeGreyScale</b>(double minimum, double maximum)</code><br/><br/>
-     * Creates a color scale that ranges from black (cold) to white (hot) with
-     * the indicated maximum and minimum.
-     * @param minimum - The lowest value for color scaling.
-     * @param maximum - The highest value for color scaling.
-     * @return Returns a <code>GradientScale</code> that maps to grey scale over
-     * the indicated range.
-     **/
-    public static GradientScale makeGreyScale(double minimum, double maximum) {
-        GradientScale gs = new GradientScale();
-        gs.setMinimum(minimum);
-        gs.setMaximum(maximum);
-        
-        return gs;
-    }
-    
-    /**
-     * <b>makeHeatScale</b><br/><br>
-     * <code>public static GradientScale <b>makeHeatScale</b>(double minimum, double maximum)</code><br/><br/>
-     * Creates a color scale that ranges from black (cold) to red (hot) with the
-     * indicated maximum and minimum.
-     * @param minimum - The lowest value for color scaling.
-     * @param maximum - The highest value for color scaling.
-     * @return Returns a <code>GradientScale</code> that maps to a black- to-red
-     * gradient over the indicated range.
-     **/
-    public static GradientScale makeHeatScale(double minimum, double maximum) {
-        GradientScale hs = new GradientScale();
-        hs.setHotColor(Color.RED);
-        hs.setColdColor(Color.BLACK);
-        hs.setMinimum(minimum);
-        hs.setMaximum(maximum);
-        
-        return hs;
-    }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
Main.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/Main.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/Main.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,87 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.awt.GraphicsDevice;
-import java.awt.GraphicsEnvironment;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Random;
-
-/**
- * The class <code>Main</code> can be used to create an event display that
- * reads from file. By default it reads from "cluster-hit.txt" at the class
- * path root. This can be changed by altering the line<br/>
- * <code>window.setDataSource("cluster-hit.txt")</code><br/>
- **/
-public class Main {
-    private static final Viewer window = new Viewer();
-    
-    public static void main(String[] args) throws IOException {
-        // Get screen size of primary monitor
-        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
-        int screenWidth = gd.getDisplayMode().getWidth();
-        int screenHeight = gd.getDisplayMode().getHeight();
-        
-        // Set the viewer location and make it visible
-        window.setLocation((screenWidth - window.getPreferredSize().width) / 2,
-                (screenHeight - window.getPreferredSize().height) / 2);
-        window.setDataSource("cluster-hit.txt");
-        window.displayNextEvent();
-        
-        /**
-        int key = 0;
-        while((key = System.in.read()) != 10) { }
-        **/
-        window.setVisible(true);
-    }
-    
-    static void makeData() {
-        // Generate a random test input file.
-        Random rng = new Random();
-        try {
-            // Make a file writer to write the results.
-            FileWriter writer = new FileWriter("cluster-hit.txt");
-            
-            // Make 10 - 100 events.
-            int events = 10 + rng.nextInt(91);
-            
-            // For each events, generate some data.
-            for (int e = 0; e < events; e++) {
-                // Write the event header.
-                writer.append("Event\n");
-                
-                // Make 3 - 15 hits.
-                int hits = 3 + rng.nextInt(13);
-                for (int h = 0; h < hits; h++) {
-                    // Write identifier.
-                    writer.append("EcalHit\t");
-                    
-                    // Make a random address.
-                    // x = [0, 46); y = [0, 11)
-                    int ix = rng.nextInt(46);
-                    int iy = rng.nextInt(11);
-                    writer.append(ix + "\t" + iy + "\n");
-                }
-                
-                // Make 0 - 4 clusters.
-                int clusters = rng.nextInt(5);
-                for (int c = 0; c < clusters; c++) {
-                    // Write identifier.
-                    writer.append("Cluster\t");
-                    
-                    // Make a random address.
-                    // x = [0, 46); y = [0, 11)
-                    int ix = rng.nextInt(46);
-                    int iy = rng.nextInt(11);
-                    writer.append(ix + "\t" + iy + "\n");
-                }
-            }
-            
-            // Close the writer.
-            writer.close();
-        }
-        catch (IOException e) {
-            System.err.println(e.getStackTrace());
-            System.exit(1);
-        }
-    }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
MultiGradientScale.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/MultiGradientScale.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/MultiGradientScale.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,148 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.awt.Color;
-import java.util.ArrayList;
-
-/**
- * The class <code>MultiGradientScale</code> is an implementation of
- * <code>ColorScale</code> that maps values to a color over several different
- * individual <code>GradientScale</code> objects to allow for multi-color
- * mapping.
- * 
- * @author Kyle McCarty
- **/
-public final class MultiGradientScale extends ColorScale {
-    // Stores the colors in the map.
-    private ArrayList<Color> colorList = new ArrayList<Color>();
-    // Stores the component mapping scales.
-    private ArrayList<GradientScale> scaleList = new ArrayList<GradientScale>();
-    
-    /**
-     * <b>addColor</b><br/><br/>
-     * <code>public void <b>addColor</b>(Color c)</code><br/><br/>
-     * Adds a new color to the mapping scale. The first color will be the
-     * coldest with subsequent colors being more and more hot.
-     * @param c - The color to add.
-     **/
-    public void addColor(Color c) {
-        colorList.add(c);
-        revalidate();
-    }
-    
-    /**
-     * <b>removeColor</b><br/><br/>
-     * <code>public boolean <b>removeColor</b>(int colorIndex)</code><br/><br/>
-     * Removes the nth color from the mapping scale.
-     * @param colorIndex - The index of the color to be removed.
-     * @return Returns <code>true</code> if the color was removed and
-     * <code>false</code> if it was not.
-     **/
-    public boolean removeColor(int colorIndex) {
-        // Only remove the value if the index is valid.
-        if (colorIndex >= 0 && colorIndex < colorList.size()) {
-            colorList.remove(colorIndex);
-            revalidate();
-            return true;
-        }
-        else { return false; }
-    }
-    
-    public Color getColor(double value) {
-        // Get the number of colors and scales.
-        int colors = colorList.size();
-        int scales = scaleList.size();
-        
-        // If there are no colors or scales, give black.
-        if (colors == 0 && scales == 0) { return Color.BLACK; }
-        
-        // If there are no scales, but there is a color, give that.
-        if (scales == 0 && colors == 1) { return colorList.get(0); }
-        
-        // Scale the value if logarithmic.
-        double sValue;
-        if (linear) { sValue = value; }
-        else { sValue = Math.log10(scale * value); }
-        
-        // Otherwise, determine which scale should get the value.
-        for (GradientScale s : scaleList) {
-            if (sValue < s.getMaximum()) {
-                return s.getColor(sValue);
-            }
-        }
-        
-        // If it didn'tappear in the list, it is the hottest color.
-        return colorList.get(colors - 1);
-    }
-    
-    protected void revalidate() {
-        // Handle the default logarithmic revalidation.
-        super.revalidate();
-        
-        // Redistribute the lists.
-        scaleList.clear();
-        
-        // We need at least colors to make a scale - otherwise, the
-        // special cases handle the color.
-        int colors = colorList.size();
-        if (colors < 2) { return; }
-        
-        // Otherwise, define the list variables.
-        double sStep;
-        double sMin;
-        if (linear) {
-            sStep = (max - min) / (colors - 1);
-            sMin = min;
-        }
-        else {
-            sStep = (lMax - lMin) / (colors - 1);
-            sMin = lMin;
-        }
-        double sMax = sMin + sStep;
-        
-        // Generate a list of scales.
-        for (int i = 0; i < (colors - 1); i++) {
-            // Make and add a scale.
-            GradientScale s = new GradientScale();
-            s.setMinimum(sMin);
-            s.setMaximum(sMax);
-            s.setColdColor(colorList.get(i));
-            s.setHotColor(colorList.get(i + 1));
-            scaleList.add(s);
-            
-            // Update the min/max.
-            sMin = sMax;
-            sMax += sStep;
-        }
-    }
-    
-    /**
-     * <b>makeRainboowScale</b><br/><br/>
-     * <code>public static <b>makeRainbowScale</b>(double minimum, double maximum)</code><br/><br>
-     * Creates a <code>MultiGradientScale</code> that maps values from purple,
-     * to blue, to cyan, to green, to yellow, and to red at the hottest.
-     * @param minimum - The lowet mapped value.
-     * @param maximum - The highest mapped value.
-     * @return Returns the rainbow color mapping scale.
-     **/
-    public static MultiGradientScale makeRainbowScale(double minimum, double maximum) {
-        int str = 165;
-        Color purple = new Color(55, 0, 55);
-        Color blue = new Color(0, 0, str);
-        Color cyan = new Color(0, str, str);
-        Color green = new Color(0, str, 0);
-        Color yellow = new Color(str, str, 0);
-        Color red = new Color(str, 0, 0);
-        
-        MultiGradientScale mgs = new MultiGradientScale();
-        mgs.addColor(purple);
-        mgs.addColor(blue);
-        mgs.addColor(cyan);
-        mgs.addColor(green);
-        mgs.addColor(yellow);
-        mgs.addColor(red);
-        mgs.setMinimum(minimum);
-        mgs.setMaximum(maximum);
-        
-        return mgs;
-    }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
StatusPanel.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/StatusPanel.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/StatusPanel.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,170 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.Graphics;
-
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-
-/**
- * Class <code>StatusPanel</code> displays text in a set of fields.
- *
- *@author Kyle McCarty
- */
-public class StatusPanel extends JPanel {
-	private static final long serialVersionUID = -8353479383875379010L;
-	private int leftBuffer = 10;
-	private int upperBuffer = 10;
-	private BackPanel background = new BackPanel();
-	private JLabel[][] field;
-	private static final String nullValue = "---";
-	
-	/**
-	 * <b>StatusPanel</b><br/><br/>
-	 * <code>public <b>StatusPanel</b>(String... fieldName)</code><br/><br/>
-	 * Creates a new status panel with display fields with the indicated
-	 * names. They will be assigned a field index in the order that they
-	 * are given starting with zero.
-	 * @param fieldName - The names of the fields to display.
-	 */
-	public StatusPanel(String... fieldName) {
-		// Initialize the component.
-		super();
-		
-		// Set the layout manager to manual.
-		setLayout(null);
-		
-		// Build the text fields.
-		int curZ = 0;
-		field = new JLabel[fieldName.length][2];
-		for(int i = 0; i < field.length; i++) {
-			for(int j = 0; j < field[i].length; j++) {
-				field[i][j] = new JLabel();
-				field[i][j].setOpaque(true);
-				field[i][j].setBackground(Color.WHITE);
-				add(field[i][j]);
-				setComponentZOrder(field[i][j], curZ);
-				curZ++;
-			}
-			field[i][0].setText(fieldName[i] + ":");
-		}
-		
-		// Start the fields as null by default.
-		clearValues();
-		
-		// Build the background panel.
-		add(background);
-		setComponentZOrder(background, curZ);
-	}
-	
-	/**
-	 * <b>setFieldValue</b><br/><br/>
-	 * Sets the value of the indicated field.
-	 * @param index - The field's index.
-	 * @param value - The new value to display.
-	 * @throws IndexOutOfBoundsException Occurs when the field index
-	 * is neither more than the existing number of fields or is negative.
-	 */
-	public void setFieldValue(int index, String value) throws IndexOutOfBoundsException {
-		if(index >= 0 && index < field.length) {
-			if(value == null) { field[index][1].setText(nullValue); }
-			else  { field[index][1].setText(value); }
-		}
-		else { throw new IndexOutOfBoundsException("Invalid field index."); }
-	}
-	
-	public void setSize(int width, int height) {
-		super.setSize(width, height);
-		resize();
-	}
-	
-	public void setSize(Dimension d) {
-		super.setSize(d);
-		resize();
-	}
-	
-	/**
-	 * <b>clearValues</b><br/><br/>
-	 * <code>public void <b>clearValues</b>()</code><br/><br/>
-	 * Sets all of the fields on the status display to the null value.
-	 */
-	public void clearValues() {
-		for(int i = 0; i < field.length; i++) {
-			field[i][1].setText(nullValue);
-		}
-	}
-	
-	private void resize() {
-		// Define the width an height as convenience variables.
-		int width = getWidth();
-		int height = getHeight();
-		
-		// Size the background panel.
-		background.setBounds(0, 0, width, height);
-		
-		// Size and place the text labels.
-		if(field.length != 0) {
-			int labelHeight = (height - (int)(upperBuffer + 5)) / field.length;
-			int labelRem = (height - upperBuffer - 8) % field.length;
-			int curY = (int)(upperBuffer + 2);
-			for(int i = 0; i < field.length; i++) {
-				// Determine the appropriate field height.
-				int thisHeight = labelHeight;
-				if(labelRem > 0) {
-					thisHeight++;
-					labelRem--;
-				}
-			
-				// Place the field.
-				field[i][0].setBounds(leftBuffer, curY, 65, thisHeight);
-				field[i][1].setBounds(getNextX(field[i][0]), curY, 100, thisHeight);
-				
-				// Increment the current position.
-				curY += thisHeight;
-			}
-		}
-	}
-	
-	/**
-	 *<b>getNextX</b><br/><br/>
-	 * <code>private int <b>getNextX</b>(Component c)</code><br/><br/>
-	 * Finds the x-coordinate immediately after the component.
-	 * @param c - The component of which to find the end.
-	 * @return Returns the x-coordinate at the end of the component. 
-	 */
-	private final static int getNextX(Component c) { return getNextX(c, 0); }
-	
-	/**
-	/**
-	 *<b>getNextX</b><br/><br/>
-	 * <code>private int <b>getNextX</b>(Component c, int buffer)</code><br/><br/>
-	 * Finds the x-coordinate after the component with a given buffer.
-	 * @param c - The component of which to find the end.
-	 * @param buffer - The extra space after the component to be included.
-	 * @return Returns the x-coordinate at the end of the component,
-	 * with a buffer length.
-	 */
-	private final static int getNextX(Component c, int buffer) {
-		return c.getX() + c.getWidth() + buffer;
-	}
-	
-	/**
-	 * Class <code>BackPanel</code> simply renders the background panel
-	 * for the status panel.
-	 */
-	private class BackPanel extends JPanel {
-		private static final long serialVersionUID = 4997805650267243080L;
-
-		public void paint(Graphics g) {
-			// Render the panel background.
-			g.setColor(Color.WHITE);
-			g.fillRect(0, upperBuffer, getWidth(), getHeight() - upperBuffer);
-			g.setColor(Color.GRAY);
-			g.drawRect(0, upperBuffer, getWidth() - 1, getHeight() - upperBuffer - 1);
-			g.setColor(Color.LIGHT_GRAY);
-			g.drawRect(1, upperBuffer + 1, getWidth() - 3, getHeight() - upperBuffer - 3);
-		}
-	}
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal
Viewer.java removed after 306
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/Viewer.java	2014-03-17 19:07:10 UTC (rev 306)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/Viewer.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -1,458 +0,0 @@
-package org.hps.monitoring.ecal;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Point;
-import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.KeyListener;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.MouseMotionListener;
-import java.io.IOException;
-import java.text.DecimalFormat;
-import java.util.HashMap;
-import java.util.List;
-
-import javax.swing.JFrame;
-
-import org.lcsim.event.EventHeader;
-import org.lcsim.hps.recon.ecal.HPSCalorimeterHit;
-import org.lcsim.hps.recon.ecal.HPSEcalCluster;
-
-/**
- * The class <code>Viewer</code> handles initialization of the calorimeter panel
- * with the proper settings, provides a window for it to live in, and feeds it
- * events.
- * 
- * @author Kyle McCarty
- **/
-public class Viewer extends JFrame {
-    // Java-suggested variable.
-    private static final long serialVersionUID = -2022819652687941812L;
-    // The calorimeter panel.
-    private final EcalPanel ecalPanel = new EcalPanel(46, 11);
-    // The crystal status panel.
-    private final StatusPanel statusPanel = new StatusPanel("x Index", "y Index", "Energy");
-    // The event data reader.
-    private EventManager em = null;
-    // Map the cluster location to the cluster object.
-    private HashMap<Point, Cluster> clusterMap = new HashMap<Point, Cluster>();
-    // Whether crystal should be highlighted when hovered over.
-    private boolean highlight = true;
-    // Store the last crystal to be highlighted.
-	private Point lastCrystal = null;
-	// DEPRECATED :: Store the old highlight color.
-	//private Color oldHighlight = null;
-	// DEPRECATED :: Store the currently highlighted cluster.
-	//private Point lastCluster = null;
-	// Stores whether the background color is set or not.
-	private boolean background = false;
-	// Define the highlight color for dark backgrounds.
-	private static final Color HIGHLIGHT_CURSOR_DARK = new Color(90, 170, 250);
-	// Define the highlight color for light backgrounds.
-	private static final Color HIGHLIGHT_CURSOR_LIGHT = Color.BLUE;
-	// Define the highlight color for cluster component hits.
-	private static final Color HIGHLIGHT_CLUSTER_COMPONENT = Color.RED;
-	// Define the highlight color for cluster shared hits.
-	private static final Color HIGHLIGHT_CLUSTER_SHARED = Color.YELLOW;
-	private boolean processing = false;
-    
-    /**
-     * <b>Viewer</b><br/><br/>
-     * <code>public <b>Viewer</b>()</code><br/><br/>
-     * Initializes the viewer window and calorimeter panel.
-     **/
-    public Viewer() {
-        // Initialize the viewer window
-        super();
-        setTitle("HPS Ecal Cluster Viewer");
-        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
-        setPreferredSize(new Dimension(1060, 600));
-        setMinimumSize(new Dimension(1060, 525));
-        setLayout(null);
-        
-        // Set the scaling settings.
-        ecalPanel.setMinimum(0.0001);
-        ecalPanel.setMaximum(3000);
-        ecalPanel.setScalingLogarithmic();
-        
-        // Disable the crystals in the calorimeter panel along the beam gap.
-        for (int i = -23; i < 24; i++) {
-            ecalPanel.setCrystalEnabled(getPanelX(i), 5, false);
-            if (i > -11 && i < -1) {
-                ecalPanel.setCrystalEnabled(getPanelX(i), 4, false);
-                ecalPanel.setCrystalEnabled(getPanelX(i), 6, false);
-            }
-        }
-        
-        // Make a key listener to change events.
-        addKeyListener(new EcalKeyListener());
-        
-        // Make a mouse motion listener to monitor mouse hovering.
-        getContentPane().addMouseListener(new EcalMouseListener());
-        getContentPane().addMouseMotionListener(new EcalMouseMotionListener());
-        
-        // Add the panels.
-        add(ecalPanel);
-        add(statusPanel);
-        
-        // Add a listener to update everything when the window changes size
-        addComponentListener(new ResizeListener());
-    }
-    
-    public void setSize(int width, int height) {
-        super.setSize(width, height);
-        resize();
-    }
-    
-    public void setSize(Dimension d) {
-        setSize(d.width, d.height);
-    }
-    
-    /**
-     * <b>setDataSouce</b><br/><br/>
-     * <code>public void <b>setDataSource</b>(String filepath)</code><br/><br/>
-     * Sets the viewer to read from the indicated data source.
-     * @param filepath - The full path to the desired data file.
-     * @throws IOException Occurs when there is an error opening the data file.
-     **/
-    public void setDataSource(String filepath) throws IOException {
-        em = new EventManager(filepath);
-    }
-    
-    /**
-     * <b>displayNextEvent</b><br/><br/>
-     * <code>public void <b>displayNextEvent</b>()</code><br/><br/>
-     * Feeds the calorimeter panel the data from the next event.
-     * @throws IOException Occurs when there is an issue with reading the data file.
-     **/
-    public void displayNextEvent() throws IOException {
-        // Clear the calorimeter panel.
-        ecalPanel.clearCrystals();
-        ecalPanel.clearHighlight();
-        
-        // Clear the cluster map and cluster highlighting.
-        clusterMap.clear();
-        resetCursor();
-        
-        // If there is no data source, we can not do anything.
-        if (em == null) { return; }
-        
-        // Otherwise, get the next event.
-        em.readEvent();
-        
-        // Display it.
-        for (EcalHit h : em.getHits()) {
-            int ix = getPanelX(h.getX());
-            int iy = getPanelY(h.getY());
-            ecalPanel.addCrystalEnergy(ix, iy, h.getEnergy());
-        }
-        for (Cluster c : em.getClusters()) {
-        	Point seedHit = c.getSeedHit();
-            int ix = getPanelX(seedHit.x);
-            int iy = getPanelY(seedHit.y);
-            ecalPanel.setCrystalCluster(ix, iy, true);
-            clusterMap.put(new Point(getPanelX(seedHit.x), getPanelY(seedHit.y)), c);
-        }
-        
-        // Redraw the calorimeter panel.
-        ecalPanel.redraw();
-    }
-    
-    public void displayLCIOEvent(EventHeader event, String ecalCollectionName, String clusterCollectionName) {
-        // Make sure that a draw is not in process.
-        if(processing) { return; }
-
-        // Otherwise, we are now processing.
-        processing = true;
-
-        // Get the list of clusters and hits.
-        List<HPSEcalCluster> clusters = event.get(HPSEcalCluster.class, clusterCollectionName);
-        List<HPSCalorimeterHit> hits = event.get(HPSCalorimeterHit.class, ecalCollectionName);
-        
-        // Reset the calorimeter panel.
-        ecalPanel.clearCrystals();
-        
-        // Add the calorimeter hits.
-        for(HPSCalorimeterHit hit : hits) {
-            int ix = hit.getIdentifierFieldValue("ix");
-            int iy = hit.getIdentifierFieldValue("iy");
-            double energy = hit.getRawEnergy();
-            ecalPanel.addCrystalEnergy(getPanelX(ix), getPanelY(iy), energy);
-        }
-        
-        // Add clusters.
-        for(HPSEcalCluster cluster : clusters) {
-            HPSCalorimeterHit seed = (HPSCalorimeterHit) cluster.getSeedHit();
-            int ix = seed.getIdentifierFieldValue("ix");
-            int iy = seed.getIdentifierFieldValue("iy");
-            ecalPanel.setCrystalCluster(getPanelX(ix), getPanelY(iy), true);
-        }
-        
-        // Redraw the panel.
-        ecalPanel.redraw();
-        
-        // We are finished drawing.
-        processing = false;
-    }
-    
-    /**
-     * <b>resize</b><br/><br/>
-     * <code>private void <b>resize</b>()</code><br/><br/>
-     * Handles proper resizing of the window and its components.
-     **/
-    private void resize() {
-    	// Define the status panel height.
-    	int statusHeight = 125;
-    	
-        // Size and position the calorimeter display.
-        ecalPanel.setLocation(0, 0);
-        ecalPanel.setSize(getContentPane().getWidth(), getContentPane().getHeight() - statusHeight);
-        
-        // Size and position the status panel.
-        statusPanel.setLocation(0, ecalPanel.getHeight());
-        statusPanel.setSize(getContentPane().getWidth(), statusHeight);
-    }
-    
-    /**
-     * <b>getPanelX</b><br/><br/>
-     * <code>public int <b>getPanelX</b>(int ecalX)</code><br/><br/>
-     * Converts the LCSim x-coordinate to the calorimeter panel's coordinate
-     * system.
-     * @param ecalX - An LCSim calorimeter x-coordinate.
-     * @return Returns the x-coordinate in the calorimeter panel's coordinate
-     * system as an <code>int</code>.
-     **/
-    public int getPanelX(int ecalX) {
-        if (ecalX <= 0) { return ecalX + 23; }
-        else { return ecalX + 22; }
-    }
-    
-    /**
-     * <b>getPanelY</b><br/><br/>
-     * <code>public int <b>getPanelY</b>(int ecalY)</code><br/><br/>
-     * Converts the LCSim y-coordinate to the calorimeter panel's coordinate
-     * system.
-     * @param ecalY - An LCSim calorimeter y-coordinate.
-     * @return Returns the y-coordinate in the calorimeter panel's coordinate
-     * system as an <code>int</code>.
-     **/
-    public int getPanelY(int ecalY) { return 5 - ecalY; }
-    
-    /**
-     * <b>getEcalX</b><br/><br/>
-     * <code>public int <b>getEcalX</b>(int panelX)</code><br/><br/>
-     * Converts the panel x-coordinate to the calorimeter's coordinate
-     * system.
-     * @param panelX - A panel x-coordinate.
-     * @return Returns the x-coordinate in the calorimeter's coordinate
-     * system as an <code>int</code>.
-     */
-    public int getEcalX(int panelX) {
-    	if(panelX > 22) { return panelX - 22; }
-    	else { return panelX - 23; }
-    }
-    
-    /**
-     * <b>getEcalY</b><br/><br/>
-     * <code>public int <b>getEcalY</b>(int panelY)</code><br/><br/>
-     * Converts the panel y-coordinate to the calorimeter's coordinate
-     * system.
-     * @param panelY - A panel y-coordinate.
-     * @return Returns the y-coordinate in the calorimeter's coordinate
-     * system as an <code>int</code>.
-     */
-    public int getEcalY(int panelY) { return 5 - panelY; }
-    
-    /**
-     * <b>resetCursor</b><br/><br/>
-     * <code>private void <b>resetCursor</b>()</code><br/><br/>
-     * Performs the cursor highlight calculations for whatever the
-     * most recently highlighted crystal was. This should be called
-     * if the panel is cleared to reset the cursor highlight.
-     */
-    private void resetCursor() {
-    	Point temp = lastCrystal;
-    	lastCrystal = null;
-    	setCursorHighlight(temp);
-    }
-    
-    /**
-     * <b>setCursorHighlight</b><br/><br/>
-     * <code>private boolean <b>setCursorHighlight</b>(Point crystal)</code><br/><br/>
-     * Sets the highlighting for the indicated crystal and clears the
-     * highlighting on any previously highlighted crystal. Note that
-     * this will clear any existing highlighting.
-     * @param crystal - The crystal to highlight.
-     * @return Returns <code>true</code> if a different crystal is
-     * highlighted than before and <code>false</code> if it is the
-     * same crystal.
-     */
-    private boolean setCursorHighlight(Point crystal) {
-		// Get the appropriate highlight color.
-		Color hc = null;
-		if(!background) { hc = HIGHLIGHT_CURSOR_DARK; }
-		else { hc = HIGHLIGHT_CURSOR_LIGHT; }
-		
-		// Define helper variables.
-		boolean crystalChanged;
-		boolean cNull = (crystal == null);
-		boolean lNull = (lastCrystal == null);
-		
-		// Determine if the selected crystal has changed.
-		if(cNull && lNull) { crystalChanged  = false; }
-		else if(cNull ^ lNull) { crystalChanged = true; }
-		else { crystalChanged = !lastCrystal.equals(crystal); }
-		
-		// If so, clear the highlighting and reset it.
-		if(crystalChanged) {
-			// Clear the old highlighting.
-			ecalPanel.clearHighlight();
-			
-			// If the current crystal is a cluster, highlight the cluster.
-			Cluster cluster = clusterMap.get(crystal);
-			if(highlight && cluster != null) {
-				for(Point ch : cluster.getComponentHits()) {
-					ecalPanel.setCrystalHighlight(getPanelX(ch.x), getPanelY(ch.y), HIGHLIGHT_CLUSTER_COMPONENT);
-				}
-				for(Point sh : cluster.getSharedHits()) {
-					ecalPanel.setCrystalHighlight(getPanelX(sh.x), getPanelY(sh.y), HIGHLIGHT_CLUSTER_SHARED);
-				}
-			}
-			
-			// If the current crystal is defined, highlight it.
-			if(highlight && crystal != null) { ecalPanel.setCrystalHighlight(crystal.x, crystal.y, hc); }
-		}
-		
-		// Set the last crystal to match the current one.
-		lastCrystal = crystal;
-		
-		// Return whether a redraw is necessary.
-		return crystalChanged;
-    }
-    
-    /**
-     * The <code>EcalListener</code> class binds the enter key to the
-     * <code>displayNextEvent</code> method. Also allows for toggling
-     * of highlighting with 'h' and background with 'b.' Swaps scale
-     * from linear to logarithmic with 'l'.
-     **/
-    private class EcalKeyListener implements KeyListener {
-        public void keyPressed(KeyEvent e) { }
-        
-        public void keyReleased(KeyEvent e) {
-            // If enter was pressed, go to the next event.
-            if (e.getKeyCode() == 10) {
-                try { displayNextEvent(); }
-                catch (IOException ex) {
-                    System.err.println(ex.getMessage());
-                    System.exit(1);
-                }
-            }
-            // 'b' toggles the default white background.
-            else if(e.getKeyCode() == 66) {
-            	if(background) { ecalPanel.setCrystalDefaultColor(null); }
-            	else { ecalPanel.setCrystalDefaultColor(Color.WHITE); }
-            	ecalPanel.redraw();
-            	background = !background;
-            }
-            // 'h' toggles highlighting the crystal under the cursor.
-            else if(e.getKeyCode() == 72) {
-            	if(highlight) {
-            		if(setCursorHighlight(null)) { ecalPanel.redraw(); }
-            	}
-            	highlight = !highlight;
-            }
-            // 'l' toggles linear or logarithmic scaling.
-            else if(e.getKeyCode() == 76) {
-            	if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
-            	else { ecalPanel.setScalingLinear(); }
-            	ecalPanel.redraw();
-            }
-            else { System.out.printf("Key Code: %d%n", e.getKeyCode()); }
-        }
-        
-        public void keyTyped(KeyEvent e) { }
-    }
-    
-    /**
-     * The <code>EcalMouseListener</code> handles removing highlighting
-     * and crystal field information when the cursor leaves the window.
-     */
-    private class EcalMouseListener implements MouseListener {
-		public void mouseClicked(MouseEvent e) { }
-		
-		public void mouseEntered(MouseEvent e) { }
-		
-		public void mouseExited(MouseEvent e) {
-			setCursorHighlight(null);
-			statusPanel.clearValues();
-			ecalPanel.redraw();
-		}
-		
-		public void mousePressed(MouseEvent e) { }
-		
-		public void mouseReleased(MouseEvent e) { }
-    }
-    
-    /**
-     * The <code>EcalMouseMotionListener</code> handles updating of
-     * the highlighted crystal and status panel information when the
-     * mouse moves over the window.
-     */
-    private class EcalMouseMotionListener implements MouseMotionListener {    	
-		public void mouseDragged(MouseEvent arg0) { }
-		
-		public void mouseMoved(MouseEvent e) {
-			// Get the mouse coordinates, corrected for the panel position.
-			int correctedX = e.getX();
-			int correctedY = e.getY();
-			
-			// Get the crystal that the mouse is in.
-			Point crystal = ecalPanel.getCrystalID(correctedX, correctedY);
-			
-			// Mark the current crystal for highlighting.
-			boolean redraw = setCursorHighlight(crystal);
-			
-			// If this necessitates a redraw of the panel, do so.
-			if(redraw) {
-				lastCrystal = crystal;
-				ecalPanel.redraw();
-				
-				if(crystal != null) {
-					// Determine if the crystal is in the beam gap.
-					boolean[] beamGap = new boolean[2];
-					beamGap[0] = (getEcalY(crystal.y) == 0);
-					beamGap[1] = Math.abs(getEcalY(crystal.y)) == 1 &&
-							(getEcalX(crystal.x) >= -10 && getEcalX(crystal.x) <= -2);
-					
-					if(beamGap[0] || beamGap[1]) { statusPanel.clearValues(); }
-					else {
-						statusPanel.setFieldValue(0, String.valueOf(getEcalX(crystal.x)));
-						statusPanel.setFieldValue(1, String.valueOf(getEcalY(crystal.y)));
-						DecimalFormat formatter = new DecimalFormat("0.####E0");
-						String energy = formatter.format(ecalPanel.getCrystalEnergy(crystal.x, crystal.y));
-						statusPanel.setFieldValue(2, energy);
-					}
-				}
-				else { statusPanel.clearValues(); }
-			}
-		}
-    }
-    
-    /**
-     * The <code>ResizeListener</code> class ensures that the components remain
-     * at the correct size and location when the window is resized.
-     **/
-    private class ResizeListener implements ComponentListener {
-        public void componentResized(ComponentEvent e) { resize(); }
-        
-        public void componentHidden(ComponentEvent e) { }
-        
-        public void componentMoved(ComponentEvent e) { }
-        
-        public void componentShown(ComponentEvent e) { }
-    }
-}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event
Association.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event/Association.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event/Association.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,61 @@
+package org.hps.monitoring.ecal.event;
+
+import java.awt.Color;
+import java.awt.Point;
+
+/**
+ * Class <code>Association</code> tells the <code>CalorimeterPanel
+</code> to highlight the child crystal in the given color whenever the
+ * parent crystal is selected.
+ * @author Kyle McCarty
+ */
+public final class Association {
+	private final Point parent;
+	private final Point child;
+	private final Color highlight;
+	
+	/**
+	 * <b>Association</b><br/><br/>
+	 * <code>public <b>Association</b>(Point parentCrystal, Point childCrystal, Color highlightColor)</code><br/><br/>
+	 * Creates an association between a child crystal and a parent
+	 * crystal.
+	 * @param parentCrystal - The crystal with which the child crystal
+	 * is connected.
+	 * @param childCrystal - The connected crystal.
+	 * @param highlightColor - The color in which the child crystal
+	 * should be highlighted.
+	 */
+	public Association(Point parentCrystal, Point childCrystal, Color highlightColor) {
+		parent = parentCrystal;
+		child = childCrystal;
+		highlight = highlightColor;
+	}
+	
+	/**
+	 * <b>getChildCrystal</b><br/><br/>
+	 * <code>public Point <b>getChildCrystal</b>()</code><br/><br/>
+	 * Indicates the indices for the child crystal.
+	 * @return Returns the child crystal's indices in a <code>Point
+	 * </code> object.
+	 */
+	public Point getChildCrystal() { return child; }
+	
+	/**
+	 * <b>getHighlight</b><br/><br/>
+	 * <code>public Color <b>getHighlight</b>()</code><br/><br/>
+	 * Gets the color with which the child crystal should be highlighted
+	 * whenever the parent crystal is selected.
+	 * @return Returns the highlight color as a <code>Color</code> object.
+	 */
+	public Color getHighlight() { return highlight; }
+	
+	/**
+	 * <b>getParentCrystal</b><br/><br/>
+	 * <code>public Point <b>getParentCrystal</b>()</code><br/><br/>
+	 * Indicates the indices for the parent crystal with which the
+	 * child crystal is connected.
+	 * @return Returns the parent crystal's indices in a <code>Point
+	 * </code> object.
+	 */
+	public Point getParentCrystal() { return parent; }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event
Cluster.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event/Cluster.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event/Cluster.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,171 @@
+package org.hps.monitoring.ecal.event;
+
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The class <code>Cluster</code> represents a cluster center and a
+ * collection of additional hits that together form a hit cluster.
+ * 
+ * @author Kyle McCarty
+ */
+public final class Cluster {
+	private final Point center;
+	private final double energy;
+	private ArrayList<Point> hitList = new ArrayList<Point>();
+	private ArrayList<Point> shareList = new ArrayList<Point>();
+	
+	/**
+	 * <b>Cluster</b><br/><br/>
+	 * <code>public <b>Cluster</b>(int ix, int iy)</code><br/><br/>
+	 * Creates a new cluster. All clusters are required to have a
+	 * cluster center.
+	 * @param ix - The cluster center's x-index.
+	 * @param iy - The cluster center's y-index.
+	 */
+	public Cluster(int ix, int iy) { this(new Point(ix, iy), Double.NaN); }
+	
+	/**
+	 * <b>Cluster</b><br/><br/>
+	 * <code>public <b>Cluster</b>(Point clusterCenter)</code><br/><br/>
+	 * Creates a new cluster. All clusters are required to have a seed
+	 * hit.
+	 * @param clusterCenter - The <code>Point</code> object indicating in
+	 * which crystal the seed hit occurred.
+	 */
+	public Cluster(Point clusterCenter) { this(clusterCenter, Double.NaN); }
+	
+	/**
+	 * <b>Cluster</b><br/><br/>
+	 * <code>public <b>Cluster</b>(int ix, int iy)</code><br/><br/>
+	 * Creates a new cluster. All clusters are required to have a
+	 * cluster center.
+	 * @param ix - The cluster center's x-index.
+	 * @param iy - The cluster center's y-index.
+	 * @param energy - The cluster's energy.
+	 */
+	public Cluster(int ix, int iy, double energy) { this(new Point(ix, iy), energy); }
+	
+	/**
+	 * <b>Cluster</b><br/><br/>
+	 * <code>public <b>Cluster</b>(Point clusterCenter)</code><br/><br/>
+	 * Creates a new cluster. All clusters are required to have a seed
+	 * hit.
+	 * @param clusterCenter - The <code>Point</code> object indicating in
+	 * which crystal the seed hit occurred.
+	 * @param energy - The cluster's energy.
+	 */
+	public Cluster(Point clusterCenter, double energy) {
+		center = clusterCenter;
+		this.energy = energy;
+	}
+	
+	/**
+	 * <b>addComponentHit</b><br/><br/>
+	 * <code>public void <b>addComponentHit</b>(int ix, int iy)</code><br/><br/>
+	 * Adds an <code>Point</code> to the list of this cluster's
+	 * component hits.
+	 * @param ix - The component hit's x-coordinate.
+	 * @param iy - The component hit's y-coordinate.
+	 */
+	public void addComponentHit(int ix, int iy) { hitList.add(new Point(ix, iy)); }
+	
+	/**
+	 * <b>addComponentHit</b><br/><br/>
+	 * <code>public void <b>addComponentHit</b>(Point eHit)</code><br/><br/>
+	 * Adds an <code>Point</code> to the list of this cluster's
+	 * component hits.
+	 * @param eHit - The <code>Point</code> object indicating in which
+	 * crystal the hit occurred.
+	 */
+	public void addComponentHit(Point eHit) { hitList.add(eHit); }
+	
+	/**
+	 * <b>addSharedHit</b><br/><br/>
+	 * <code>public void <b>addSharedHit</b>(int ix, int iy)</code><br/><br/>
+	 * Adds an <code>Point</code> to the list of this cluster's shared
+	 * hits.
+	 * @param ix - The shared hit's x-coordinate.
+	 * @param iy - The shared hit's y-coordinate.
+	 */
+	public void addSharedHit(int ix, int iy) { shareList.add(new Point(ix, iy)); }
+	
+	/**
+	 * <b>addSharedHit</b><br/><br/>
+	 * <code>public void <b>addSharedHit</b>(Point eHit)</code><br/><br/>
+	 * Adds an <code>Point</code> to the list of this cluster's shared
+	 * hits.
+	 * @param eHit - The <code>Point</code> object indicating in which
+	 * crystal the hit occurred.
+	 */
+	public void addSharedHit(Point eHit) { shareList.add(eHit); }
+	
+	/**
+	 * <b>getClusterCenter</b><br/><br/>
+	 * <code>public Point <b>getClusterCenter</b>()</code><br/><br/>
+	 * Gets the hit representing the cluster center.
+	 * @return Returns the cluster center hit as an <code>Point</code>.
+	 */
+	public Point getClusterCenter() { return center; }
+	
+	/**
+	 * <b>getClusterEnergy</b><br/><br/>
+	 * <code>public double <b>getClusterEnergy</b>()</code><br/><br/>
+	 * Gets the cluster's energy, if it was defined when the cluster
+	 * was constructed.
+	 * @return Returns the energy of the cluster if it was defined,
+	 * and <code>NaN</code> otherwise.
+	 */
+	public double getClusterEnergy() { return energy; }
+	
+	/**
+	 * <b>getComponentHitCount</b><br/><br/>
+	 * <code>public int <b>getComponentHitCount</b>()</code><br/><br/>
+	 * Indicates how many component hits compose this cluster. Note
+	 * that this does not include the seed hit or shared hits.
+	 * @return Returns the number of component hits in the cluster
+	 * as an <code>int</code>.
+	 */
+	public int getComponentHitCount() { return hitList.size(); }
+	
+	/**
+	 * <b>getComponentHits</b><br/><br/>
+	 * <code>public List<Point> <b>getComponentHits</b>()</code><br/><br/>
+	 * Gets the list of hits that make up the cluster, exempting the
+	 * seed hit and shared hits.
+	 * @return Returns the cluster hits as a <code>List</code> object
+	 * composed of <code>Point</code> objects.
+	 */
+	public List<Point> getComponentHits() { return hitList; }
+	
+	/**
+	 * <b>getHitCount</b><br/><br/>
+	 * <code>public int <b>getHitCount</b>()</code><br/><br/>
+	 * Indicates how many total hits compose this cluster. This includes
+	 * component hits, shared hits, and the seed hit.
+	 * @return Returns the number of component hits in the cluster
+	 * as an <code>int</code>.
+	 */
+	public int getHitCount() { return hitList.size() + shareList.size() + 1; }
+	
+	/**
+	 * <b>getSharedHitCount</b><br/><br/>
+	 * <code>public int <b>getSharedHitCount</b>()</code><br/><br/>
+	 * Indicates how many shared hits compose this cluster. Note that
+	 * this does not include the seed hit or component hits.
+	 * @return Returns the number of shared hits in the cluster as an
+	 * <code>int</code>.
+	 */
+	public int getSharedHitCount() { return shareList.size(); }
+	
+	/**
+	 * <b>getSharedHits</b><br/><br/>
+	 * <code>public List<Point> <b>getSharedHits</b>()</code><br/><br/>
+	 * Gets the list of hits that make up the cluster, exempting the
+	 * seed hit and component hits.
+	 * @return Returns the shared hits as a <code>List</code> object
+	 * composed of <code>Point</code> objects.
+	 */
+	public List<Point> getSharedHits() { return shareList; }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event
EcalHit.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event/EcalHit.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event/EcalHit.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,97 @@
+package org.hps.monitoring.ecal.event;
+
+import java.awt.Point;
+
+/**
+ * The class <code>EcalHit</code> is an extension of <code>Datum</code>
+ * that stores an energy. This is used for reading input from a text
+ * file. <code>CalorimeterHit</code> should be used when reading from
+ * an LCIO file.
+ **/
+public final class EcalHit {
+    // The coordinate on the calorimeter panel.
+    protected final Point loc;
+    // The (raw) energy of this hit.
+    private double energy = 0.0;
+    
+    /**
+     * <b>EcalHit</b><br/><br/>
+     * <code>public <b>EcalHit</b>(int ix, int iy, double energy)</code><br/><br/>
+     * Initializes a calorimeter hit object.
+     * @param ix - The x-coordinate of the hit.
+     * @param iy - The y-coordinate of the hit.
+     * @param energy - The raw energy of the hit.
+     **/
+    public EcalHit(int ix, int iy, double energy) {
+    	this(new Point(ix, iy), energy);
+    }
+    
+    /**
+     * <b>EcalHit</b><br/><br/>
+     * <code>public <b>EcalHit</b>(Point ixy, double energy)</code><br/><br/>
+     * Initializes a calorimeter hit object.
+     * @param ixy - The x/y-coordinates of the hit.
+     * @param energy - The raw energy of the hit.
+     **/
+    public EcalHit(Point ixy, double energy) {
+    	loc = ixy;
+        this.energy = energy;
+    }
+    
+    /**
+     * <b>getEnergy</b><br/><br/>
+     * <code>public double <b>getEnergy</b>()</code><br/><br/>
+     * Indicates the raw energy of this hit.
+     * @return Returns the raw energy as a <code>double</code>.
+     **/
+    public double getEnergy() { return energy; }
+    
+    /**
+     * <b>getLocation</b><br/><br/>
+     * <code>public Point <b>getLocation</b>()</code><br/><br/>
+     * Indicates the location of the object.
+     * @return Returns the object's location as a <code>Point
+     * </code> object.
+     **/
+    public Point getLocation() { return loc; }
+    
+    /**
+     * <b>getX</b><br/><br/>
+     * <code>public int <b>getX</b>()</code><br/><br/>
+     * Indicates the x-coordinate of the object.
+     * @return Returns the x-coordinate as an <code>int</code>.
+     **/
+    public int getX() { return loc.x; }
+    
+    /**
+     * <b>getY</b><br/><br/>
+     * <code>public int <b>getY</b>()</code><br/><br/>
+     * Indicates the y-coordinate of the object.
+     * @return Returns the y-coordinate as an <code>int</code>.
+     **/
+    public int getY() { return loc.y; }
+    
+    /**
+     * <b>setEnergy</b><br/><br/>
+     * <code>public void <b>setEnergy</b>(double energy)</code><br/><br/>
+     * Sets the energy of the hit to the indicated value.
+     * @param energy - The new energy of the hit.
+     **/
+    public void setEnergy(double energy) { this.energy = energy; }
+    
+    /**
+     * <b>setX</b><br/><br/>
+     * <code>public void <b>setX</b>(int x)</code><br/><br/>
+     * Sets the object's x-coordinate.
+     * @param x - The new x-coordinate.
+     **/
+    public void setX(int x) { loc.x = x; }
+    
+    /**
+     * <b>setY</b><br/><br/>
+     * <code>public void <b>setY</b>(int y)</code><br/><br/>
+     * Sets the obejct's y-coordinate.
+     * @param y - The new y-coordinate.
+     **/
+    public void setY(int y) { loc.y = y; }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event
Event.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event/Event.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/event/Event.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,128 @@
+package org.hps.monitoring.ecal.event;
+
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class <code>Event</code> stores all the information the <code>
+ * CalorimeterPanel</code> needs to display an event.
+ * @author Kyle McCarty
+ */
+public final class Event {
+	private List<EcalHit> hitList;
+	private List<Point> clusterList;
+	private List<Association> connectList;
+	
+	/**
+	 * <b>Event</b><br/><br/>
+	 * <code>public <b>Event</b>()</code><br/><br/>
+	 * Creates a new <code>Event</code>.
+	 */
+	public Event() { this(10, 10, 10); }
+	
+	/**
+	 * <b>Event</b><br/><br/>
+	 * <code>public <b>Event</b>(int hits, int clusters)</code><br/><br/>
+	 * Creates a new <code>Event</code> and reserves spaces for the
+	 * given number of hits and cluster centers.
+	 * @param hits - The number of hits for which to reserve space.
+	 * @param clusters - The number of cluster centers for which to
+	 * reserve space.
+	 */
+	public Event(int hits, int clusters) { this(hits, clusters, 10); }
+	
+	/**
+	 * <b>Event</b><br/><br/>
+	 * <code>public <b>Event</b>(int hits, int clusters, int associations)</code><br/><br/>
+	 * Creates a new <code>Event</code> and reserves spaces for the
+	 * given number of hits, cluster centers, and crystal associations.
+	 * @param hits - The number of hits for which to reserve space.
+	 * @param clusters - The number of cluster centers for which to
+	 * reserve space.
+	 * @param associations - The number of crystal associations for
+	 * which to reserve space.
+	 */
+	public Event(int hits, int clusters, int associations) {
+		hitList = new ArrayList<EcalHit>(hits);
+		clusterList = new ArrayList<Point>(clusters);
+		connectList = new ArrayList<Association>(associations);
+	}
+	
+	/**
+	 * <b>Event</b><br/><br/>
+	 * <code>public <b>Event</b>(List<EcalHit> hits, List<Point> clusters, List<Association> associations)</code><br/><br/>
+	 * Creates a new <code>Event</code> and sets its contents to those
+	 * of the given lists. The crystal association list will be empty.
+	 * @param hits - The list of calorimeter hits.
+	 * @param clusters - The list of cluster centers.
+	 */
+	public Event(List<EcalHit> hits, List<Point> clusters) {
+		this(hits, clusters, new ArrayList<Association>());
+	}
+	
+	/**
+	 * <b>Event</b><br/><br/>
+	 * <code>public <b>Event</b>(List<EcalHit> hits, List<Point> clusters, List<Association> associations)</code><br/><br/>
+	 * Creates a new <code>Event</code> and sets its contents to those
+	 * of the given lists.
+	 * @param hits - The list of calorimeter hits.
+	 * @param clusters - The list of cluster centers.
+	 * @param associations - The list of crystal associations.
+	 */
+	public Event(List<EcalHit> hits, List<Point> clusters, List<Association> associations) {
+		hitList = hits;
+		clusterList = clusters;
+		connectList = associations;
+	}
+	
+	/**
+	 * <b>addAssociation</b><br/><br/>
+	 * <code>public void <b>addAssociation</b>(Association connectedCrystal)</code><br/><br/>
+	 * Adds a crystal association to the event.
+	 * @param connectedCrystal - The crystal association to add.
+	 */
+	public void addAssociation(Association connectedCrystal) {
+		connectList.add(connectedCrystal);
+	}
+	
+	/**
+	 * <b>addCluster</b><br/><br/>
+	 * <code>public void <b>addCluster</b>(Point cluster)</code><br/><br/>
+	 * Adds a cluster center to the event.
+	 * @param cluster - The cluster center to add.
+	 */
+	public void addCluster(Point cluster) { clusterList.add(cluster); }
+	
+	/**
+	 * <b>addHit</b><br/><br/>
+	 * <code>public void <b>addHit</b>(EcalHit hit)</code><br/><br/>
+	 * Adds a calorimeter hit to the event.
+	 * @param hit - The calorimeter hit to add.
+	 */
+	public void addHit(EcalHit hit) { hitList.add(hit); }
+	
+	/**
+	 * <b>getAssociations</b><br/><br/>
+	 * <code>public List<Association> <b>getAssociations</b>()</code><br/><br/>
+	 * Gets the list of associated crystals for this event.
+	 * @return Returns the associations in a <code>List</code>.
+	 */
+	public List<Association> getAssociations() { return connectList; }
+	
+	/**
+	 * <b>getClusterCenters</b><br/><br/>
+	 * <code>List<Cluster><b>getClusterCenters</b>()</code><br/><br/>
+	 * Gets the list of cluster centers for this event.
+	 * @return Returns the cluster centers in a <code>List</code>.
+	 */
+	public List<Point> getClusterCenters() { return clusterList; }
+	
+	/**
+	 * <b>getHits</b><br/><br/>
+	 * <code>public List<EcalHit> <b>getHits</b>()</code><br/><br/>
+	 * Gets the list of calorimeter hits for this event.
+	 * @return Returns the hits in a <code>List</code>.
+	 */
+	public List<EcalHit> getHits() { return hitList; }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/exec
Main.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/exec/Main.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/exec/Main.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,84 @@
+package org.hps.monitoring.ecal.exec;
+
+import org.hps.monitoring.ecal.io.TextManager;
+
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.io.IOException;
+
+import org.hps.monitoring.ecal.ui.ActiveViewer;
+import org.hps.monitoring.ecal.ui.ClusterViewer;
+import org.hps.monitoring.ecal.ui.FileViewer;
+import org.hps.monitoring.ecal.ui.OccupancyViewer;
+
+/**
+ * The class <code>Main</code> can be used to create an event display that
+ * reads from file. By default it reads from "cluster-hit.txt" at the class
+ * path root. This can be changed by altering the line<br/>
+ * <code>window.setDataSource("cluster-hit.txt")</code><br/>
+ **/
+public class Main {
+	
+    public static void main(String[] args) throws IOException {
+    	// If "-h" was given as a command line argument, print the
+    	// help text.
+    	for(String s : args) {
+    		if(s.compareTo("-h") == 0 || s.compareTo("--help") == 0) {
+    			System.out.println("HPS Event Display");
+    			System.out.println("Options:");
+    			System.out.printf("%-4s%-12s%s%n", "-h", "--help", "Display help text.");
+    			System.out.printf("%-4s%-12s%s%n", "-i", "--input", "Defines the input file.");
+    			System.out.printf("%-4s%-12s%s%n", "-t", "--type", "Select the type of display.");
+    			System.out.printf("%-4s%-12s%s%n", "", "", "Allowed types: Event, Cluster, Occupancy");
+    			System.exit(0);
+    		}
+    	}
+    	
+    	// If -i or --input was given, set the input file.
+    	String filepath = "raw-hits.txt";
+    	for(int i = 0; i < args.length; i++) {
+    		if(args[i].compareTo("-i") == 0 || args[i].compareTo("--input") == 0) {
+    			if(args.length <= i + 1) { System.out.println("Error: Expected additional argument."); }
+    			else { filepath = args[i + 1]; }
+    		}
+    	}
+    	
+        // Define the viewer. By default, we employ a file viewer.
+        TextManager dataManager = new TextManager(filepath);
+        ActiveViewer window = new FileViewer(dataManager);
+        
+        // Command line argument "-t" allows a different type to be declared.
+        if(args.length >= 2 && (args[0].compareTo("-t") == 0 || args[0].compareTo("--type") == 0)) {
+        	// If an occupancy viewer has been specified...
+        	if(args[1].compareToIgnoreCase("Occupancy") == 0) {
+                window = new OccupancyViewer(dataManager);
+        	}
+        	// If an event viewer has been specified...
+        	else if(args[1].compareToIgnoreCase("Event") == 0) {
+                window = new FileViewer(dataManager);
+        	}
+        	// If a cluster viewer has been specified...
+        	else if(args[1].compareToIgnoreCase("Cluster") == 0) {
+                window = new ClusterViewer(dataManager, 2);
+        	}
+        	// Otherwise, it is an invalid type.
+        	else {
+        		System.out.println("Display type \"" + args[1] + "\" not recognized.");
+        		System.exit(0);
+        	}
+        }
+        
+        // Get screen size of primary monitor
+        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
+        int screenWidth = gd.getDisplayMode().getWidth();
+        int screenHeight = gd.getDisplayMode().getHeight();
+        
+        // Set the viewer window location and make it visible.
+        window.setLocation((screenWidth - window.getPreferredSize().width) / 2,
+                (screenHeight - window.getPreferredSize().height) / 2);
+        window.setVisible(true);
+        
+        // Start the viewer with the first event.
+        window.displayNextEvent();
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/io
AdvancedReader.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/io/AdvancedReader.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/io/AdvancedReader.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,249 @@
+package org.hps.monitoring.ecal.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.Reader;
+
+/**
+ * Class <code>AdvancedReader</code> is an implementation of <code>Reader
+ * </code> that allows lines to be read both forwards and backwards.
+ * 
+ * @author Kyle McCarty
+ */
+public class AdvancedReader extends Reader {
+	private RandomAccessFile file;
+	private long mark = -1;
+	
+	/**
+	 * <b>AdvancedReader</b><br/><br/>
+	 * <code>public <b>AdvancedReader</b>(String filepath)</code><br/><br/>
+	 * Constructs an <code>AdvancedReader</code> from the file located
+	 * at the given file path.
+	 * @param filepath - The path to the file that is to be loaded.
+	 * @throws FileNotFoundException - Occurs when there is no file at
+	 * the indicated file path.
+	 */
+	public AdvancedReader(String filepath) throws FileNotFoundException {
+		file = new RandomAccessFile(filepath, "r");
+	}
+	
+	/**
+	 * <b>AdvancedReader</b><br/><br/>
+	 * <code>public <b>AdvancedReader</b>(File inputFile)</code><br/><br/>
+	 * Constructs an <code>AdvancedReader</code> from the indicated file.
+	 * @param inputFile - The file that is to be loaded.
+	 * @throws FileNotFoundException - Occurs when the referenced file
+	 * does not exist.
+	 */
+	public AdvancedReader(File inputFile) throws FileNotFoundException {
+		file = new RandomAccessFile(inputFile.getAbsolutePath(), "r");
+	}
+	
+	public void close() throws IOException { file.close(); }
+	
+	public void mark(int readAheadLimit) throws IOException { mark = file.getFilePointer(); }
+	
+	public boolean markSupported() { return true; }
+	
+	public int read() throws IOException { return file.read(); }
+	
+	public int read(char[] cbuf) throws IOException {
+		// Read a character from the file into each spot in the array.
+		for(int i = 0; i < cbuf.length; i++) {
+			// Get the next character.
+			int curChar = file.read();
+			
+			// If the character exists, write it to the array.
+			if(curChar != 0) { cbuf[i] = (char)curChar; }
+			
+			// Otherwise, return noting the number of filled slots.
+			else { return i; }
+		}
+		
+		// If we reach the end of the loop, all array slots are filled.
+		return cbuf.length;
+	}
+	
+	public int read(char[] cbuf, int off, int len) throws IOException {
+		// We will fill the char array until either the indicated number
+		// of slots are filled or until we run out of slots.
+		int[] max = { cbuf.length, off + len };
+		
+		// Track the number of slots written to.
+		int slotsFilled = 0;
+		
+		// Fill the array slots.
+		for(int i = off; (i < max[0] && i < max[1]); i++) {
+			// Get the next character.
+			int curChar = file.read();
+			
+			// If the character is defined, write it.
+			if(curChar != -1) {
+				cbuf[i] = (char)curChar;
+				slotsFilled++;
+			}
+			
+			// Otherwise, return noting the number of filled slots.
+			else { return slotsFilled; }
+		}
+		
+		// If we reach the end of the loop, all array slots are filled.
+		return slotsFilled;
+	}
+	
+	/**
+	 * <b>readNextLine</b><br/><br/>
+	 * <code>public String <b>readNextLine</b>()</code><br/><br/>
+	 * Reads the next line of text. A line is considered to be
+	 * terminated by any one of a line feed ('\n'), a carriage return
+	 * ('\r'), or a carriage return followed immediately by a linefeed.
+	 * @return A String containing the contents of the line, not
+	 * including any line-termination characters, or null if the end
+	 * of the stream has been reached.
+	 * @throws IOException - Occurs if there is an issue reading
+	 * the file.
+	 */
+	public String readNextLine() throws IOException {
+		// Store whether the new line we found was a carriage return
+		// or a proper newline.
+		boolean readCarriageReturn = false;
+		
+		// Add characters to a buffer until we reach either a new line
+		// or a carriage return.
+		int curChar = -1;
+		StringBuffer curLine = new StringBuffer();
+		while((curChar = file.read()) != -1) {
+			if(curChar != '\n' && curChar != '\r') { curLine.append((char)curChar); }
+			else if(curChar == '\r') {
+				readCarriageReturn = true;
+				break;
+			}
+			else { break; }
+		}
+		
+		// If we reached this point because we hit the end of the file,
+		// check to make sure that the string buffer contains something.
+		// If it doesn't, then we started at the end of the file and
+		// should return null. Otherwise, just return what is in the
+		// string buffer.
+		if(curChar == -1) {
+			if(curLine.length() == 0) { return null; }
+			else { return curLine.toString(); }
+		}
+		
+		// If we found a carriage return as the new line character, then
+		// we need to check if the next line is a newline '\n' character,
+		// since some systems us '/r/n' for a new line signifier.
+		if(readCarriageReturn) {
+			curChar = file.read();
+			if(curChar != '\n') { file.seek(file.getFilePointer() - 1); }
+		}
+		
+		// Return the buffer.
+		return curLine.toString();
+	}
+	
+	/**
+	 * <b>readPreviousLine</b><br/><br/>
+	 * <code>public String <b>readPreviousLine</b>()</code><br/><br/>
+	 * Reads the previous line of text. A line is considered to be
+	 * terminated by any one of a line feed ('\n'), a carriage return
+	 * ('\r'), or a carriage return followed immediately by a linefeed.
+	 * @return A <code>String</code> containing the contents of the
+	 * line, not including any line-termination characters, or null
+	 * if the beginning of the stream has been reached.
+	 * @throws IOException - Occurs if there is an issue reading
+	 * the file.
+	 */
+	public String readPreviousLine() throws IOException {
+		// Define variables.
+		short newlinesRead = 0;
+		boolean allowDuplicate = false;
+		int lastChar = -1;
+		long offset = file.getFilePointer();
+		int curChar;
+		
+		// If we are at the start of the file, return null.
+		if(offset == 0) { return null; }
+		
+		while(newlinesRead < 3) {	
+			// Decrement the offset.
+			offset -= 1;
+			
+			// If the offset is still within the bounds of the file,
+			// then go to it.
+			if(offset >= 0) {
+				// Get the new file position.
+				file.seek(offset);
+				
+				// Read the character there.
+				curChar = file.read();
+				
+				// If this a new line character, account for it.
+				if(curChar == '\n' || curChar == '\r') {
+					// If we have read a newline, and we then read another
+					// immediately after it, then we will accept that newline
+					// as well, since some systems use 
+					if(((lastChar == '\n' && curChar == '\r') || (lastChar == '\r' && curChar == '\n')) && allowDuplicate) {
+						allowDuplicate = false;
+					}
+					
+					// If this is the first newline character we have
+					// seen, then we have reached the end of the desired
+					// line. We need to keep going until we find the start.
+					else if(newlinesRead < 3) {
+						newlinesRead++;
+						allowDuplicate = true;
+					}
+				}
+				
+				// If a character other than a newline was read after
+				// a newline, then a second newline is always unrelated.
+				else { allowDuplicate = false; }
+				
+				// Set the current character to the last read character.
+				lastChar = curChar;
+			}
+			// If the offset has reached zero, but didn't start there,
+			// then we have reached the start of the file. We handle
+			// this case.
+			else {
+				// Set the pointer to the beginning of the file.
+				file.seek(0);
+				
+				// If we are looking for the final new line, then the
+				// start of the file counts and we should return.
+				if(newlinesRead == 2) { return readNextLine(); }
+				
+				// Otherwise, there is no previous line.
+				else { return null; }
+			}
+		}
+		
+		// If we reach this return line, we should be at the correct
+		// position to read the previous line.
+		return readNextLine();
+	}
+	
+	public void reset() throws IOException {
+		if(mark != -1) { file.seek(mark); }
+		else { throw new IOException("mark() must be called before reset()."); }
+	}
+	
+	public long skip(long n) throws IOException {
+		// Read n characters.
+		for(int i = 0; i < n; i++) {
+			// Get the skipped character.
+			int curChar = file.read();
+			
+			// If it is invalid, return.
+			if(curChar == -1) { return i; }
+		}
+		
+		// If we reached the end of the loop, the requested number of
+		// characters have been skipped.
+		return n;
+	}
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/io
EventManager.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/io/EventManager.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/io/EventManager.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,62 @@
+package org.hps.monitoring.ecal.io;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.hps.monitoring.ecal.event.Cluster;
+import org.hps.monitoring.ecal.event.EcalHit;
+
+/**
+ * Interface <code>EventManager</code> is responsible for traversing
+ * an event data file and extracting lists of calorimeter hits and
+ * hit clusters from it to be passed to a <code>FileViewer</code>.
+ * 
+ * @author Kyle McCarty
+ */
+public interface EventManager {
+    /**
+     * <b>close</b><br/><br/>
+     * <code>public void <b>close</b>()</code><br/><br/>
+     * Closes the event manager. Once this is performed, no additional events
+     * may be read.
+     * @throws IOException Occurs if there is an error closing the file stream.
+     **/
+	public void close() throws IOException;
+	
+    /**
+     * <b>getClusters</b><br/><br/>
+     * <code>public ArrayList<Cluster> <b>getClusters</b>()</code><br/><br/>
+     * Allows access to the current event's list of clusters.
+     * @return Returns the current clusters as an <code>ArrayList
+     * </code> object.
+     **/
+	public List<Cluster> getClusters();
+	
+	/**
+     * <b>getHits</b><br/><br/>
+     * <code>public ArrayList<EcalHit> <b>getHits</b>()</code><br/><br/>
+     * Allows access to the current event's list of hits.
+     * @return Returns the current hits as an <code>ArrayList</code> object.
+     **/
+	public List<EcalHit> getHits();
+	
+	/**
+     * <b>nextEvent</b><br/><br/>
+     * <code>public boolean <b>nextEvent</b>()</code><br/><br/>
+     * Populates the event manager with hits and clusters from the next event.
+     * @return Returns <code>true</code> if an event was read and <code>false
+     * </code> if it was not.
+     * @throws IOException Occurs if there was a file read error.
+     **/
+	public boolean nextEvent() throws IOException;
+	
+    /**
+     * <b>previousEvent</b><br/><br/>
+     * <code>public boolean <b>previousEvent</b>()</code><br/><br/>
+     * Populates the event manager with hits and clusters from the previous event.
+     * @return Returns <code>true</code> if an event was read and <code>false
+     * </code> if it was not.
+     * @throws IOException Occurs if there was a file read error.
+     **/
+	public boolean previousEvent() throws IOException;
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/io
TextManager.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/io/TextManager.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/io/TextManager.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,191 @@
+package org.hps.monitoring.ecal.io;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+import org.hps.monitoring.ecal.event.Cluster;
+import org.hps.monitoring.ecal.event.EcalHit;
+
+/**
+ * The class <code>TextManager</code> handles loading hits and clusters from a
+ * text file to populate the calorimeter panel. Input should be of the form
+ * Event [Number]
+ * Indicates the start of a new event.
+ * 
+ * EcalHit [X] [Y] [Energy]
+ * Represents a calorimeter hit at coordinates ([X], [Y]) and with energy
+ * [Energy]. Coordinates should be in calorimeter form (x = [-23, 23] and
+ * y = [-5, 5]) and must integers. Energy can be a decimal value. Brackets
+ * should not be included in the line.
+ * 
+ * Cluster [X] [Y] [Energy]
+ * Represents the location of a cluster at coordinates ([X], [Y]) with
+ * [Energy] total cluster energy. Note that the [Energy] field is not
+ * required. Brackets should not be included in the line.
+ * 
+ * CompHit [X] [Y]
+ * Represents a component hit of the previously declared cluster and located
+ * at coordinates ([X], [Y]). Brackets should not be included in the line.
+ * 
+ * SharHit [X] [Y]
+ * Represents a hit that is shared between two or more clusters which is
+ * located at coordinates ([X], [Y]). Brackets should not be included in
+ * the line.
+ * 
+ * EndEvent
+ * Indicates that the event has ended.
+ * 
+ * @author Kyle McCarty
+ **/
+public final class TextManager implements EventManager {
+    // File reader for reading the input.
+	private AdvancedReader reader;
+    // List for storing the hits from the current event.
+    private ArrayList<EcalHit> hitList = new ArrayList<EcalHit>();
+    // List for storing the clusters from the current hit.
+    private ArrayList<Cluster> clusterList = new ArrayList<Cluster>();
+    // Whether the event manager has an open file.
+    private boolean open = true;
+    // Track the current event number.
+    private int curEvent = 0;
+    
+    /**
+     * <b>EventManager</b><br/><br/>
+     * <code>public <b>EventManager</b>(String filename)</code><br/><br/>
+     * Initializes an event manager that will read from the indicated file.
+     * @param filename - The path to the file containing hit information.
+     **/
+    public TextManager(String filename) throws IOException {
+    	reader = new AdvancedReader(filename);
+    }
+    
+    public void close() throws IOException {
+        reader.close();
+        open = false;
+    }
+    
+    public ArrayList<Cluster> getClusters() {
+        if (!open) { return null; }
+        else { return clusterList; }
+    }
+    
+    public ArrayList<EcalHit> getHits() {
+        if (!open) { return null; }
+        else { return hitList; }
+    }
+    
+    public boolean nextEvent() throws IOException {
+        // We can only read of the reader is open.
+        if (!open) { return false; }
+        
+        // Store the current line.
+        String curLine = reader.readNextLine();
+        
+        // Keep sorting until we hit a null or an event header.
+        while (curLine != null && !curLine.contains("Event")) {
+            curLine = reader.readNextLine();
+        }
+        
+        // If we hit a null, we are at the end of the file.
+        if (curLine == null) { return false; }
+        
+        // Clear the data lists.
+        hitList = new ArrayList<EcalHit>();
+        clusterList = new ArrayList<Cluster>();
+        
+        // Get the current event.
+        StringTokenizer et = new StringTokenizer(curLine);
+        et.nextToken();
+        curEvent = Integer.parseInt(et.nextToken());
+        
+        // Otherwise, we have read an event header and must populate
+        // the data lists.
+        curLine = reader.readNextLine();
+        while (curLine != null && curLine.compareTo("EndEvent") != 0) {
+            // Break apart the line.
+            StringTokenizer st = new StringTokenizer(curLine);
+            String name = st.nextToken();
+            int ix = Integer.parseInt(st.nextToken());
+            int iy = Integer.parseInt(st.nextToken());
+            
+            // If this is a cluster, add a new cluster object.
+            if (name.compareTo("Cluster") == 0) {
+            	// Get the cluster energy, if it is given.
+            	double clusterEnergy = Double.NaN;
+            	if(st.hasMoreTokens()) { clusterEnergy = Double.parseDouble(st.nextToken()); }
+            	
+            	// Add a new cluster.
+            	clusterList.add(new Cluster(ix, iy, clusterEnergy));
+            }
+            
+            // If this is a calorimeter hit, add a new calorimeter hit object.
+            else if (name.compareTo("EcalHit") == 0) {
+                double energy = Double.parseDouble(st.nextToken());
+                hitList.add(new EcalHit(ix, iy, energy));
+            }
+            
+            // If this is a cluster component hit, add it to the last cluster.
+            else if(name.compareTo("CompHit") == 0) {
+            	// There must be a last cluster to process this hit type.
+            	if(clusterList.size() == 0) {
+            		System.err.println("File Format Error: A cluster component hit was read, but" +
+            				" no cluster has been declared. Terminating.");
+            		System.exit(1);
+            	}
+            	else { clusterList.get(clusterList.size() - 1).addComponentHit(ix, iy); }
+            }
+            
+            // If this is a cluster shared hit, add it to the last cluster.
+            else if(name.compareTo("SharHit") == 0) {
+            	// There must be a last cluster to process this hit type.
+            	if(clusterList.size() == 0) {
+            		System.err.println("File Format Error: A cluster shared hit was read, but" +
+            				" no cluster has been declared. Terminating.");
+            		System.exit(1);
+            	}
+            	else { clusterList.get(clusterList.size() - 1).addSharedHit(ix, iy); }
+            }
+            
+            // Get the next line.
+            curLine = reader.readNextLine();
+        }
+        
+        // Indicate that an event was processed.
+        return true;
+    }
+    
+    public boolean previousEvent() throws IOException {
+    	// If we are at the first event, do nothing. There is no
+    	// previous event to display.
+    	if(curEvent == 1) { return false; }
+    	
+    	// Otherwise, loop backward until we find the previous event header.
+    	String curLine;
+    	while(true) {
+    		// Get the previous line.
+    		curLine = reader.readPreviousLine();
+    		
+    		// Otherwise, if it is null, we've reached the start of the file.
+    		if(curLine == null) {
+    			System.err.println("Error: Unexpectedly reached SOF.");
+    			System.exit(1);
+    		}
+    		
+    		// If the previous line is an event, note it.
+    		if(curLine.substring(0, 5).compareTo("Event") == 0) {
+    	        // Get the event number of the current event.
+    	        StringTokenizer et = new StringTokenizer(curLine);
+    	        et.nextToken();
+    	        int readEvent = Integer.parseInt(et.nextToken());
+    	        
+    	        // If the read event number is one back from the current
+    	        // event, jump back a step and read the event.
+    	        if(readEvent == (curEvent - 1)) {
+    	        	reader.readPreviousLine();
+    	        	return nextEvent();
+    	        }
+    		}
+    	}
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/lcsim
LCIOBridgeDriver.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/lcsim/LCIOBridgeDriver.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/lcsim/LCIOBridgeDriver.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,180 @@
+package org.hps.monitoring.ecal.lcsim;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.EventHeader;
+import org.lcsim.hps.recon.ecal.HPSEcalCluster;
+import org.lcsim.util.Driver;
+
+import org.hps.monitoring.ecal.event.Cluster;
+import org.hps.monitoring.ecal.event.EcalHit;
+import org.hps.monitoring.ecal.ui.PEventViewer;
+
+/**
+ * Class <code>LCIOBridgeDriver</code> displays LCIO events on the
+ * event display.
+ *
+ * @author Kyle McCarty
+ */
+public class LCIOBridgeDriver extends Driver {
+    // The identification name for getting the calorimeter object.
+    String ecalName;
+    // The collection name for the calorimeter hits.
+    String ecalCollectionName;
+    // The collection name in which to store clusters.
+    String clusterCollectionName = "EcalClusters";
+    // Sets how many events should pass before the display updates.
+    int displayInterval = 0;
+    // Whether the event display is currently processing an event or not.
+    private boolean updating = false;
+    // How many events where processed from the last displayed event.
+    private int eventsProcessed = 0;
+    // The event display.
+    private PEventViewer eventDisplay = new PEventViewer();
+    
+    /**
+     * <b>process</b><br/><br/>
+     * <code>public void <b>process</b>(EventHeader event)</code><br/><br/>
+     * Converts an LCIO event into an event display compatible format.
+     * Additionally handles updating the event display, if appropriate.
+     * @param event - The LCIO event.
+     */
+    public void process(EventHeader event) {
+    	// If we are still updating the display, skip this event.
+    	if(updating) { return; }
+    	
+        // Make sure that this event has calorimeter hits.
+        if (event.hasCollection(CalorimeterHit.class, ecalCollectionName)) {
+            // Get the list of calorimeter hits from the event.
+            List<CalorimeterHit> hits = event.get(CalorimeterHit.class, ecalCollectionName);
+            
+            // Define a list of clusters from the event.
+            List<HPSEcalCluster> clusters = event.get(HPSEcalCluster.class, clusterCollectionName);
+            
+            // Increment the number of events we have seen.
+            eventsProcessed++;
+            
+            // If this is the correct place to update, do so.
+            if(eventsProcessed >= displayInterval) {
+            	// Lock the update method for the duration of the update.
+            	updating = true;
+            	
+            	// Clear the event display.
+            	eventDisplay.resetDisplay();
+            	
+            	// Add all of the hits.
+            	for(CalorimeterHit hit : hits) {
+            		// Get the hit's location and energy.
+            		int ix = hit.getIdentifierFieldValue("ix");
+            		int iy = hit.getIdentifierFieldValue("iy");
+            		double energy = hit.getRawEnergy();
+            		
+            		// Add the hit energy to the event display.
+            		eventDisplay.addHit(new EcalHit(ix, iy, energy));
+            	}
+            	
+            	// Add all the clusters.
+            	for(HPSEcalCluster cluster : clusters) {
+            		// Get the seed hit.
+            		CalorimeterHit seed = cluster.getSeedHit();
+            		int ix = seed.getIdentifierFieldValue("ix");
+            		int iy = seed.getIdentifierFieldValue("iy");
+            		double energy = seed.getRawEnergy();
+            		
+            		// Add the cluster center to the event display.
+            		Cluster cc = new Cluster(ix, iy, energy);
+            		eventDisplay.addCluster(cc);
+            	}
+            	
+            	// Update the display.
+            	eventDisplay.updateDisplay();
+            	
+            	// Reset the number of events we've seen since the last update.
+            	eventsProcessed = 0;
+            	
+            	// Unlock the update method so that more events can be processed.
+            	updating = false;
+            }
+        }
+    }
+    
+    /**
+     * <b>setClusterCollectionName</b><br/><br/>
+     * <code>public void <b>setClusterCollectionName</b>(String clusterCollectionName)</code><br/><br/>
+     * Sets the name of the LCIO collection that contains calorimeter
+     * cluster information.
+     * @param clusterCollectionName - The name of the LCIO collection. 
+     */
+    public void setClusterCollectionName(String clusterCollectionName) {
+        this.clusterCollectionName = clusterCollectionName;
+    }
+    
+    /**
+     * <b>setDisplayInterval</b><br/><br/>
+     * <code>public void <b>setDisplayInterval</b>(String displayInterval)</code><br/><br/>
+     * Sets the rate at which events are displayed. The driver will
+     * render a new event when <code>displayInterval</code> events
+     * have occurred since the last one. Note that e value of 0 or
+     * 1 will display events as quickly as they can be displayed.
+     * @param displayInterval - The number of events to skip before
+     * a new event is displayed.
+     */
+    public void setDisplayInterval(String displayInterval) {
+    	// Convert the argument to an integer.
+    	int disp = Integer.parseInt(displayInterval);
+    	
+    	// If it is negative, make it zero.
+    	if(disp < 0) { disp = 0; }
+    	
+    	// Set the display interval.
+    	this.displayInterval = disp;
+    }
+    
+    /**
+     * <b>setEcalCollectionName</b><br/><br/>
+     * <code>public void <b>setEcalCollectionName</b>(String ecalCollectionName)</code><br/><br/>
+     * Sets the name of the LCIO collection that contains calorimeter
+     * hit information.
+     * @param ecalCollectionName - The name of the LCIO collection. 
+     */
+    public void setEcalCollectionName(String ecalCollectionName) {
+        this.ecalCollectionName = ecalCollectionName;
+    }
+    
+    /**
+     * <b>setEcalName</b><br/><br/>
+     * <code>public void <b>setEcalName</b>(String ecalName)</code><br/><br/>
+     * Sets which detector configuration should be used.
+     * @param ecalName - The name of the detector configuration.
+     */
+    public void setEcalName(String ecalName) { this.ecalName = ecalName; }
+    
+    /**
+     * <b>startOfData</b><br/><br/>
+     * <code>public void <b>startOfData</b>()</code><br/><br/>
+     * Ensures that critical collection names are defined.
+     */
+    public void startOfData() {
+        // Make sure that there is a cluster collection name into which clusters may be placed.
+        if (ecalCollectionName == null) {
+            throw new RuntimeException("The parameter ecalCollectionName was not set!");
+        }
+        
+        // Make sure that there is a calorimeter detector.
+        if (ecalName == null) {
+            throw new RuntimeException("The parameter ecalName was not set!");
+        }
+        
+        // Set the events passed so that the first event will display.
+        eventsProcessed = displayInterval - 1;
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
ActiveViewer.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/ActiveViewer.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/ActiveViewer.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,153 @@
+package org.hps.monitoring.ecal.ui;
+
+import java.awt.Color;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+import org.hps.monitoring.ecal.io.EventManager;
+
+/**
+ * Abstract class <code>ActiveViewer</code> describes a <code>Viewer
+ * </code> object that is connected to a static data source. It is
+ * designed to instruct the data source when to provide new events.
+ * 
+ * @author Kyle McCarty
+ */
+public abstract class ActiveViewer extends Viewer {
+	private static final long serialVersionUID = -6107646224627009923L;
+	// Stores whether the background color is set or not.
+	private boolean background = false;
+	// Gets events from some file.
+	protected final EventManager em;
+	
+	/**
+	 * <b>ActiveViewer</b><br/><br/>
+     * <code>public <b>ActiveViewer</b>(EventManager em)</code><br/><br/>
+     * Creates an active-type <code>Viewer</code> window which draws
+     * events from the indicated data source.
+	 * @param em - The data source event manager.
+	 */
+	public ActiveViewer(EventManager em) { this(em, new String[0]); }
+	
+	/**
+	 * <b>ActiveViewer</b><br/><br/>
+     * <code>public <b>ActiveViewer</b>(EventManager em, String... fieldNames)</code><br/><br/>
+     * Creates an active-type <code>Viewer</code> window which draws
+     * events from the indicated data source with additional status
+     * fields defined by the <code>fieldNames</code> argument.
+	 * @param em - The data source event manager.
+	 * @param fieldNames - An array of additional status fields
+	 * that should be displayed.
+	 */
+	public ActiveViewer(EventManager em, String... fieldNames) {
+		// Pass any additional field values to the super class.
+		super(fieldNames);
+		
+		// Set the data source.
+		this.em = em;
+		
+        // Make a key listener to change events.
+        addKeyListener(new EcalKeyListener());
+	}
+	
+    /**
+     * <b>displayNextEvent</b><br/><br/>
+     * <code>public void <b>displayNextEvent</b>()</code><br/><br/>
+     * Feeds the calorimeter panel the data from the next event.
+     * @throws IOException Occurs when there is an issue with reading the data file.
+     **/
+	public abstract void displayNextEvent() throws IOException;
+	
+    /**
+     * <b>displayPreviousEvent</b><br/><br/>
+     * <code>public void <b>displayPreviousEvent</b>()</code><br/><br/>
+     * Feeds the calorimeter panel the data from the previous event.
+     * @throws IOException Occurs when there is an issue with reading the data file.
+     **/
+	public abstract void displayPreviousEvent() throws IOException;
+	
+    /**
+     * The <code>EcalListener</code> class binds keys to actions.
+     * Bound actions include:
+     * [Right Arrow] :: Next event (stand-alone mode only)
+     * [Left Arrow ] :: Previous event (stand-alone mode only)
+     * b             :: Toggle color-mapping for 0 energy crystals
+     * h             :: Toggle selected crystal highlighting
+     * l             :: Toggle logarithmic versus linear scaling
+     * s			 :: Saves the current display to a file
+     **/
+    private class EcalKeyListener implements KeyListener {
+        public void keyPressed(KeyEvent e) { }
+        
+        public void keyReleased(KeyEvent e) {
+            // If right-arrow was pressed, go to the next event.
+            if (e.getKeyCode() == 39) {
+                try { displayNextEvent(); }
+                catch (IOException ex) {
+                    System.err.println(ex.getMessage());
+                    System.exit(1);
+                }
+            }
+            
+            // If right-arrow was pressed, go to the next event.
+            else if (e.getKeyCode() == 37) {
+                try { displayPreviousEvent(); }
+                catch (IOException ex) {
+                    System.err.println(ex.getMessage());
+                    System.exit(1);
+                }
+            }
+            
+            // 'b' toggles the default white background.
+            else if(e.getKeyCode() == 66) {
+            	if(background) { ecalPanel.setDefaultCrystalColor(null); }
+            	else { ecalPanel.setDefaultCrystalColor(Color.GRAY); }
+            	background = !background;
+            }
+            
+            // 'h' toggles highlighting the crystal under the cursor.
+            else if(e.getKeyCode() == 72) { ecalPanel.setSelectionHighlighting(!ecalPanel.isSelectionEnabled()); }
+            
+            // 'l' toggles linear or logarithmic scaling.
+            else if(e.getKeyCode() == 76) {
+            	if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
+            	else { ecalPanel.setScalingLinear(); }
+            }
+            
+            // 's' saves the panel to a file.
+            else if(e.getKeyCode() == 83) {
+            	// Make a new buffered image on which to draw the content pane.
+            	BufferedImage screenshot = new BufferedImage(getContentPane().getWidth(),
+            			getContentPane().getHeight(), BufferedImage.TYPE_INT_ARGB);
+            	
+            	// Paint the content pane to image.
+            	getContentPane().paint(screenshot.getGraphics());
+            	
+            	// Get the lowest available file name.
+            	int fileNum = 0;
+            	File imageFile = new File("screenshot_" + fileNum + ".png");
+            	while(imageFile.exists()) {
+            		fileNum++;
+            		imageFile = new File("screenshot_" + fileNum + ".png");
+            	}
+            	
+            	// Save the image to a PNG file.
+            	try { ImageIO.write(screenshot, "PNG", imageFile); }
+            	catch(IOException ioe) {
+            		System.err.println("Error saving file \"screenshot.png\".");
+            	}
+            	System.out.println("Screenshot saved to: " + imageFile.getAbsolutePath());
+            }
+            
+            // Otherwise, print out the key code for the pressed key.
+            else { System.out.printf("Key Code: %d%n", e.getKeyCode()); }
+        }
+        
+        public void keyTyped(KeyEvent e) { }
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
CalorimeterPanel.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/CalorimeterPanel.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/CalorimeterPanel.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,1347 @@
+package org.hps.monitoring.ecal.ui;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.JPanel;
+
+import org.hps.monitoring.ecal.event.Association;
+import org.hps.monitoring.ecal.util.MultiGradientScale;
+
+/**
+ * The class <code>CalorimeterPanel</code> handles the rendering of the
+ * calorimeter crystals as well as mapping colors to their values,
+ * rendering a color scale, and marking cluster crystals.
+ * 
+ * @author Kyle McCarty
+ **/
+public final class CalorimeterPanel extends JPanel {
+    // Java-suggested variable.
+    private static final long serialVersionUID = 6292751227464151897L;
+    // The color used for rendering seed hits.
+    private Color clusterColor = Color.GREEN;
+    // The default color of the calorimeter crystals.
+    private Color defaultColor = null;
+    // The color if disabled crystals.
+    private Color disabledColor = Color.BLACK;
+	// The color for the selected crystal.
+	private Color selectedColor = new Color(90, 170, 250);
+	// Whether to allow highlighting of the selected crystal.
+	private boolean enabledSelection = true;
+	// Whether the crystals should redraw automatically or not.
+	private boolean suppress = false;
+    // The color-mapping scale used by to color calorimeter crystals.
+    private MultiGradientScale scale = MultiGradientScale.makeRainbowScale(0.0, 1.0);
+    // The number of boxes in the x-direction.
+    private int xBoxes = 1;
+    // The number of boxes in the y-direction.
+    private int yBoxes = 1;
+    // The width of the scale.
+    private int scaleWidth = 75;
+    // Store the crystal panels.
+    private Crystal[][] crystal;
+    // Store the highest and lowest energy value observed.
+    private double[] extremum = { Double.MAX_VALUE, 0.0 };
+    // Store the currently selected crystal.
+    private Point selectedCrystal = null;
+    // The panel on which the scale is rendered.
+    private ScalePanel scalePanel = new ScalePanel();
+    
+    // Efficiency variables for crystal placement.
+    private int[] xPosition;
+    private int[] yPosition;
+    private int[] clusterSpace = new int[3];
+    
+    // ================================================================================
+    // === Constructors ===============================================================
+    // ================================================================================
+    
+    /**
+     * <b>EcalPanel</b><br/><br/>
+     * Initializes the calorimeter panel.
+     * @param numXBoxes - The number of crystals in the x-direction.
+     * @param numYBoxes - The number of crystals in the y-direction.
+     **/
+    public CalorimeterPanel(int numXBoxes, int numYBoxes) {
+        // Initialize the base component.
+        super();
+        
+        // Set the number of calorimeter crystals.
+        xBoxes = numXBoxes;
+        yBoxes = numYBoxes;
+        
+        // Initialize the crystal arrays.
+        crystal = new Crystal[xBoxes][yBoxes];
+        
+        for(int x = 0; x < xBoxes; x++) {
+        	for(int y = 0; y < yBoxes; y++) {
+        		crystal[x][y] = new Crystal();
+        		add(crystal[x][y]);
+        	}
+        }
+        
+        // Initialize the size arrays,
+        xPosition = new int [xBoxes + 1];
+        yPosition = new int[yBoxes + 1];
+        
+        // Add the scale panel.
+        setLayout(null);
+        add(scalePanel);
+    }
+    
+    // ================================================================================
+    // === Methods ====================================================================
+    // ================================================================================
+    
+    /**
+     * <b>addAssociation</b><br/><br/>
+     * <code>public void <b>addAssociation</b>(Association crystalAssociation)</code><br/><br/>
+     * Connects the parent crystal to the child crystal such that when
+     * the parent crystal is active, the child will be highlighted with
+     * the highlight color set in the <code>Asscoiation</code> object.
+     * @param crystalAssociation
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates for either the parent or child crystals do
+     * not correspond to a crystal.
+     */
+    public void addAssociation(Association crystalAssociation) throws IndexOutOfBoundsException {
+    	// Validate the parent crystal's indices.
+    	Point parent = crystalAssociation.getParentCrystal();
+    	if(validateIndices(parent)) {
+        	// Validate the parent crystal's indices.
+    		Point child = crystalAssociation.getChildCrystal();
+    		if(validateIndices(child)) {
+    			// If both crystal index sets are valid, add the association.
+    			crystal[parent.x][parent.y].addAssociation(crystalAssociation);
+    		}
+        	else { throw new IndexOutOfBoundsException(String.format("Invalid child crystal address (%2d, %2d).", parent.x, parent.y)); }
+    	}
+    	else { throw new IndexOutOfBoundsException(String.format("Invalid parent crystal address (%2d, %2d).", parent.x, parent.y)); }
+    }
+    
+    /**
+     * <b>addCrystalEnergy</b><br/><br/>
+     * <code>public void <b>addCrystalEnergy</b>(int xIndex, int yIndex, double energy)</code><br/><br/>
+     * Adds the indicated quantity of energy to the crystal at the given
+     * coordinates.
+     * @param ix - The x-index of the crystal.
+     * @param iy - The y-index of the crystal.
+     * @param energy - The energy to add.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     **/
+    public void addCrystalEnergy(int ix, int iy, double energy) throws IndexOutOfBoundsException {
+        if (validateIndices(ix, iy)) { crystal[ix][iy].addEnergy(energy); }
+        else { throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", ix, iy)); }
+    }
+    
+    /**
+     * <b>addCrystalEnergy</b><br/><br/>
+     * <code>public void <b>addCrystalEnergy</b>(Point ixy, double energy)</code><br/><br/>
+     * Adds the indicated quantity of energy to the crystal at the given
+     * coordinates.
+     * @param ixy - The crystal's x/y-indices.
+     * @param energy - The energy to add.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     **/
+    public void addCrystalEnergy(Point ixy, double energy) throws IndexOutOfBoundsException {
+        addCrystalEnergy(ixy.x, ixy.y, energy);
+    }
+    
+    /**
+     * <b>autoScale</b><br/><br/>
+     * <code>public void <b>autoScale</b>()</code><br/><br/>
+     * Chooses a maximum and minimum value for the scale that goes
+     * from slightly under the smallest recorded value to the highest
+     * recorded value.
+     */
+	public void autoScale() {
+		// If there is no energy on the calorimeter, just set the
+		// scale to some default value.
+		if(extremum[0] == Double.MAX_VALUE && extremum[1] == 0.0) {
+			scale.setMaximum(1.0);
+			scale.setMinimum(0.01);
+		}
+		
+		// If the minimum is defined, set the scale such that it
+		// will map that value to the low end and the highest value
+		// to the top.
+		else if(extremum[0] != Double.MAX_VALUE) {
+			scale.setMaximum(extremum[1]);
+			scale.setMinimum(extremum[0] / 2);
+		}
+	}
+    
+    /**
+     * <b>clearCrystals</b><br/><br/>
+     * <code>public void <b>clearCrystals</b>()</code><br/><br/>
+     * Sets all crystal energies to zero, removes all clusters, and
+     * clears all highlighting. This <b>does not</b> enable disabled
+     * crystals.
+     **/
+    public void clearCrystals() {
+        for (int ix = 0; ix < xBoxes; ix++) {
+            for (int iy = 0; iy < yBoxes; iy++) {
+            	crystal[ix][iy].setState(0.0, false, null);
+            	crystal[ix][iy].clearAssociations();
+            	extremum[0] = Double.MAX_VALUE;
+            	extremum[1] = 0.0;
+            }
+        }
+    }
+    
+    /**
+     * <b>clearHighlight</b><br/><br/>
+     * <code>public void <b>clearHighlight</b>()</code><br/><br/>
+     * Clears any highlighting on the crystals.
+     */
+    public void clearHighlight() {
+        for (int x = 0; x < xBoxes; x++) {
+            for (int y = 0; y < yBoxes; y++) { crystal[x][y].setHighlight(null); }
+        }
+    }
+    
+    /**
+     * <b>clearSelectedCrystal</b><br/><br/>
+     * <code>public void <b>clearSelectedCrystal</b>()</code><br/><br/>
+     * Clears crystal selection and sets it to <code>null</code>.
+     */
+    public void clearSelectedCrystal() {
+    	// Clear any currently selected crystals.
+    	if(selectedCrystal != null) { crystal[selectedCrystal.x][selectedCrystal.y].setSelected(false); }
+    	
+    	// Set the selected crystal to null.
+    	selectedCrystal = null;
+    }
+    
+    /**
+     * <b>getCrystalEnergy</b><br/><br/>
+     * <code>public double <b>getCrystalEnergy</b>(int ix, int iy)</code><br/><br/>
+     * Provides the energy stored in the indicated crystal.
+     * @param ix - The crystal's x-index.
+     * @param iy - The crystal's y-index.
+     * @return Returns the energy as a <code>double</code>.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public double getCrystalEnergy(int ix, int iy) throws IndexOutOfBoundsException {
+    	if(validateIndices(ix, iy)) { return crystal[ix][iy].getEnergy(); }
+        else { throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", ix, iy)); }
+    }
+    
+    /**
+     * <b>getCrystalEnergy</b><br/><br/>
+     * <code>public double <b>getCrystalEnergy</b>(Point ixy)</code><br/><br/>
+     * Provides the energy stored in the indicated crystal.
+     * @param ixy - The crystal's x/y-indices.
+     * @return Returns the energy as a <code>double</code>.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public double getCrystalEnergy(Point ixy) throws IndexOutOfBoundsException {
+    	return getCrystalEnergy(ixy.x, ixy.y);
+    }
+    
+    /**
+     * <b>getCrystalHighlight</b><br/><br/>
+     * <code>public Color <b>getCrystalHighlight</b>(int ix, int iy)</code><br/><br/>
+     * Provides the highlight color for the indicated crystal.
+     * @param ix - The crystal's x-index.
+     * @param iy - The crystal's y-index.
+     * @return Returns the highlight color as a <code>Color</code>
+     * object.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public Color getCrystalHighlight(int ix, int iy) throws IndexOutOfBoundsException {
+    	if(validateIndices(ix, iy)) { return crystal[ix][iy].getHighlight(); }
+        else { throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", ix, iy)); }
+    }
+    
+    /**
+     * <b>getCrystalHighlight</b><br/><br/>
+     * <code>public Color <b>getCrystalHighlight</b>(Point ixy)</code><br/><br/>
+     * Provides the highlight color for the indicated crystal.
+     * @param ixy - The crystal's x/y-indices.
+     * @return Returns the highlight color as a <code>Color</code>
+     * object.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public Color getCrystalHighlight(Point ixy) throws IndexOutOfBoundsException {
+    	return getCrystalHighlight(ixy.x, ixy.y);
+    }
+    
+    /**
+     * <b>getCrystalID</b><br/><br/>
+     * <code>public Point <b>getCrystalID</b>(int xCoor, int yCoor)</code><br/><br/>
+     * Determines the panel crystal index of the crystal at the given
+     * panel coordinates.
+     * @param xCoor - The x-coordinate on the panel.
+     * @param yCoor - The y-coordinate on the panel.
+     * @return Returns a <code>Point</code> object containing the panel
+     * crystal indices of the crystal at the given panel coordinates.
+     * Returns <code>null</code> if the coordinates do not map to a crystal.
+     */
+    public Point getCrystalID(int xCoor, int yCoor) {
+    	// If either coordinate is negative, return the null result.
+    	if(xCoor < 0 || yCoor < 0) { return null; }
+    	
+    	// If either coordinate is too large, return the nul result.
+    	if(xCoor > xPosition[xBoxes] || yCoor > yPosition[yBoxes]) {
+    		return null;
+    	}
+    	
+    	// Make a point to identify the crystal index.
+    	Point loc = new Point(-1, -1);
+    	
+    	// Determine which y index it is.
+    	for(int y = 0; y < yBoxes; y++) {
+    		if(yCoor <= yPosition[y + 1]) {
+    			loc.y = y;
+    			break;
+    		}
+    	}
+    	
+    	// Determine which x index it is.
+    	for(int x = 0; x < xBoxes; x++) {
+    		if(xCoor <= xPosition[x + 1]) {
+    			loc.x = x;
+    			break;
+    		}
+    	}
+    	
+    	// If either coordinate is not valid, return null.
+    	if(loc.x == -1 || loc.y == -1) { return null; }
+    	
+    	// Return the crystal identifier.
+    	return loc;
+    }
+    
+    /**
+     * <b>getCrystalID</b><br/><br/>
+     * <code>public Point <b>getCrystalID</b>(Point panelCoor)</code><br/><br/>
+     * Determines the panel crystal index of the crystal at the given
+     * panel coordinates.
+     * @param panelCoor - The x/y-coordinates on the panel.
+     * @return Returns a <code>Point</code> object containing the panel
+     * crystal indices of the crystal at the given panel coordinates.
+     * Returns <code>null</code> if the coordinates do not map to a crystal.
+     */
+    public Point getCrystalID(Point panelCoor) { return getCrystalID(panelCoor.x, panelCoor.y); }
+    
+    /**
+     * <b>getCrystalBounds</b><br/><br/>
+     * <code>public Dimension <b>getCrystalBounds</b>()</code><br/><br/>
+     * Returns calorimeter panel's width and height in crystals.
+     * @return Returns the number of crystals are on the calorimeter
+     * in width and height.
+     */
+    public Dimension getCrystalBounds() { return new Dimension(xBoxes, yBoxes); }
+    
+    /**
+     * <b>getNeighbors</b><br/><br/>
+     * <code>public Set<Point> <b>getNeighbors</b>(int cix, int ciy)</code><br/><br/>
+     * Gets the set of valid crystals that immediately surround the
+     * central crystal. Valid crystals must both have valid indices
+     * and also be enabled.
+     * @param cix - The x-index of the central crystal.
+     * @param ciy - The y-index of the central crystal.
+     * @return Returns the neighboring crystals as a <code>Set</code>
+     * of <code>Point</code> objects, with each element containing the
+     * coordinates of a valid neighbor.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y central crystal coordinates do not correspond to a crystal.
+     */
+    public Set<Point> getNeighbors(int cix, int ciy) { return getNeighbors(new Point(cix, ciy)); }
+    
+    /**
+     * <b>getNeighbors</b><br/><br/>
+     * <code>public Set<Point> <b>getNeighbors</b>(Point centralCrystal)</code><br/><br/>
+     * Gets the set of valid crystals that immediately surround the
+     * central crystal. Valid crystals must both have valid indices
+     * and also be enabled.
+     * @param centralCrystal - What crystal the neighbors should surround.
+     * @return Returns the neighboring crystals as a <code>Set</code>
+     * of <code>Point</code> objects, with each element containing the
+     * coordinates of a valid neighbor.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y central crystal coordinates do not correspond to a crystal.
+     */
+    public Set<Point> getNeighbors(Point centralCrystal) throws IndexOutOfBoundsException {
+    	// Make sure that the root is a valid crystal.
+    	if(!validateIndices(centralCrystal)) {
+            throw new IndexOutOfBoundsException(String.format("Invalid central crystal address (%2d, %2d).",
+            		centralCrystal.x, centralCrystal.y));
+    	}
+    	
+    	// Make a set to store the neighbors in.
+    	HashSet<Point> neighborSet = new HashSet<Point>();
+    	
+    	// Check all the crystals in a 3-by-3 square around the root.
+    	// If they are valid crystals and they are not disable, then
+    	// they are neighbors.
+    	for(int xMod = -1; xMod <= 1; xMod++) {
+    		for(int yMod = -1; yMod <= 1; yMod++) {
+    			// Get the possible neighbor.
+    			Point possibleNeighbor = new Point(centralCrystal.x + xMod, centralCrystal.y + yMod);
+    			
+    			// Make sure that the possible neighbor is neither the
+    			// root crystal nor invalid.
+    			boolean isRoot = centralCrystal.equals(possibleNeighbor);
+    			boolean isValid = validateIndices(possibleNeighbor);
+    			
+    			// If the neighbor passes these tests, add it to the set
+    			// if it is active.
+    			if(!isRoot && isValid) {
+    				if(!crystal[possibleNeighbor.x][possibleNeighbor.y].isDisabled()) {
+    					neighborSet.add(possibleNeighbor);
+    				}
+    			}
+    		}
+    	}
+    	
+    	// Return the neighbor set.
+    	return neighborSet;
+    }
+    
+    /**
+     * <b>getSelectedCrystal</b><br/><br/>
+     * <code>public Point <b>getSelectedCrystal</b>()</code><br/><br/>
+     * Gives the x/y indices for the currently selected crystal.
+     * @return Returns the x/y indices in a <code>Point</code> object.
+     * If no crystal is currently selected, returns <code>null</code>.
+     */
+    public Point getSelectedCrystal() { return selectedCrystal; }
+    
+    /**
+     * <b>isCluster</b><br/><br/>
+     * <code>public boolean <b>isCluster</b></code>(int ix, int iy)<br/><br/>
+     * Determines if the crystal at the given coordinates is a cluster
+     * center or not.
+     * @param ix - The x-index of the crystal.
+     * @param iy - The y-index of the crystal.
+     * @return Returns <code>true</code> if the crystal is a cluster
+     * center and <code>false</code> if it is not or if the indices
+     * are invalid.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public boolean isCrystalCluster(int ix, int iy) throws IndexOutOfBoundsException {
+    	if(validateIndices(ix, iy)) { return crystal[ix][iy].isClusterCenter(); }
+        else { throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", ix, iy)); }
+    }
+    
+    /**
+     * <b>isCluster</b><br/><br/>
+     * <code>public boolean <b>isCluster</b></code>(Point ixy)<br/><br/>
+     * Determines if the crystal at the given coordinates is a cluster
+     * center or not.
+     * @param ixy - The crystal's x/y-indices.
+     * @return Returns <code>true</code> if the crystal is a cluster
+     * center and <code>false</code> if it is not or if the indices
+     * are invalid.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public boolean isCrystalCluster(Point ixy) throws IndexOutOfBoundsException {
+    	return isCrystalCluster(ixy.x, ixy.y);
+    }
+    
+    /**
+     * <b>isCrystalDisabled</b><br/><br/>
+     * <code>public boolean <b>isCrystalDisabled</b></code>(int ix, int iy)<br/><br/>
+     * Determines if the crystal at the given coordinates is a active
+     * or not.
+     * @param xCoor - The x-index of the crystal.
+     * @param yCoor - The y-index of the crystal.
+     * @return Returns <code>true</code> if the crystal is active
+     * and <code>false</code> if it is not.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public boolean isCrystalDisabled(int ix, int iy) throws IndexOutOfBoundsException {
+    	if(validateIndices(ix, iy)) { return crystal[ix][iy].isDisabled(); }
+        else { throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", ix, iy)); }
+    }
+    
+    /**
+     * <b>isCrystalDisabled</b><br/><br/>
+     * <code>public boolean <b>isCrystalDisabled</b></code>(Point ixy)<br/><br/>
+     * Determines if the crystal at the given coordinates is a active
+     * or not.
+     * @param ixy - The crystal's x/y-indices.
+     * @return Returns <code>true</code> if the crystal is active
+     * and <code>false</code> if it is not.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public boolean isCrystalDisabled(Point ixy) throws IndexOutOfBoundsException {
+    	return isCrystalDisabled(ixy.x, ixy.y);
+    }
+    
+    /**
+     * <b>isScalingLinear</b><br/><br/>
+     * <code>public boolean <b>isScalingLinear</b></code>()<br/><br/>
+     * Indicates whether the crystal colors are mapped linearly.
+     * @return Returns <code>true</code> if the mapping is linear
+     * and <code>false</code> otherwise.
+     */
+    public boolean isScalingLinear() { return scale.isLinear(); }
+    
+    /**
+     * <b>isScalingLogarithmic</b><br/><br/>
+     * <code>public boolean <b>isScalingLogarithmic</b></code>()<br/><br/>
+     * Indicates whether the crystal colors are mapped logarithmically.
+     * @return Returns <code>true</code> if the mapping is logarithmic
+     * and <code>false</code> otherwise.
+     */
+    public boolean isScalingLogarithmic() { return scale.isLogarithmic(); }
+    
+    /**
+     * <b>isSelectionEnabled</b><br/><br/>
+     * <code>public boolean <b>isSelectionEnabled</b></code>()<br/><br/>
+     * Indicates whether highlighting of the currently selected crystal
+     * is active or not.
+     * @return Returns <code>true</code> if the currently selected
+     * crystal will be highlighted and <code>false</code> otherwise.
+     */
+    public boolean isSelectionEnabled() { return enabledSelection; }
+    
+    /**
+     * <b>setClusterColor</b><br/><br/>
+     * <code>public void <b>setClusterColor</b>(Color c)</code><br/><br/>
+     * Sets the color of the cluster center marker.
+     * @param c - The color to be used for cluster center markers. A
+     * value of <code>null</code> will result in seed hit markers being
+     * the inverse color of the crystal in which they appear.
+     **/
+    public void setClusterColor(Color c) { clusterColor = c; }
+    
+    /**
+     * <b>setCrystalCluster</b><br/><br/>
+     * <code>public void <b>setCrystalCluster</b>(int ix, int iy, boolean cluster)</code><br/><br/>
+     * Sets whether a crystal is also the location of a cluster center.
+     * @param ix - The x-index of the crystal.
+     * @param iy - The y-index of the crystal.
+     * @param cluster - This should be <code>true</code> if there
+     * is a cluster center and <code>false</code> if there is not.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     **/
+    public void setCrystalCluster(int ix, int iy, boolean cluster) throws IndexOutOfBoundsException {
+        if (validateIndices(ix, iy)) { crystal[ix][iy].setClusterCenter(cluster); }
+        else { throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", ix, iy)); }
+    }
+    
+    /**
+     * <b>setCrystalCluster</b><br/><br/>
+     * <code>public void <b>setCrystalCluster</b>(Point ixy, boolean cluster)</code><br/><br/>
+     * Sets whether a crystal is also the location of a seed hit.
+     * @param ixy - The crystal's x/y-indices.
+     * @param cluster - This should be <code>true</code> if there
+     * is a cluster center and <code>false</code> if there is not.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     **/
+    public void setCrystalCluster(Point ixy, boolean cluster) throws IndexOutOfBoundsException {
+        setCrystalCluster(ixy.x, ixy.y, cluster);
+    }
+    
+    /**
+     * <b>setCrystalEnabled</b><br/><br/>
+     * <code>public void <b>setCrystalEnabled</b>(int ix, int iy, boolean active)</code><br/><br/>
+     * Sets whether the indicated crystal is enabled or not.
+     * @param ix - The x-index of the crystal.
+     * @param iy - The y-index of the crystal.
+     * @param active - This should be <code>true</code> if the crystal is
+     * active and <code>false</code> if it is not.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     **/
+    public void setCrystalEnabled(int ix, int iy, boolean active) throws IndexOutOfBoundsException {
+        if (validateIndices(ix, iy)) { crystal[ix][iy].setDisabled(!active); }
+        else { throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", ix, iy)); }
+    }
+    
+    /**
+     * <b>setCrystalEnabled</b><br/><br/>
+     * <code>public void <b>setCrystalEnabled</b>(Point ixy, boolean active)</code><br/><br/>
+     * Sets whether the indicated crystal is enabled or not.
+     * @param ixy - The crystal's x/y-indices.
+     * @param active - This should be <code>true</code> if the crystal is
+     * active and <code>false</code> if it is not.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     **/
+    public void setCrystalEnabled(Point ixy, boolean active) throws IndexOutOfBoundsException {
+        setCrystalEnabled(ixy.x, ixy.y, active);
+    }
+    
+    /**
+     * <b>setCrystalHighlight</b><br/><br/>
+     * <code>public void <b>setCrystalHighlight</b>(int ix, int iy, Color highlight)</code><br/><br/>
+     * @param ix - The x-index of the crystal.
+     * @param iy - The y-index of the crystal.
+     * @param highlight - The color which the indicated crystal should
+     * be highlighted. A value of <code>null</code> indicates that no
+     * highlight should be used.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public void setCrystalHighlight(int ix, int iy, Color highlight) throws IndexOutOfBoundsException {
+        if (validateIndices(ix, iy)) { crystal[ix][iy].setHighlight(highlight); }
+        else { throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", ix, iy)); }
+    }
+    
+    /**
+     * <b>setCrystalHighlight</b><br/><br/>
+     * <code>public void <b>setCrystalHighlight</b>(Point ixy, Color highlight)</code><br/><br/>
+     * @param ixy - The crystal's x/y-indices.
+     * @param highlight - The color which the indicated crystal should
+     * be highlighted. A value of <code>null</code> indicates that no
+     * highlight should be used.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public void setCrystalHighlight(Point ixy, Color highlight) throws IndexOutOfBoundsException {
+        setCrystalHighlight(ixy.x, ixy.y, highlight);
+    }
+    
+    /**
+     * <b>setCrystalDefaultColor</b><br/><br/>
+     * <code>public void <b>setCrystalDefaultColor</b>(Color c)</code><br/><br/>
+     * Sets the color that crystals with zero energy will display.
+     * @param c - The color to use for zero energy crystals. A value
+     * of <code>null</code> will use the appropriate energy color
+     * map value.
+     */
+    public void setDefaultCrystalColor(Color c) {
+    	// Only update the crystals if the default color has changed.
+    	if(c != defaultColor) {
+    		// Store the new default color.
+    		defaultColor = c;
+    		
+    		// Inform the crystals of the change.
+    		for(int ix = 0; ix < xBoxes; ix++) {
+    			for(int iy = 0; iy < yBoxes; iy++) { crystal[ix][iy].setUseDefaultColor(c != null, false); }
+    		}
+    		
+    		// Reset the colors and repaint.
+    		resetCrystalColors();
+    		repaint();
+    	}
+    }
+    
+    /**
+     * <b>setScaleEnabled</b><br/><br/>
+     * <code>public void <b>setScaleEnabled</b>(boolean enabled)</code><br/><br/>
+     * Sets whether the scale should be visible or not.
+     * @param enabled - <code>true</code> indicates that the scale should
+     * be visible and <code>false</code> that it should be hidden.
+     **/
+    public void setScaleEnabled(boolean enabled) {
+        if (scalePanel.isVisible() != enabled) {
+            scalePanel.setVisible(enabled);
+        }
+    }
+    
+    /**
+     * <b>setScaleMaximum</b><br/><br/>
+     * <code>public void <b>setScaleMaximum</b>(double maximum)</code><br/><br/>
+     * Sets the maximum value of the color mapping scale. Energies above this
+     * value will all be the same maximum color.
+     * @param maximum - The maximum energy to be mapped.
+     **/
+    public void setScaleMaximum(double maximum) {
+        scale.setMaximum(maximum);
+    }
+    
+    /**
+     * <b>setScaleMinimum</b><br/><br>
+     * <code>public void <b>setScaleMinimum</b>(double minimum)</code><br/><br/>
+     * Sets the minimum value of the color mapping scale. Energies below this
+     * value will all be the same minimum color.
+     * @param minimum - The minimum energy to be mapped.
+     **/
+    public void setScaleMinimum(double minimum) {
+        scale.setMinimum(minimum);
+    }
+    
+    /**
+     * <b>setScalingLinear</b><br/><br/>
+     * <code>public void <b>setScalingLinear</b>()<br/><br/>
+     * Sets the color mapping scale behavior to linear mapping.
+     **/
+    public void setScalingLinear() {
+        scale.setScalingLinear();
+        resetCrystalColors();
+        repaint();
+    }
+    
+    /**
+     * <b>setScalingLogarithmic</b><br/><br/>
+     * <code>public void <b>setScalingLogarithmic</b>()</code><br/><br/>
+     * Sets the color mapping scale behavior to logarithmic mapping.
+     **/
+    public void setScalingLogarithmic() {
+        scale.setScalingLogarithmic();
+        resetCrystalColors();
+        repaint();
+    }
+    
+    /**
+     * <b>setSelectedCrystal</b><br/><br/>
+     * <code>public void <b>setSelectedCrystal</b></code>(int ix, int iy)<br/><br/>
+     * Sets which crystal is currently selected.
+     * @param ix - The x-index of the crystal.
+     * @param iy - The y-index of the crystal.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     */
+    public void setSelectedCrystal(int ix, int iy) {
+        if (validateIndices(ix, iy)) {
+        	if(selectedCrystal != null) { crystal[selectedCrystal.x][selectedCrystal.y].setSelected(false); }
+            crystal[ix][iy].setSelected(true);
+            selectedCrystal = new Point(ix, iy);
+        }
+        else {
+            throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", ix, iy));
+        }
+    }
+    
+    /**
+     * <b>setSelectedCrystal</b><br/><br/>
+     * <code>public void <b>setSelectedCrystal</b></code>(Point ixy)<br/><br/>
+     * Sets which crystal is currently selected.
+     * @param ixy - The crystal's x/y-indices.
+     * @throws IndexOutOfBoundsException Occurs when either of the given
+     * x/y crystal coordinates do not correspond to a crystal.
+     * @throws NullPointerException Occurs when the argument <code>Point
+     * </code> is <code>null</code>.
+     */
+    public void setSelectedCrystal(Point ixy) throws NullPointerException {
+    	if(crystal != null) { setSelectedCrystal(ixy.x, ixy.y); }
+    	else { throw new NullPointerException("Crystal ID must be defined."); }
+    }
+    
+    /**
+     * <b>setSelectedCrystalHighlight</b><br/><br/>
+     * <code>public void <b>setSelectedCrystalHighlight</b>(Color c)</code><br/><br/>
+     * Sets the color in which selected crystals should be highlighted.
+     * @param c - The new selection highlight color.
+     * @throws IllegalArgumentException Occurs if the selection color
+     * is set to <code>null</code>.
+     */
+    public void setSelectedCrystalHighlight(Color c) throws IllegalArgumentException {
+    	// We do not allow null values for the selected crystal color.
+    	if(c == null) { throw new IllegalArgumentException("Crystal selection color can not be null."); }
+    	else { selectedColor = c; }
+    }
+    
+    /**
+     * <b>setSelectionHighlighting</b><br/><br/>
+     * <code>public void <b>setSelectionHighlighting</b>(boolean state)</code><br/><br/>
+     * Sets whether or not the currently selected crystal should be
+     * highlighted or not.
+     * @param state - <code>true</code> indicates that the selected
+     * crystals should be highlighted and <code>false</code> that
+     * it should not.
+     */
+    public void setSelectionHighlighting(boolean state) {
+    	if(enabledSelection != state) {
+    		enabledSelection = state;
+    		if(selectedCrystal != null) { crystal[selectedCrystal.x][selectedCrystal.y].repaint(); }
+    	}
+    }
+    
+    public void setSize(Dimension d) { setSize(d.width, d.height); }
+    
+    public void setSize(int width, int height) {
+    	// Run the superclass method.
+        super.setSize(width, height);
+        
+        // Resize the scale panel.
+        scalePanel.setLocation(width - scaleWidth, 0);
+        scalePanel.setSize(scaleWidth, height);
+        
+        // Determine the width and heights of the calorimeter crystals.
+        if (scalePanel.isVisible()) { width = getWidth() - scaleWidth; }
+        else { width = getWidth(); }
+        
+        int boxWidth = width / xBoxes;
+        int widthRem = width % xBoxes;
+        int boxHeight = height / yBoxes;
+        int heightRem = height % yBoxes;
+        
+        // Set positioning and sizing variables.
+        int[] crystalRem = { widthRem, heightRem };
+        int curX = 0;
+        int curY = 0;
+        
+        // Loop over the rows of crystals.
+        xPosition[0] = 0;
+        for(int x = 0; x < xBoxes; x++) {
+        	// Get the appropriate width for a crystal.
+        	int crystalWidth = boxWidth;
+        	if(crystalRem[0] != 0) {
+        		crystalWidth++;
+        		crystalRem[0]--;
+        	}
+        	
+        	// Note the x-coordinate for this column.
+        	xPosition[x + 1] = xPosition[x] + crystalWidth;
+        	
+        	// Loop over the columns of crystals.
+        	for(int y = 0; y < yBoxes; y++) {
+        		// Get the appropriate height for a crystal.
+        		int crystalHeight = boxHeight;
+        		if(crystalRem[1] != 0) {
+        			crystalHeight++;
+        			crystalRem[1]--;
+        		}
+        		
+        		// Note the y-coordinate for this row.
+        		yPosition[y + 1] = yPosition[y] + crystalHeight;
+        		
+        		// Set the crystal size and location.
+        		crystal[x][y].setSize(crystalWidth, crystalHeight);
+        		crystal[x][y].setLocation(curX, curY);
+        		
+        		// Increment the current y-coordinate.
+        		curY += crystalHeight;
+        	}
+        	
+        	// Increment the current x-coordinate and reset the current y-coordinate.
+        	curX += crystalWidth;
+        	curY = 0;
+        	
+        	// Reset the height remainder for the next column.
+        	crystalRem[1] = heightRem;
+        }
+            
+        // Calculate the cluster position variables.
+        double ltw = 0.25 * boxWidth;
+        double lth = 0.25 * boxHeight;
+        
+        if(ltw > lth) {
+        	clusterSpace[0] = (int)Math.round((boxWidth - lth - lth) / 2.0);
+        	clusterSpace[1] = (int)Math.round(lth);
+        	clusterSpace[2] = (int)Math.round(lth + lth);
+        }
+        else {
+        	clusterSpace[0] = (int)Math.round(ltw);
+        	clusterSpace[1] = (int)Math.round((boxHeight - ltw - ltw) / 2.0);
+        	clusterSpace[2] = (int)Math.round(ltw + ltw);
+        }
+	}
+    
+    /**
+     * <b>setSuppressRedraw</b><br/><br/>
+     * <code>public void <b>setSuppressRedraw</b>(boolean state)</code><br/><br/>
+     * Sets whether the panel crystals should repaint automatically
+     * whenever their state changes.
+     * @param state - <code>true</code> indicates that the crystal
+     * panels will repaint automatically, while <code>false</code>
+     * indicates that they will not repaint.
+     */
+    public void setSuppressRedraw(boolean state) { suppress = state; }
+    
+    // ================================================================================
+    // === Private Methods ============================================================
+    // ================================================================================
+    
+    /**
+     * <b>resetCrystalColors</b><br/><br/>
+     * <code>private void <b>resetCrystalColors</b>()</code><br/><br/>
+     * Forces all crystals to revalidate their colors.
+     */
+    private void resetCrystalColors() {
+    	// Force all the crystals to update their colors.
+    	for(int ix = 0; ix < xBoxes; ix++) {
+    		for(int iy = 0; iy < yBoxes; iy++) { crystal[ix][iy].resetColor(); }
+    	}
+    }
+    
+    /**
+     * <b>validateIndices</b><br/><br/>
+     * <code>private boolean <b>validateIndices</b>(int ix, int iy)</code><br/><br/>
+     * Indicates whether the given indices corresponds to a valid
+     * crystal or not.
+     * @param ix - The crystal's x index.
+     * @param iy - The crystal's y index.
+     * @return Returns <code>true</code> if the indices are valid
+     * and <code>false</code> if they are not.
+     */
+    private boolean validateIndices(int ix, int iy) {
+    	boolean lowX = (ix > -1);
+    	boolean highX = (ix < xBoxes);
+    	boolean lowY = (iy > -1);
+    	boolean highY = (iy < yBoxes);
+    	
+    	return (lowX && highX && lowY && highY);
+    }
+    
+    /**
+     * <b>validateIndices</b><br/><br/>
+     * <code>private boolean <b>validateIndices</b>(Point p)</code><br/><br/>
+     * Indicates whether the given indices corresponds to a valid
+     * crystal or not.
+     * @param p - A <code>Point</code> object containing the crystal's
+     * x and y indices.
+     * @return Returns <code>true</code> if the indices are valid
+     * and <code>false</code> if they are not.
+     */
+    private boolean validateIndices(Point p) { return validateIndices(p.x, p.y); }
+    
+    // ================================================================================
+    // === Private Internal Classes ===================================================
+    // ================================================================================
+    
+    /**
+     * Class <code>Crystal</code> holds all pertinent information needed
+     * to display a calorimeter crystal in the panel. It also handles
+     * drawing itself.
+     * @author Kyle McCarty
+     */
+    private class Crystal extends JPanel {
+		private static final long serialVersionUID = -5666423016127997831L;
+		// The energy stored in the crystal.
+		private double energy = 0.0;
+		// Whether the crystal can store energy.
+		private boolean disabled = false;
+		// Whether the crystal is a cluster center.
+		private boolean cluster = false;
+		// Whether zero-energy crystals should use color mapping.
+		private boolean useDefaultColor = false;
+		// Whether the crystal is selected.
+		private boolean selected = false;
+		// What color the crystal should be highlighted in.
+		private Color highlight = null;
+		// Store associated crystals.
+		private ArrayList<Association> componentList = new ArrayList<Association>();
+		
+		/**
+		 * <b>Crystal</b><br/><br/>
+		 * <code>public <b>Crystal</b>()</code><br/><br/>
+		 * Initializes a new calorimeter crystal panel.
+		 */
+    	public Crystal() {
+    		setOpaque(true);
+    		resetColor();
+    	}
+		
+		/**
+		 * <b>addAssociation</b><br/><br/>
+		 * <code>public void <b>addAssociation</b>(Association a)</code><br/><br/>
+		 * Adds a new associated crystal to this crystal.
+		 * @param a - The <code>Association</code> object representing
+		 * the associated crystal and its highlighting color.
+		 */
+		public void addAssociation(Association a) {
+			// Add the association.
+			componentList.add(a);
+			
+			// If this crystal is selected, then activate the new crystal.
+			if(selected) { setCrystalHighlight(a.getChildCrystal(), a.getHighlight()); }
+		}
+    	
+    	/**
+    	 * <b>addEnergy</b><br/><br/>
+		 * <code>public void <b>addEnergy</b>(double energy)</code><br/><br/>
+		 * Increments the crystal's energy by the given amount.
+    	 * @param energy - The energy by which the crystal's stored
+    	 * energy should be increased.
+    	 */
+    	public void addEnergy(double energy) { setEnergy(this.energy + energy); }
+    	
+		/**
+		 * <b>clearAssociations</b><br/><br/>
+		 * <code>public void <b>clearAssociations</b>()</code><br/><br/>
+		 * Clears all the associated crystal from this crystal.
+		 */
+		public void clearAssociations() {
+			// Remove the highlighting from any associated crystals,
+			// if this crystal is selected.
+			if(selected) {
+	    		for(Association a : componentList) { setCrystalHighlight(a.getChildCrystal(), null); }
+			}
+			
+			// Clear the list.
+			componentList.clear();
+		}
+    	
+    	/**
+    	 * <b>getEnergy</b><br/><br/>
+		 * <code>public double <b>getEnergy</b>()</code><br/><br/>
+		 * Indicates how much energy is stored in the crystal.
+    	 * @return Returns the crystal's energy as a <code>double</code>.
+    	 */
+    	public double getEnergy() { return energy; }
+    	
+    	/**
+    	 * <b>isClusterCenter</b><br/><br/>
+		 * <code>public boolean <b>isClusterCenter</b>()</code><br/><br/>
+		 * Indicates whether this crystal is also a cluster center.
+    	 * @return Returns <code>true</code> if the crystal is a cluster
+    	 * center and <code>false</code> if it is not.
+    	 */
[truncated at 1000 lines; 350 more skipped]

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
ClusterViewer.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/ClusterViewer.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/ClusterViewer.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,448 @@
+package org.hps.monitoring.ecal.ui;
+
+import org.hps.monitoring.ecal.io.EventManager;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.imageio.ImageIO;
+
+import org.hps.monitoring.ecal.event.Association;
+import org.hps.monitoring.ecal.event.Cluster;
+import org.hps.monitoring.ecal.event.EcalHit;
+
+/**
+ * Class <code>FileViewer</code> is an implementation of the <code>
+ * Viewer</code> abstract class that reads events from a file data
+ * source. Any file type can be used, so long as it has a manager
+ * which implements the <code>EventManager</code> interface.
+ * 
+ * @author Kyle McCarty
+ */
+public class ClusterViewer extends ActiveViewer {
+	private static final long serialVersionUID = 17058336873349781L;
+	// Stores whether the background color is set or not.
+	private boolean background = false;
+    // Store the index in the buffer of the displayed event.
+    private int bufferIndex;
+    // Map cluster location to a cluster object.
+    private HashMap<Point, Cluster> clusterMap = new HashMap<Point, Cluster>();
+    // Store the cluster list for the currently displayed event.
+    private List<Cluster> eventClusters;
+    // Store the event energies in a buffer.
+    private LinkedList<Double[][]> eventEnergyBuffer;
+    // Store the event hits in a buffer.
+    private LinkedList<List<EcalHit>> eventHitBuffer;
+    // Store the size of the event window.
+    private int eventWindow;
+    // Additional status display field names for this data type.
+    private static final String[] fieldNames = { "Shared Hits", "Component Hits", "Cluster Energy", "Buffer Index" };
+    
+	/**
+	 * <b>ClusterViewer</b><br/><br/>
+     * <code>public <b>ClusterViewer</b>()</code><br/><br/>
+     * Constructs a new <code>Viewer</code> for displaying data read
+     * from a file.
+	 * @param dataSource - The <code>EventManager</code> responsible
+	 * for reading data from a file.
+	 * @throws NullPointerException Occurs if the event manager is
+	 * <code>null</code>.
+	 */
+	public ClusterViewer(EventManager dataSource, int eventWindow) throws NullPointerException {
+		// Pass any additional fields required by the event manager
+		// to the underlying Viewer object to be added to the status
+		// display panel.
+		super(dataSource, fieldNames);
+		
+		// Define the event window and initialize the event data.
+		this.eventWindow = eventWindow;
+		eventEnergyBuffer = new LinkedList<Double[][]>();
+		eventHitBuffer = new LinkedList<List<EcalHit>>();
+		
+		// Prepare the event buffer to display the first event.
+		try {
+			// Make an empty array. At the start, there are no previous
+			// events to load.
+			Double[][] emptyArray = new Double[46][11];
+			for(int x = 0; x < 46; x++) {
+				for(int y = 0; y < 11; y++) { emptyArray[x][y] = new Double(0.0); }
+			}
+			
+			// Populate the eventWindow before section of the buffer
+			// with the empty events.
+			for(int i = 0; i <= eventWindow; i++) {
+				eventEnergyBuffer.addFirst(emptyArray);
+				eventHitBuffer.addFirst(new ArrayList<EcalHit>());
+			}
+			
+			// Fill the rest of the array with future events.
+			for(int i = 0; i < eventWindow; i++) {
+				em.nextEvent();
+				eventEnergyBuffer.addFirst(toEnergyArray(em.getHits()));
+				eventHitBuffer.addFirst(em.getHits());
+			}
+		}
+		catch(java.io.IOException e) { System.exit(1); }
+		
+        // Make a key listener to change events.
+        addKeyListener(new EcalKeyListener());
+	}
+    
+    /**
+     * <b>displayNextEvent</b><br/><br/>
+     * <code>public void <b>displayNextEvent</b>()</code><br/><br/>
+     * Feeds the calorimeter panel the data from the next event.
+     * @throws IOException Occurs when there is an issue with reading the data file.
+     **/
+    public void displayNextEvent() throws IOException { getEvent(true); }
+    
+    /**
+     * <b>displayPreviousEvent</b><br/><br/>
+     * <code>public void <b>displayPreviousEvent</b>()</code><br/><br/>
+     * Feeds the calorimeter panel the data from the previous event.
+     * @throws IOException Occurs when there is an issue with reading the data file.
+     **/
+    public void displayPreviousEvent() throws IOException { getEvent(false); }
+	
+	public List<Cluster> getClusters() {
+		// Get the set of hits in the middle of the buffer. This is
+		// the "current" event.
+		List<EcalHit> activeEvent = eventHitBuffer.get(eventWindow);
+		
+		// Store clusters.
+		ArrayList<Cluster> clusterList = new ArrayList<Cluster>();
+		
+		// For each hit, check if it meets the criteria for a cluster.
+		for(EcalHit hit : activeEvent) {
+			// Track whether this hit is a cluster.
+			boolean isCluster = true;
+			
+			// Track the current hit's cluster energy.
+			double clusterEnergy = 0.0;
+			
+			// Convert the current hit to the proper coordinates.
+			Point hitLoc = toPanelPoint(hit.getLocation());
+			
+			// Track which crystals are part of the cluster.
+			HashSet<Point> componentSet = new HashSet<Point>();
+			
+			// Get the set of the current hit's neighbors.
+			Set<Point> neighbors = ecalPanel.getNeighbors(hitLoc);
+			
+			// Loop through the buffer and perform comparisons.
+			for(Double[][] event : eventEnergyBuffer) {
+				// Increment the cluster energy by the hit's energy at
+				// the current time in the buffer.
+				clusterEnergy += event[hitLoc.x][hitLoc.y];
+				
+				// A hit must be larger than itself at all other times
+				// stored in the buffer.
+				if(event[hitLoc.x][hitLoc.y] > hit.getEnergy()) {
+					isCluster = false;
+					break;
+				}
+				
+				// A hit must be larger than its immediate neighbors
+				// at all times in the buffer as well.
+				for(Point neighbor : neighbors) {
+					// Increment the cluster energy by the neighbor's
+					// energy at the current time in the buffer.
+					clusterEnergy += event[neighbor.x][neighbor.y];
+					
+					// Check that the neighbor's energy is not higher
+					// than the present hit's.
+					if(event[neighbor.x][neighbor.y] > hit.getEnergy()) {
+						isCluster = false;
+						break;
+					}
+					
+					// If this neighbor has a non-zero energy, it is
+					// a component of the potential cluster.
+					if(event[neighbor.x][neighbor.y] != 0) { componentSet.add(neighbor); }
+				}
+			}
+			
+			// If the current hit did not fail any of the preceding
+			// checks, then it is a cluster and should be added to
+			// the cluster list.
+			if(isCluster) {
+				Cluster cluster = new Cluster(hit.getLocation(), clusterEnergy);
+				for(Point neighbor : componentSet) { cluster.addComponentHit(toEcalPoint(neighbor)); }
+				clusterList.add(cluster);
+			}
+		}
+		
+		// Return the list of clusters.
+		return clusterList;
+	}
+    
+    protected void updateStatusPanel() {
+    	super.updateStatusPanel();
+    	
+		// Get the currently selected crystal.
+		Point crystal = ecalPanel.getSelectedCrystal();
+    	
+		// If the active crystal is not null, see if it is a cluster.
+		if(crystal != null) {
+			// Get the cluster associated with this point.
+			Cluster activeCluster = clusterMap.get(crystal);
+			
+			// If the cluster is null, we set everything to undefined.
+			if(activeCluster == null) {
+				for(String field : fieldNames) { setStatusField(field, StatusPanel.NULL_VALUE); }
+			}
+			
+			// Otherwise, define the fields based on the cluster.
+			else {
+				// Get the shared and component hit counts.
+				setStatusField(fieldNames[0], Integer.toString(activeCluster.getSharedHitCount()));
+				setStatusField(fieldNames[1], Integer.toString(activeCluster.getComponentHitCount()));
+				
+				// Format the cluster energy, or account for it if it
+				// doesn't exist.
+				String energy;
+				if(activeCluster.getClusterEnergy() != Double.NaN) {
+					DecimalFormat formatter = new DecimalFormat("0.####E0");
+					energy = formatter.format(activeCluster.getClusterEnergy());
+				}
+				else { energy = "---"; }
+				setStatusField(fieldNames[2], energy);
+			}
+		}
+		// Otherwise, clear the field values.
+		else { for(String field : fieldNames) { setStatusField(field, StatusPanel.NULL_VALUE); } }
+		
+		// Write the current buffer index.
+		
+		setStatusField(fieldNames[3], Integer.toString(eventWindow - bufferIndex));
+    }
+    
+	/**
+	 * <b>displayEvent</b><br/><br/>
+	 * <code>private void <b>displayEvent</b></code><br/><br/>
+	 * Displays the given lists of hits and clusters on the calorimeter
+	 * panel.
+	 * @param hitList - A list of hits for the current event.
+	 * @param clusterList  - A list of clusters for the current event.
+	 */
+	private void displayEvent(List<EcalHit> hitList, List<Cluster> clusterList) {
+		// Suppress the calorimeter panel.
+		ecalPanel.setSuppressRedraw(true);
+		
+        // Display the hits.
+        for (EcalHit h : hitList) {
+            int ix = toPanelX(h.getX());
+            int iy = toPanelY(h.getY());
+            ecalPanel.addCrystalEnergy(ix, iy, h.getEnergy());
+        }
+        
+        // Display the clusters.
+        for(Cluster cluster : clusterList) {
+        	Point rawCluster = cluster.getClusterCenter();
+        	Point clusterCenter = toPanelPoint(rawCluster);
+            ecalPanel.setCrystalCluster(clusterCenter.x, clusterCenter.y, true);
+            
+        	// Add component hits to the calorimeter panel.
+        	for(Point ch : cluster.getComponentHits()) {
+        		ecalPanel.addAssociation(new Association(clusterCenter, toPanelPoint(ch), HIGHLIGHT_CLUSTER_COMPONENT));
+        	}
+        	
+        	// Add shared hits to the calorimeter panel.
+        	for(Point sh : cluster.getSharedHits()) {
+        		ecalPanel.addAssociation(new Association(clusterCenter, toPanelPoint(sh), HIGHLIGHT_CLUSTER_SHARED));
+        	}
+        }
+        
+		// Stop suppressing the panel and order it to redraw.
+		ecalPanel.setSuppressRedraw(false);
+		ecalPanel.repaint();
+        
+        // Update the status panel to account for the new event.
+        updateStatusPanel();
+	}
+    
+    /**
+     * <b>getEvent</b><br/><br/>
+     * <code>private void <b>getEvent</b>(boolean forward)</code><br/><br/>
+     * Reads either the next or the previous event from the event manager.
+     * @param forward - Whether the event data should be read forward
+     * or backward.
+     * @throws IOException Occurs when there is an issue with reading the data file.
+     */
+    private void getEvent(boolean forward) throws IOException {
+        // Clear the calorimeter panel.
+        ecalPanel.clearCrystals();
+        
+        // If there is no data source, we can not do anything.
+        if (em == null) { return; }
+        
+        // Otherwise, get the next event.
+        if(forward) { em.nextEvent(); }
+        else { em.previousEvent(); }
+        
+        // Remove the last buffer event and add the new one.
+        if(forward) {
+        	eventEnergyBuffer.removeLast();
+        	eventHitBuffer.removeLast();
+        	eventEnergyBuffer.addFirst(toEnergyArray(em.getHits()));
+        	eventHitBuffer.addFirst(em.getHits());
+        }
+        else {
+        	eventEnergyBuffer.removeFirst();
+        	eventHitBuffer.removeFirst();
+        	eventEnergyBuffer.addLast(toEnergyArray(em.getHits()));
+        	eventHitBuffer.addLast(em.getHits());
+        }
+        
+        // Determine if any of the hits in the active event are
+        // clusters. The active event is the event in the middle of
+        // the event buffer (i.e. index eventWindow).
+        eventClusters = getClusters();
+        
+        // Load the cluster map.
+        clusterMap.clear();
+        for(Cluster c : eventClusters) { clusterMap.put(toPanelPoint(c.getClusterCenter()), c); }
+        
+        // Update the displayed buffer index.
+        bufferIndex = eventWindow;
+        
+        // Display it.
+        displayEvent(eventHitBuffer.get(eventWindow), eventClusters);
+    }
+	
+	private Double[][] toEnergyArray(List<EcalHit> hits) {
+		// Define the energy array.
+		Double[][] energy = new Double[46][11];
+		for(int x = 0; x < energy.length; x++) {
+			for(int y = 0; y < energy[x].length; y++) {
+				energy[x][y] = new Double(0);
+			}
+		}
+		
+		// For each hit, place its energy in the array.
+		for(EcalHit hit : hits) {
+			// Get the converted crystal index.
+			Point panelLoc = toPanelPoint(hit.getLocation());
+			
+			// Add the energy to the array.
+			energy[panelLoc.x][panelLoc.y] += hit.getEnergy(); 
+		}
+		
+		// Return the resulting array.
+		return energy;
+	}
+	
+    /**
+     * The <code>EcalListener</code> class binds keys to actions.
+     * Bound actions include:
+     * [Right Arrow] :: Next event (stand-alone mode only)
+     * [Left Arrow ] :: Previous event (stand-alone mode only)
+     * b             :: Toggle color-mapping for 0 energy crystals
+     * h             :: Toggle selected crystal highlighting
+     * l             :: Toggle logarithmic versus linear scaling
+     * s			 :: Saves the current display to a file
+     **/
+    private class EcalKeyListener implements KeyListener {
+        public void keyPressed(KeyEvent e) { }
+        
+        public void keyReleased(KeyEvent e) {
+            // If right-arrow was pressed, go to the next event.
+            if (e.getKeyCode() == 39) {
+                try { displayNextEvent(); }
+                catch (IOException ex) {
+                    System.err.println(ex.getMessage());
+                    System.exit(1);
+                }
+            }
+            
+            // If left-arrow was pressed, go to the previous event.
+            else if (e.getKeyCode() == 37) {
+                //try { displayPreviousEvent(); }
+                //catch (IOException ex) {
+                //    System.err.println(ex.getMessage());
+                //    System.exit(1);
+                //}
+            }
+            
+            // If the down-arrow was pressed, move down a time step in
+            // the buffer and display it.
+            else if(e.getKeyCode() == 40) {
+            	if(bufferIndex == eventHitBuffer.size() - 1) { return; }
+            	else {
+                	bufferIndex++;
+            		ecalPanel.clearCrystals();
+            		displayEvent(eventHitBuffer.get(bufferIndex), eventClusters);
+            	}
+            }
+            
+            // If the up-arrow was pressed, move up a time step in
+            // the buffer and display it.
+            else if(e.getKeyCode() == 38) {
+            	if(bufferIndex == 0) { return; }
+            	else {
+            		bufferIndex--;
+            		ecalPanel.clearCrystals();
+            		displayEvent(eventHitBuffer.get(bufferIndex), eventClusters);
+            	}
+            }
+            
+            // 'b' toggles the default white background.
+            else if(e.getKeyCode() == 66) {
+            	if(background) { ecalPanel.setDefaultCrystalColor(null); }
+            	else { ecalPanel.setDefaultCrystalColor(Color.GRAY); }
+            	background = !background;
+            }
+            
+            // 'h' toggles highlighting the crystal under the cursor.
+            else if(e.getKeyCode() == 72) { ecalPanel.setSelectionHighlighting(!ecalPanel.isSelectionEnabled()); }
+            
+            // 'l' toggles linear or logarithmic scaling.
+            else if(e.getKeyCode() == 76) {
+            	if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
+            	else { ecalPanel.setScalingLinear(); }
+            }
+            
+            // 's' saves the panel to a file.
+            else if(e.getKeyCode() == 83) {
+            	// Make a new buffered image on which to draw the content pane.
+            	BufferedImage screenshot = new BufferedImage(getContentPane().getWidth(),
+            			getContentPane().getHeight(), BufferedImage.TYPE_INT_ARGB);
+            	
+            	// Paint the content pane to image.
+            	getContentPane().paint(screenshot.getGraphics());
+            	
+            	// Get the lowest available file name.
+            	int fileNum = 0;
+            	File imageFile = new File("screenshot_" + fileNum + ".png");
+            	while(imageFile.exists()) {
+            		fileNum++;
+            		imageFile = new File("screenshot_" + fileNum + ".png");
+            	}
+            	
+            	// Save the image to a PNG file.
+            	try { ImageIO.write(screenshot, "PNG", imageFile); }
+            	catch(IOException ioe) {
+            		System.err.println("Error saving file \"screenshot.png\".");
+            	}
+            	System.out.println("Screenshot saved to: " + imageFile.getAbsolutePath());
+            }
+            
+            // Otherwise, print out the key code for the pressed key.
+            else { System.out.printf("Key Code: %d%n", e.getKeyCode()); }
+        }
+        
+        public void keyTyped(KeyEvent e) { }
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
FileViewer.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/FileViewer.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/FileViewer.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,158 @@
+package org.hps.monitoring.ecal.ui;
+
+import org.hps.monitoring.ecal.io.EventManager;
+
+import java.awt.Point;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.HashMap;
+import java.util.List;
+
+import org.hps.monitoring.ecal.event.Association;
+import org.hps.monitoring.ecal.event.Cluster;
+import org.hps.monitoring.ecal.event.EcalHit;
+
+/**
+ * Class <code>FileViewer</code> is an implementation of the <code>
+ * Viewer</code> abstract class that reads events from a file data
+ * source. Any file type can be used, so long as it has a manager
+ * which implements the <code>EventManager</code> interface.
+ * 
+ * @author Kyle McCarty
+ */
+public class FileViewer extends ActiveViewer {
+	private static final long serialVersionUID = 17058336873349781L;
+    // Map cluster location to a cluster object.
+    private HashMap<Point, Cluster> clusterMap = new HashMap<Point, Cluster>();
+    // Additional status display field names for this data type.
+    private static final String[] fieldNames = { "Shared Hits", "Component Hits", "Cluster Energy" };
+    
+	/**
+	 * <b>FileViewer</b><br/><br/>
+     * <code>public <b>FileViewer</b>()</code><br/><br/>
+     * Constructs a new <code>Viewer</code> for displaying data read
+     * from a file.
+	 * @param dataSource - The <code>EventManager</code> responsible
+	 * for reading data from a file.
+	 * @throws NullPointerException Occurs if the event manager is
+	 * <code>null</code>.
+	 */
+	public FileViewer(EventManager dataSource) throws NullPointerException {
+		// Pass any additional fields required by the event manager
+		// to the underlying Viewer object to be added to the status
+		// display panel.
+		super(dataSource, fieldNames);
+	}
+    
+    public void displayNextEvent() throws IOException { getEvent(true); }
+    
+    public void displayPreviousEvent() throws IOException { getEvent(false); }
+    
+    protected void updateStatusPanel() {
+    	super.updateStatusPanel();
+    	
+		// Get the currently selected crystal.
+		Point crystal = ecalPanel.getSelectedCrystal();
+    	
+		// If the active crystal is not null, see if it is a cluster.
+		if(crystal != null) {
+			// Get the cluster associated with this point.
+			Cluster activeCluster = clusterMap.get(crystal);
+			
+			// If the cluster is null, we set everything to undefined.
+			if(activeCluster == null) {
+				for(String field : fieldNames) { setStatusField(field, StatusPanel.NULL_VALUE); }
+			}
+			
+			// Otherwise, define the fields based on the cluster.
+			else {
+				// Get the shared and component hit counts.
+				setStatusField(fieldNames[0], Integer.toString(activeCluster.getSharedHitCount()));
+				setStatusField(fieldNames[1], Integer.toString(activeCluster.getComponentHitCount()));
+				
+				// Format the cluster energy, or account for it if it
+				// doesn't exist.
+				String energy;
+				if(activeCluster.getClusterEnergy() != Double.NaN) {
+					DecimalFormat formatter = new DecimalFormat("0.####E0");
+					energy = formatter.format(activeCluster.getClusterEnergy());
+				}
+				else { energy = "---"; }
+				setStatusField(fieldNames[2], energy);
+			}
+		}
+		// Otherwise, clear the field values.
+		else { for(String field : fieldNames) { setStatusField(field, StatusPanel.NULL_VALUE); } }
+    }
+    
+	/**
+	 * <b>displayEvent</b><br/><br/>
+	 * <code>private void <b>displayEvent</b>(List<EcalHit> hitList, List<Cluster> clusterList)</code><br/><br/>
+	 * Displays the given lists of hits and clusters on the calorimeter
+	 * panel.
+	 * @param hitList - A list of hits for the current event.
+	 * @param clusterList  - A list of clusters for the current event.
+	 */
+	private void displayEvent(List<EcalHit> hitList, List<Cluster> clusterList) {
+		// Suppress the calorimeter panel's redrawing.
+		ecalPanel.setSuppressRedraw(true);
+		
+        // Display the hits.
+        for (EcalHit h : hitList) {
+            int ix = toPanelX(h.getX());
+            int iy = toPanelY(h.getY());
+            ecalPanel.addCrystalEnergy(ix, iy, h.getEnergy());
+        }
+        
+        // Display the clusters.
+        for(Cluster cluster : clusterList) {
+        	Point rawCluster = cluster.getClusterCenter();
+        	Point clusterCenter = toPanelPoint(rawCluster);
+            ecalPanel.setCrystalCluster(clusterCenter.x, clusterCenter.y, true);
+            
+        	// Add component hits to the calorimeter panel.
+        	for(Point ch : cluster.getComponentHits()) {
+        		ecalPanel.addAssociation(new Association(clusterCenter, toPanelPoint(ch), HIGHLIGHT_CLUSTER_COMPONENT));
+        	}
+        	
+        	// Add shared hits to the calorimeter panel.
+        	for(Point sh : cluster.getSharedHits()) {
+        		ecalPanel.addAssociation(new Association(clusterCenter, toPanelPoint(sh), HIGHLIGHT_CLUSTER_SHARED));
+        	}
+        }
+        
+        // Stop suppressing the redraw and order the panel to update.
+        ecalPanel.setSuppressRedraw(false);
+        ecalPanel.repaint();
+        
+        // Update the status panel to account for the new event.
+        updateStatusPanel();
+	}
+    
+    /**
+     * <b>getEvent</b><br/><br/>
+     * <code>private void <b>getEvent</b>(boolean forward)</code><br/><br/>
+     * Reads either the next or the previous event from the event manager.
+     * @param forward - Whether the event data should be read forward
+     * or backward.
+     * @throws IOException Occurs when there is an issue with reading the data file.
+     */
+    private void getEvent(boolean forward) throws IOException {
+        // Clear the calorimeter panel.
+        ecalPanel.clearCrystals();
+        
+        // If there is no data source, we can not do anything.
+        if (em == null) { return; }
+        
+        // Otherwise, get the next event.
+        if(forward) { em.nextEvent(); }
+        else { em.previousEvent(); }
+        
+        // Load the cluster map.
+        clusterMap.clear();
+        for(Cluster c : em.getClusters()) { clusterMap.put(toPanelPoint(c.getClusterCenter()), c); }
+        
+        // Display it.
+        displayEvent(em.getHits(), em.getClusters());
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
OccupancyViewer.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/OccupancyViewer.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/OccupancyViewer.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,150 @@
+package org.hps.monitoring.ecal.ui;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hps.monitoring.ecal.event.EcalHit;
+import org.hps.monitoring.ecal.io.EventManager;
+
+/**
+ * Class <code>OccupancyViewer</code> is an active implementation of
+ * the <code>Viewer</code> class that displays occupancies on each
+ * crystal.
+ * 
+ * @author Kyle McCarty
+ */
+public class OccupancyViewer extends ActiveViewer {
+	private static final long serialVersionUID = 3712604287904215617L;
+	// The number of events that have been read so far.
+	private long events = 0;
+	// The total number of hits for each crystal position.
+	private long[][] hits;
+	
+	/**
+	 * <b>OccupancyViewer</b><br/><br/>
+     * <code>public <b>OccupancyViewer</b>(EventManager em)</code><br/><br/>
+     * Creates a new occupancy display that draws event data from the
+     * indicated data source.
+	 * @param em - The data source from which to draw events.
+	 */
+	public OccupancyViewer(EventManager em) {
+		// Initialize the super class.
+		super(em);
+		
+		// Set the title and scale.
+		setTitle("HPS Calorimeter Occupancies");
+		ecalPanel.setScaleMaximum(1.0);
+		
+		// Initialize the hit counts array.
+		Dimension ecalSize = ecalPanel.getCrystalBounds();
+		hits = new long[ecalSize.width][ecalSize.height];
+	}
+    
+    public void displayNextEvent() throws IOException { getEvent(true); }
+    
+    public void displayPreviousEvent() throws IOException { getEvent(false); }
+    
+    /**
+     * <b>resetOccupancies</b><br/><br/>
+     * <code>public void <b>resetOccupancies</b>()</code><br/><br/>
+     * Clears the current occupancy data.
+     */
+    public void resetOccupancies() {
+    	// Clear the crystal hit counts.
+    	for(int x = 0; x < hits.length; x++) {
+    		for(int y = 0; y < hits[0].length; y++) {
+    			hits[x][y] = 0;
+    		}
+    	}
+    	
+    	// Clear the number of events.
+    	events = 0;
+    }
+    
+	/**
+	 * <b>displayEvent</b><br/><br/>
+	 * <code>private void <b>displayEvent</b>(List<EcalHit> hitList)</code><br/><br/>
+	 * Displays the given lists of hits on the calorimeter panel.
+	 * @param hitList - A list of hits for the current event.
+	 */
+	private void displayEvent(List<EcalHit> hitList) {
+		// Suppress the calorimeter panel's redrawing.
+		ecalPanel.setSuppressRedraw(true);
+		
+        // Display the hits.
+        for (EcalHit h : hitList) {
+            ecalPanel.addCrystalEnergy(h.getX(), h.getY(), h.getEnergy());
+        }
+        
+        // Stop suppressing the redraw and order the panel to update.
+        ecalPanel.setSuppressRedraw(false);
+        ecalPanel.repaint();
+        
+        // Update the status panel to account for the new event.
+        updateStatusPanel();
+	}
+    
+    /**
+     * <b>getEvent</b><br/><br/>
+     * <code>private void <b>getEvent</b>(boolean forward)</code><br/><br/>
+     * Reads either the next or the previous event from the event manager.
+     * @param forward - Whether the event data should be read forward
+     * or backward.
+     * @throws IOException Occurs when there is an issue with reading the data file.
+     */
+    private void getEvent(boolean forward) throws IOException {
+        // Clear the calorimeter panel.
+        ecalPanel.clearCrystals();
+        
+        // If there is no data source, we can not do anything.
+        if (em == null) { return; }
+        
+        // Get the next event.
+        if(forward) {
+        	// Get the next event.
+        	em.nextEvent();
+        	
+        	// Increment the event count.
+        	events++;
+        	
+        	// For each hit, increment the hit count for the relevant
+        	// crystal by one.
+        	for(EcalHit hit : em.getHits()) {
+        		hits[toPanelX(hit.getX())][toPanelY(hit.getY())]++;
+        	}
+        }
+        else {
+        	// Get the previous event.
+        	em.previousEvent();
+        	
+        	// Decrement the event count.
+        	events--;
+        	
+        	// For each hit, decrement the hit count for the relevant
+        	// crystal by one.
+        	for(EcalHit hit : em.getHits()) {
+        		hits[toPanelX(hit.getX())][toPanelY(hit.getY())]--;
+        	}
+        }
+        
+        // Build a "hit list" from the occupancies.
+        ArrayList<EcalHit> occupancyList = new ArrayList<EcalHit>();
+        for(int x = 0; x < hits.length; x++) {
+        	for(int y = 0; y < hits[0].length; y++) {
+        		if(hits[x][y] != 0) {
+        			// Define the crystal ID and "energy."
+        			Point cid = new Point(x, y);
+        			double occupancy = ((double) hits[x][y]) / events;
+        			EcalHit occupancyHit = new EcalHit(cid, occupancy);
+        			occupancyList.add(occupancyHit);
+        		}
+        	}
+        }
+        
+        // Display it the occupancies.
+        displayEvent(occupancyList);
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
PEventViewer.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/PEventViewer.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/PEventViewer.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,167 @@
+package org.hps.monitoring.ecal.ui;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javax.imageio.ImageIO;
+
+import org.hps.monitoring.ecal.event.Association;
+import org.hps.monitoring.ecal.event.Cluster;
+import org.hps.monitoring.ecal.event.EcalHit;
+
+/**
+ * Class <code>PEventViewer</code> represents a <code>PassiveViewer
+ * </code> implementation which displays hits and clusters.
+ * 
+ * @author Kyle McCarty
+ */
+public class PEventViewer extends PassiveViewer {
+	private static final long serialVersionUID = -7479125553259270894L;
+	// Stores whether the background color is set or not.
+	private boolean background = false;
+	// Stores cluster objects.
+	protected ArrayList<Cluster> clusterList = new ArrayList<Cluster>();
+	// Stores hit objects.
+	protected ArrayList<EcalHit> hitList = new ArrayList<EcalHit>();
+	
+	/**
+	 * <b>PEventViewer</b><br/><br/>
+	 * <code>public <b>PEventViewer</b>(String... fieldValues)</code><br/><br/>
+	 * Creates a passive viewer for displaying hits and clusters in
+	 * an event.
+	 * @param fieldValues - Any additional status fields to display.
+	 */
+	public PEventViewer(String... fieldValues) {
+		// Pass the field values to the superclass.
+		super(fieldValues);
+		
+		// Set the key bindings.
+		addKeyListener(new EcalKeyListener());
+	}
+	
+	public void addHit(EcalHit hit) { hitList.add(hit); }
+	
+	public void addCluster(Cluster cluster) { clusterList.add(cluster); }
+	
+	/**
+	 * <b>clearHits</b><br/><br/>
+	 * <code>public void <b>clearHits</b>()</code><br/><br/>
+	 * Removes all of the hit data from the viewer.
+	 */
+	public void clearHits() { hitList.clear(); }
+	
+	/**
+	 * <b>clearClusters</b><br/><br/>
+	 * <code>public void <b>clearClusters</b>()</code><br/><br/>
+	 * Removes all of the cluster data from the viewer.
+	 */
+	public void clearClusters() { hitList.clear(); }
+	
+	public void resetDisplay() {
+		// Reset the hit and cluster lists.
+		hitList.clear();
+		clusterList.clear();
+	}
+	
+	public void updateDisplay() {
+		// Suppress the calorimeter panel's redrawing.
+		ecalPanel.setSuppressRedraw(true);
+		
+        // Display the hits.
+        for (EcalHit h : hitList) {
+            int ix = toPanelX(h.getX());
+            int iy = toPanelY(h.getY());
+            ecalPanel.addCrystalEnergy(ix, iy, h.getEnergy());
+        }
+        
+        // Display the clusters.
+        for(Cluster cluster : clusterList) {
+        	Point rawCluster = cluster.getClusterCenter();
+        	Point clusterCenter = toPanelPoint(rawCluster);
+            ecalPanel.setCrystalCluster(clusterCenter.x, clusterCenter.y, true);
+            
+        	// Add component hits to the calorimeter panel.
+        	for(Point ch : cluster.getComponentHits()) {
+        		ecalPanel.addAssociation(new Association(clusterCenter, toPanelPoint(ch), HIGHLIGHT_CLUSTER_COMPONENT));
+        	}
+        	
+        	// Add shared hits to the calorimeter panel.
+        	for(Point sh : cluster.getSharedHits()) {
+        		ecalPanel.addAssociation(new Association(clusterCenter, toPanelPoint(sh), HIGHLIGHT_CLUSTER_SHARED));
+        	}
+        }
+        
+        // Stop suppressing the redraw and order the panel to update.
+        ecalPanel.setSuppressRedraw(false);
+        ecalPanel.repaint();
+        
+        // Update the status panel to account for the new event.
+        updateStatusPanel();
+	}
+	
+    /**
+     * The <code>EcalListener</code> class binds keys to actions.
+     * Bound actions include:
+     * b             :: Toggle color-mapping for 0 energy crystals
+     * h             :: Toggle selected crystal highlighting
+     * l             :: Toggle logarithmic versus linear scaling
+     * s			 :: Saves the current display to a file
+     **/
+    private class EcalKeyListener implements KeyListener {
+        public void keyPressed(KeyEvent e) { }
+        
+        public void keyReleased(KeyEvent e) {
+            // 'b' toggles the default white background.
+            if(e.getKeyCode() == 66) {
+            	if(background) { ecalPanel.setDefaultCrystalColor(null); }
+            	else { ecalPanel.setDefaultCrystalColor(Color.GRAY); }
+            	background = !background;
+            }
+            
+            // 'h' toggles highlighting the crystal under the cursor.
+            else if(e.getKeyCode() == 72) { ecalPanel.setSelectionHighlighting(!ecalPanel.isSelectionEnabled()); }
+            
+            // 'l' toggles linear or logarithmic scaling.
+            else if(e.getKeyCode() == 76) {
+            	if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
+            	else { ecalPanel.setScalingLinear(); }
+            }
+            
+            // 's' saves the panel to a file.
+            else if(e.getKeyCode() == 83) {
+            	// Make a new buffered image on which to draw the content pane.
+            	BufferedImage screenshot = new BufferedImage(getContentPane().getWidth(),
+            			getContentPane().getHeight(), BufferedImage.TYPE_INT_ARGB);
+            	
+            	// Paint the content pane to image.
+            	getContentPane().paint(screenshot.getGraphics());
+            	
+            	// Get the lowest available file name.
+            	int fileNum = 0;
+            	File imageFile = new File("screenshot_" + fileNum + ".png");
+            	while(imageFile.exists()) {
+            		fileNum++;
+            		imageFile = new File("screenshot_" + fileNum + ".png");
+            	}
+            	
+            	// Save the image to a PNG file.
+            	try { ImageIO.write(screenshot, "PNG", imageFile); }
+            	catch(IOException ioe) {
+            		System.err.println("Error saving file \"screenshot.png\".");
+            	}
+            	System.out.println("Screenshot saved to: " + imageFile.getAbsolutePath());
+            }
+            
+            // Otherwise, print out the key code for the pressed key.
+            else { System.out.printf("Key Code: %d%n", e.getKeyCode()); }
+        }
+        
+        public void keyTyped(KeyEvent e) { }
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
POccupancyViewer.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/POccupancyViewer.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/POccupancyViewer.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,127 @@
+package org.hps.monitoring.ecal.ui;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+
+import org.hps.monitoring.ecal.event.Cluster;
+import org.hps.monitoring.ecal.event.EcalHit;
+
+/**
+ * The class <code>POccupancyViewer</code> is an extension of the <code>
+ * PassiveViewer</code> class for displaying occupancies from a stream.
+ * Like all passive viewers, it is designed to receive instructions on
+ * when to update its display or read data from the stream.
+ * 
+ * @author Kyle McCarty
+ */
+public class POccupancyViewer extends PassiveViewer {
+	private static final long serialVersionUID = 3712604287904215617L;
+	// Store the number of hits for each crystal.
+	private long[][] hits;
+	// Store the total number of events read.
+	private long events = 0;
+	// Stores hit objects.
+	protected ArrayList<EcalHit> hitList = new ArrayList<EcalHit>();
+	
+	/**
+	 * <b>POccupancyViewer</b><br/><br/>
+     * <code>public <b>POccupancyViewer</b>(int updateRate, boolean resetAtUpdate)</code><br/><br/>
+     * Initializes a <code>Viewer</code> window that displays will
+     * occupancies from a data stream.
+	 */
+	public POccupancyViewer() {
+		// Set the title and scale.
+		setTitle("HPS Calorimeter Occupancies");
+		ecalPanel.setScaleMaximum(1.0);
+		
+		// Initialize the hit counts array.
+		Dimension ecalSize = ecalPanel.getCrystalBounds();
+		hits = new long[ecalSize.width][ecalSize.height];
+	}
+    
+	public void addHit(EcalHit hit) {
+		// Get the panel coordinates of the hit.
+		int ix = toPanelX(hit.getX());
+		int iy = toPanelY(hit.getY());
+		
+		// Increment the hit count at the indicated location.
+		hits[ix][iy]++;
+	}
+	
+	/**
+	 * <b>addCluster</b><br/><br/>
+	 * <code>public void <b>addCluster</b>(Cluster cluster)</code><br/><br/>
+	 * Adds a new cluster to the display.<br/><br/>
+	 * <b>Note:</b> This operation is not supported for occupancies.
+	 */
+	public void addCluster(Cluster cluster) { }
+	
+	/**
+	 * <b>removeHit</b><br/><br/>
+	 * <code>public void <b>removeHit</b>(EcalHit hit)</code><br/><br/>
+	 * Removes a hit from the display.
+	 * @param hit - The hit to be removed.
+	 */
+	public void removeHit(EcalHit hit) {
+		// Get the panel coordinates of the hit.
+		int ix = toPanelX(hit.getX());
+		int iy = toPanelY(hit.getY());
+		
+		// Decrement the hit count at the indicated location.
+		hits[ix][iy]--;
+	}
+	
+	public void resetDisplay() { hitList.clear(); }
+	
+	/**
+	 * <b>incrementEventCount</b><br/><br/>
+	 * <code>public void <b>incrementEventCount</b>(int amount)</code><br/><br/>
+	 * Increments the number of events represented by the current data
+	 * set by the indicated amount. Note that this may be negative to
+	 * reduce the number of events.
+	 * @param amount - The number of events to add.
+	 */
+	public void incrementEventCount(int amount) { events += amount; }
+	
+	/**
+	 * <b>updateDisplay</b><br/><br/>
+	 * <code>public void <b>updateDisplay</b>()</code><br/><br/>
+	 * Displays the hits and clusters added by the <code>addHit</code>
+	 * and <code>addCluster</code> methods.
+	 */
+	public void updateDisplay() { 
+        // Build a "hit list" from the occupancies.
+        for(int x = 0; x < hits.length; x++) {
+        	for(int y = 0; y < hits[0].length; y++) {
+        		// Don't bother performing calculations or building
+        		// any objects if there are zero hits.
+        		if(hits[x][y] != 0) {
+        			// Define the crystal ID and "energy."
+        			Point cid = new Point(x, y);
+        			double occupancy = ((double) hits[x][y]) / events;
+        			
+        			// Add a "hit" formed from these values.
+        			hitList.add(new EcalHit(cid, occupancy));
+        		}
+        	}
+        }
+        
+		// Suppress the calorimeter panel's redrawing.
+		ecalPanel.setSuppressRedraw(true);
+		
+        // Display the hits.
+        for (EcalHit h : hitList) {
+            int ix = toPanelX(h.getX());
+            int iy = toPanelY(h.getY());
+            ecalPanel.addCrystalEnergy(ix, iy, h.getEnergy());
+        }
+        
+        // Stop suppressing the redraw and order the panel to update.
+        ecalPanel.setSuppressRedraw(false);
+        ecalPanel.repaint();
+        
+        // Update the status panel to account for the new event.
+        updateStatusPanel();
+	}
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
PassiveViewer.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/PassiveViewer.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/PassiveViewer.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,163 @@
+package org.hps.monitoring.ecal.ui;
+
+import java.awt.Color;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+import org.hps.monitoring.ecal.event.Cluster;
+import org.hps.monitoring.ecal.event.EcalHit;
+
+/**
+ * Abstract class <code>PassiveViewer</code> represents a <code>Viewer
+ * </code> implementation which updates based on information passed to
+ * it by an external source.
+ * 
+ * @author Kyle McCarty
+ */
+public abstract class PassiveViewer extends Viewer {
+	private static final long serialVersionUID = -7479125553259270894L;
+	// Stores whether the background color is set or not.
+	private boolean background = false;
+	
+	/**
+	 * <b>PassiveViewer</b><br/><br/>
+	 * <code>public <b>PassiveViewer</b>(String... fieldValues)</code><br/><br/>
+	 * @param fieldValues
+	 */
+	public PassiveViewer(String... fieldValues) {
+		// Pass the field values to the superclass.
+		super(fieldValues);
+		
+		// Set the key bindings.
+		addKeyListener(new EcalKeyListener());
+	}
+	
+	/**
+	 * <b>addHit</b><br/><br/>
+	 * <code>public void <b>addHit</b>(EcalHit hit)</code><br/><br/>
+	 * Adds a new hit to the display.
+	 * @param hit - The hit to be added.
+	 */
+	public abstract void addHit(EcalHit hit);
+	
+	/**
+	 * <b>addCluster</b><br/><br/>
+	 * <code>public void <b>addCluster</b>(Cluster cluster)</code><br/><br/>
+	 * Adds a new cluster to the display.
+	 * @param cluster - The cluster to be added.
+	 */
+	public abstract void addCluster(Cluster cluster);
+	
+	/**
+	 * <b>resetDisplay</b><br/><br/>
+	 * <code>public void <b>resetDisplay</b>()</code><br/><br/>
+	 * Clears any hits or clusters that have been added to the viewer.
+	 * Note that this does not automatically update the displayed panel.
+	 * <code>updateDisplay</code> must be called separately.
+	 */
+	public abstract void resetDisplay();
+	
+	/**
+	 * <b>setScale</b><br/><br/>
+	 * <code>public void <b>setScale</b>(int min, int max)</code><br/><br/>
+	 * Sets the upper and lower bounds of for the calorimeter display's
+	 * color mapping scale.
+	 * @param min - The lower bound.
+	 * @param max - The upper bound.
+	 */
+	public void setScale(int min, int max) {
+		ecalPanel.setScaleMinimum(min);
+		ecalPanel.setScaleMaximum(max);
+	}
+	
+	/**
+	 * <b>setScaleMaximum</b><br/><br/>
+	 * <code>public void <b>setScaleMaximum</b>(int max)</code><br/><br/>
+	 * Sets the upper bound for the calorimeter display's color mapping
+	 * scale.
+	 * @param max - The upper bound.
+	 */
+	public void setScaleMaximum(int max) { ecalPanel.setScaleMaximum(max); }
+	
+	/**
+	 * <b>setScaleMinimum</b><br/><br/>
+	 * <code>public void <b>setScaleMinimum</b>(int min)</code><br/><br/>
+	 * Sets the lower bound for the calorimeter display's color mapping
+	 * scale.
+	 * @param min - The lower bound.
+	 */
+	public void setScaleMinimum(int min) { ecalPanel.setScaleMinimum(min); }
+	
+	/**
+	 * <b>updateDisplay</b><br/><br/>
+	 * <code>public void <b>updateDisplay</b>()</code><br/><br/>
+	 * Displays the hits and clusters added by the <code>addHit</code>
+	 * and <code>addCluster</code> methods.
+	 */
+	public abstract void updateDisplay();
+	
+    /**
+     * The <code>EcalListener</code> class binds keys to actions.
+     * Bound actions include:
+     * b             :: Toggle color-mapping for 0 energy crystals
+     * h             :: Toggle selected crystal highlighting
+     * l             :: Toggle logarithmic versus linear scaling
+     * s			 :: Saves the current display to a file
+     **/
+    private class EcalKeyListener implements KeyListener {
+        public void keyPressed(KeyEvent e) { }
+        
+        public void keyReleased(KeyEvent e) {
+            // 'b' toggles the default white background.
+            if(e.getKeyCode() == 66) {
+            	if(background) { ecalPanel.setDefaultCrystalColor(null); }
+            	else { ecalPanel.setDefaultCrystalColor(Color.GRAY); }
+            	background = !background;
+            }
+            
+            // 'h' toggles highlighting the crystal under the cursor.
+            else if(e.getKeyCode() == 72) { ecalPanel.setSelectionHighlighting(!ecalPanel.isSelectionEnabled()); }
+            
+            // 'l' toggles linear or logarithmic scaling.
+            else if(e.getKeyCode() == 76) {
+            	if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
+            	else { ecalPanel.setScalingLinear(); }
+            }
+            
+            // 's' saves the panel to a file.
+            else if(e.getKeyCode() == 83) {
+            	// Make a new buffered image on which to draw the content pane.
+            	BufferedImage screenshot = new BufferedImage(getContentPane().getWidth(),
+            			getContentPane().getHeight(), BufferedImage.TYPE_INT_ARGB);
+            	
+            	// Paint the content pane to image.
+            	getContentPane().paint(screenshot.getGraphics());
+            	
+            	// Get the lowest available file name.
+            	int fileNum = 0;
+            	File imageFile = new File("screenshot_" + fileNum + ".png");
+            	while(imageFile.exists()) {
+            		fileNum++;
+            		imageFile = new File("screenshot_" + fileNum + ".png");
+            	}
+            	
+            	// Save the image to a PNG file.
+            	try { ImageIO.write(screenshot, "PNG", imageFile); }
+            	catch(IOException ioe) {
+            		System.err.println("Error saving file \"screenshot.png\".");
+            	}
+            	System.out.println("Screenshot saved to: " + imageFile.getAbsolutePath());
+            }
+            
+            // Otherwise, print out the key code for the pressed key.
+            else { System.out.printf("Key Code: %d%n", e.getKeyCode()); }
+        }
+        
+        public void keyTyped(KeyEvent e) { }
+    }
+}
\ No newline at end of file

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
StatusPanel.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/StatusPanel.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/StatusPanel.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,196 @@
+package org.hps.monitoring.ecal.ui;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * Class <code>StatusPanel</code> displays text in a set of fields.
+ *
+ * @author Kyle McCarty
+ */
+public class StatusPanel extends JPanel {
+	private static final long serialVersionUID = -8353479383875379010L;
+	// The panel that displays behind the status field.
+	private BackPanel background = new BackPanel();
+	// The status fields. The first array index represents which status
+	// field and the second is always of size two, with index 0 mapping
+	// to the label that displays the field name and index 1 mapping to
+	// the label displaying the field value.
+	private JLabel[][] field;
+	// Spacing variables for panel layout.
+	private int leftBuffer = 10;
+	private int upperBuffer = 10;
+	
+	/**
+	 * <b>NULL_VALUE</b><br/><br/>
+	 * <code><b>static final String <b>NULL_VALUE</b></code><br/><br/>
+	 * A <code>String</code> representing the default value to be
+	 * displayed on the status panel whenever there is no value for
+	 * that field.
+	 */
+	static final String NULL_VALUE = "---";
+	
+	/**
+	 * <b>StatusPanel</b><br/><br/>
+	 * <code>public <b>StatusPanel</b>(String... fieldName)</code><br/><br/>
+	 * Creates a new status panel with display fields with the indicated
+	 * names. They will be assigned a field index in the order that they
+	 * are given starting with zero.
+	 * @param fieldName - The names of the fields to display.
+	 */
+	public StatusPanel(String... fieldName) {
+		// Initialize the component.
+		super();
+		
+		// Set the layout manager to manual.
+		setLayout(null);
+		
+		// Build the text fields.
+		int curZ = 0;
+		field = new JLabel[fieldName.length][2];
+		for(int i = 0; i < field.length; i++) {
+			for(int j = 0; j < field[i].length; j++) {
+				field[i][j] = new JLabel();
+				field[i][j].setOpaque(true);
+				field[i][j].setBackground(Color.WHITE);
+				add(field[i][j]);
+				setComponentZOrder(field[i][j], curZ);
+				curZ++;
+			}
+			field[i][0].setText(fieldName[i] + ":   ");
+			field[i][0].setHorizontalAlignment(JLabel.RIGHT);
+		}
+		
+		// Start the fields as null by default.
+		clearValues();
+		
+		// Build the background panel.
+		add(background);
+		setComponentZOrder(background, curZ);
+	}
+	
+	/**
+	 * <b>clearValues</b><br/><br/>
+	 * <code>public void <b>clearValues</b>()</code><br/><br/>
+	 * Sets all of the fields on the status display to the null value.
+	 */
+	public void clearValues() {
+		for(int i = 0; i < field.length; i++) {
+			field[i][1].setText(NULL_VALUE);
+		}
+	}
+	
+	/**
+	 * <b>setFieldValue</b><br/><br/>
+	 * Sets the value of the indicated field.
+	 * @param index - The field's index.
+	 * @param value - The new value to display.
+	 * @throws IndexOutOfBoundsException Occurs when the field index
+	 * is neither more than the existing number of fields or is negative.
+	 */
+	public void setFieldValue(int index, String value) throws IndexOutOfBoundsException {
+		if(index >= 0 && index < field.length) {
+			if(value == null) { field[index][1].setText(NULL_VALUE); }
+			else  { field[index][1].setText(value); }
+		}
+		else { throw new IndexOutOfBoundsException("Invalid field index."); }
+	}
+	
+	public void setSize(int width, int height) {
+		super.setSize(width, height);
+		resize();
+	}
+	
+	public void setSize(Dimension d) {
+		super.setSize(d);
+		resize();
+	}
+	
+	/**
+	 *<b>getNextX</b><br/><br/>
+	 * <code>private int <b>getNextX</b>(Component c)</code><br/><br/>
+	 * Finds the x-coordinate immediately after the component.
+	 * @param c - The component of which to find the end.
+	 * @return Returns the x-coordinate at the end of the component. 
+	 */
+	private final static int getNextX(Component c) { return getNextX(c, 0); }
+	
+	/**
+	/**
+	 *<b>getNextX</b><br/><br/>
+	 * <code>private int <b>getNextX</b>(Component c, int buffer)</code><br/><br/>
+	 * Finds the x-coordinate after the component with a given buffer.
+	 * @param c - The component of which to find the end.
+	 * @param buffer - The extra space after the component to be included.
+	 * @return Returns the x-coordinate at the end of the component,
+	 * with a buffer length.
+	 */
+	private final static int getNextX(Component c, int buffer) {
+		return c.getX() + c.getWidth() + buffer;
+	}
+	
+	/**
+	 * <b>resize</b><br/><br/>
+	 * <code>private void <b>resize</b>()</code><br/><br/>
+	 * Updates the layout of the component to the panel's current size.
+	 */
+	private void resize() {
+		// Define the width an height as convenience variables.
+		int width = getWidth();
+		int height = getHeight();
+		
+		// Size the background panel.
+		background.setBounds(0, 0, width, height);
+		
+		// Size and place the text labels.
+		if(field.length != 0) {
+			int labelHeight = (height - (int)(upperBuffer + 5)) / 3;
+			int labelRem = (height - upperBuffer - 8) % field.length;
+			int curX = leftBuffer;
+			int curY = (int)(upperBuffer + 2);
+			for(int i = 0; i < field.length; i++) {
+				// Determine the appropriate field height.
+				int thisHeight = labelHeight;
+				if(labelRem > 0) {
+					thisHeight++;
+					labelRem--;
+				}
+				
+				// Place the field.
+				field[i][0].setBounds(curX, curY, 130, thisHeight);
+				field[i][1].setBounds(getNextX(field[i][0]), curY, 75, thisHeight);
+				
+				// If we have written three labels, then start a new column.
+				if(i % 3 == 2) {
+					curX = getNextX(field[i][1], 10);
+					curY = (int)(upperBuffer + 2);
+				}
+				
+				// Otherwise just increment the current height.
+				else { curY += thisHeight; }
+			}
+		}
+	}
+	
+	/**
+	 * Class <code>BackPanel</code> simply renders the background panel
+	 * for the status panel.
+	 */
+	private class BackPanel extends JPanel {
+		private static final long serialVersionUID = 4997805650267243080L;
+
+		public void paint(Graphics g) {
+			// Render the panel background.
+			g.setColor(Color.WHITE);
+			g.fillRect(0, upperBuffer, getWidth(), getHeight() - upperBuffer);
+			g.setColor(Color.GRAY);
+			g.drawRect(0, upperBuffer, getWidth() - 1, getHeight() - upperBuffer - 1);
+			g.setColor(Color.LIGHT_GRAY);
+			g.drawRect(1, upperBuffer + 1, getWidth() - 3, getHeight() - upperBuffer - 3);
+		}
+	}
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui
Viewer.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/Viewer.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/ui/Viewer.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,445 @@
+package org.hps.monitoring.ecal.ui;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.NoSuchElementException;
+
+import javax.swing.JFrame;
+
+import org.hps.monitoring.ecal.util.CrystalEvent;
+import org.hps.monitoring.ecal.util.CrystalListener;
+
+/**
+ * The abstract class <code>Viewer</code> handles initialization of the
+ * calorimeter panel with the proper settings and provides a window for
+ * it to live in. Subclasses of <code>Viewer</code> should implement a
+ * means for events to be fed to the calorimeter display.
+ * 
+ * @author Kyle McCarty
+ **/
+public abstract class Viewer extends JFrame {
+    // Java-suggested variable.
+    private static final long serialVersionUID = -2022819652687941812L;
+    // A map of field names to field indices.
+    private final HashMap<String, Integer> fieldMap = new HashMap<String, Integer>();
+    // A list of crystal listeners attached to the viewer.
+    private ArrayList<CrystalListener> listenerList = new ArrayList<CrystalListener>();
+    // The default field names.
+    private static final String[] defaultFields = { "x Index", "y Index", "Cell Value" };
+    
+    /**
+     * <b><statusPanel/b><br/><br/>
+     * <code>protected final StatusPanel <b>statusPanel</b></code><br/><br/>
+     * The component responsible for displaying status information 
+     * about the currently selected crystal.
+     */
+    protected final StatusPanel statusPanel;
+    
+    /**
+     * <b>ecalPanel</b><br/><br/>
+     * <code>protected final CalorimeterPanel <b>ecalPanel</b></code><br/><br/>
+     * The panel displaying the calorimeter crystals and scale.
+     */
+    protected final CalorimeterPanel ecalPanel = new CalorimeterPanel(46, 11);
+	
+    /**
+     * <b>HIGHLIGHT_CLUSTER_COMPONENT</b><br/><br/>
+     * <code>public static final Color <b>HIGHLIGHT_CLUSTER_COMPONENT</b></code><br/><br/>
+     * The default color for highlighting cluster components.
+     */
+	public static final Color HIGHLIGHT_CLUSTER_COMPONENT = Color.RED;
+	
+	/**
+	 * <b>HIGHLIGHT_CLUSTER_SHARED</b><br/><br/>
+     * <code>public static final Color <b>HIGHLIGHT_CLUSTER_SHARED</b></code><br/><br/>
+     * The default color for highlighting cluster shared hits.
+	 */
+	public static final Color HIGHLIGHT_CLUSTER_SHARED = Color.YELLOW;
+    
+    /**
+     * <b>Viewer</b><br/><br/>
+     * <code>public <b>Viewer</b>(String... statusFields)</code><br/><br/>
+     * Initializes the viewer window and calorimeter panel.
+     * @param statusFields - Additional fields to display in the status
+     * panel. This can not be <code>null</code>.
+     * @throws NullPointerException Occurs if any of the additional field
+     * arguments are <code>null</code>.
+     **/
+    public Viewer(String... statusFields) throws NullPointerException {
+        // Initialize the underlying JPanel.
+        super();
+        
+        // Define the status panel fields and map them to indices.
+        String[] fields = new String[statusFields.length + 3];
+        for(int i = 0; i < defaultFields.length; i++) {
+        	fields[i] = defaultFields[i];
+        	fieldMap.put(defaultFields[i], i);
+        }
+        for(int i = 0; i < statusFields.length; i++) {
+        	int index = i + 3;
+        	fields[index] = statusFields[i];
+        	fieldMap.put(statusFields[i], index);
+        }
+        
+        // Generate the status panel.
+        statusPanel = new StatusPanel(fields);
+        
+        // Set the scaling settings.
+        ecalPanel.setScaleMinimum(0.0001);
+        ecalPanel.setScaleMaximum(3000);
+        ecalPanel.setScalingLogarithmic();
+        
+        // Disable the crystals in the calorimeter panel along the beam gap.
+        for (int i = -23; i < 24; i++) {
+            ecalPanel.setCrystalEnabled(toPanelX(i), 5, false);
+            if (i > -11 && i < -1) {
+                ecalPanel.setCrystalEnabled(toPanelX(i), 4, false);
+                ecalPanel.setCrystalEnabled(toPanelX(i), 6, false);
+            }
+        }
+        
+        // Make a mouse motion listener to monitor mouse hovering.
+        getContentPane().addMouseListener(new EcalMouseListener());
+        getContentPane().addMouseMotionListener(new EcalMouseMotionListener());
+        
+        // Add the panels.
+        add(ecalPanel);
+        add(statusPanel);
+        
+        // Define viewer panel properties.
+        setTitle("HPS Ecal Event Display");
+        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+        setPreferredSize(new Dimension(1060, 600));
+        setMinimumSize(new Dimension(1060, 525));
+        setLayout(null);
+        
+        // Add a listener to update everything when the window changes size
+        addComponentListener(new ResizeListener());
+    }
+    
+    /**
+     * <b>addCrystalListener</b><br/><br/>
+     * <code>public void <b>addCrystalListener</b>(CrystalListener cl)</code><br/><br/>
+     * Adds the specified crystal listener to receive crystal events
+     * from this component when the calorimeter panel's crystal status
+     * is changed. If listener <code>cl</code> is <code>null</code>,
+     * no exception is thrown and no action is performed. 
+     * @param cl - The listener to add.
+     */
+    public void addCrystalListener(CrystalListener cl) {
+    	if(cl != null) { listenerList.add(cl); }
+    }
+    
+    /**
+     * <b>toEcalPoint</b><br/><br/>
+     * <code>public Point <b>toEcalPoint</b>(Point panelPoint)</code><br/><br/>
+     * Converts the calorimeter panel's coordinate pair to the LCSim
+     * coordinate system.
+     * @param panelPoint - A calorimeter panel coordinate pair..
+     * @return Returns the coordinate pair in LCSim's coordinate system
+     * as an <code>int</code>.
+     **/
+	public static final Point toEcalPoint(Point panelPoint) {
+		// Convert the point coordinates.
+		int ix = toEcalX(panelPoint.x);
+		int iy = toEcalY(panelPoint.y);
+		
+		// Return the new point.
+		return new Point(ix, iy);
+	}
+    
+    /**
+     * <b>toEcalX</b><br/><br/>
+     * <code>public int <b>toEcalX</b>(int panelX)</code><br/><br/>
+     * Converts the panel x-coordinate to the calorimeter's
+     * coordinate system.
+     * @param panelX - A panel x-coordinate.
+     * @return Returns the x-coordinate in the calorimeter's
+     * coordinate system as an <code>int</code>.
+     */
+    public static final int toEcalX(int panelX) {
+    	if(panelX > 22) { return panelX - 22; }
+    	else { return panelX - 23; }
+    }
+    
+    /**
+     * <b>toEcalY</b><br/><br/>
+     * <code>public int <b>toEcalY</b>(int panelY)</code><br/><br/>
+     * Converts the panel y-coordinate to the calorimeter's
+     * coordinate system.
+     * @param panelY - A panel y-coordinate.
+     * @return Returns the y-coordinate in the calorimeter's
+     * coordinate system as an <code>int</code>.
+     */
+    public static final int toEcalY(int panelY) { return 5 - panelY; }
+    
+    /**
+     * <b>toPanelPoint</b><br/><br/>
+     * <code>public Point <b>toPanelPoint</b>(Point ecalPoint)</code><br/><br/>
+     * Converts the LCSim coordinate pair to the calorimeter panel's
+     * coordinate system.
+     * @param ecalPoint - An LCSim calorimeter coordinate pair..
+     * @return Returns the coordinate pair in the calorimeter panel's
+     * coordinate system as an <code>int</code>.
+     **/
+	public static final Point toPanelPoint(Point ecalPoint) {
+		// Convert the point coordinates.
+		int ix = toPanelX(ecalPoint.x);
+		int iy = toPanelY(ecalPoint.y);
+		
+		// Return the new point.
+		return new Point(ix, iy);
+	}
+    
+    /**
+     * <b>toPanelX</b><br/><br/>
+     * <code>public int <b>toPanelX</b>(int ecalX)</code><br/><br/>
+     * Converts the LCSim x-coordinate to the calorimeter panel's
+     * coordinate system.
+     * @param ecalX - An LCSim calorimeter x-coordinate.
+     * @return Returns the x-coordinate in the calorimeter panel's
+     * coordinate system as an <code>int</code>.
+     **/
+    public static final int toPanelX(int ecalX) {
+        if (ecalX <= 0) { return ecalX + 23; }
+        else { return ecalX + 22; }
+    }
+    
+    /**
+     * <b>toPanelY</b><br/><br/>
+     * <code>public int <b>toPanelY</b>(int ecalY)</code><br/><br/>
+     * Converts the LCSim y-coordinate to the calorimeter panel's
+     * coordinate system.
+     * @param ecalY - An LCSim calorimeter y-coordinate.
+     * @return Returns the y-coordinate in the calorimeter panel's
+     * coordinate system as an <code>int</code>.
+     **/
+    public static final int toPanelY(int ecalY) { return 5 - ecalY; }
+    
+    /**
+     * <b>removeCrystalListener</b><br/><br/>
+     * <code>public void <b>removeCrystalListener</b>(CrystalListener cl)</code><br/><br/>
+     * Removes the specified crystal listener so that it no longer
+     * receives crystal events from this component. This method performs
+     * no function, nor does it throw an exception, if the listener
+     * specified by the argument was not previously added to this
+     * component. If listener <code>cl</code> is <code>null</code>, no
+     * exception is thrown and no action is performed. 
+     * @param cl - The listener to remove.
+     */
+    public void removeCrystalListener(CrystalListener cl) {
+    	if(cl != null) { listenerList.remove(cl); }
+    }
+    
+    public void setSize(int width, int height) {
+        super.setSize(width, height);
+        resize();
+    }
+    
+    public void setSize(Dimension d) {
+        setSize(d.width, d.height);
+    }
+    
+    /**
+     * <b>setStatusField</b><br/><br/>
+     * <code>public void <b>setStatusField</b>(String fieldName, String value)</code><br/><br/>
+     * Sets the value of the indicated status field on the calorimeter
+     * display.
+     * @param fieldName - The name of the field to set.
+     * @param value - The value to display in relation to the field.
+     * @throws NoSuchElementException Occurs if an invalid field name
+     * is provided for argument <code>fieldName</code>.
+     */
+    public final void setStatusField(String fieldName, String value) throws NoSuchElementException {
+    	// Get the index for the indicated field.
+    	Integer index = fieldMap.get(fieldName);
+    	
+    	// If it is null, the field does not exist.
+    	if(index == null) { throw new NoSuchElementException("Field \"" + fieldName + "\" does not exist."); }
+    	
+    	// Otherwise, set the field.
+    	else { statusPanel.setFieldValue(index, value); }
+    }
+    
+	/**
+	 * <b>updateStatusPanel</b><br/><br/>
+	 * <code>protected void <b>updateStatusPanel</b>()</code><br/><br/>
+	 * Updates the information on the status panel to match that of
+	 * the calorimeter panel's currently selected crystal.
+	 */
+	protected void updateStatusPanel() {
+		// Get the currently selected crystal.
+		Point crystal = ecalPanel.getSelectedCrystal();
+		
+		// If the crystal is null, there is no selection.
+		if(crystal == null || ecalPanel.isCrystalDisabled(crystal.x, crystal.y)) { statusPanel.clearValues(); }
+		
+		// Otherwise, write the crystal's data to the panel.
+		else {
+			setStatusField(defaultFields[0], String.valueOf(toEcalX(crystal.x)));
+			setStatusField(defaultFields[1], String.valueOf(toEcalY(crystal.y)));
+			DecimalFormat formatter = new DecimalFormat("0.####E0");
+			String energy = formatter.format(ecalPanel.getCrystalEnergy(crystal.x, crystal.y));
+			setStatusField(defaultFields[2], energy);
+		}
+	}
+    
+    /**
+     * <b>resize</b><br/><br/>
+     * <code>private void <b>resize</b>()</code><br/><br/>
+     * Handles proper resizing of the window and its components.
+     **/
+    private void resize() {
+    	// Define the size constants.
+    	int statusHeight = 125;
+    	
+        // Size and position the calorimeter display.
+        ecalPanel.setLocation(0, 0);
+        ecalPanel.setSize(getContentPane().getWidth(), getContentPane().getHeight() - statusHeight);
+        
+        // Size and position the status panel.
+        statusPanel.setLocation(0, ecalPanel.getHeight());
+        statusPanel.setSize(getContentPane().getWidth(), statusHeight);
+    }
+    
+    /**
+     * The <code>EcalMouseListener</code> handles removing highlighting
+     * and crystal field information when the cursor leaves the window.
+     * It also triggers crystal click events.
+     */
+    private class EcalMouseListener implements MouseListener {
+		public void mouseClicked(MouseEvent e) {
+			// If there is a selected crystal, trigger a crystal click event.
+			if(ecalPanel.getSelectedCrystal() != null) {
+				// Get the selected crystal.
+				Point crystal = ecalPanel.getSelectedCrystal();
+				
+				// Construct a crystal event.
+				CrystalEvent ce = new CrystalEvent(Viewer.this, crystal);
+				
+				// Loop through all the crystal listeners and trigger them.
+				for(CrystalListener cl : listenerList) { cl.crystalClicked(ce); }
+			}
+		}
+		
+		public void mouseEntered(MouseEvent e) { }
+		
+		public void mouseExited(MouseEvent e) {
+			ecalPanel.clearSelectedCrystal();
+			statusPanel.clearValues();
+		}
+		
+		public void mousePressed(MouseEvent e) { }
+		
+		public void mouseReleased(MouseEvent e) { }
+    }
+    
+    /**
+     * The <code>EcalMouseMotionListener</code> handles updating of
+     * the highlighted crystal and status panel information when the
+     * mouse moves over the window. Additionally triggers crystal
+     * activation and deactivation events.
+     */
+    private class EcalMouseMotionListener implements MouseMotionListener {    	
+		public void mouseDragged(MouseEvent arg0) { }
+		
+		public void mouseMoved(MouseEvent e) {
+			// Get the panel coordinates.
+			int x = e.getX();
+			int y = e.getY();
+			
+			// Get the crystal index for these coordinates.
+			Point crystal = ecalPanel.getCrystalID(x, y);
+			
+			// If either of the crystal indices are negative, then
+			// the mouse is not in a crystal and the selection should
+			// be cleared.
+			boolean validCrystal = (crystal != null);
+			
+			// Get the currently selected calorimeter crystal.
+			Point curCrystal = ecalPanel.getSelectedCrystal();
+			
+			// Perform event comparison checks.
+			boolean[] nullCrystal = { !validCrystal, curCrystal == null };
+			boolean[] disabledCrystal = { true, true };
+			if(!nullCrystal[0]) { disabledCrystal[0] = ecalPanel.isCrystalDisabled(crystal); }
+			if(!nullCrystal[1]) { disabledCrystal[1] = ecalPanel.isCrystalDisabled(curCrystal); }
+			boolean sameCrystal = true;
+			if(validCrystal) { sameCrystal = crystal.equals(curCrystal); }
+			
+			// If the crystals are the same, there are no events to throw.
+			if(!sameCrystal) {
+				// If the new crystal is non-null and enabled, throw an event.
+				if(!nullCrystal[0] && !disabledCrystal[0]) { throwActivationEvent(crystal); }
+				
+				// If the old crystal is non-null and enabled, throw an event.
+				if(!nullCrystal[1] && !disabledCrystal[1]) { throwDeactivationEvent(curCrystal); }
+			}
+			
+			// If the crystal is valid, then set the selected crystal
+			// to the current one.
+			if(validCrystal) { ecalPanel.setSelectedCrystal(crystal); }
+			
+			// Otherwise, clear the selection.
+			else { ecalPanel.clearSelectedCrystal(); }
+			
+			// Update the status panel.
+			updateStatusPanel();
+		}
+		
+		/**
+		 * <b>throwActivationEvent</b><br/><br/>
+		 * <code>private void <b>throwActivationEvent</b>()</code><br/><br/>
+		 * Triggers crystal activation events on all listeners for
+		 * this component.
+		 * @param activatedCrystal - The panel coordinates for the
+		 * activated crystal.
+		 */
+		private void throwActivationEvent(Point activatedCrystal) {
+			// Create a crystal event.
+			CrystalEvent ce = new CrystalEvent(Viewer.this, activatedCrystal);
+			
+			// Throw the event with every listener.
+			for(CrystalListener cl : listenerList) { cl.crystalActivated(ce); }
+		}
+		
+		/**
+		 * <b>throwDeactivationEvent</b><br/><br/>
+		 * <code>private void <b>throwDeactivationEvent</b>()</code><br/><br/>
+		 * Triggers crystal deactivation events on all listeners for
+		 * this component.
+		 * @param deactivatedCrystal - The panel coordinates for the
+		 * deactivated crystal.
+		 */
+		private void throwDeactivationEvent(Point deactivatedCrystal) {
+			// Create a crystal event.
+			CrystalEvent ce = new CrystalEvent(Viewer.this, deactivatedCrystal);
+			
+			// Throw the event with every listener.
+			for(CrystalListener cl : listenerList) { cl.crystalDeactivated(ce); }
+		}
+    }
+    
+    /**
+     * The <code>ResizeListener</code> class ensures that the components remain
+     * at the correct size and location when the window is resized.
+     **/
+    private class ResizeListener implements ComponentListener {
+        public void componentResized(ComponentEvent e) { resize(); }
+        
+        public void componentHidden(ComponentEvent e) { }
+        
+        public void componentMoved(ComponentEvent e) { }
+        
+        public void componentShown(ComponentEvent e) { }
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util
BooleanMap.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/BooleanMap.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/BooleanMap.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,217 @@
+package org.hps.monitoring.ecal.util;
+
+import java.awt.Color;
+
+/**
+ * Class <code>BooleanMap</code> defines an implementation of the <code>
+ * ColorMap</code> interface which maps values to colors based on whether
+ * or not the values pass a boolean comparison.
+ * 
+ * @author Kyle McCarty
+ */
+public final class BooleanMap implements ColorMap<Double> {
+	// The color to display for values which pass the boolean check.
+	private Color activeColor = new Color(255, 50, 50);
+	// The color to display for values that fail the boolean check.
+	private Color inactiveColor = Color.WHITE;
+	// The critical value against which the boolean check is performed.
+	private double value = 0.0;
+	// The type of this boolean scale.
+	private final BooleanType boolType;
+	
+	/**
+	 * <b>BooleanMap</b><br/><br/>
+	 * <code>public <b>BooleanMap</b>(BooleanType type, double comparisonValue)</code><br/><br/>
+	 * Defines a <code>ColorScale</code> which maps values to colors
+	 * based on a boolean comparison.
+	 * @param type - The type of boolean comparison to perform.
+	 * @param comparisonValue - The value against which the comparison
+	 * should be made.
+	 */
+	public BooleanMap(BooleanType type, double comparisonValue) {
+		// Make sure the comparison type is not null.
+		if(type == null) { throw new IllegalArgumentException("Boolean comparison type can not be null."); }
+		
+		// Define the critical value and the boolean type.
+		value = comparisonValue;
+		boolType = type;
+	}
+	
+	/**
+	 * <b>BooleanMap</b><br/><br/>
+	 * <code>public <b>BooleanMap</b>(BooleanType type, double comparisonValue,
+	 * 			Color activeColor)</code><br/><br/>
+	 * Defines a <code>ColorScale</code> which maps values to colors
+	 * based on a boolean comparison.
+	 * @param type - The type of boolean comparison to perform.
+	 * @param comparisonValue - The value against which the comparison
+	 * should be made.
+	 * @param activeColor - The color in which values that pass the
+	 * comparison should be displayed.
+	 */
+	public BooleanMap(BooleanType type, double comparisonValue, Color activeColor) {
+		// Set the critical value and the boolean type.
+		this(type, comparisonValue);
+		
+		// Set the active color.
+		this.activeColor = activeColor;
+	}
+	
+	/**
+	 * <b>BooleanMap</b><br/><br/>
+	 * <code>public <b>BooleanMap</b>(BooleanType type, double comparisonValue,
+	 * 			Color activeColor, Color inactiveColor)</code><br/><br/>
+	 * Defines a <code>ColorScale</code> which maps values to colors
+	 * based on a boolean comparison.
+	 * @param type - The type of boolean comparison to perform.
+	 * @param comparisonValue - The value against which the comparison
+	 * should be made.
+	 * @param activeColor - The color in which values that pass the
+	 * comparison should be displayed.
+	 * @param inactiveColor - The color in which values that fail the
+	 * comparison should be displayed.
+	 */
+	public BooleanMap(BooleanType type, double comparisonValue, Color activeColor, Color inactiveColor) {
+		// Set the critical value and the boolean type.
+		this(type, comparisonValue);
+		
+		// Set the active and inactive colors.
+		this.activeColor = activeColor;
+		this.inactiveColor = inactiveColor;
+	}
+	
+	public Color getColor(Double value) {
+		// If the argument is null, treat it is zero.
+		if(value == null) { value = 0.0; }
+		
+		// If it passes the boolean comparison, return the active color.
+		if(passes(value)) { return activeColor; }
+		
+		// Otherwise, return the inactive color.
+		else { return inactiveColor; }
+	}
+	
+	/**
+	 * <b>getActiveColor</b><br/><br/>
+	 * <code>public Color <b>getActiveColor</b>()</code><br/><br/>
+	 * Gets the color used by the scale for values which pass the
+	 * boolean comparison.
+	 * @return Returns the color as a <code>Color</code> object.
+	 */
+	public Color getActiveColor() { return activeColor; }
+	
+	/**
+	 * <b>getBooleanType</b><br/><br/>
+	 * <code>public BooleanType <b>getBooleanType</b>()</code><br/><br/>
+	 * Indicates what type of boolean comparison is performed by this
+	 * scale.
+	 * @return Returns the type of comparison as a <code>BooleanType
+	 * </code> enumerable.
+	 */
+	public BooleanType getBooleanType() { return boolType; }
+	
+	/**
+	 * <b>getComparisonValue</b><br/><br/>
+	 * <code>public double <b>getComparisonValue</b>()</code><br/><br/>
+	 * Gets the value against which the boolean comparisons are
+	 * performed.
+	 * @return Returns the value which is compared against.
+	 */
+	public double getComparisonValue() { return value; }
+	
+	/**
+	 * <b>getInactiveColor</b><br/><br/>
+	 * <code>public Color <b>getInactiveColor</b>()</code><br/><br/>
+	 * Gets the color used by the scale for values which fail the
+	 * boolean comparison.
+	 * @return Returns the color as a <code>Color</code> object.
+	 */
+	public Color getInactiveColor() { return inactiveColor; }
+	
+	/**
+	 * <b>setComparisonValue</b><br/><br/>
+	 * <code>public void <b>setComparisonValue</b>(double value)</code><br/><br/>
+	 * Sets the value against which the boolean comparison is performed.
+	 * @param value - The value to compare against.
+	 */
+	public void setComparisonValue(double value) { this.value = value; }
+	
+	/**
+	 * <b>passes</b><br/><br/>
+	 * <code>private boolean <b>passes</b>(double d)</code><br/><br/>
+	 * Determines whether a given external value passes the boolean
+	 * check or not.
+	 * @param d - The external value to compare.
+	 * @return Returns <code>true</code> if the value passes the boolean
+	 * check and <code>false</code> if it does not.
+	 */
+	private boolean passes(double d) {
+		// Perform the appropriate comparison. Note that the default
+		// case is included to satisfy the compiler -- it should not
+		// ever actually be used.
+		switch(boolType) {
+			case EQUAL_TO:
+				return d == value;
+			case NOT_EQUAL_TO:
+				return d != value;
+			case GREATER_THAN:
+				return d > value;
+			case LESS_THAN:
+				return d < value;
+			case GREATER_THAN_OR_EQUAL_TO:
+				return d >= value;
+			case LESS_THAN_OR_EQUAL_TO:
+				return d<= value;
+			default:
+				return false;
+		}
+	}
+	
+	/**
+	 * Enumerable <code>BooleanType</code> defines the type of boolean
+	 * comparison that is to be performed by the scale.
+	 */
+	public enum BooleanType {
+		/**
+		 * <b>EQUAL_TO</b><br/><br/>
+		 * Performs the boolean check:<br/><br/>
+		 * <code>[External Value] == [Comparison Value]</code>
+		 */
+		EQUAL_TO,
+		
+		/**
+		 * <b>NOT_EQUAL_TO</b><br/><br/>
+		 * Performs the boolean check:<br/><br/>
+		 * <code>[External Value] != [Comparison Value]</code>
+		 */
+		NOT_EQUAL_TO,
+		
+		/**
+		 * <b>GREATER_THAN</b><br/><br/>
+		 * Performs the boolean check:<br/><br/>
+		 * <code>[External Value] > [Comparison Value]</code>
+		 */
+		GREATER_THAN,
+		
+		/**
+		 * <b>LESS_THAN</b><br/><br/>
+		 * Performs the boolean check:<br/><br/>
+		 * <code>[External Value] < [Comparison Value]</code>
+		 */
+		LESS_THAN,
+		
+		/**
+		 * <b>GREATER_THAN_OR_EQUAL_TO</b><br/><br/>
+		 * Performs the boolean check:<br/><br/>
+		 * <code>[External Value] >= [Comparison Value]</code>
+		 */
+		GREATER_THAN_OR_EQUAL_TO,
+		
+		/**
+		 * <b>LESS_THAN_OR_EQUAL_TO</b><br/><br/>
+		 * Performs the boolean check:<br/><br/>
+		 * <code>[External Value] <= [Comparison Value]</code>
+		 */
+		LESS_THAN_OR_EQUAL_TO
+	};
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util
ColorMap.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/ColorMap.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/ColorMap.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,19 @@
+package org.hps.monitoring.ecal.util;
+
+import java.awt.Color;
+
+/**
+ * Interface <code>ColorMap</code> maps some value to a color.
+ * 
+ * @author Kyle McCarty
+ */
+public interface ColorMap<T> {
+    /**
+     * <b>getColor</b><br/><br/>
+     * <code>public Color <b>getColor</b>(T value)</code><br/><br/>
+     * Determines the color representing the indicated value.
+     * @param value - The value to relate to a color.
+     * @return Returns a <code>Color</code> object associated with the argument value.
+     **/
+    public Color getColor(T value);
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util
ColorScale.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/ColorScale.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/ColorScale.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,156 @@
+package org.hps.monitoring.ecal.util;
+
+/**
+ * The abstract class <code>ColorScale</code> contains shared methods
+ * for implementations of the <code>ColorMap</code> interface which
+ * map based on position in a range of values.
+ * 
+ * @author Kyle McCarty
+ **/
+public abstract class ColorScale implements ColorMap<Double> {
+    // Indicates if linear or logarithmic scaling should be used.
+    protected boolean linear = true;
+    // The minimum value for color scaling.
+    protected double min = 0;
+    // The maximum value for color scaling.
+    protected double max = 1;
+    // A scale variable used for mapping logarithmic values.
+    protected double scale = 1.0;
+    // An efficiency variable used for logarithmic mapping.
+    protected double lMin = 0.0;
+    // An efficiency variable used for logarithmic mapping.
+    protected double lMax = 1.0;
+    
+    /**
+     * <b>getMaximum</b><br/><br/>
+     * <code>public double <b>getMaximum</b>()</code><br/><br/>
+     * Gets the highest value for which color scaling is performed.
+     * @return Returns the maximum value for color scaling as a <code>double</code>.
+     **/
+    public double getMaximum() { return max; }
+    
+    /**
+     * <b>getMinimum</b><br/><br/>
+     * <code>public double <b>getMinimum</b>()</code><br/><br/>
+     * Gets the lowest value for which color scaling is performed.
+     * @return Returns the minimum value for color scaling as a <code>double</code>.
+     **/
+    public double getMinimum() { return min; }
+    
+    /**
+     * <b>getScaledMaximum</b><br/><br/>
+     * <code>public double <b>getScaledMaximum</b>()</code><br/><br/>
+     * Gets the highest for which color scaling is performed, scaled
+     * based on scale type.
+     * @return Returns the maximum value for color scaling as a <code>
+     * double</code>, if scaling is linear, and the logarithm of this
+     * value, if scaling is logarithmic.
+     */
+    public double getScaledMaximum() {
+    	if(linear) { return max; }
+    	else { return lMax; }
+    }
+    
+    /**
+     * <b>getScaledMinimum</b><br/><br/>
+     * <code>public double <b>getScaledMinimum</b>()</code><br/><br/>
+     * Gets the lowest for which color scaling is performed, scaled
+     * based on scale type.
+     * @return Returns the minimum value for color scaling as a <code>
+     * double</code>, if scaling is linear, and the logarithm of this
+     * value, if scaling is logarithmic.
+     */
+    public double getScaledMinimum() {
+    	if(linear) { return min; }
+    	else { return lMin; }
+    }
+    
+    /**
+     * <b>isLinear</b><br/><br/>
+     * <code>public boolean <b>isLinear</b>()</code><br/><br/>
+     * Indicates whether this color mapping is linear or not.
+     * @return Returns <code>true</code> if this is a linear mapping and <code>false
+     * </code> otherwise.
+     **/
+    public boolean isLinear() { return linear; }
+    
+    /**
+     * <b>isLogairthmic</b><br/><br/>
+     * <code>public boolean <b>isLogairthmic</b>()</code><br/><br/>
+     * Indicates whether this color mapping is logarithmic or not.
+     * @return Returns <code>true</code> if this is a logarithmic mapping and <code>
+     * false</code> if it is not.
+     **/
+    public boolean isLogarithmic() { return !linear; }
+    
+    /**
+     * <b>setMaximum</b><br/><br/>
+     * <code>public void <b>setMaximum</b>(double maximum)</code><br/><br/>
+     * Sets the value over which no color scaling will be performed.
+     * @param maximum - The highest value for which color scaling will be performed.
+     **/
+    public void setMaximum(double maximum) {
+        max = maximum;
+        revalidate();
+    }
+    
+    /**
+     * <b>setMinimum</b><br/><br/>
+     * <code>public void <b>setMinimum</b>(double minimum)</code><br/><br/>
+     * Sets the value under which no color scaling will be performed.
+     * @param minimum - The lowest value for which color scaling will be performed.
+     **/
+    public void setMinimum(double minimum) {
+        min = minimum;
+        revalidate();
+    }
+    
+    /**
+     * <b>setScalingLinear</b><br/><br/>
+     * <code>public void <b>setScalingLinear</b>()</code><br/><br/>
+     * Sets the scaling behavior to linear.
+     **/
+    public void setScalingLinear() {
+        linear = true;
+        revalidate();
+    }
+    
+    /**
+     * <b>setScalingLogarithmic</b><br/><br/>
+     * <code>public void <b>setScalingLogarithmic</b>()</code><br/><br/>
+     * Sets the scaling behavior to logarithmic.
+     **/
+    public void setScalingLogarithmic() {
+        linear = false;
+        revalidate();
+    }
+    
+    /**
+     * <b>revalidate</b><br/><br/>
+     * <code>protected void <b>revalidate</b>()</code><br/><br/>
+     * Makes any necessary changes whenever a critical value is changed.
+     **/
+    protected void revalidate() {
+        // Ensure that the minimum is not zero in the case of log scaling.
+        if (!linear && min == 0) {
+            if (max < 0.01) { min = max / 100.0; }
+            else { min = 0.01; }
+        }
+        
+        // We only need to revalidate if we are using a logarithmic scale.
+        if (!linear) {
+            // Determine the scaling variable for logarithmic results.
+            double temp = min;
+            int steps = 0;
+            while (temp < 1) {
+                temp = temp * 10;
+                steps++;
+            }
+            scale = Math.pow(10, steps);
+            
+            // Revalidate the logarithmic variables.
+            lMax = Math.log10(scale * max);
+            lMin = Math.log10(scale * min);
+        }
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util
CrystalEvent.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/CrystalEvent.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/CrystalEvent.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,54 @@
+package org.hps.monitoring.ecal.util;
+
+import java.awt.AWTEvent;
+import java.awt.Point;
+
+import org.hps.monitoring.ecal.ui.Viewer;
+
+/**
+ * Class <code>CrystalEvent</code> represents some event that occurred
+ * with respect to a crystal. It is thrown when a crystal either gains
+ * or loses focus or is clicked. Crystal ID indices are always with
+ * respect to the panel coordinate system.
+ * 
+ * @author Kyle McCarty
+ */
+public class CrystalEvent extends AWTEvent {
+	private static final long serialVersionUID = 77198267255387212L;
+	// Stores the location of the triggering crystal.
+	private final Point crystal;
+	// The AWTEvent id for this event.
+	private static final int AWT_ID = AWTEvent.RESERVED_ID_MAX + 10;
+	
+	/**
+	 * <b>CrystalEvent</b><br/><br/>
+	 * <code>public <b>CrystalEvent</b>(Viewer parent, Point triggerCrystal)</code><br/><br/>
+	 * Creates a crystal event for the indicated crystal and triggering
+	 * component.
+	 * @param source - The triggering component.
+	 * @param triggerCrystal - The crystal associated with the event.
+	 * @throws IllegalArgumentException Occurs if the associated crystal
+	 * is <code>null</code>.
+	 */
+	public CrystalEvent(Viewer source, Point triggerCrystal) throws IllegalArgumentException {
+		// Run the superclass constructor.
+		super(source, AWT_ID);
+		
+		// Make sure that the trigger crystal is not null.
+		if(triggerCrystal == null) {
+			throw new IllegalArgumentException("Crystal events can not occur with respect to non-exstant crystals.");
+		}
+		
+		// Define the event parameters.
+		crystal = triggerCrystal;
+	}
+	
+	/**
+	 * <b>getCrystalID</b><br/><br/>
+	 * <code>public Point <b>getCrystalID</b>()</code><br/><br/>
+	 * Indicates the panel indices at which the crystal is located.
+	 * @return Returns the crystal's panel indices as a <code>Point
+	 * </code> object.
+	 */
+	public Point getCrystalID() { return crystal; }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util
CrystalListener.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/CrystalListener.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/CrystalListener.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,37 @@
+package org.hps.monitoring.ecal.util;
+
+import java.util.EventListener;
+
+/**
+ * Interface <code>CrystalListener</code> receives events from a <code>
+ * Viewer</code> component regarding crystals. These include whenever
+ * a crystal is activated (i.e. it becomes highlighted), deactivated
+ * (i.e. it is no longer highlighted), and clicked. 
+ * 
+ * @author Kyle McCarty
+ */
+public interface CrystalListener extends EventListener {
+	/**
+	 * <b>crystalActivated</b><br/><br/>
+	 * <code>public void <b>crystalActivated</b>(CrystalEvent e)</code><br/><br/>
+	 * Invoked when a crystal becomes highlighted.
+	 * @param e - An object describing the event.
+	 */
+	public void crystalActivated(CrystalEvent e);
+	
+	/**
+	 * <b>crystalDeactivated</b><br/><br/>
+	 * <code>public void <b>crystalDeactivated</b>(CrystalEvent e)</code><br/><br/>
+	 * Invoked when a crystal ceases to be highlighted.
+	 * @param e - An object describing the event.
+	 */
+	public void crystalDeactivated(CrystalEvent e);
+	
+	/**
+	 * <b>crystalClicked</b><br/><br/>
+	 * <code>public void <b>crystalClicked</b>(CrystalEvent e)</code><br/><br/>
+	 * Invoked when a crystal is clicked
+	 * @param e - An object describing the event.
+	 */
+	public void crystalClicked(CrystalEvent e);
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util
GradientScale.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/GradientScale.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/GradientScale.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,121 @@
+package org.hps.monitoring.ecal.util;
+import java.awt.Color;
+
+/**
+ * The class <code>GradientScale</code> is an implementation of the abstract
+ * class <code>ColorScale</code> which represents a simple gradient from one
+ * color to another. It will map any argument value that exceeds its maximum
+ * value to the hot color and any argument value that is below its minimum value
+ * to its cold color. All other argument values will be mapped somewhere between
+ * the cold and hot colors using either a linear or logarithmic scale.
+ * 
+ * @author Kyle McCarty
+ **/
+public final class GradientScale extends ColorScale {
+    // The color associated with the maximum value.
+    private Color hotColor = Color.WHITE;
+    // The color associated with the minimum value.
+    private Color coldColor = Color.BLACK;
+    // Efficiency variable holding the rgb difference between the two
+    // colors. This is used to prevent recalculation when mapping values.
+    private int[] drgb = { 255, 255, 255 };
+    
+    public Color getColor(Double value) {
+    	// If the argument is null, treat it as zero.
+    	if(value == null) { value = 0.0; }
+    	
+        // If the value is less than the minimum, return the cold color.
+        if (value < min) { return coldColor; }
+        
+        // If the value is greater than the maximum, return the hot color.
+        if (value > max) { return hotColor; }
+        
+        // Otherwise, calculate how far along the gradient the value is.
+        double percent;
+        if (linear) { percent = (value - min) / (max - min); }
+        else {
+            double lValue = Math.log10(scale * value);
+            percent = (lValue - lMin) / (lMax - lMin);
+        }
+        
+        // Scale the color.
+        int dr = (int) Math.round(percent * drgb[0]);
+        int dg = (int) Math.round(percent * drgb[1]);
+        int db = (int) Math.round(percent * drgb[2]);
+        
+        // Return the result.
+        return new Color(coldColor.getRed() + dr, coldColor.getGreen() + dg, coldColor.getBlue() + db);
+    }
+    
+    /**
+     * <b>setColdColor</b><br/><br/>
+     * <code>public void <b>setColdColor</b>(Color c)</code><br/><br/>
+     * Sets the color associated with the minimum value.
+     * @param c - The color to use.
+     **/
+    public void setColdColor(Color c) {
+        coldColor = c;
+        revalidateColor();
+    }
+    
+    /**
+     * <b>setHotColor</b><br/><br/>
+     * <code>public void <b>setHotColor</b>(Color c)</code><br/><br/>
+     * Sets the color associated with the maximum value.
+     * @param c - The new color to use.
+     **/
+    public void setHotColor(Color c) {
+        hotColor = c;
+        revalidateColor();
+    }
+    
+    /**
+     * <b>makeGreyScale</b><br/><br/>
+     * <code>public static GradientScale <b>makeGreyScale</b>(double minimum, double maximum)</code><br/><br/>
+     * Creates a color scale that ranges from black (cold) to white (hot) with
+     * the indicated maximum and minimum.
+     * @param minimum - The lowest value for color scaling.
+     * @param maximum - The highest value for color scaling.
+     * @return Returns a <code>GradientScale</code> that maps to grey scale over
+     * the indicated range.
+     **/
+    public static GradientScale makeGreyScale(double minimum, double maximum) {
+        GradientScale gs = new GradientScale();
+        gs.setMinimum(minimum);
+        gs.setMaximum(maximum);
+        
+        return gs;
+    }
+    
+    /**
+     * <b>makeHeatScale</b><br/><br>
+     * <code>public static GradientScale <b>makeHeatScale</b>(double minimum, double maximum)</code><br/><br/>
+     * Creates a color scale that ranges from black (cold) to red (hot) with the
+     * indicated maximum and minimum.
+     * @param minimum - The lowest value for color scaling.
+     * @param maximum - The highest value for color scaling.
+     * @return Returns a <code>GradientScale</code> that maps to a black- to-red
+     * gradient over the indicated range.
+     **/
+    public static GradientScale makeHeatScale(double minimum, double maximum) {
+        GradientScale hs = new GradientScale();
+        hs.setHotColor(Color.RED);
+        hs.setColdColor(Color.BLACK);
+        hs.setMinimum(minimum);
+        hs.setMaximum(maximum);
+        
+        return hs;
+    }
+    
+    /**
+     * <b>revalidateColor</b><br/><br/>
+     * <code>private void <b>revalidateColor</b>()</code><br/><br/>
+     * Calculates the differences between the hot and cold colors and sets the
+     * class related class variables.
+     **/
+    private void revalidateColor() {
+        drgb[0] = hotColor.getRed() - coldColor.getRed();
+        drgb[1] = hotColor.getGreen() - coldColor.getGreen();
+        drgb[2] = hotColor.getBlue() - coldColor.getBlue();
+    }
+}

java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util
MultiGradientScale.java added at 307
--- java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/MultiGradientScale.java	                        (rev 0)
+++ java/trunk/monitoring-drivers/src/main/java/org/hps/monitoring/ecal/util/MultiGradientScale.java	2014-03-17 23:13:16 UTC (rev 307)
@@ -0,0 +1,150 @@
+package org.hps.monitoring.ecal.util;
+import java.awt.Color;
+import java.util.ArrayList;
+
+/**
+ * The class <code>MultiGradientScale</code> is an implementation of
+ * <code>ColorScale</code> that maps values to a color over several different
+ * individual <code>GradientScale</code> objects to allow for multi-color
+ * mapping.
+ * 
+ * @author Kyle McCarty
+ **/
+public final class MultiGradientScale extends ColorScale {
+    // Stores the colors in the map.
+    private ArrayList<Color> colorList = new ArrayList<Color>();
+    // Stores the component mapping scales.
+    private ArrayList<GradientScale> scaleList = new ArrayList<GradientScale>();
+    
+    /**
+     * <b>addColor</b><br/><br/>
+     * <code>public void <b>addColor</b>(Color c)</code><br/><br/>
+     * Adds a new color to the mapping scale. The first color will be the
+     * coldest with subsequent colors being more and more hot.
+     * @param c - The color to add.
+     **/
+    public void addColor(Color c) {
+        colorList.add(c);
+        revalidate();
+    }
+    
+    public Color getColor(Double value) {
+    	// If the value is null, treat it as zero.
+    	if(value == null) { value = 0.0; }
+    	
+        // Get the number of colors and scales.
+        int colors = colorList.size();
+        int scales = scaleList.size();
+        
+        // If there are no colors or scales, give black.
+        if (colors == 0 && scales == 0) { return Color.BLACK; }
+        
+        // If there are no scales, but there is a color, give that.
+        if (scales == 0 && colors == 1) { return colorList.get(0); }
+        
+        // Scale the value if logarithmic.
+        double sValue;
+        if (linear) { sValue = value; }
+        else { sValue = Math.log10(scale * value); }
+        
+        // Otherwise, determine which scale should get the value.
+        for (GradientScale s : scaleList) {
+            if (sValue < s.getMaximum()) {
+                return s.getColor(sValue);
+            }
+        }
+        
+        // If it didn'tappear in the list, it is the hottest color.
+        return colorList.get(colors - 1);
+    }
+    
+    /**
+     * <b>removeColor</b><br/><br/>
+     * <code>public boolean <b>removeColor</b>(int colorIndex)</code><br/><br/>
+     * Removes the nth color from the mapping scale.
+     * @param colorIndex - The index of the color to be removed.
+     * @return Returns <code>true</code> if the color was removed and
+     * <code>false</code> if it was not.
+     **/
+    public boolean removeColor(int colorIndex) {
+        // Only remove the value if the index is valid.
+        if (colorIndex >= 0 && colorIndex < colorList.size()) {
+            colorList.remove(colorIndex);
+            revalidate();
+            return true;
+        }
+        else { return false; }
+    }
+    
+    /**
+     * <b>makeRainboowScale</b><br/><br/>
+     * <code>public static <b>makeRainbowScale</b>(double minimum, double maximum)</code><br/><br>
+     * Creates a <code>MultiGradientScale</code> that maps values from purple,
+     * to blue, to cyan, to green, to yellow, and to red at the hottest.
+     * @param minimum - The lowest mapped value.
+     * @param maximum - The highest mapped value.
+     * @return Returns the rainbow color mapping scale.
+     **/
+    public static MultiGradientScale makeRainbowScale(double minimum, double maximum) {
+        int str = 165;
+        Color purple = new Color(55, 0, 55);
+        Color blue = new Color(0, 0, str);
+        Color cyan = new Color(0, str, str);
+        Color green = new Color(0, str, 0);
+        Color yellow = new Color(str, str, 0);
+        Color red = new Color(str, 0, 0);
+        
+        MultiGradientScale mgs = new MultiGradientScale();
+        mgs.addColor(purple);
+        mgs.addColor(blue);
+        mgs.addColor(cyan);
+        mgs.addColor(green);
+        mgs.addColor(yellow);
+        mgs.addColor(red);
+        mgs.setMinimum(minimum);
+        mgs.setMaximum(maximum);
+        
+        return mgs;
+    }
+    
+    protected void revalidate() {
+        // Handle the default logarithmic revalidation.
+        super.revalidate();
+        
+        // Redistribute the lists.
+        scaleList.clear();
+        
+        // We need at least colors to make a scale - otherwise, the
+        // special cases handle the color.
+        int colors = colorList.size();
+        if (colors < 2) { return; }
+        
+        // Otherwise, define the list variables.
+        double sStep;
+        double sMin;
+        if (linear) {
+            sStep = (max - min) / (colors - 1);
+            sMin = min;
+        }
+        else {
+            sStep = (lMax - lMin) / (colors - 1);
+            sMin = lMin;
+        }
+        double sMax = sMin + sStep;
+        
+        // Generate a list of scales.
+        for (int i = 0; i < (colors - 1); i++) {
+            // Make and add a scale.
+            GradientScale s = new GradientScale();
+            s.setMinimum(sMin);
+            s.setMaximum(sMax);
+            s.setColdColor(colorList.get(i));
+            s.setHotColor(colorList.get(i + 1));
+            scaleList.add(s);
+            
+            // Update the min/max.
+            sMin = sMax;
+            sMax += sStep;
+        }
+    }
+}
SVNspam 0.1