Print

Print


Author: mccaky
Date: Sat Nov  1 02:29:56 2014
New Revision: 1384

Log:
Added updated event display GUI and ability to display ecal hardware settings on status panel.

Added:
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/DataFileViewer.java   (with props)
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PDataEventViewer.java   (with props)
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ResizableFieldPanel.java   (with props)
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/util/CrystalDataSet.java   (with props)
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/util/EcalWiringManager.java   (with props)
Modified:
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ActiveViewer.java
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ClusterViewer.java
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/FileViewer.java
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/OccupancyViewer.java
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PEventViewer.java
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/POccupancyViewer.java
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PassiveViewer.java
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/StatusPanel.java
    java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/Viewer.java

Modified: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ActiveViewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ActiveViewer.java	(original)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ActiveViewer.java	Sat Nov  1 02:29:56 2014
@@ -19,50 +19,43 @@
  * @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;
-	
-	/**
-     * 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]); }
-	
-	/**
+    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;
+    
+    /**
      * 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;
-		
+     * @param em - The data source event manager.
+     * @param fieldNames - An array of additional status fields
+     * that should be displayed.
+     */
+    public ActiveViewer(EventManager em) {
+        // Pass any additional field values to the super class.
+        super();
+        
+        // Set the data source.
+        this.em = em;
+        
         // Make a key listener to change events.
         addKeyListener(new EcalKeyListener());
-	}
-	
+    }
+    
     /**
      * 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;
-	
+    public abstract void displayNextEvent() throws IOException;
+    
     /**
      * 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;
-	
+    public abstract void displayPreviousEvent() throws IOException;
+    
     /**
      * The <code>EcalListener</code> class binds keys to actions.
      * Bound actions include:
@@ -71,7 +64,7 @@
      * 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
+     * s             :: Saves the current display to a file
      **/
     private class EcalKeyListener implements KeyListener {
         public void keyPressed(KeyEvent e) { }
@@ -97,9 +90,9 @@
             
             // 'b' toggles the default white background.
             else if(e.getKeyCode() == 66) {
-            	if(background) { ecalPanel.setDefaultCrystalColor(null); }
-            	else { ecalPanel.setDefaultCrystalColor(Color.GRAY); }
-            	background = !background;
+                if(background) { ecalPanel.setDefaultCrystalColor(null); }
+                else { ecalPanel.setDefaultCrystalColor(Color.GRAY); }
+                background = !background;
             }
             
             // 'h' toggles highlighting the crystal under the cursor.
@@ -107,45 +100,45 @@
             
             // 'l' toggles linear or logarithmic scaling.
             else if(e.getKeyCode() == 76) {
-            	if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
-            	else { ecalPanel.setScalingLinear(); }
+                if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
+                else { ecalPanel.setScalingLinear(); }
             }
             
             // 'x' toggles x-axis mirroring.
             else if(e.getKeyCode() == 88) {
-            	ecalPanel.setMirrorX(!ecalPanel.isMirroredX());
-            	updateStatusPanel();
+                ecalPanel.setMirrorX(!ecalPanel.isMirroredX());
+                updateStatusPanel();
             }
             
             // 'y' toggles y-axis mirroring.
             else if(e.getKeyCode() == 89) {
-            	ecalPanel.setMirrorY(!ecalPanel.isMirroredY());
-            	updateStatusPanel();
+                ecalPanel.setMirrorY(!ecalPanel.isMirroredY());
+                updateStatusPanel();
             }
             
             // '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());
+                // 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.

Modified: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ClusterViewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ClusterViewer.java	(original)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ClusterViewer.java	Sat Nov  1 02:29:56 2014
@@ -31,10 +31,11 @@
  * 
  * @author Kyle McCarty
  */
+@Deprecated
 public class ClusterViewer extends ActiveViewer {
-	private static final long serialVersionUID = 17058336873349781L;
-	// Stores whether the background color is set or not.
-	private boolean background = false;
+    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.
@@ -50,197 +51,205 @@
     // 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/>
+    /**
+     * <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); }
-		
+     * @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 {
+        // Initialize the superclass.
+        super(dataSource);
+        
+        // Add the additional fields.
+        for(String field : fieldNames) {
+            addStatusField(field);
+        }
+        
+        // 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.
      **/
+    @Override
     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.
      **/
+    @Override
     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;
-	}
-    
+    
+    /**
+     * Generates a list of clusters from the list of hits in the event.
+     * This was used as a debugging method for the current clustering
+     * algorithm.
+     * @return Returns a generated list of clusters.
+     */
+    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;
+    }
+    
+    @Override
     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);
-		
+        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());
@@ -250,28 +259,28 @@
         
         // Display the clusters.
         for(Cluster cluster : clusterList) {
-        	Point rawCluster = cluster.getClusterCenter();
-        	Point clusterCenter = toPanelPoint(rawCluster);
+            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();
+            // 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/>
@@ -294,16 +303,16 @@
         
         // 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());
+            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());
+            eventEnergyBuffer.removeFirst();
+            eventHitBuffer.removeFirst();
+            eventEnergyBuffer.addLast(toEnergyArray(em.getHits()));
+            eventHitBuffer.addLast(em.getHits());
         }
         
         // Determine if any of the hits in the active event are
@@ -321,29 +330,36 @@
         // 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;
-	}
-	
+    
+    /**
+     * Gets the energy that should be stored in each crystal of the
+     * calorimeter.
+     * @param hits - The list of hits for the event.
+     * @return Returns the energy of each crystal as an array of <code>
+     * Double</code> objects.
+     */
+    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:
@@ -352,11 +368,13 @@
      * 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
+     * s             :: Saves the current display to a file
      **/
     private class EcalKeyListener implements KeyListener {
+        @Override
         public void keyPressed(KeyEvent e) { }
         
+        @Override
         public void keyReleased(KeyEvent e) {
             // If right-arrow was pressed, go to the next event.
             if (e.getKeyCode() == 39) {
@@ -379,30 +397,30 @@
             // 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(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);
-            	}
+                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;
+                if(background) { ecalPanel.setDefaultCrystalColor(null); }
+                else { ecalPanel.setDefaultCrystalColor(Color.GRAY); }
+                background = !background;
             }
             
             // 'h' toggles highlighting the crystal under the cursor.
@@ -410,39 +428,40 @@
             
             // 'l' toggles linear or logarithmic scaling.
             else if(e.getKeyCode() == 76) {
-            	if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
-            	else { ecalPanel.setScalingLinear(); }
+                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());
+                // 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()); }
         }
         
+        @Override
         public void keyTyped(KeyEvent e) { }
     }
 }

