Print

Print


Commit in java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal on MAIN
Cluster.java+136added 255
Datum.java+2-2254 -> 255
EcalPanel.java+287-148254 -> 255
EventManager.java+29-7254 -> 255
Main.java+5-2254 -> 255
StatusPanel.java+170added 255
Viewer.java+286-67254 -> 255
+915-226
2 added + 5 modified, total 7 files
Event display major update.

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
Cluster.java added at 255
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Cluster.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Cluster.java	2014-02-25 16:44:47 UTC (rev 255)
@@ -0,0 +1,136 @@
+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-app/src/main/java/org/hps/monitoring/ecal
Datum.java 254 -> 255
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Datum.java	2014-02-25 04:08:56 UTC (rev 254)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Datum.java	2014-02-25 16:44:47 UTC (rev 255)
@@ -34,7 +34,7 @@
      * <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-cooridinate as an <code>int</code>.
+     * @return Returns the x-coordinate as an <code>int</code>.
      **/
     public int getX() { return loc.x; }
     
@@ -42,7 +42,7 @@
      * <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-coordiate as an <code>int</code>.
+     * @return Returns the y-coordinate as an <code>int</code>.
      **/
     public int getY() { return loc.y; }
     

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
EcalPanel.java 254 -> 255
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EcalPanel.java	2014-02-25 04:08:56 UTC (rev 254)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EcalPanel.java	2014-02-25 16:44:47 UTC (rev 255)
@@ -1,12 +1,10 @@
 package org.hps.monitoring.ecal;
 
-import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.geom.Line2D;
+import java.awt.Point;
 import java.text.DecimalFormat;
 import java.text.NumberFormat;
 
@@ -24,6 +22,8 @@
     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.
@@ -32,26 +32,25 @@
     private int yBoxes = 1;
     // The width of the scale.
     private int scaleWidth = 75;
-    // Whether the scale has changed or not since its last rendering.
-    private boolean scaleChanged = true;
     // 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 whether a crystal has changed.
-    private boolean changed[][];
-    // Stores whether the panel size has chaged.
-    private boolean sizeChanged = true;
+    // Stores what color to highlight the crystal with.
+    private Color[][] highlight;
     // The panel on which the scale is rendered.
