//===================================================================== // 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) {} }