Added: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/DataFileViewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/DataFileViewer.java	(added)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/DataFileViewer.java	Sat Nov  1 02:29:56 2014
@@ -0,0 +1,99 @@
+package org.hps.monitoring.ecal.eventdisplay.ui;
+
+import java.awt.Point;
+import java.io.IOException;
+
+import org.hps.monitoring.ecal.eventdisplay.io.EventManager;
+import org.hps.monitoring.ecal.eventdisplay.util.CrystalDataSet;
+import org.hps.monitoring.ecal.eventdisplay.util.EcalWiringManager;
+
+/**
+ * Class <code>DataFileViewer</code> is the active variant of a data
+ * viewer. It displays crystal hardware information read from a given
+ * data file and displays it along with crystal energy and index data.
+ * 
+ * @author Kyle McCarty
+ */
+public class DataFileViewer extends FileViewer {
+    // Local variables.
+    private static final long serialVersionUID = 1L;
+    private final EcalWiringManager ewm;
+    
+    // Hardware display fields.
+    private static final String[] fieldNames = {
+        "APD Number", "Preamp Number", "LED Channel", "LED Driver",
+        "FADC Slot", "FADC Channel", "Splitter Number", "HV Group",
+        "Jout", "MB", "Channel", "Gain"
+    };
+    
+    // Hardware display field indices.
+    private static final int FIELD_APD = 0;
+    private static final int FIELD_PREAMP = 1;
+    private static final int FIELD_LED_CHANNEL = 2;
+    private static final int FIELD_LED_DRIVER = 3;
+    private static final int FIELD_FADC_SLOT = 4;
+    private static final int FIELD_FADC_CHANNEL = 5;
+    private static final int FIELD_SPLITTER = 6;
+    private static final int FIELD_HV_GROUP = 7;
+    private static final int FIELD_JOUT = 8;
+    private static final int FIELD_MB = 9;
+    private static final int FIELD_CHANNEL = 10;
+    private static final int FIELD_GAIN = 11;
+    
+    /**
+     * Initializes a new <code>DataFileViewer</code> that reads from
+     * the given event manager for event data and the given hardware
+     * data file for crystal hardware data readout.
+     * @param dataSource - The manager for event data.
+     * @param crystalDataFilePath - The data file for crystal hardware
+     * information.
+     * @throws IOException Occurs if there is an error reading from
+     * either data source.
+     */
+    public DataFileViewer(EventManager dataSource, String crystalDataFilePath) throws IOException {
+        // Initialize the super class file.
+        super(dataSource);
+        
+        // Load the crystal data mapping.
+        ewm = new EcalWiringManager(crystalDataFilePath);
+        
+        // Add the crystal data fields.
+        for(String fieldName : fieldNames) {
+            addStatusField(fieldName);
+        }
+    }
+    
+    @Override
+    protected void updateStatusPanel() {
+        // Run the superclass method.
+        super.updateStatusPanel();
+        
+        // Get the selected crystal.
+        Point crystal = ecalPanel.getSelectedCrystal();
+        
+        // If a crystal is selected, display its data set.
+        if(crystal != null) {
+            // Get the LCSim coordinate system version of the crystal.
+            Point lcsimCrystal = Viewer.toEcalPoint(crystal);
+            
+            // Get the hardware data set associated with the crystal.
+            CrystalDataSet cds = ewm.getCrystalData(lcsimCrystal);
+            
+            // If the data set exists, update the all the fields.
+            if(cds != null) {
+                setStatusField(fieldNames[FIELD_APD], "" + cds.getAPDNumber());
+                setStatusField(fieldNames[FIELD_PREAMP], cds.getPreamplifierNumber().toString());
+                setStatusField(fieldNames[FIELD_LED_CHANNEL], "" + cds.getLEDChannel());
+                setStatusField(fieldNames[FIELD_LED_DRIVER], "" + cds.getLEDDriver());
+                setStatusField(fieldNames[FIELD_FADC_SLOT], "" + cds.getFADCSlot());
+                setStatusField(fieldNames[FIELD_FADC_CHANNEL], "" + cds.getFADCChannel());
+                setStatusField(fieldNames[FIELD_SPLITTER], "" + cds.getSplitterNumber());
+                setStatusField(fieldNames[FIELD_HV_GROUP], "" + cds.getHighVoltageGroup());
+                setStatusField(fieldNames[FIELD_JOUT], "" + cds.getJout());
+                setStatusField(fieldNames[FIELD_MB], "" + cds.getMB());
+                setStatusField(fieldNames[FIELD_CHANNEL], "" + cds.getChannel());
+                setStatusField(fieldNames[FIELD_GAIN], "" + cds.getGain());
+            }
+        }
+    }
+}

