//=====================================================================
// File: CutoffSlider.java
// Class: CutoffSlider
// Package: AFLPgui
//
// Author: James J. Benham
// Date: August 11, 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.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import AFLPcore.DataList;
import AFLPcore.Cutoff;
import AFLPcore.CutoffFunction;
import AFLPcore.Lane;
import AFLPcore.Option;
// Use this as the default cutoff type.
import AFLPcore.LinearCutoff;
/**
* This class provides a way to manipulate a cutoff with the mouse.
* It should be told several important pieces of information, specifically
* the cutoff to display, the lanes to apply the cutoff too, the scale
* for coverting pixels to height, and the starting position of the cutoff.
* The cutoff will be displayed as a bar with arrows at positions
* corresponding to the levels of the cutoff. The cutoff can be changed
* by dragging the arrows or by clicking and having the arrows snap to
* the location. The class also provides a message that gives some info
* about the slider, normally what a cutoff was set to.
*
* @author James J. Benham
* @version 1.0.0
* @date August 11, 1998
*/
public class CutoffSlider extends Canvas implements MouseListener,
MouseMotionListener
{
private static int BAR_WIDTH = 6;
private static int BAR_H_INSET = 10;
private static int ARROW_WIDTH = 13;
private static int ARROW_HEIGHT = 9;
private static int ARROW_H_INSET = BAR_H_INSET;
private static final int MARK = 0;
private static final int BLANK = 1;
private Cutoff cutoff;
private DataList applyToLanes;
private String message;
private Polygon arrow;
private CutoffFunction currentFunction;
private double scale;
private double size;
private int type;
private int currentLevel;
/**
* Create a new cutoff slider. It will be initialized with a
* default cutoff. This should be changed using the
* setCutoff
method.
*/
public CutoffSlider()
{
cutoff = new Cutoff(0, 1);
applyToLanes = new DataList();
message = "";
scale = 0;
size = 0;
currentFunction = new LinearCutoff();
addMouseListener(this);
addMouseMotionListener(this);
arrow = createArrow();
}
/**
* Adds the specified CutoffFucntion
to all of the lanes
* known to this class. If a cutoff does not already exist at the
* position this slider applies to, then a new cutoff is added. Otherwise,
* the cutoff is replaced.
*
* @param ct the function to add.
*
* @see CutoffSlider#setSize
* @see CutoffSlider#setLanes
*/
public void addCutoffFunction(CutoffFunction ct)
{
Lane ln;
Cutoff ctff = null;
for(int i=0; i < applyToLanes.size(); i++)
{
ln = (Lane) applyToLanes.dataAt(i);
ctff = ln.cutoffUnder(size);
// see if we need to create a new cutoff function for this region.
if(ctff.getStartPos() < size)
{
ctff = (Cutoff) ctff.clone();
ctff.setStartPos(size);
ln.addCutoff(ctff);
}
ctff.addFunction(ct);
}
// set the cutoff that this class is using to the one in the first
// lane, since it might have changed.
cutoff = ((Lane) applyToLanes.dataAt(0)).cutoffUnder(size);
}
/**
* Sets the cutoff that this slider is based on. The slider will use
* this to determine the levels, etc.
*
* @param cutoff the cutoff to use
*/
public void setCutoff(Cutoff cutoff)
{
this.cutoff = cutoff;
}
/**
* This determines where the cutoff should be applied in the lanes.
* A cutoff applies from size
to the next cutoff.
* This class will use this value when it adds new cutoffs to lanes.
*
* @param size the location in bp, where this cutoff should first apply.
*/
public void setSize(double size)
{
this.size = size;
}
/**
* Gets a message describing the current state of the slider.
*
* @return info about the slider
*/
public synchronized String getMessage()
{
return message;
}
/**
* Sets the info message for the slider.
*
* @param s the message
*/
private synchronized void setMessage(String s)
{
message = s;
}
/**
* Draws a bar with arrows at the different cutoff levels.
* Called every time this objects needs to be redisplayed.
*/
public void paint(Graphics g)
{
// find the height;
int height = getSize().height;
// First, draw the bar.
g.setColor(Color.lightGray);
g.fillRect(BAR_H_INSET, 0, BAR_WIDTH, height);
// ============Draw an arrow for every cutoff position======
// make sure we have a cutoff, there might not be one, especially
// if no bin is selected.
if(cutoff != null)
{
g.setColor(Color.black);
int yPos;
int numLevels = cutoff.getNumLevels();
for(int i=0; i < numLevels; i++)
{
// find the position of the cutoff
yPos = getSize().height - 1 - (int) (scale* cutoff.getCutoff(size,
i));
// adjust for the size of the arrow
yPos -= ARROW_HEIGHT/2;
// move the arrow
arrow.translate(0, yPos);
g.fillPolygon(arrow);
// move it back
arrow.translate(0, -yPos);
}
}
}
/**
* Sets the lanes known to the cutoff. These are the lanes that any
* changes to the cutoff wil be applied to.
*
* @param lanes the lanes to have their cutoffs modified by the slider.
*/
public void setLanes(DataList lanes)
{
applyToLanes = lanes;
}
/**
* Sets the scale for the cutoff slider. The scale is used to
* convert between pixels and intensity.
*
* @param scale the intensity per pixel.
*/
public void setScale(double scale)
{
this.scale = scale;
}
/**
* Handles the mouse clicks. If the mouse is clicked over a blank
* (non-arrow) part of the bar, then one of the cutoff levels will be
* set to the point the mouse clicked on. First, a level above the point
* the mouse clicked will be searched for. If this is not found, then
* the method looks for a level below the click point.
*/
public void mouseClicked(MouseEvent e)
{
if(type == BLANK)
{
int level = 0;
// Find the level above the mouse and use that.
// If there isn't one above, try below
int yPos;
int numLevels = cutoff.getNumLevels();
for(int i=0; i < numLevels; i++)
{
// find the position of the cutoff
yPos = getSize().height - 1 - (int) (scale* cutoff.getCutoff(size,
i));
level = i;
// see if we are below the mouse event yet
if( yPos < e.getY())
break; // reached the point we want;
}
setFunction(e.getY(), level);
refresh();
}
}
/** Unused */
public void mousePressed(MouseEvent e)
{
if(type == MARK)
{
}
}
/** Unused */
public void mouseReleased(MouseEvent e)
{
}
/**
* Determines if the mouse is over an arrow or a blank spot.
*/
public void mouseMoved(MouseEvent e)
{
type = overType(e.getX(), e.getY());
}
/**
* If the mouse is over an arrow, it moves the associated function
* to the new location.
*/
public void mouseDragged(MouseEvent e)
{
if(type == MARK)
{
setFunction(e.getY(), currentLevel);
refresh();
}
}
/**
* Determines if the given point is in a blank area or in an arrow.
*
* @param x the x position
* @param y the y position
*/
public int overType(int x, int y)
{
int yPos;
currentLevel = -1;
if( x < BAR_H_INSET)
return BLANK;
// see if we are over a mark
int numLevels = cutoff.getNumLevels();
for(int i=0; i < numLevels; i++)
{
// find the position of the cutoff
yPos = getSize().height - 1 - (int) (scale* cutoff.getCutoff(size, i));
// see if the y coordinate is in the arrow range
if( (y >= (yPos - ARROW_HEIGHT/2)) &&
(y <= (yPos + ARROW_HEIGHT/2)))
{
currentLevel = i;
return MARK;
}
}
// otherwise, we must be over a blank;
return BLANK;
}
/**
* Adjusts the cutoff function at level
so that it's height
* matches the height represented by pos
. The function
* is adjusted for all of the lanes defined. If a cutoff function does
* not exist at the position of this slider, then a new cutoff is added.
* Otherwise, the existing cutoff is replaced.
*
* @param pos the position, in pixels, to move the cutoff function to
* @param level specifies the part of the cutoff to adjust.
*
* @see CutoffSlider#setLanes
* @see CutoffSlider#setSize
*/
public void setFunction(int pos, int level)
{
double newHeight = (getSize().height -pos - 1)/scale;
Lane ln;
Cutoff ctff = null;
for(int i = 0; i < applyToLanes.size(); i++)
{
ln = (Lane) applyToLanes.dataAt(i);
ctff = ln.cutoffUnder(size);
// see if we need to create a new cutoff function for this region.
if(ctff.getStartPos() < size)
{
ctff = (Cutoff) ctff.clone();
ctff.setStartPos(size);
ln.addCutoff(ctff);
}
CutoffFunction fn = ctff.getCutoffFunction(level);
Option[] opts = fn.getOptions();
opts[0].setValue(newHeight);
fn.setOptions(opts);
}
newHeight = Math.round(newHeight * 100)/100.0;
message = "Cutoff number " + (level + 1) + " set to " + newHeight;
// set the cutoff that this class is using to the one in the first
// lane, since it might have changed.
cutoff = ((Lane) applyToLanes.dataAt(0)).cutoffUnder(size);
refresh();
}
/**
* Creates a polygon that is in the shape of the arrow used to indicate
* different levels.
*
* @return the arrow
*/
public Polygon createArrow()
{
Polygon poly = new Polygon();
poly.addPoint(ARROW_H_INSET, 0);
poly.addPoint(ARROW_H_INSET + 2, 0);
poly.addPoint(ARROW_H_INSET + 2, 1);
poly.addPoint(ARROW_H_INSET, 1);
poly.addPoint(ARROW_H_INSET, 0);
poly.addPoint(ARROW_H_INSET + ARROW_WIDTH - 1, ARROW_HEIGHT/2);
poly.addPoint(ARROW_H_INSET + ARROW_WIDTH, ARROW_HEIGHT/2);
poly.addPoint(ARROW_H_INSET, ARROW_HEIGHT);
return poly;
}
/**
* Causes any changes in the underlying data to be shown on the screen.
*/
public void refresh()
{
repaint();
}
//================== unused methods from interfaces==================
/**Unused*/public void mouseEntered(MouseEvent e) {}
/**Unused*/public void mouseExited(MouseEvent e) {}
}