-    ScalePanel scalePanel = new ScalePanel();
+    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.
-    int boxWidth = 0;
-    int widthRem = 0;
-    int boxHeight = 0;
-    int heightRem = 0;
+    private int[] widths;
+    private int[] heights;
+    private int[] xPosition;
+    private int[] yPosition;
+    private int[] clusterSpace = new int[3];
     
     /**
      * <b>EcalPanel</b><br/><br/>
@@ -67,17 +66,27 @@
         xBoxes = numXBoxes;
         yBoxes = numYBoxes;
         
-        // Initialize the arrays.
+        // Initialize data the arrays.
         disabled = new boolean[xBoxes][yBoxes];
         hit = new double[xBoxes][yBoxes];
         cluster = new boolean[xBoxes][yBoxes];
-        changed = 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);
-        sizeChanged = true;
-        scaleChanged = true;
     }
     
     /**
@@ -95,7 +104,6 @@
     public void setCrystalEnabled(int xIndex, int yIndex, boolean active) throws IndexOutOfBoundsException {
         if (xIndex >= 0 && xIndex < xBoxes && yIndex >= 0 && yIndex < yBoxes) {
             disabled[xIndex][yIndex] = !active;
-            changed[xIndex][yIndex] = true;
         }
         else {
             throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", xIndex, yIndex));
@@ -116,7 +124,6 @@
     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;
-            changed[xIndex][yIndex] = true;
         }
         else {
             throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", xIndex, yIndex));
@@ -137,7 +144,6 @@
     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;
-            changed[xIndex][yIndex] = true;
         }
         else {
             throw new IndexOutOfBoundsException(String.format("Invalid crystal address (%2d, %2d).", xIndex, yIndex));
@@ -145,27 +151,52 @@
     }
     
     /**
+     * <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 any disabled crystals.
+     * 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++) {
-                if (hit[x][y] != 0.0) {
-                    hit[x][y] = 0.0;
-                    changed[x][y] = true;
-                }
-                if (cluster[x][y]) {
-                    cluster[x][y] = false;
-                    changed[x][y] = true;
-                }
+                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.
@@ -184,7 +215,6 @@
      **/
     public void setMinimum(double minimum) {
         scale.setMinimum(minimum);
-        scaleChanged = true;
     }
     
     /**
@@ -196,7 +226,6 @@
      **/
     public void setMaximum(double maximum) {
         scale.setMaximum(maximum);
-        scaleChanged = true;
     }
     
     /**
@@ -206,20 +235,55 @@
      **/
     public void setScalingLinear() {
         scale.setScalingLinear();
-        scaleChanged = true;
     }
     
     /**
      * <b>setScalingLogarithmic</b><br/><br/>
-     * <code>public void <b>setScalingLogarithmic</b></code><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();
-        scaleChanged = true;
     }
     
     /**
+     * <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.
@@ -229,12 +293,90 @@
     public void setScaleEnabled(boolean enabled) {
         if (scalePanel.isVisible() != enabled) {
             scalePanel.setVisible(enabled);
-            scaleChanged = true;
-            sizeChanged = true;
         }
     }
     
     /**
+     * <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.
@@ -247,10 +389,20 @@
         super.setSize(width, height);
         scalePanel.setLocation(width - scaleWidth, 0);
         scalePanel.setSize(scaleWidth, height);
-        sizeChanged = true;
     }
     
     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;
@@ -258,118 +410,119 @@
             else { width = getWidth(); }
             int height = getHeight();
             
-            boxWidth = width / xBoxes;
-            widthRem = width % xBoxes;
-            boxHeight = height / yBoxes;
-            heightRem = height % yBoxes;
+            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);
+            	
+            }
         }
-        int heightRemReset = heightRem;
-        int widthRemReset = widthRem;
         
-        // Start drawing the calorimeter crystals. To avoid having empty
-        // space, we distribute the extra widthRem pixels to the boxes at
-        // a rate of one pixel for box until we run out. We do the same thing
-        // for the heightRem.
-        int curX = 0;
-        int curY = 0;
+        // Render the crystals at the locations calculated in the size
+        // change block.
         for (int x = 0; x < xBoxes; x++) {
-            // Determine if this column should use an extra pixel.
-            int tw = boxWidth;
-            if (widthRem != 0) {
-                tw++;
-                widthRem--;
-            }
             for (int y = 0; y < yBoxes; y++) {
-                // Determine if this row should use an extra pixel.
-                int th = boxHeight;
-                if (heightRem != 0) {
-                    th++;
-                    heightRem--;
+                // 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 (sizeChanged || scaleChanged || changed[x][y]) {
-                    // Determine the appropriate color for the box.
-                    Color crystalColor;
-                    if (disabled[x][y]) { crystalColor = Color.BLACK; }
-                    else { crystalColor = scale.getColor(hit[x][y]); }
-                    g.setColor(crystalColor);
-                    
-                    // Draw the box.
-                    g.fillRect(curX, curY, tw, th);
-                    g.setColor(Color.BLACK);
-                    g.drawRect(curX, curY, tw, th);
-                    
-                    // If there is a cluster, draw an x.
-                    if (cluster[x][y]) {
-                        // Get the correct coordinates.
-                        double ltw = (0.3 * tw) / 2;
-                        double lth = (0.5 * th) / 2;
-                        double[] lx = { curX + ltw, curX + tw - ltw };
-                        double[] ly = { curY + lth, curY + th - lth };
-                        
-                        // 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 x on the cluster crystal.
-                        Graphics2D g2 = (Graphics2D) g;
-                        g2.setColor(c);
-                        // g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-                        // RenderingHints.VALUE_ANTIALIAS_ON);
-                        g2.setStroke(new BasicStroke(2));
-                        g2.draw(new Line2D.Double(lx[0], ly[0], lx[1], ly[1]));
-                        g2.draw(new Line2D.Double(lx[0], ly[1], lx[1], ly[0]));
-                        // g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-                        // RenderingHints.VALUE_ANTIALIAS_OFF);
+                // 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; }
                     
-                    // Note that this crystals has been updated.
-                    changed[x][y] = false;
+                    // Draw an circle on the cluster crystal.
+                    g.setColor(c);
+                    g.fillOval(xPosition[x] + clusterSpace[0], yPosition[y] + clusterSpace[1],
+                    		clusterSpace[2], clusterSpace[2]);
                 }
-                
-                // Increment the current y position.
-                curY += th;
-            }
-            
-            // Increment the current x position.
-            curX += tw;
-            
-            // Reset the current y position and heightRem.
-            curY = 0;
-            heightRem = heightRemReset;
+          	}
         }
-        
-        // If the scale has changed, redraw the scale panel as well.
-        if (scaleChanged && scalePanel.isVisible()) {
-            scalePanel.redraw();
-        }
-        
-        // Indicate that the any size changes have been handled.
-        scaleChanged = false;
-        sizeChanged = false;
-        
-        // Reset the height and width remainder variables.
-        heightRem = heightRemReset;
-        widthRem = widthRemReset;
     }
     
     /**
+     * <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 {
-        /**
-         * <b>redraw</b><br/><br/>
-         * <code>public void <b>redraw</b>()</code><br/><br/>
-         * Orders the scale to re-render itself.
-         **/
-        public void redraw() { super.repaint(); }
+		private static final long serialVersionUID = -2644562244208528609L;
         
         protected void paintComponent(Graphics g) {
             // Set the text region width.
@@ -433,21 +586,7 @@
                 // Determine the spacing of the text.
                 FontMetrics fm = g.getFontMetrics(g.getFont());
                 int fontHeight = fm.getHeight();
-                double fStep = (height - 2.0 * fontHeight) / fontHeight;
-                double halfStep = fStep / 2.0;
                 
-                // Get the scaling value.
-                double fScale;
-                double fMin;
-                if (linear) {
-                    fScale = scale.getMaximum() - scale.getMinimum();
-                    fMin = scale.getMinimum();
-                }
-                else {
-                    fScale = Math.log10(scale.getMaximum() - Math.log10(scale.getMinimum()));
-                    fMin = Math.log10(scale.getMinimum());
-                }
-                
                 // Populate the first and last values.
                 NumberFormat nf = new DecimalFormat("0.#E0");
                 g.setColor(Color.WHITE);

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
EventManager.java 254 -> 255
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EventManager.java	2014-02-25 04:08:56 UTC (rev 254)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/EventManager.java	2014-02-25 16:44:47 UTC (rev 255)
@@ -31,7 +31,7 @@
     // 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<Datum> clusterList = new ArrayList<Datum>();
+    private ArrayList<Cluster> clusterList = new ArrayList<Cluster>();
     // Whether the event manager has an open file.
     private boolean open = true;
     
@@ -82,15 +82,37 @@
             int ix = Integer.parseInt(st.nextToken());
             int iy = Integer.parseInt(st.nextToken());
             
-            // Convert it to an object.
-            if (name.compareTo("Cluster") == 0) {
-                clusterList.add(new Datum(ix, iy));
-            }
+            // 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();
         }