Modified: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/FileViewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/FileViewer.java	(original)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/FileViewer.java	Sat Nov  1 02:29:56 2014
@@ -21,7 +21,7 @@
  * @author Kyle McCarty
  */
 public class FileViewer extends ActiveViewer {
-	private static final long serialVersionUID = 17058336873349781L;
+    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.
@@ -32,80 +32,87 @@
     private static final int COMPONENT_HITS = 2;
     private static final int CLUSTER_ENERGY = 3;
     
-	/**
-	 * <b>FileViewer</b><br/><br/>
+    /**
+     * <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);
-	}
+     * @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 {
+        // Initialize the superclass viewer.
+        super(dataSource);
+        
+        // Add additional fields.
+        insertStatusField(0, fieldNames[0]);
+        for(int index = 1; index < fieldNames.length; index++) {
+            addStatusField(fieldNames[index]);
+        }
+    }
     
+    @Override
     public void displayNextEvent() throws IOException { getEvent(true); }
     
+    @Override
     public void displayPreviousEvent() throws IOException { getEvent(false); }
     
+    @Override
     protected void updateStatusPanel() {
-    	// Update the superclass status fields.
-    	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[SHARED_HITS], Integer.toString(activeCluster.getSharedHitCount()));
-				setStatusField(fieldNames[COMPONENT_HITS], 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[CLUSTER_ENERGY], energy);
-			}
-		}
-		// Otherwise, clear the field values.
-		else { for(String field : fieldNames) { setStatusField(field, StatusPanel.NULL_VALUE); } }
-    	
-    	// Set the event number.
-    	setStatusField(fieldNames[EVENT_NUMBER], Integer.toString(em.getEventNumber()));
+        // Update the superclass status fields.
+        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, ResizableFieldPanel.NULL_VALUE); }
+            }
+            
+            // Otherwise, define the fields based on the cluster.
+            else {
+                // Get the shared and component hit counts.
+                setStatusField(fieldNames[SHARED_HITS], Integer.toString(activeCluster.getSharedHitCount()));
+                setStatusField(fieldNames[COMPONENT_HITS], 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[CLUSTER_ENERGY], energy);
+            }
+        }
+        // Otherwise, clear the field values.
+        else { for(String field : fieldNames) { setStatusField(field, ResizableFieldPanel.NULL_VALUE); } }
+        
+        // Set the event number.
+        setStatusField(fieldNames[EVENT_NUMBER], Integer.toString(em.getEventNumber()));
     }
     
-	/**
-	 * <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);
-		
+    /**
+     * <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());
@@ -115,19 +122,19 @@
         
         // Display the clusters.
         for(Cluster cluster : clusterList) {
-        	Point rawCluster = cluster.getClusterCenter();
-        	Point clusterCenter = toPanelPoint(rawCluster);
+            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));
-        	}
+            // 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.
@@ -136,7 +143,7 @@
         
         // Update the status panel to account for the new event.
         updateStatusPanel();
-	}
+    }
     
     /**
      * <b>getEvent</b><br/><br/>

Modified: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/OccupancyViewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/OccupancyViewer.java	(original)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/OccupancyViewer.java	Sat Nov  1 02:29:56 2014
@@ -17,63 +17,59 @@
  * @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/>
+    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;
+    
+    /**
      * 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];
-	}
+     * @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];
+    }
     
+    @Override
     public void displayNextEvent() throws IOException { getEvent(true); }
     
+    @Override
     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;
+        // 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);
-		
+    /**
+     * 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());
@@ -85,11 +81,9 @@
         
         // 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.
@@ -104,44 +98,44 @@
         
         // 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())]++;
-        	}
+            // 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())]--;
-        	}
+            // 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);
-        		}
-        	}
+            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.

Added: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PDataEventViewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PDataEventViewer.java	(added)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PDataEventViewer.java	Sat Nov  1 02:29:56 2014
@@ -0,0 +1,98 @@
+package org.hps.monitoring.ecal.eventdisplay.ui;
+
+import java.awt.Point;
+import java.io.IOException;
+
+import org.hps.monitoring.ecal.eventdisplay.util.CrystalDataSet;
+import org.hps.monitoring.ecal.eventdisplay.util.EcalWiringManager;
+
+/**
+ * Class <code>PDataEventViewer</code> is the passive variant of a data
+ * viewer. It displays crystal hardware information read from a given
+ * data file and displays it along with crystal energy and index data.
+ * 
+ * @author Kyle McCarty
+ */
+public class PDataEventViewer extends PEventViewer {
+    // Local variables.
+    private static final long serialVersionUID = 1L;
+    private final EcalWiringManager ewm;
+    
+    // Hardware display fields.
+    private static final String[] fieldNames = {
+        "APD Number", "Preamp Number", "LED Channel", "LED Driver",
+        "FADC Slot", "FADC Channel", "Splitter Number", "HV Group",
+        "Jout", "MB", "Channel", "Gain"
+    };
+    
+    // Hardware display field indices.
+    private static final int FIELD_APD = 0;
+    private static final int FIELD_PREAMP = 1;
+    private static final int FIELD_LED_CHANNEL = 2;
+    private static final int FIELD_LED_DRIVER = 3;
+    private static final int FIELD_FADC_SLOT = 4;
+    private static final int FIELD_FADC_CHANNEL = 5;
+    private static final int FIELD_SPLITTER = 6;
+    private static final int FIELD_HV_GROUP = 7;
+    private static final int FIELD_JOUT = 8;
+    private static final int FIELD_MB = 9;
+    private static final int FIELD_CHANNEL = 10;
+    private static final int FIELD_GAIN = 11;
+    
+    /**
+     * Initializes a new <code>DataFileViewer</code> that reads from
+     * the given event manager for event data and the given hardware
+     * data file for crystal hardware data readout.
+     * @param dataSource - The manager for event data.
+     * @param crystalDataFilePath - The data file for crystal hardware
+     * information.
+     * @throws IOException Occurs if there is an error reading from
+     * either data source.
+     */
+    public PDataEventViewer(String crystalDataFilePath) throws IOException {
+        // Initialize the super class file.
+        super();
+        
+        // Load the crystal data mapping.
+        ewm = new EcalWiringManager(crystalDataFilePath);
+        
+        // Add the crystal data fields.
+        for(String fieldName : fieldNames) {
+            addStatusField(fieldName);
+        }
+    }
+    
+    @Override
+    protected void updateStatusPanel() {
+        // Run the superclass method.
+        super.updateStatusPanel();
+        
+        // Get the selected crystal.
+        Point crystal = ecalPanel.getSelectedCrystal();
+        
+        // If a crystal is selected, display its data set.
+        if(crystal != null) {
+            // Get the LCSim coordinate system version of the crystal.
+            Point lcsimCrystal = Viewer.toEcalPoint(crystal);
+            
+            // Get the hardware data set associated with the crystal.
+            CrystalDataSet cds = ewm.getCrystalData(lcsimCrystal);
+            
+            // If the data set exists, update the all the fields.
+            if(cds != null) {
+                setStatusField(fieldNames[FIELD_APD], "" + cds.getAPDNumber());
+                setStatusField(fieldNames[FIELD_PREAMP], cds.getPreamplifierNumber().toString());
+                setStatusField(fieldNames[FIELD_LED_CHANNEL], "" + cds.getLEDChannel());
+                setStatusField(fieldNames[FIELD_LED_DRIVER], "" + cds.getLEDDriver());
+                setStatusField(fieldNames[FIELD_FADC_SLOT], "" + cds.getFADCSlot());
+                setStatusField(fieldNames[FIELD_FADC_CHANNEL], "" + cds.getFADCChannel());
+                setStatusField(fieldNames[FIELD_SPLITTER], "" + cds.getSplitterNumber());
+                setStatusField(fieldNames[FIELD_HV_GROUP], "" + cds.getHighVoltageGroup());
+                setStatusField(fieldNames[FIELD_JOUT], "" + cds.getJout());
+                setStatusField(fieldNames[FIELD_MB], "" + cds.getMB());
+                setStatusField(fieldNames[FIELD_CHANNEL], "" + cds.getChannel());
+                setStatusField(fieldNames[FIELD_GAIN], "" + cds.getGain());
+            }
+        }
+    }
+}

Modified: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PEventViewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PEventViewer.java	(original)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PEventViewer.java	Sat Nov  1 02:29:56 2014
@@ -22,60 +22,58 @@
  * @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);
-		
-		// Clear the panel data.
-		ecalPanel.clearCrystals();
-		
+    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>();
+    
+    /**
+     * Creates a passive viewer for displaying hits and clusters in
+     * an event.
+     * @param fieldValues - Any additional status fields to display.
+     */
+    public PEventViewer() {
+        // Initialize the superclass.
+        super();
+        
+        // Set the key bindings.
+        addKeyListener(new EcalKeyListener());
+    }
+    
+    @Override
+    public void addHit(EcalHit hit) { hitList.add(hit); }
+    
+    @Override
+    public void addCluster(Cluster cluster) { clusterList.add(cluster); }
+    
+    /**
+     * Removes all of the hit data from the viewer.
+     */
+    public void clearHits() { hitList.clear(); }
+    
+    /**
+     * Removes all of the cluster data from the viewer.
+     */
+    public void clearClusters() { hitList.clear(); }
+    
+    @Override
+    public void resetDisplay() {
+        // Reset the hit and cluster lists.
+        hitList.clear();
+        clusterList.clear();
+    }
+    
+    @Override
+    public void updateDisplay() {
+        // Suppress the calorimeter panel's redrawing.
+        ecalPanel.setSuppressRedraw(true);
+        
+        // Clear the panel data.
+        ecalPanel.clearCrystals();
+        
         // Display the hits.
         for (EcalHit h : hitList) {
             int ix = toPanelX(h.getX());
@@ -85,19 +83,19 @@
         
         // Display the clusters.
         for(Cluster cluster : clusterList) {
-        	Point rawCluster = cluster.getClusterCenter();
-        	Point clusterCenter = toPanelPoint(rawCluster);
+            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));
-        	}
+            // 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.
@@ -106,25 +104,27 @@
         
         // 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
+     * s             :: Saves the current display to a file
      **/
     private class EcalKeyListener implements KeyListener {
+        @Override
         public void keyPressed(KeyEvent e) { }
         
+        @Override
         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;
+                if(background) { ecalPanel.setDefaultCrystalColor(null); }
+                else { ecalPanel.setDefaultCrystalColor(Color.GRAY); }
+                background = !background;
             }
             
             // 'h' toggles highlighting the crystal under the cursor.
@@ -132,39 +132,40 @@
             
             // 'l' toggles linear or logarithmic scaling.
             else if(e.getKeyCode() == 76) {
-            	if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
-            	else { ecalPanel.setScalingLinear(); }
+                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());
+                // 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()); }
         }
         
+        @Override
         public void keyTyped(KeyEvent e) { }
     }
 }

Modified: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/POccupancyViewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/POccupancyViewer.java	(original)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/POccupancyViewer.java	Sat Nov  1 02:29:56 2014
@@ -16,100 +16,93 @@
  * @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/>
+    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>();
+    
+    /**
      * 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 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() { 
+    @Override
+    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]++;
+    }
+    
+    /**
+     * Adds a new cluster to the display.<br/><br/>
+     * <b>Note:</b> This operation is not supported for occupancies.
+     */
+    public void addCluster(Cluster cluster) { }
+    
+    /**
+     * 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]--;
+    }
+    
+    @Override
+    public void resetDisplay() { hitList.clear(); }
+    
+    /**
+     * 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; }
+    
+    /**
+     * Displays the hits and clusters added by the <code>addHit</code>
+     * and <code>addCluster</code> methods.
+     */
+    @Override
+    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));
-        		}
-        	}
+            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);
-		
+        // Suppress the calorimeter panel's redrawing.
+        ecalPanel.setSuppressRedraw(true);
+        
         // Display the hits.
         for (EcalHit h : hitList) {
             int ix = toPanelX(h.getX());
@@ -123,5 +116,5 @@
         
         // Update the status panel to account for the new event.
         updateStatusPanel();
-	}
+    }
 }

