//=====================================================================
// File: TraceView.java
// Class: TraceView
// Package: AFLPgui
//
// Author: James J. Benham
// Date: August 12, 1998
// Contact: james_benham@hmc.edu
//
// Genographer v1.0 - Computer assisted scoring of gels.
// Copyright (C) 1998 Montana State University
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; version 2
// of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// The GNU General Public License is distributed in the file GPL
//=====================================================================
package AFLPgui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Label;
import java.awt.Panel;
import java.awt.PrintGraphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import AFLPcore.Cutoff;
import AFLPcore.CutoffFunction;
import AFLPcore.DataList;
import AFLPcore.Lane;
import AFLPcore.LinearCutoff;
import AFLPcore.ProgOptions;
/**
* Displays the trace of a lane. The lane can be set using the
* init
method. The trace's size can be adjusted and
* the name of the lane will be displayed on the trace. The
* paint
method is capable of drawing to a page for the
* printer, but this class should be sized appropriately first. When
* drawing to the screen, the trace display is double-buffered.
*
* @author James J. Benham
* @version 1.0.0
* @date August 12, 1998
*/
public class TraceView extends Panel implements ActionListener
{
// Info Bar component parameters
private static int LABEL_H_INSET = 5;
private static int LABEL_V_INSET = 3;
private static int LABEL_WIDTH = 600;
private static int LABEL_HEIGHT = 18;
// Error message location
private static int LABEL_HORZ = 20;
private static int LABEL_VERT = 20;
// Button bar constants
private static int HORZ_SPACE = 5;
// Files for images
private static String REL_PATH = "";
private static String INVERT_FILE = "invert.gif";
private static String PREVIOUS_FILE = "l_arrow.gif";
private static String NEXT_FILE = "r_arrow.gif";
// Double buffering stuff
private Dimension offDimension;
private Image offImage;
private Graphics offGraphics;
// Bar stuff
private ButtonBar buttonBar;
private Bar infoBar;
private Label infoLabel;
// Button Bar components
private ImgButton previousButton;
private ImgButton nextButton;
private Frame topWindow;
private DataList lanes;
private Lane lane;
private int h_inset;
private int v_inset;
private int width;
private int height;
private double maxIntensity;
private double hScale;
private double vScale;
private double minSize;
private double maxSize;
/**
* Create a new TraceView with the specified parameters.
*
* @param parentWindow used as an owner for various dialog boxes.
*/
public TraceView(Frame parentWindow)
{
topWindow = parentWindow;
lane = null;
h_inset = 10;
v_inset = 60;
width = 500;
height = 240;
setLayout(null);
setBounds(0, 0, h_inset + width, v_inset + height);
createButtonBar();
createInfoBar();
}
/**
* Sets the trace to the specified lane and sets the scales.
*
* @param ln the lane whose trace should be displayed
*/
public void init(Lane ln, DataList lanes)
{
this.lanes = lanes;
lane = ln;
if(lane != null)
{
//==============find the scales===================
minSize = ln.getMinSize();
maxSize = ln.getMaxSize();
maxIntensity = ln.getMaxHeight(minSize, maxSize);
hScale = (maxSize - minSize)/(width - 2); // -2 for left border
vScale = (height - 1)/maxIntensity;
}
}
/**
* Gives the width of the trace.
*
* @return the width, excluding borders
*/
public int getWidth()
{
return width;
}
/**
* Sets the width of the trace, including borders, to the specified
* value.
*
* @param width the new width for this object.
*/
public void setWidth(int width)
{
this.width = width - h_inset;
setBounds(0, 0, width, v_inset + height);
}
/**
* Gives the height of the trace, excluding borders.
*
* @return the height of the trace area.
*/
public int getHeight()
{
return height;
}
/**
* Sets the height of the trace, including borders to the specified
* value.
*
* @param height the new height for this
*/
public void setHeight(int height)
{
this.height = height - v_inset;
setBounds(0, 0, h_inset + width, height);
}
/**
* Draws the trace to the specified graphics. If the graphics is
* a PrintGraphics
then specail care is taken, such
* as disabeling double-buffering.
*/
public void paint(Graphics g)
{
//==============find the scales===================
// printing could change it.
hScale = (maxSize - minSize)/(width - 2); // -2 for left border
vScale = (height - 1)/maxIntensity;
if(g instanceof PrintGraphics)
{
// we don't need to doulbe buffer the printer :)
offGraphics = g;
}
else
{
//=======================Off screen buffer setup==========
// make sure we have the offscreen buffer and that it is the right
// size.
Dimension d = getSize();
if ( (offGraphics == null)
|| (d.width != offDimension.width)
|| (d.height != offDimension.height) )
{
offDimension = d;
offImage = createImage(d.width, d.height);
offGraphics = offImage.getGraphics();
}
}
// make sure we have a lane. If not, we don't have anything to display.
// Draw to the graphics screen and then bail. don't use the double
// buffer.
if(lane == null)
{
g.clearRect(0, 0, h_inset + width, v_inset + height);
g.drawString("No Lanes! Lane trace not available.",
LABEL_HORZ, LABEL_VERT);
return; // bail!
}
// ============== Normal drawing ===================
offGraphics.clearRect(0, 0, h_inset + width, v_inset + height);
// draw the axis
int lowY = v_inset + height - 1;
offGraphics.drawLine(h_inset, v_inset, h_inset, lowY);
offGraphics.drawLine(h_inset, lowY, h_inset + width, lowY);
// draw the header
offGraphics.setColor(Color.black);
offGraphics.drawString("" + lane.getLaneNumber() + " " + lane.getName(),
h_inset, v_inset - 1);
//================Draw the trace=====================
// find the color and set it
switch(lane.getColor())
{
case Lane.RED:
offGraphics.setColor(Color.red);
break;
case Lane.GREEN:
offGraphics.setColor(Color.green);
break;
case Lane.BLUE:
offGraphics.setColor(Color.blue);
break;
case Lane.YELLOW:
offGraphics.setColor(Color.yellow);
break;
}
// draw the actual trace
double size = minSize;
int previousIntensity = lowY - (int)(vScale*lane.getHeightAt(minSize));
int intensity;
for(int i=1; i < (width - 1); i++)
{
// find the height, -1 to place it above the border.
intensity = lowY - (int) (vScale * lane.getHeightAt(size));
offGraphics.drawLine(h_inset + i, previousIntensity,
h_inset + i + 1, intensity);
previousIntensity = intensity;
size += hScale;
}
//================Draw the cutoff functions============
offGraphics.setColor(Color.lightGray);
DataList cutoffs = lane.getCutoffs();
if(!cutoffs.isEmpty())
{
int startPoint;
int endPoint;
Cutoff ct1 = (Cutoff) cutoffs.dataAt(0);
Cutoff ct2;
for(int i=1; i < (cutoffs.size() - 1); i++)
{
ct2 = (Cutoff) cutoffs.dataAt(i);
endPoint = h_inset + (int)(hScale*ct2.getStartPos()) + 1;
size = ct1.getStartPos();
startPoint = h_inset + (int)(hScale*size) + 1;
drawCutoff(offGraphics, ct1, size, startPoint, endPoint, lowY);
// set up the next cutoff pair.
ct1 = ct2;
}
// draw the final one.
endPoint = h_inset + (int)(hScale*maxSize);
size = ct1.getStartPos();
startPoint = h_inset + (int)(hScale*size) + 1;
drawCutoff(offGraphics, ct1, size, startPoint, endPoint, lowY);
}
if(!(g instanceof PrintGraphics))
g.drawImage(offImage, 0, 0, this);
}
/**
* Draws a cutoff using the specified parameters. If one of the levels
* is a line, a line is simply drawn. Otherwise, the method will
* play connect the dots at each size to produce the cutoff function.
*
* @param g the graphics object to draw on
* @param ct the cutoff to draw
* @param size the starting size of the cutoff
* @param startPoint the first horizontal position, in pixels
* @param endPoint the value to stop drawing at, in pixels
* @param lowY the y-value of the baseline.
*/
private void drawCutoff(Graphics g, Cutoff ct, double size,
int startPoint, int endPoint, int lowY)
{
int intensity;
int previousIntensity;
int numLevels = ct.getNumLevels();
for(int i=0; i < numLevels; i++)
{
if(ct.getCutoffFunction(i) instanceof LinearCutoff)
{
intensity = lowY - (int) (vScale * ct.getCutoff(size, i));
g.drawLine(startPoint, intensity, endPoint, intensity);
}
else
{
previousIntensity = (lowY - (int)(vScale*ct.getCutoff(size, i)));
for(int j=startPoint; j < (endPoint); j++)
{
// find the height,
intensity = lowY - (int) (vScale * ct.getCutoff(size, i));
g.drawLine(h_inset + j, previousIntensity,
h_inset + j + 1, intensity);
previousIntensity = intensity;
size += hScale;
}
}
}
}
/**
* Called by JVM to refresh screen, but it simply calls paint.
*/
public void update(Graphics g)
{
paint(g);
}
/**
* Gives the ButtonBar associated with the trace.
*
* @return the button bar.
*/
public ButtonBar getButtonBar()
{
return buttonBar;
}
/**
* Lays out the components on the button bar, uses the constants declared
* in this class to control the layout.
*/
private void createButtonBar()
{
buttonBar = new ButtonBar();
buttonBar.setBounds(0, 0, 640, 32);
// Let the parent window handle the action event for the orignal
// button bar components
buttonBar.sendActionEventsTo( (ActionListener) topWindow);
//===========Create the previous button================
previousButton = new ImgButton(ButtonBar.retrieveImage(ProgOptions.homePath
+ REL_PATH +
PREVIOUS_FILE));
buttonBar.add(previousButton);
int startX = buttonBar.getFreeHorzPos();
previousButton.setBounds(startX + HORZ_SPACE, ButtonBar.VERT_INSET,
ButtonBar.BUTTON_WIDTH, ButtonBar.BUTTON_HEIGHT);
startX += ButtonBar.BUTTON_WIDTH + HORZ_SPACE;
previousButton.addActionListener(this);
//===========Create the next button====================
nextButton = new ImgButton(ButtonBar.retrieveImage(ProgOptions.homePath +
REL_PATH +
NEXT_FILE));
buttonBar.add(nextButton);
nextButton.setBounds(startX, ButtonBar.VERT_INSET,
ButtonBar.BUTTON_WIDTH, ButtonBar.BUTTON_HEIGHT);
startX += ButtonBar.BUTTON_WIDTH;
nextButton.addActionListener(this);
}
/**
* Returns a bar that displays information about the trace. This object
* should be displayed somewhere by the container of this object.
*
* @return the bar as described above
*/
public Bar getInfoBar()
{
return infoBar;
}
/**
* Lays out the components for the info bar. Layout is controlled with
* constants in this class.
*/
private void createInfoBar()
{
// create the label
infoLabel = new Label("Graph info...");
// create the bar, no bottom border.
infoBar = new Bar(true, false);
infoBar.setLayout(null);
infoBar.add(infoLabel);
infoLabel.setBounds(LABEL_H_INSET, LABEL_V_INSET,
LABEL_WIDTH, LABEL_HEIGHT);
infoBar.setBounds(0, 0, 600, FragmentMap.BAR_HEIGHT);
}
/**
* Update the display so that it matches the data.
*/
public void refresh()
{
repaint();
}
/**
* Handles the buttons in the button bar.
*/
public void actionPerformed(ActionEvent e)
{
if(e.getSource() == previousButton)
{
// find the location of the lane in the list
int location = -1;
for(int i=0; i < lanes.size(); i++)
if(lane == (Lane) lanes.dataAt(i))
{
location = i;
break;
}
// now move to the previous one in the list
if(location <= 0)
infoLabel.setText("Already on the first lane.");
else
{
lane = (Lane) lanes.dataAt(location - 1);
infoLabel.setText("Showing lane number " + (location));
init(lane, lanes);
refresh();
}
}
else if(e.getSource() == nextButton)
{
// find the location of the lane in the list
int location = -1;
for(int i=0; i < lanes.size(); i++)
if(lane == (Lane) lanes.dataAt(i))
{
location = i;
break;
}
// now move to the previous one in the list
if(location == (lanes.size() - 1))
infoLabel.setText("Already on the last lane.");
else
{
lane = (Lane) lanes.dataAt(location + 1);
infoLabel.setText("Showing lane number " + (location + 2));
init(lane, lanes);
refresh();
}
}
}
}