@@ -125,12 +147,12 @@
     
     /**
      * <b>getClusters</b><br/><br/>
-     * <code>public ArrayList<Datum> <b>getClusters</b></code><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<Datum> getClusters() {
+    public ArrayList<Cluster> getClusters() {
         if (!open) { return null; }
         else { return clusterList; }
     }

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
Main.java 254 -> 255
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Main.java	2014-02-25 04:08:56 UTC (rev 254)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Main.java	2014-02-25 16:44:47 UTC (rev 255)
@@ -26,9 +26,12 @@
                 (screenHeight - window.getPreferredSize().height) / 2);
         window.setDataSource("cluster-hit.txt");
         window.displayNextEvent();
+        
+        /**
+        int key = 0;
+        while((key = System.in.read()) != 10) { }
+        **/
         window.setVisible(true);
-        
-        // makeData();
     }
     
     static void makeData() {

java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal
StatusPanel.java added at 255
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/StatusPanel.java	                        (rev 0)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/StatusPanel.java	2014-02-25 16:44:47 UTC (rev 255)
@@ -0,0 +1,170 @@
+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-app/src/main/java/org/hps/monitoring/ecal
Viewer.java 254 -> 255
--- java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Viewer.java	2014-02-25 04:08:56 UTC (rev 254)
+++ java/trunk/monitoring-app/src/main/java/org/hps/monitoring/ecal/Viewer.java	2014-02-25 16:44:47 UTC (rev 255)
@@ -1,11 +1,18 @@
 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;
@@ -25,11 +32,32 @@
     // Java-suggested variable.
     private static final long serialVersionUID = -2022819652687941812L;
     // The calorimeter panel.
-    private static final EcalPanel ecalPanel = new EcalPanel(46, 11);
+    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;
-    // Whether an LCIO event is being processed.
-    private boolean processing = false;
+    // 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/>
@@ -42,15 +70,15 @@
         setTitle("HPS Ecal Cluster Viewer");
         setDefaultCloseOperation(DISPOSE_ON_CLOSE);
         setPreferredSize(new Dimension(1060, 600));
-        setMinimumSize(new Dimension(1060, 400));
+        setMinimumSize(new Dimension(1060, 525));
         setLayout(null);
         
         // Set the scaling settings.
-        ecalPanel.setMinimum(0.001);
+        ecalPanel.setMinimum(0.0001);
         ecalPanel.setMaximum(3000);
         ecalPanel.setScalingLogarithmic();
         
-        // Disable the crystals in the ecal panel along the beam gap.
+        // 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) {
@@ -60,10 +88,15 @@
         }
         
         // Make a key listener to change events.