Modified: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PassiveViewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PassiveViewer.java	(original)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/PassiveViewer.java	Sat Nov  1 02:29:56 2014
@@ -20,94 +20,94 @@
  * @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();
-	
+    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() {
+        // Initialize the superclass.
+        super();
+        
+        // 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
+     * s             :: Saves the current display to a file
      **/
     private class EcalKeyListener implements KeyListener {
         public void keyPressed(KeyEvent e) { }
@@ -115,9 +115,9 @@
         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;
+                if(background) { ecalPanel.setDefaultCrystalColor(null); }
+                else { ecalPanel.setDefaultCrystalColor(Color.GRAY); }
+                background = !background;
             }
             
             // 'h' toggles highlighting the crystal under the cursor.
@@ -125,33 +125,33 @@
             
             // 'l' toggles linear or logarithmic scaling.
             else if(e.getKeyCode() == 76) {
-            	if(ecalPanel.isScalingLinear()) { ecalPanel.setScalingLogarithmic(); }
-            	else { ecalPanel.setScalingLinear(); }
+                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());
+                // 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.

Added: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ResizableFieldPanel.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ResizableFieldPanel.java	(added)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/ResizableFieldPanel.java	Sat Nov  1 02:29:56 2014
@@ -0,0 +1,515 @@
+package org.hps.monitoring.ecal.eventdisplay.ui;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.LayoutManager;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * Class <code>ResizableFieldPanel</code> is an extension of <code>
+ * JPanel</code> that contains a series of "fields." Each field is
+ * composed of a header label that displays its name and a value
+ * label that displays its value. Field names are constant once set
+ * but values may be changed.<br/>
+ * <br/>
+ * <code>ResizableFieldPanel</code> automatically handles the layout
+ * of its fields, displaying the header label next to the value label
+ * and inserting as many in a row as possible before wrapping to the
+ * next row. <code>ResizableFieldPanel</code> ensures that each field
+ * in a column is the same size as the other fields in the same column
+ * and that all fields in the same column are aligned. Individual
+ * columns may be different widths.<br/>
+ * <br/>
+ * The preferred width of a column is set by the preferred size of the
+ * largest field in that column or a minimum row size, if it is set.
+ * Fields that exceed the minimum row size are still granted their full
+ * preferred size.
+ * 
+ * @author Kyle McCarty
+ * @see JPanel
+ */
+public class ResizableFieldPanel extends JPanel {
+    // Local variables.
+    private static final long serialVersionUID = 1L;
+    private Font boldFont = getFont().deriveFont(Font.BOLD);
+    private static final int horizontal = 10;
+    private static final int vertical = 10;
+    private Dimension oldSize = new Dimension(0, 0);
+    private int oldFieldCount = 0;
+    private int minDisplayWidth = 0;
+    private Dimension userPreferred = null;
+    private Dimension actualPreferred = new Dimension(0, 0);
+    
+    // Constituent components.
+    private List<JLabel> headerList = new ArrayList<JLabel>();
+    private List<JLabel> displayList = new ArrayList<JLabel>();
+    
+    // Class variables.
+    /** The display text for a field with no value. */
+    static final String NULL_VALUE = "---";
+    
+    /**
+     * Instantiates a new <code>ResizableFieldPanel</code> with no
+     * minimum column width.
+     */
+    public ResizableFieldPanel() { this(0); }
+    
+    /**
+     * Instantiates a new <code>ResizableFieldPanel</code> with the
+     * indicated minimum column width.
+     * @param minWidth - The minimum column width.
+     */
+    public ResizableFieldPanel(int minWidth) {
+        // Component handles constituent component placement manually.
+        super.setLayout(null);
+        
+        // Set the minimum component width.
+        minDisplayWidth = minWidth >= 0 ? minWidth : 0;
+        
+        // Reset the constituent component placement whenever the base
+        // component changes size.
+        addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(ComponentEvent e) {
+                // Issue an order to reset the component layout.
+                resetLayout();
+            }
+        });
+    }
+    
+    /**
+     * Adds a new field to the panel.
+     * @param fieldName - The name of the field.
+     * @throws NullPointerException Occurs if the field name is <code>
+     * null</code>.
+     */
+    public void addField(String fieldName) throws NullPointerException {
+        addField(fieldName, null);
+    }
+    
+    /**
+     * Adds a new field to the panel.
+     * @param fieldName - The name of the field.
+     * @param fieldValue - The initial value of the field.
+     * @throws NullPointerException Occurs if the field name is <code>
+     * null</code>.
+     */
+    public void addField(String fieldName, String fieldValue) throws NullPointerException {
+        // Require that the field have a non-null name.
+        if(fieldName == null) { throw new NullPointerException("Field names can not be null."); }
+        
+        // Null values default the default value.
+        if(fieldValue == null) { fieldValue = NULL_VALUE; }
+        
+        // Create new labels to populate the component.
+        JLabel header = new JLabel(fieldName + ":");
+        header.setFont(boldFont);
+        header.setHorizontalAlignment(JLabel.RIGHT);
+        JLabel display = new JLabel(fieldValue);
+        display.setFont(getFont());
+        
+        // Add the labels to the list.
+        headerList.add(header);
+        displayList.add(display);
+        
+        // Add the components to the base panel.
+        add(header);
+        add(display);
+        
+        // Reset the label layout.
+        resetLayout();
+    }
+    
+    /**
+     * Sets the values of all fields to <code>NULL_VALUE</code>.
+     */
+    public void clearFields() {
+        for(JLabel display : displayList) {
+            display.setText(NULL_VALUE);
+        }
+    }
+    
+    /**
+     * Gets the number of fields contained in the component.
+     * @return Returns the number of fields as an <code>int</code>
+     * primitive.
+     */
+    public int getFieldCount() { return headerList.size(); }
+    
+    /**
+     * Generates a map that links field name to field index.
+     * @return Returns a <code>Map</code> object linking the <code>
+     * String</code> field name to the <code>int</code> field index.
+     */
+    public Map<String, Integer> getFieldNameIndexMap() {
+        // Get the number of fields.
+        int fields = headerList.size();
+        
+        // Create the map.
+        Map<String, Integer> fieldMap = new HashMap<String, Integer>(fields);
+        
+        // Populate the map.
+        for(int index = 0; index < fields; index++) {
+            // Get the header name and remove the colon at the end.
+            String header = headerList.get(index).getText();
+            header = header.substring(0, header.length() - 1);
+            
+            // Add it to the map.
+            fieldMap.put(header, new Integer(index));
+        }
+        
+        // Return the map.
+        return fieldMap;
+    }
+    
+    /**
+     * Gets the list of all field names in order of field index.
+     * @return Returns the set of field names as an array of <code>
+     * String</code> objects.
+     */
+    public String[] getFieldNames() {
+        // Create an array for the field names.
+        String[] fieldNames = new String[headerList.size()];
+        
+        // Populate the array.
+        for(int index = 0; index < headerList.size(); index++) {
+            String fieldName = headerList.get(index).getText();
+            fieldNames[index] = fieldName.substring(0, fieldName.length() - 2);
+        }
+        
+        // Return the result.
+        return fieldNames;
+    }
+    
+    /**
+     * Gets the value for the field at the indicated index.
+     * @param fieldIndex - The index of the field.
+     * @return Returns the field value as a <code>String</code> object.
+     * @throws IndexOutOfBoundsException Occurs if an invalid field index
+     * is given.
+     */
+    public String getFieldValue(int fieldIndex) throws IndexOutOfBoundsException {
+        // Validate the index.
+        validateFieldIndex(fieldIndex);
+        
+        // Return the value of the requested field.
+        return displayList.get(fieldIndex).getText();
+    }
+    
+    @Override
+    public Dimension getPreferredSize() {
+        if(userPreferred == null) { return actualPreferred; }
+        else { return userPreferred; }
+    }
+    
+    /**
+     * Inserts a field at the indicated index, if possible.
+     * @param index - The index at which to insert the field.
+     * @param fieldName - The name of the field.
+     * @throws IndexOutOfBoundsException Occurs if the insertion index
+     * is not a valid index within the component.
+     * @throws NullPointerException Occurs if the field name is <code>
+     * null</code>.
+     */
+    public void insertField(int index, String fieldName) throws IndexOutOfBoundsException, NullPointerException {
+        insertField(index, fieldName, "");
+    }
+    
+    /**
+     * Inserts a field at the indicated index, if possible.
+     * @param index - The index at which to insert the field.
+     * @param fieldName - The name of the field.
+     * @param fieldValue - The initial value of the field.
+     * @throws IndexOutOfBoundsException Occurs if the insertion index
+     * is not a valid index within the component.
+     * @throws NullPointerException Occurs if the field name is <code>
+     * null</code>.
+     */
+    public void insertField(int index, String fieldName, String fieldValue) throws IndexOutOfBoundsException, NullPointerException {
+        // Require that the field have a non-null name.
+        if(fieldName == null) { throw new NullPointerException("Field names can not be null."); }
+        
+        // Null values default the default value.
+        if(fieldValue == null) { fieldValue = NULL_VALUE; }
+        
+        // Create new labels to populate the component.
+        JLabel header = new JLabel(fieldName + ":");
+        header.setFont(boldFont);
+        header.setHorizontalAlignment(JLabel.RIGHT);
+        JLabel display = new JLabel(fieldValue);
+        display.setFont(getFont());
+        
+        // Add the labels to the list.
+        headerList.add(index, header);
+        displayList.add(index, display);
+        
+        // Add the components to the base panel.
+        add(header);
+        add(display);
+        
+        // Reset the label layout.
+        resetLayout();
+    }
+    
+    /**
+     * Removes the field at the indicated index.
+     * @param fieldIndex - The index of the field.
+     * @throws IndexOutOfBoundsException Occurs if an invalid field index
+     * is given.
+     */
+    public void removeField(int fieldIndex) throws IndexOutOfBoundsException {
+        // Validate the index.
+        validateFieldIndex(fieldIndex);
+        
+        // Remove the requested field.
+        remove(headerList.get(fieldIndex));
+        remove(displayList.get(fieldIndex));
+        headerList.remove(fieldIndex);
+        displayList.remove(fieldIndex);
+        
+        // Reset the layout.
+        resetLayout();
+    }
+    
+    /**
+     * Sets the value of the field at the indicated index.
+     * @param fieldIndex - The index of the field.
+     * @param fieldValue - The new value of the field.
+     * @throws IndexOutOfBoundsException Occurs if an invalid field index
+     * is given.
+     */
+    public void setFieldValue(int fieldIndex, String fieldValue) throws IndexOutOfBoundsException {
+        // Validate the index.
+        validateFieldIndex(fieldIndex);
+        
+        // Set the value of the requested field.
+        displayList.get(fieldIndex).setText(fieldValue);
+    }
+    
+    @Override
+    public void setFont(Font font) {
+        // If the superclass font is null, the component is still
+        // being initialized and needs to just run the superclass
+        // method.
+        if(getFont() == null) { super.setFont(font); }
+        
+        // Otherwise, set any constituent components to the correct
+        // font as well.
+        else {
+            // If the font is being set to null, use the default
+            // system font.
+            if(font == null) { font = new Font(Font.DIALOG, Font.PLAIN, 11); }
+            
+            // Set the base panel's font to the indicated value.
+            super.setFont(font);
+            
+            // Create a new bold font.
+            boldFont = super.getFont().deriveFont(Font.BOLD);
+            
+            // Set each display label to the indicated font.
+            for(JLabel display : displayList) { display.setFont(font); }
+            
+            // Set each header label to the bold font.
+            for(JLabel header : headerList) { header.setFont(boldFont); }
+        }
+    }
+    
+    @Override
+    public void setLayout(LayoutManager mgr) {
+        // ResizableDisplayManager handles its own layout, so do nothing.
+        return;
+    }
+    
+    @Override
+    public void setPreferredSize(Dimension preferredSize) {
+        userPreferred = preferredSize;
+    }
+    
+    /**
+     * Repositions the fields to the appropriate positions given the
+     * current width of the component.
+     */
+    private void resetLayout() {
+        // If the neither the size of the component nor the number of
+        // fields have changed, there is nothing to do here.
+        if(headerList.size() == oldFieldCount && oldSize.width == getSize().width
+                && oldSize.height == getSize().height) {
+            return;
+        }
+        
+        // Set the last width and field counts.
+        oldSize = getSize();
+        oldFieldCount = headerList.size();
+        
+        // Otherwise, determine the ideal maximum number of fields that
+        // can be displayed in a single row.
+        int availableWidth = getWidth() - horizontal;
+        int max = 0;
+        idealLoop:
+        for(int index = 0; index < headerList.size(); index++) {
+            // Subtract the necessary width for the header label.
+            availableWidth -= headerList.get(index).getPreferredSize().width;
+            availableWidth -= horizontal;
+            
+            // Subtract the required width for the display label.
+            int displayWidth = displayList.get(index).getPreferredSize().width;
+            if(displayWidth < minDisplayWidth) { displayWidth = minDisplayWidth; }
+            availableWidth -= displayWidth;
+            availableWidth -= horizontal;
+            
+            // Increment the maximum number of displayable headers.
+            max++;
+            
+            // If the available width is depleted, exit the loop.
+            if(availableWidth <= 0) { break idealLoop; }
+        }
+        
+        // If the maximum displayable number is either 1 or equal to
+        // actual number, the fields should be displayed with their
+        // preferred widths.
+        if(max == 1 || max == headerList.size()) {
+            // Track the current position on the component.
+            int curX = horizontal;
+            int curY = vertical;
+            
+            // Add each field.
+            for(int index = 0; index < headerList.size(); index++) {
+                // Get the current header and display label.
+                JLabel header = headerList.get(index);
+                JLabel display = displayList.get(index);
+                
+                // Set the label sizes to the preferred size.
+                header.setSize(header.getPreferredSize());
+                display.setSize(display.getPreferredSize());
+                
+                // Set the label locations to the current correct place.
+                header.setLocation(curX, curY);
+                header.setSize(header.getPreferredSize());
+                curX += header.getSize().width + horizontal;
+                display.setLocation(curX, curY);
+                display.setSize(header.getPreferredSize());
+                curX += display.getWidth() + horizontal;
+            }
+            
+            // The process is finished, so return.
+            return;
+        }
+        
+        // Otherwise, the labels in subsequent rows must be analyzed
+        // to determine what the actual maximum number of labels per
+        // row can be used. Try the ideal maximum first and work down
+        // from there to the minimum of one per row.
+        else {
+            // Track the number of columns needed and the proper widths
+            // of each column.
+            int columns = 1;
+            int[] columnWidth = { 0 };
+            actualLoop:
+            for(int actual = max; actual > 0; actual--) {
+                // Store the actual necessary width for each component in
+                // each column for the current row size.
+                int[] actualColWidth = new int[actual + actual];
+                
+                // Track the current column.
+                int curCol = 0;
+                
+                // Iterate over the labels and find the largest necessary
+                // width for each column.
+                for(int index = 0; index < headerList.size(); index++) {
+                    // Get the widths needed for the current header and
+                    // display labels.
+                    int headerWidth = headerList.get(index).getPreferredSize().width;
+                    int displayWidth = displayList.get(index).getPreferredSize().width;
+                    if(displayWidth < minDisplayWidth) { displayWidth = minDisplayWidth; }
+                    
+                    // If the needed widths exceed the current maximum, they
+                    // should replace it.
+                    if(actualColWidth[2 * curCol] < headerWidth) { actualColWidth[2 * curCol] = headerWidth; }
+                    if(actualColWidth[(2 * curCol) + 1] < displayWidth) { actualColWidth[(2 * curCol) + 1] = displayWidth; }
+                    
+                    // Increment the current column.
+                    curCol++;
+                    if(curCol == actual) { curCol = 0; }
+                }
+                
+                // Check if the actual needed width is less than the width
+                // available for the labels.
+                availableWidth = getWidth() - horizontal;
+                for(int width : actualColWidth) {
+                    availableWidth -= width;
+                    availableWidth -= horizontal;
+                }
+                
+                // Store the results for this number of columns.
+                columns = actual;
+                columnWidth = actualColWidth;
+                
+                // If the result is either zero of positive, then there is
+                // enough space available and the current number of columns
+                // may be employed. Otherwise, continue to the next smaller
+                // number of columns.
+                if(availableWidth >= 0) { break actualLoop; }
+            }
+            
+            // The necessary width for each column and the number of columns
+            // should now be calculated. Set the constituent component
+            // positions and sizes to match it.
+            int curX = horizontal;
+            int curY = vertical;
+            int curCol = 0;
+            for(int index = 0; index < headerList.size(); index++) {
+                // Get the current header and display label.
+                JLabel header = headerList.get(index);
+                JLabel display = displayList.get(index);
+                
+                // Set the label sizes to the preferred size.
+                header.setSize(header.getPreferredSize());
+                display.setSize(display.getPreferredSize());
+                
+                // Set the label locations to the current correct place.
+                header.setLocation(curX, curY);
+                header.setSize(columnWidth[2 * curCol], header.getPreferredSize().height);
+                curX += header.getSize().width + horizontal;
+                display.setLocation(curX, curY);
+                display.setSize(columnWidth[(2 * curCol) + 1], display.getPreferredSize().height);
+                curX += display.getWidth() + horizontal;
+                
+                // Increment the current column index.
+                curCol++;
+                if(curCol == columns) {
+                    curCol = 0;
+                    curY += header.getPreferredSize().height + vertical;
+                    curX = horizontal;
+                }
+            }
+        }
+        
+        // Update the preferred size such that the preferred height will
+        // encompass all of the rows.
+        JLabel lastHeader = headerList.get(headerList.size() - 1);
+        int preferredHeight = lastHeader.getY() + lastHeader.getHeight() + vertical;
+        actualPreferred = new Dimension(getSize().width, preferredHeight);
+    }
+    
+    /**
+     * Throws an <code>IndexOutOfBoundsException</code> if the argument
+     * index is not a valid field index. This is used to validate the
+     * index given in methods requiring one.
+     * @param index - The index to validate.
+     * @throws IndexOutOfBoundsException Occurs if the index fails
+     * to validate.
+     */
+    private void validateFieldIndex(int index) throws IndexOutOfBoundsException {
+        if(index < 0 || index >= headerList.size()) {
+            throw new IndexOutOfBoundsException(String.format("Index %d is not a valid field index.", index));
+        }
+    }
+}

Modified: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/StatusPanel.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/StatusPanel.java	(original)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/StatusPanel.java	Sat Nov  1 02:29:56 2014
@@ -9,188 +9,180 @@
 
 /**
  * Class <code>StatusPanel</code> displays text in a set of fields.
+ * This class is being phased out in favor of <code>ResizableFieldPanel
+ * </code> and should no longer be used. It will be removed next update.
  *
  * @author Kyle McCarty
  */
+@Deprecated
 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;
+    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;
+    
+    /**
+     * 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 = "---";
+    
+    /**
+     * 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);
+    }
+    
+    /**
+     * 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);
+        }
+    }
+    
+    /**
+     * 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."); }
+    }
+    
+    @Override
+    public void setSize(int width, int height) {
+        super.setSize(width, height);
+        resize();
+    }
+    
+    @Override
+    public void setSize(Dimension d) {
+        super.setSize(d);
+        resize();
+    }
+    
+    /**
+     * 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); }
+    
+    /**
+    /**
+     * 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;
+    }
+    
+    /**
+     * 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);
-		}
-	}
+        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);
+        }
+    }
 }

Modified: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/Viewer.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/Viewer.java	(original)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/ui/Viewer.java	Sat Nov  1 02:29:56 2014
@@ -11,6 +11,7 @@
 import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.NoSuchElementException;
 
 import javax.swing.JFrame;
@@ -30,7 +31,7 @@
     // 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>();
+    private Map<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.
@@ -41,33 +42,25 @@
     private static final int CELL_VALUE = 2;
     
     /**
-     * <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/>
+    protected final ResizableFieldPanel statusPanel;
+    
+    /**
      * 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/>
+    public static final Color HIGHLIGHT_CLUSTER_COMPONENT = Color.RED;
+    
+    /**
      * The default color for highlighting cluster shared hits.
-	 */
-	public static final Color HIGHLIGHT_CLUSTER_SHARED = Color.YELLOW;
+     */
+    public static final Color HIGHLIGHT_CLUSTER_SHARED = Color.YELLOW;
     
     /**
      * Initializes the viewer window and calorimeter panel.
@@ -76,29 +69,20 @@
      * @throws NullPointerException Occurs if any of the additional field
      * arguments are <code>null</code>.
      **/