-        addKeyListener(new EcalListener());
+        addKeyListener(new EcalKeyListener());
         
-        // Add the ecal pane
+        // 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());
@@ -89,13 +122,52 @@
         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);
@@ -127,103 +199,250 @@
     }
     
     /**
-     * <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 ecal panel.
-        ecalPanel.clearCrystals();
-        
-        // 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 (Datum d : em.getClusters()) {
-            int ix = getPanelX(d.getX());
-            int iy = getPanelY(d.getY());
-            ecalPanel.setCrystalCluster(ix, iy, true);
-        }
-        
-        // Redraw the ecal panel.
-        ecalPanel.redraw();
-    }
-    
-    /**
      * <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() {
-        // Size and position the calorimeter display
+    	// Define the status panel height.
+    	int statusHeight = 125;
+    	
+        // Size and position the calorimeter display.
         ecalPanel.setLocation(0, 0);
-        ecalPanel.setSize(getContentPane().getSize());
+        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>private int <b>getPanelX</b>(int ecalX)</code><br/><br/>
-     * Converts the lcsim x-coordinate to the calorimeter panel's coordinate
+     * <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.
+     * @param ecalX - An LCSim calorimeter x-coordinate.
      * @return Returns the x-coordinate in the calorimeter panel's coordinate
      * system as an <code>int</code>.
      **/
-    private int getPanelX(int ecalX) {
-        if (ecalX <= 0) {
-            return ecalX + 23;
-        } else {
-            return ecalX + 22;
-        }
+    public int getPanelX(int ecalX) {
+        if (ecalX <= 0) { return ecalX + 23; }
+        else { return ecalX + 22; }
     }
     
     /**
      * <b>getPanelY</b><br/><br/>
-     * <code>private int <b>getPanelY</b>(int ecalY)</code><br/><br/>
-     * Converts the lcsim y-coordinate to the calorimeter panel's coordinate
+     * <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.
+     * @param ecalY - An LCSim calorimeter y-coordinate.
      * @return Returns the y-coordinate in the calorimeter panel's coordinate
      * system as an <code>int</code>.
      **/
-    private int getPanelY(int ecalY) {
-        return 5 - ecalY;
+    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.
+     * <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 EcalListener implements KeyListener {
-        public void keyPressed(KeyEvent e) {
-        }
+    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) {
+                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) {
-        }
+        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.
      **/
SVNspam 0.1