-    public Viewer(String... statusFields) throws NullPointerException {
+    public Viewer() throws NullPointerException {
         // Initialize the underlying JPanel.
         super();
         
-        // Define the status panel fields and map them to indices.
-        String[] fields = new String[statusFields.length + defaultFields.length];
-        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 + defaultFields.length;
-        	fields[index] = statusFields[i];
-        	fieldMap.put(statusFields[i], index);
-        }
-        
         // Generate the status panel.
-        statusPanel = new StatusPanel(fields);
+        statusPanel = new ResizableFieldPanel(100);
+        statusPanel.setBackground(Color.WHITE);
+        
+        // Add the default fields.
+        for(String field : defaultFields) { addStatusField(field); }
         
         // Set the scaling settings.
-        ecalPanel.setScaleMinimum(0.00001);
-        ecalPanel.setScaleMaximum(3);
-       // ecalPanel.setScalingLogarithmic();
+        ecalPanel.setScaleMinimum(0.001);
+        ecalPanel.setScaleMaximum(3.0);
         ecalPanel.setScalingLinear();
         
         // Disable the crystals in the calorimeter panel along the beam gap.
@@ -137,7 +121,28 @@
      * @param cl - The listener to add.
      */
     public void addCrystalListener(CrystalListener cl) {
-    	if(cl != null) { listenerList.add(cl); }
+        if(cl != null) { listenerList.add(cl); }
+    }
+    
+    /**
+     * Adds a new field to the status panel.
+     * @param fieldName - The name to display for the field and that
+     * links to the field when calling <code>setStatusField</code>.
+     */
+    protected void addStatusField(String fieldName) {
+        fieldMap.put(fieldName, statusPanel.getFieldCount());
+        statusPanel.addField(fieldName);
+    }
+    
+    /**
+     * Inserts the field at the indicated location on the status panel.
+     * @param index - The index at which to insert the field.
+     * @param fieldName - The name to display for the field and that
+     * links to the field when calling <code>setStatusField</code>.
+     */
+    protected void insertStatusField(int index, String fieldName) {
+        statusPanel.insertField(index, fieldName);
+        fieldMap = statusPanel.getFieldNameIndexMap();
     }
     
     /**
@@ -147,14 +152,14 @@
      * @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);
-	}
+    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);
+    }
     
     /**
      * Converts the panel x-coordinate to the calorimeter's
@@ -164,8 +169,8 @@
      * coordinate system as an <code>int</code>.
      */
     public static final int toEcalX(int panelX) {
-    	if(panelX > 22) { return panelX - 22; }
-    	else { return panelX - 23; }
+        if(panelX > 22) { return panelX - 22; }
+        else { return panelX - 23; }
     }
     
     /**
@@ -184,14 +189,14 @@
      * @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);
-	}
+    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);
+    }
     
     /**
      * Converts the LCSim x-coordinate to the calorimeter panel's
@@ -238,7 +243,7 @@
      * @param cl - The listener to remove.
      */
     public void removeCrystalListener(CrystalListener cl) {
-    	if(cl != null) { listenerList.remove(cl); }
+        if(cl != null) { listenerList.remove(cl); }
     }
     
     public void setSize(int width, int height) {
@@ -259,44 +264,46 @@
      * 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); }
-    }
-    
-	/**
-	 * 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[X_INDEX], String.valueOf(toEcalX(crystal.x)));
-			setStatusField(defaultFields[Y_INDEX], String.valueOf(toEcalY(crystal.y)));
-			DecimalFormat formatter = new DecimalFormat("0.####E0");
-			String energy = formatter.format(ecalPanel.getCrystalEnergy(crystal.x, crystal.y));
-			setStatusField(defaultFields[CELL_VALUE], energy);
-		}
-	}
+        // 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); }
+    }
+    
+    /**
+     * 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.clearFields();
+        }
+        
+        // Otherwise, write the crystal's data to the panel.
+        else {
+            setStatusField(defaultFields[X_INDEX], String.valueOf(toEcalX(crystal.x)));
+            setStatusField(defaultFields[Y_INDEX], String.valueOf(toEcalY(crystal.y)));
+            DecimalFormat formatter = new DecimalFormat("0.####E0");
+            String energy = formatter.format(ecalPanel.getCrystalEnergy(crystal.x, crystal.y));
+            setStatusField(defaultFields[CELL_VALUE], energy);
+        }
+    }
     
     /**
      * Handles proper resizing of the window and its components.
      **/
     private void resize() {
-    	// Define the size constants.
-    	int statusHeight = 125;
-    	
+        // Define the size constants.
+        int statusHeight = 125;
+        
         // Size and position the calorimeter display.
         ecalPanel.setLocation(0, 0);
         ecalPanel.setSize(getContentPane().getWidth(), getContentPane().getHeight() - statusHeight);
@@ -312,30 +319,30 @@
      * 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) { }
+        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.clearFields();
+        }
+        
+        public void mousePressed(MouseEvent e) { }
+        
+        public void mouseReleased(MouseEvent e) { }
     }
     
     /**
@@ -344,80 +351,80 @@
      * 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();
-		}
-		
-		/**
-		 * 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); }
-		}
-		
-		/**
-		 * 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); }
-		}
+    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();
+        }
+        
+        /**
+         * 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); }
+        }
+        
+        /**
+         * 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); }
+        }
     }
     
     /**

Added: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/util/CrystalDataSet.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/util/CrystalDataSet.java	(added)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/util/CrystalDataSet.java	Sat Nov  1 02:29:56 2014
@@ -0,0 +1,213 @@
+package org.hps.monitoring.ecal.eventdisplay.util;
+
+import java.awt.Point;
+
+/**
+ * Class <code>CrystalDataSet</code> contains all of the hardware data
+ * for a single calorimeter crystal as defined in the crystal hardware
+ * reference sheet.
+ * 
+ * @author Kyle McCarty
+ */
+public class CrystalDataSet {
+    // Data points.
+    private final Point crystalIndex;
+    private final short apd;
+    private final PreamplifierNumber preamp;
+    private final short ledChannel;
+    private final byte[] ledDriver;
+    private final byte fadcSlot;
+    private final byte fadcChannel;
+    private final byte splitterNum;
+    private final byte hvGroup;
+    private final byte jout;
+    private final String mb;
+    private final short channel;
+    private final int gain;
+    
+    /**
+     * Defines the data set.
+     * @param ix - The crystal's x-index in the LCSimcoordinate system.
+     * @param iy - The crystal's y-index in the LCSimcoordinate system.
+     * @param apd - The number of the APD attached to the crystal.
+     * @param preamp - The number of the crystal's premaplifier. This
+     * may also include a reference wire color.
+     * @param ledChannel - The channel number for the crystal's LED.
+     * @param ledDriver
+     * @param fadcSlot - The FADC slot the crystal occupies.
+     * @param fadcChannel - The channel number through which the crystal
+     * communicates with the FADC.
+     * @param splitter
+     * @param hvGroup - The high voltage group for which the crystal
+     * is a member.
+     * @param jout
+     * @param mb
+     * @param channel
+     * @param gain - The crystal's gain.
+     */
+    public CrystalDataSet(int ix, int iy, int apd, String preamp, int ledChannel,
+            double ledDriver, int fadcSlot, int fadcChannel, int splitter, int hvGroup,
+            int jout, String mb, int channel, int gain) {
+        // Define crystal indices.
+        crystalIndex = new Point(ix, iy);
+        
+        // Define the general properties.
+        this.apd = (short) apd;
+        this.ledChannel = (short) ledChannel;
+        this.fadcSlot = (byte) fadcSlot;
+        this.fadcChannel = (byte) fadcChannel;
+        this.splitterNum = (byte) splitter;
+        this.hvGroup = (byte) hvGroup;
+        this.jout = (byte) jout;
+        this.channel = (short) channel;
+        this.gain = gain;
+        this.mb = mb;
+        
+        // Define the LED driver.
+        this.ledDriver = new byte[2];
+        this.ledDriver[0] = (byte) Math.floor(ledDriver);
+        this.ledDriver[1] = (byte) ((ledDriver - this.ledDriver[0]) * 10);
+        
+        // Handle the preamplifier number.
+        StringBuffer num = new StringBuffer();
+        StringBuffer col = new StringBuffer();
+        for(char c : preamp.toCharArray()) {
+            if(Character.isDigit(c)) { num.append(c); }
+            else { col.append(c); }
+        }
+        int number = Integer.parseInt(num.toString());
+        String color = null;
+        if(col.length() != 0) { color = col.toString(); }
+        this.preamp = new PreamplifierNumber(number, color);
+    }
+    
+    /**
+     * Gets the crystal's positional indices in the LCSim coordinate
+     * system.
+     * @return Returns the crystal's positional indices as a <code>
+     * Point</code> object.
+     */
+    public Point getCrystalIndex() { return crystalIndex; }
+    
+    /**
+     * Gets the crystal's x-index in the LCSim coordinate system.
+     * @return Returns the crystal's x-index as an <code>int</code>
+     * primitive.
+     */
+    public int getCrystalXIndex() { return crystalIndex.x; }
+    
+    /**
+     * Gets the crystal's y-index in the LCSim coordinate system.
+     * @return Returns the crystal's y-index as an <code>int</code>
+     * primitive.
+     */
+    public int getCrystalYIndex() { return crystalIndex.y; }
+    
+    /**
+     * Gets the number of the APD attached to the crystal.
+     * @return Returns the crystal's APD number as an <code>int</code>
+     * primitive.
+     */
+    public int getAPDNumber() { return apd; }
+    
+    /**
+     * Gets the crystal's preamplifier reference data.
+     * @return Returns the preamplifier reference as a <code>
+     * PreamplifierNumber</code> object.
+     */
+    public PreamplifierNumber getPreamplifierNumber() { return preamp; }
+    
+    /**
+     * Gets the crystal's LED channel.
+     * @return Returns the LED channel as an <code>int</code> primitive.
+     */
+    public int getLEDChannel() { return ledChannel; }
+    
+    public double getLEDDriver() {
+        return ((double) ledDriver[0]) + ((double) ledDriver[1] / 10);
+    }
+    
+    /**
+     * Gets the crystal's FADC slot.
+     * @return Returns the FADC slot as an <code>int</code> primitive.
+     */
+    public int getFADCSlot() { return fadcSlot; }
+    
+    /**
+     * Gets the crystal's FADC channel.
+     * @return Returns the FADC channel as an <code>int</code> primitive.
+     */
+    public int getFADCChannel() { return fadcChannel; }
+    
+    public int getSplitterNumber() { return splitterNum; }
+    
+    /**
+     * Gets the crystal's high voltage group.
+     * @return Returns the high voltage group number as an <code>int
+     * </code> primitive.
+     */
+    public int getHighVoltageGroup() { return hvGroup; }
+    
+    public int getJout() { return jout; }
+    
+    public String getMB() { return mb; }
+    
+    public int getChannel() { return channel; }
+    
+    /**
+     * Gets the crystal's gain.
+     * @return Returns the gain as an <code>int</code> primitive.
+     */
+    public int getGain() { return gain; }
+    
+    /**
+     * Class <code>PreamplifierNumber</code> represents the number
+     * of a crystal's preamplifier. It can also contain a reference
+     * wire color if necessary.
+     * 
+     * @author Kyle McCarty
+     */
+    public class PreamplifierNumber {
+        private final short number;
+        private final String color;
+        
+        /**
+         * Initializes a preamplifier number with no reference wire
+         * color.
+         * @param number - The preamplifier's number.
+         */
+        public PreamplifierNumber(int number) { this(number, null); }
+        
+        /**
+         * Initializes a preamplifier number with the specified reference
+         * wire color.
+         * @param number - The preamplifier's number.
+         * @param color - The reference wire color.
+         */
+        public PreamplifierNumber(int number, String color) {
+            this.number = (short) number;
+            this.color = color;
+        }
+        
+        /**
+         * Gets the number of the preamplifier.
+         * @return Returns the preamplifier number as an <code>int
+         * </code> primitive.
+         */
+        public int getNumber() { return number; }
+        
+        /**
+         * Gets the reference wire color associated with the crystal's
+         * preamplifier if it exists.
+         * @return Returns the reference wire color as a <code>String
+         * </code> object or <code>null</code> if it does not exist.
+         */
+        public String getColor() { return color; }
+        
+        @Override
+        public String toString() {
+            if(color == null) { return "" + number; }
+            else { return number + " (" + color + ")"; }
+        }
+    }
+}

Added: java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/util/EcalWiringManager.java
 =============================================================================
--- java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/util/EcalWiringManager.java	(added)
+++ java/trunk/ecal-event-display/src/main/java/org/hps/monitoring/ecal/eventdisplay/util/EcalWiringManager.java	Sat Nov  1 02:29:56 2014
@@ -0,0 +1,112 @@
+package org.hps.monitoring.ecal.eventdisplay.util;
+
+import java.awt.Point;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+
+/**
+ * Class <code>EcalWiringManager</code> reads in the crystal hardware
+ * data sheet for the calorimeter and stores the data in <code>
+ * CrystalDataSet</code> objects for access and reference by the <code>
+ * Viewer</code> classes. Crystal LCSim indices are mapped to the data
+ * set that corresponds to that crystal.
+ * 
+ * @author Kyle McCarty
+ */
+public class EcalWiringManager {
+    // Delimiter class statics.
+    public static final int SPACE_DELIMITED = 1;
+    public static final int TAB_DELIMITED = 2;
+    public static final int COMMA_DELIMITED = 3;
+    
+    // Internal variables.
+    private Map<Point, CrystalDataSet> crystalMap = new HashMap<Point, CrystalDataSet>(442);
+    
+    /**
+     * Initializes an <code>EcalWiringManager</code> database with
+     * hardware information loaded from the indicated file. The data
+     * file is assumed to be comma delimited and the first line is
+     * assumed to be a header line and to not contain data.
+     * @param dataFile - The path to the data file.
+     * @throws IOException Occurs if there is an error opening or parsing
+     * the data file.
+     */
+    public EcalWiringManager(String dataFile) throws IOException {
+        this(dataFile, COMMA_DELIMITED, true);
+    }
+    
+    /**
+     * Initializes an <code>EcalWiringManager</code> database with
+     * hardware information loaded from the indicated file.
+     * @param dataFile - The path to the data file.
+     * @param delimiter - The delimiter used by the data file.
+     * @param skipFirstLine - Whether the first line should be skipped.
+     * @throws IOException Occurs if there is an error opening or parsing
+     * the data file.
+     */
+    public EcalWiringManager(String dataFile, int delimiter, boolean skipFirstLine) throws IOException {
+        // Create a file reader.
+        FileReader baseReader = new FileReader(dataFile);
+        BufferedReader reader = new BufferedReader(baseReader);
+        
+        // If the first line should be skipped, do that.
+        if(skipFirstLine) { reader.readLine(); }
+        
+        // Iterate over the lines of the data file.
+        String curLine = null;
+        readLoop:
+        while((curLine = reader.readLine()) != null) {
+            // If the current line is empty, skip it.
+            if(curLine.isEmpty()) { continue readLoop; }
+            
+            // Create a line scanner.
+            Scanner lineScan = new Scanner(curLine);
+            if(delimiter == COMMA_DELIMITED) { lineScan.useDelimiter(","); }
+            else if(delimiter == SPACE_DELIMITED) { lineScan.useDelimiter(" *"); }
+            else if(delimiter == TAB_DELIMITED) { lineScan.useDelimiter("\t"); }
+            
+            // Get the crystal data values.
+            int ix = lineScan.nextInt();
+            int iy = lineScan.nextInt();
+            int apd = lineScan.nextInt();
+            String preamp = lineScan.next();
+            int ledChan = lineScan.nextInt();
+            double ledDriver = lineScan.nextDouble();
+            int fadcSlot = lineScan.nextInt();
+            int fadcChan = lineScan.nextInt();
+            int splitter = lineScan.nextInt();
+            int hvGroup = lineScan.nextInt();
+            int jout = lineScan.nextInt();
+            String mb = lineScan.next();
+            int channel = lineScan.nextInt();
+            int gain = lineScan.nextInt();
+            lineScan.close();
+            
+            // Create a crystal data set in which to store the data.
+            CrystalDataSet cds = new CrystalDataSet(ix, iy, apd, preamp, ledChan, ledDriver, fadcSlot,
+                    fadcChan, splitter, hvGroup, jout, mb, channel, gain);
+            
+            // Map the crystal index to the crystal data set.
+            crystalMap.put(cds.getCrystalIndex(), cds);
+        }
+        
+        // Close the readers.
+        reader.close();
+        baseReader.close();
+    }
+    
+    /**
+     * Gets the set of calorimeter hardware data associated with the
+     * crystal at the given index.
+     * @param crystalIndex - The index of the crystal for which to obtain
+     * the hardware information. This should be in LCSim coordinates.
+     * @return Returns the hardware information for the crystal as a
+     * <code>CrystalDataSet</code> object or returns <code>null</code>
+     * if the crystal index is invalid.
+     */
+    public CrystalDataSet getCrystalData(Point crystalIndex) { return crystalMap.get(crystalIndex); }
+}