//=====================================================================
// File: GelView.java
// Class: GelView
// 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.Button;
import java.awt.Canvas;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Label;
import java.awt.Point;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;
import java.awt.image.MemoryImageSource;
import AFLPcore.Bin;
import AFLPcore.DataList;
import AFLPcore.Gel;
import AFLPcore.Lane;
import AFLPcore.NoDataError;
import AFLPcore.ProgOptions;
/**
* Displays the gel as an image and provides the methods used to allow
* user interaction with the gel, such as placing bins and zooming.
* It also lets the user adjust the size range displayed and the intensity
* of the image. The class will resize itself to contain the whole gel
* image, so the parent should accomadate these changes.
*
* @author James J. Benham
* @version 1.0.0
* @date August 11, 1998
*/
public class GelView extends Canvas implements ActionListener,
ItemListener,
KeyListener,
MouseListener,
MouseMotionListener
{
private static int GEL_HORZ_INSET = 30;
private static int GEL_VERT_INSET = 25;
private static int BIN_HEADER_WIDTH = 20;
private static int LANE_HEADER_HEIGHT = 15;
private static int LANE_MARK_VERT_INSET = 4;
private static int LANE_MARK_HORZ_INSET = 2;
private static int LANE_MARK_HEIGHT = 7;
private static int DEFAULT_LANE_WIDTH = 12;
private static int DEFAULT_BORDER_WIDTH = 4;
private static int DEFAULT_TOP_BORDER = 15;
private static int DEFAULT_MIN_SIZE = 0;
private static int DEFAULT_MAX_SIZE = 300;
private static int INITIAL_LENGTH = 500;
// Button Bar component parameters
private static int HORZ_SPACE = 5;
private static int ZOOM_WIDTH = 60;
private static int ZOOM_HEIGHT_ADJUST = -5;
private static int FIELD_WIDTH = 60;
private static int SIZEL_WIDTH = 30;
private static int TOL_WIDTH = 20;
private static int INTENSITYL_WIDTH = 55;
private static String REL_PATH = "";
private static String REFRESH_FILE = "refresh.gif";
private static String BIN_FILE = "bin.gif";
// 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;
// Constants for the state of the mouse
private final static int LANE_HEAD = 0;
private final static int BIN_HEAD = 1;
private final static int BIN = 2;
private final static int BIN_BORDER_TOP = 3;
private final static int BIN_BORDER_BOT = 4;
private final static int BLANK = 5;
private final static int OFF_GEL = 6;
// Double Buffering Variables
private Dimension offDimension;
private Image offImage;
private Graphics offGraphics;
// GUI Components
private ButtonBar gelBar;
private Bar infoBar;
private Label infoLabel;
private Choice zoomSelect;
private TextField minSizeField;
private TextField maxSizeField;
private TextField intensityField;
private ImgButton refreshButton;
private ImgButton binButton;
// Dialogs
private BinDialog binDialog;
private EntryDialog entryDialog;
private ErrorDialog errorDialog;
// Variables for the gel,
private Gel gel;
private int gelLength;
private int gelWidth;
private int[] pixel;
private Image gelImage;
private MemoryImageSource imgSource;
// The zoom level last selected. We don't want to recompute everything
// just to get to the same spot, so remember where we are
private double zoomFactor;
private double previousZoom;
private int zoomIndex;
// Mouse motion control variables
private double sizeLoc; // the size in bp at the current mouse position
private int laneLoc; // the lane at the current mouse position
private int overType; // the type of thing the mouse is over
private Bin currentBin;
private Bin selectedBin;
private int dragMode; // used to determine what to do when the mouse
// is dragged
private boolean dragged; // set if the mouse dragged between press/release
private Point dragStart;
private Bin oldBin;
// Different Cursors
private Cursor standardCursor;
private Cursor resizeCursor;
private Cursor moveCursor;
// Program variables
private Frame topWindow; // this is the main program window.
private boolean sizeSet;
/**
* Creates a new viewable object of the specified gel
*
* @param gel the gel to be displayed and manipulated
*/
public GelView(Gel gel, Frame topWindow)
{
this.gel = gel;
this.topWindow = topWindow;
gel.setLaneWidth(DEFAULT_LANE_WIDTH);
gel.setLaneBorder(DEFAULT_BORDER_WIDTH);
gel.setGelTopBorder(DEFAULT_TOP_BORDER);
gelLength = INITIAL_LENGTH;
gel.setIntensity(gel.getGlobalMaxIntensity());
// Create dialog box
binDialog = new BinDialog(topWindow, "Bin Dialog", true);
entryDialog = new EntryDialog(topWindow, "Entery Dialog", true);
errorDialog = new ErrorDialog(topWindow);
// construct the button bar, we only want to do this once.
createButtonBar();
// construct the info bar,
createInfoBar();
// get the mouse listeners
addMouseListener(this);
addMouseMotionListener(this);
// see if we can set the current bin
if( !(gel.getBins()).isEmpty())
currentBin = (Bin) (gel.getBins()).dataAt(0);
standardCursor = new Cursor(Cursor.DEFAULT_CURSOR);
resizeCursor = new Cursor(Cursor.N_RESIZE_CURSOR);
moveCursor = new Cursor(Cursor.MOVE_CURSOR);
// set the size based on lane 0, if it exists. Do this here because
// we have to wait for some of the GUI components to be created first.
setGelSizeToMax(0);
// rebuild the gel
rebuild();
}
public void syncDisplay()
{
// update the display
minSizeField.setText("" + Math.round(gel.getMinSize()*100)/100);
maxSizeField.setText("" + Math.round(gel.getMaxSize()*100)/100);
intensityField.setText("" + Math.round(gel.getGlobalMaxIntensity()/
gel.getIntensity()*100)/100.0);
}
public boolean isSizeSet()
{
return sizeSet;
}
public void setGelSizeToMax(int laneNum)
{
if(gel.getNumLanes() != 0)
{
Lane firstLane = gel.laneAt(laneNum);
gel.setMinSize(firstLane.getMinSize());
gel.setMaxSize(firstLane.getMaxSize());
sizeSet = true;
}
else
{
// make up some defaults, this will let the program work, but
// set sizeSet to false so we know to reassign.
gel.setMinSize(DEFAULT_MIN_SIZE);
gel.setMaxSize(DEFAULT_MAX_SIZE);
sizeSet = false;
}
// update the display
syncDisplay();
}
/**
* Manages the display area and is called automatically when needed.
* If the gel has been changed and needs to be redrawn, then
* refresh
should be called. It will determine what action
* to take. Paint will only display the image of the gel known to this
* class, which may have to be rebuilt. The paint method makes use of
* double buffering to speed the display.
*
* @see GelView#refresh
*/
public void paint(Graphics g)
{
// 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();
}
offGraphics.clearRect(0, 0, d.width, d.height);
if(gel.getNumLanes() != 0)
{
offGraphics.drawImage(gelImage, GEL_HORZ_INSET, GEL_VERT_INSET, this);
drawBins(offGraphics);
drawLaneHeaders(offGraphics);
// let the parent know about any changes so it can adjust if neccessary
//getParent().validate();
}
// Flip the backscreen buffer to the front
g.drawImage(offImage, 0, 0, this);
}
/**
* Draws all the bins in the gel onto the specified graphics object.
*
* @param g the graphics object to draw to
*/
public void drawBins(Graphics g)
{
DataList bins = gel.getBins();
int length = bins.size();
Bin currentBin;
double y_pos;
double binTopSize;
int range;
int height;
// Clear the bin header area
g.clearRect(0, 0, GEL_HORZ_INSET, getSize().height);
for(int i=0; i < length; i++)
{
currentBin = (Bin) bins.dataAt(i);
g.setColor(new Color(192, 192, 192));
height = (int) (2*currentBin.getRange()/gel.getSizeInc());
// the vertical position is given by, finding the length in pixels
// between the location and the maximum size, adjusting the starting
// point from the center to the edge with the range, and offsetting
// by the position of the top of the gel.
binTopSize = (gel.getMaxSize() - currentBin.getLocation() -
currentBin.getRange());
y_pos = binTopSize/gel.getSizeInc() + GEL_VERT_INSET;
// if we didn't hit it exactly, subtract one so that the bin
// will include things inside it only.
if(y_pos > (int) y_pos)
y_pos += 1;
// subtract 1 from the start pos and and 2 to the height to move
// the border outside of the bin
g.drawRect(GEL_HORZ_INSET, (int) y_pos - 1, gelWidth, height + 2);
// draw the bin headers
g.setColor(Color.lightGray);
g.fillRoundRect(GEL_HORZ_INSET - BIN_HEADER_WIDTH, (int) y_pos - 1,
BIN_HEADER_WIDTH, height + 2, 2, 2);
}
// Draw the selected bin
// Find the positions like above
if(selectedBin != null)
{
height = (int) (2*selectedBin.getRange()/gel.getSizeInc());
binTopSize = gel.getMaxSize() - selectedBin.getLocation() -
selectedBin.getRange();
y_pos = binTopSize/gel.getSizeInc() + GEL_VERT_INSET;
if(y_pos > (int) y_pos)
y_pos += 1;
g.setColor(Color.red);
g.fillRoundRect(GEL_HORZ_INSET - BIN_HEADER_WIDTH, (int) y_pos - 1,
BIN_HEADER_WIDTH, height + 2, 2, 2);
}
}
/**
* Draws the headers for the lanes, (the little grey tabs at the top
* of the gel).
*
* @param g the graphics object to draw on.
*/
public void drawLaneHeaders(Graphics g)
{
int x;
int y;
// move to the correct vertical postion
y = GEL_VERT_INSET - LANE_HEADER_HEIGHT;
// get the space between lanes as well as the width
int width = gel.getLaneWidth();
int spacing = width + gel.getLaneBorder();
//====================Draw the headers=================
//
// find out how many we need to draw
int numLanes = gel.getLanes().size();
g.setColor(Color.lightGray);
for(int i=1; i <= numLanes; i++)
{
// find the correct x positioin
x = GEL_HORZ_INSET + spacing*i - width;
g.fillRoundRect(x, y, width, LANE_HEADER_HEIGHT, 2, 2);
}
//====================Draw the selection marks===========
//
DataList lanes = gel.getLanes();
DataList selected = gel.getSelectedLanes();
g.setColor(Color.black);
// adjust the width
//width = 4;
width = width - 2*LANE_MARK_HORZ_INSET;
// adjust the height
y = y + LANE_MARK_VERT_INSET;
for(int i=1; i <= numLanes; i++)
{
// see if it should be included.
if((selected.contains(lanes.dataAt(i - 1))))
{
// find the correct x positioin
x = GEL_HORZ_INSET + spacing*i - width;
g.fillOval(x - LANE_MARK_HORZ_INSET, y, width, LANE_MARK_HEIGHT);
}
}
}
/**
* Called when the screen needs to be updated.
*/
public void update(Graphics g)
{
paint(g);
}
/**
* Gives the button bar that should be used with this object. The bar
* contains the controls for the gel.
*
* @return a bar with the controls for the gel, to be displayed by the
* parent class
*/
public ButtonBar getButtonBar()
{
return gelBar;
}
/**
* Lays out the components on the button bar, uses the constants declared
* in this class to control the layout.
*/
private void createButtonBar()
{
gelBar = new ButtonBar();
gelBar.setBounds(0, 0, 640, 32);
// Let the parent window handle the action event for the orignal
// button bar components
gelBar.sendActionEventsTo((ActionListener) topWindow);
int start_x = gelBar.getFreeHorzPos();
// ===========Add Choice box====================
zoomSelect = new Choice();
zoomSelect.add("50%");
zoomSelect.add("100%");
zoomSelect.add("150%");
zoomSelect.add("200%");
zoomSelect.add("400%");
zoomSelect.add("To Bin");
zoomSelect.add("Full Gel");
zoomSelect.add("Other...");
zoomSelect.select("100%");
// set the previous zoom level and the zoom index
zoomFactor = 1.0;
previousZoom = 1.0;
zoomIndex = 1;
zoomSelect.addItemListener(this);
gelBar.add(zoomSelect);
// The height does not seem to get set properly for some reason.
zoomSelect.setBounds(start_x + HORZ_SPACE, ButtonBar.VERT_INSET,
ZOOM_WIDTH,
ButtonBar.BUTTON_HEIGHT + ZOOM_HEIGHT_ADJUST);
// adjust the starting position
start_x += HORZ_SPACE + ZOOM_WIDTH;
// =============Add the size ranges======================
minSizeField = new TextField("" + gel.getMinSize());
maxSizeField = new TextField("" + gel.getMaxSize());
Label sizeL = new Label("Size:", Label.RIGHT);
Label toL = new Label("to", Label.CENTER);
gelBar.add(minSizeField);
gelBar.add(maxSizeField);
gelBar.add(sizeL);
gelBar.add(toL);
minSizeField.addKeyListener(this);
maxSizeField.addKeyListener(this);
sizeL.setBounds(start_x + HORZ_SPACE, ButtonBar.VERT_INSET,
SIZEL_WIDTH, ButtonBar.BUTTON_HEIGHT);
start_x += HORZ_SPACE + SIZEL_WIDTH;
minSizeField.setBounds(start_x, ButtonBar.VERT_INSET,
FIELD_WIDTH, ButtonBar.BUTTON_HEIGHT);
start_x += FIELD_WIDTH;
toL.setBounds(start_x, ButtonBar.VERT_INSET,
TOL_WIDTH, ButtonBar.BUTTON_HEIGHT);
start_x += TOL_WIDTH;
maxSizeField.setBounds(start_x, ButtonBar.VERT_INSET,
FIELD_WIDTH, ButtonBar.BUTTON_HEIGHT);
start_x += FIELD_WIDTH;
//===================Add the intensity controls===============
intensityField = new TextField("1.0");
Label intensityL = new Label("Intensity: ", Label.RIGHT);
gelBar.add(intensityL);
gelBar.add(intensityField);
intensityField.addKeyListener(this);
intensityL.setBounds(start_x + HORZ_SPACE, ButtonBar.VERT_INSET,
INTENSITYL_WIDTH, ButtonBar.BUTTON_HEIGHT);
start_x += INTENSITYL_WIDTH + HORZ_SPACE;
intensityField.setBounds(start_x, ButtonBar.VERT_INSET,
FIELD_WIDTH, ButtonBar.BUTTON_HEIGHT);
start_x += FIELD_WIDTH;
//=================Add button to read in new values===========
String imgPath = ProgOptions.homePath + REL_PATH + REFRESH_FILE;
refreshButton = new ImgButton(ButtonBar.retrieveImage(imgPath));
gelBar.add(refreshButton);
refreshButton.setBounds(start_x + HORZ_SPACE, ButtonBar.VERT_INSET,
ButtonBar.BUTTON_WIDTH, ButtonBar.BUTTON_HEIGHT);
refreshButton.addActionListener(this);
start_x += HORZ_SPACE + ButtonBar.BUTTON_WIDTH;
//================Add bin button=============================
binButton = new ImgButton(ButtonBar.retrieveImage(ProgOptions.homePath +
REL_PATH + BIN_FILE));
gelBar.add(binButton);
binButton.setBounds(start_x + HORZ_SPACE, ButtonBar.VERT_INSET,
ButtonBar.BUTTON_WIDTH, ButtonBar.BUTTON_HEIGHT);
binButton.addActionListener(this);
}
/**
* Returns a bar that displays information about the gel. 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 bar, no bottom border.
infoBar = new Bar(true, false);
infoBar.setLayout(null);
infoLabel = new Label("Testing program info display");
infoBar.add(infoLabel);
infoLabel.setBounds(LABEL_H_INSET, LABEL_V_INSET,
LABEL_WIDTH, LABEL_HEIGHT);
infoBar.setBounds(0, 0, 600, FragmentMap.BAR_HEIGHT);
}
/**
* Deal with action events. The two buttons are the bin dialog button,
* which when clicked displays the dialog, and the refreshButton, which
* reads in the sizes and intensity from the bar and then redisplays the
* gel.
*/
public void actionPerformed(ActionEvent e)
{
try{
if(e.getSource() == binButton)
{
binDialog.setBins(gel.getBins());
binDialog.setVisible(true);
}
else if(e.getSource() == refreshButton)
{
readDisplay();
// redisplay
refresh(true);
}
}
catch(Throwable error)
{
errorDialog.showError(error);
}
}
/**
* Handle events for the choice box, which adjusts the zoom.
*/
public void itemStateChanged(ItemEvent e)
{
try{
if(e.getSource() == zoomSelect)
{
// Select the zoom level and set the scale factor;
switch(zoomSelect.getSelectedIndex())
{
case 0: //50%
zoomFactor = 0.5;
zoomIndex = 0;
break;
case 1: //100%
zoomFactor = 1;
zoomIndex = 1;
break;
case 2: //150%
zoomFactor = 1.5;
zoomIndex = 2;
break;
case 3: // 200
zoomFactor = 2;
zoomIndex = 3;
break;
case 4: // 400%
zoomFactor = 4;
zoomIndex = 4;
break;
case 5: // To Bin
if(currentBin == null)
throw new NoDataError("No bin selected for zoom.");
gel.setMinSize(currentBin.getLocation() - currentBin.getRange());
gel.setMaxSize(currentBin.getLocation() + currentBin.getRange());
// update the display
minSizeField.setText("" + gel.getMinSize());
maxSizeField.setText("" + gel.getMaxSize());
// adjust the zoom to force the rebuilding of the image
zoomFactor = previousZoom;
previousZoom = -1;
break;
case 6: // Full
// set the size, based on lane 0
setGelSizeToMax(0);
// adjust the zoom to force the rebuilding of the image
zoomFactor = previousZoom;
previousZoom = -1;
break;
case 7: // Other
zoomFactor = entryDialog.getPercentage();
if(zoomFactor == -1)
{
// user cancelled entry
zoomFactor = previousZoom;
}
else
{
zoomSelect.add("" + Math.round(zoomFactor*100) + "%");
zoomIndex = 8;
}
break;
case 8: // Specific other value
// We don't need to change anything since we should already
// be at this level.
break;
}
// Set the selction to the correct magnification
zoomSelect.select(zoomIndex);
// remove the other entry if we're done with it.
if( (zoomSelect.getItemCount() > 8) && (zoomIndex < 8) )
zoomSelect.remove(8);
//Make sure that we really changed it
if(zoomFactor != previousZoom)
zoom(zoomFactor);
}
}
catch(Throwable error)
{
errorDialog.showError(error);
}
}
/**
* Rebuilds the gel image so that its size is increased by the
* specified amout. Both the lane width and lane border are
* calculated seperately, so rounding errors coul be a factor.
*
* @param zoomFactor the factor to multiply the length and width
* by to obtain the new image.
*
* @exception OutOfMemoryError these images can get really big,
* so zooming in too much may cause the program to run out
* of memory.
*/
private void zoom(double zoomFactor)
{
// adjust the values for length, lanewidth, and border width
gelLength = (int)(INITIAL_LENGTH * zoomFactor);
gel.setLaneWidth( (int) (DEFAULT_LANE_WIDTH * zoomFactor));
gel.setLaneBorder( (int) (DEFAULT_BORDER_WIDTH * zoomFactor));
// redisplay and rebuild the gel
refresh(true);
// remember where we are.
previousZoom = zoomFactor;
}
/**
* Reads in the values for the minimum size, maximum size, and
* intensity form the respective text fields on the button bar.
*
* @exception NumberFormatException if the entries are not valid
* numbers.
*/
private void readDisplay()
{
double minSize = 0;
double maxSize = 0;
double intensity = 0;
// read in the values
try{
minSize = (new Double(minSizeField.getText())).doubleValue();
maxSize = (new Double(maxSizeField.getText())).doubleValue();
intensity = (new Double(intensityField.getText())).doubleValue();
}
catch(NumberFormatException e)
{
throw new NumberFormatException("Size and intensity must be numbers.");
}
// set the sizes
gel.setMinSize(minSize);
gel.setMaxSize(maxSize);
// scale the intensity
gel.setIntensity(gel.getGlobalMaxIntensity()/intensity);
}
/**
* Handle the click of the mouse. Includes double clicks.
* When the mouse is clicked on the gel, a bin is added.
* If it is double-clicked on a lane header, that lane is selected.
*/
public void mouseClicked(MouseEvent e)
{
//==============Single Click actions=======================
if(e.getClickCount() == 1)
{
switch(overType)
{
case BIN:
break;
case BLANK:
selectedBin = new Bin(sizeLoc);
gel.addBin(selectedBin);
refresh(false);
break;
case BIN_BORDER_TOP:
case BIN_BORDER_BOT:
break;
case BIN_HEAD:
selectedBin = currentBin;
refresh(false);
break;
}
}
// ====================Double click===================
if(e.getClickCount() == 2)
{
switch(overType)
{
case LANE_HEAD:
// get the lane, and toggle it's selection state by
// adding or removing it from the selected list
gel.toggleSelectedLane((Lane) gel.laneAt(laneLoc - 1));
// invalidate the scoring on the current bin since it now
// has a different set of lanes
if(currentBin != null)
currentBin.setScore(false);
refresh(false);
break;
}
}
}
/**
* Don't care about this event.
*/
public void mouseEntered(MouseEvent e)
{
//Don't care about this
}
/**
* Don't care about this event.
*/
public void mouseExited(MouseEvent e)
{
// Don't care about this one either
}
/**
* Handle the event of the mouse button being pressed. Use this to
* set up for drags, however, be careful not to duplicate work done be
* the click handler. The type of action performed depends on where the
* mouse is pressed. Over a blank area will create a new bin, while pressing
* down on a bin border will prepare the program to move that border.
* The types of places that the mouse can be over are defined by constants
* and the mouseOverType
method. The type the mouse is
* currently over is stored in an instance variable and is updated whenever
* the mouse moves.
*
* @see GelView#mouseOverType
*/
public void mousePressed(MouseEvent e)
{
// tell drag what we're going to do
dragMode = overType;
// variable to say we haven't dragged yet, used to that
// click and press/release don't both do something on one click
dragged = false;
dragStart = e.getPoint();
switch(dragMode)
{
case BIN_BORDER_TOP:
case BIN_BORDER_BOT:
case BIN:
oldBin = new Bin(currentBin.getLocation(),
currentBin.getRange());
break;
case BLANK:
// set up a new bin and then just pretend we're dragging the
// bottom of it.
oldBin = new Bin(sizeLoc, 0);
currentBin = new Bin(sizeLoc, 0);
gel.addBin(currentBin);
break;
}
}
/**
* Handle the event of the mouse button being released. This will finalize
* any dragging only if the mouse has been dragged between when this
* is called and the press event has been called. This is since
* press + release w/o drag = click. The action performed depends on where
* the mouse started.
*
* @see GelView#mouseOverType
*/
public void mouseReleased(MouseEvent e)
{
if(dragged) // otherwise, let the click event take it
{
switch(dragMode)
{
case BIN_BORDER_TOP:
case BIN_BORDER_BOT:
// remove the bin so it can be sorted if need be
gel.removeBin(currentBin);
// see if the range is still positive, if it is, add it back in
// so the list remains sorted, if not, discard it.
if(currentBin.getRange() > 0)
{
gel.addBin(currentBin);
selectedBin = currentBin;
refresh(false);
}
break;
case BIN:
// remove and then add to keep sorted
gel.removeBin(currentBin);
gel.addBin(currentBin);
selectedBin = currentBin;
refresh(false);
break;
}
}
else
{
switch(overType)
{
case BLANK:
// Clean up the new bin created by dragging in a blank and let
// click make the bin.
gel.removeBin(currentBin);
break;
}
}
}
/**
* Handle the dragging of the mouse. Mostly used to adjust bin borders
* or to move bins around.
*/
public void mouseDragged(MouseEvent e)
{
dragged = true;
double adjust;
double round;
switch(dragMode)
{
case BIN_BORDER_TOP:
// ==================adjust the border=====================
// Find the difference in the two points and convert to bp
adjust = (dragStart.y - e.getY()) * gel.getSizeInc();
// shift the center, and then increase the range
round = Math.round((oldBin.getLocation() + adjust/2)*100)/100.0;
currentBin.setLocation(round);
round = Math.round((oldBin.getRange() + adjust/2)*100)/100.0;
currentBin.setRange(round);
refresh(false);
infoLabel.setText(" Size: " + sizeLoc +
" Bin: " + currentBin.getLocation() +
" +/- " + currentBin.getRange());
break;
case BLANK: //fallsthrough
case BIN_BORDER_BOT:
// ==================adjust the border=====================
// Find the difference in the two points and convert to bp
adjust = (e.getY() - dragStart.y) * gel.getSizeInc();
// moving down
round = Math.round((oldBin.getLocation() - adjust/2)*100)/100.0;
currentBin.setLocation(round);
round = Math.round((oldBin.getRange() + adjust/2)*100)/100.0;
currentBin.setRange(round);
refresh(false);
infoLabel.setText(" Size: " + sizeLoc +
" Bin: " + currentBin.getLocation() +
" +/- " + currentBin.getRange());
break;
case BIN:
adjust = Math.round((dragStart.y - e.getY()) *
gel.getSizeInc() * 100)/100.0;
// move the bin
currentBin.setLocation(oldBin.getLocation() + adjust);
refresh(false);
infoLabel.setText(" Size: " + sizeLoc +
" Bin: " + currentBin.getLocation() +
" +/- " + currentBin.getRange());
break;
}
}
/**
* Handle moving the mouse. It displays information about whatever the
* mouse is pointing to in the gel. This is done by calling the
* mouseOverType
method, and then displaying the appropriate
* information.
*
* @see GelView#mouseOverType
*/
public void mouseMoved(MouseEvent e)
{
// Find the position in bp, round to two places
sizeLoc = Math.round(findSizeAt(e.getY())*100)/100.0;
// Figure out which lane we're in
laneLoc = findLaneAt(e.getX());
// set the type we're over
overType = mouseOverType(e.getX(), e.getY());
// update the status
switch(overType)
{
case BIN:
setCursor(moveCursor);
infoLabel.setText(" Size: " + sizeLoc +
" Lane: " + laneLoc +
" Bin: " + currentBin.getLocation() +
" +/- " + currentBin.getRange());
break;
case BLANK:
setCursor(standardCursor);
infoLabel.setText(" Size: " + sizeLoc +
" Lane: " + laneLoc);
break;
case BIN_BORDER_TOP:
case BIN_BORDER_BOT:
setCursor(resizeCursor);
infoLabel.setText(" Size: " + sizeLoc +
" Drag mouse to resize bin.");
break;
case BIN_HEAD:
setCursor(standardCursor);
infoLabel.setText("Bin: " + currentBin.getLocation() +
" +/- " + currentBin.getRange());
break;
case LANE_HEAD:
setCursor(standardCursor);
Lane lane = (Lane) gel.getLanes().dataAt(laneLoc - 1);
infoLabel.setText("Lane " + laneLoc + ": " +
lane.getName() + " from lane " +
lane.getLaneNumber() + " in " +
lane.getGelName());
break;
case OFF_GEL:
setCursor(standardCursor);
infoLabel.setText("Gel has " + gel.getLanes().size() +
" lanes with " + gel.getBins().size() +
" bins defined.");
break;
}
}
/**
* Determines what the mouse is pointing to. This is done be looking
* first to see if the mouse is even on the gel. Then the position is used
* to look for a bin at that position. If none is found, the method looks
* for a bin up one pixel from the current position of the mouse. If it
* finds one, then the mouse must be on the border of the bin. If not, the
* same search is performed, except one pixel lower. This also allows the
* method to tell which border it is on, which is important to know when
* adjusting the borders and figuring out which direction should increase
* the bin size and which way should decrease it. If no bin is found,
* and the mouse is on the gel, then the mouse is assumed to be on a blank
* portion of the gel.
*
* @param x the x coordinate of the mouse, relative to this component.
* @param y the y coordinate of the mouse, relative to this component.
*
* @return the type of object the mouse is over, defined by static variables
* in this class.
*/
public int mouseOverType(int x, int y)
{
// Do simple checks of positoin for other stuff
if( (x < (GEL_HORZ_INSET - BIN_HEADER_WIDTH)) ||
(x >= (GEL_HORZ_INSET + gelWidth)))
return OFF_GEL;
if( (y < (GEL_VERT_INSET - LANE_HEADER_HEIGHT)) ||
(y >= (GEL_VERT_INSET + gelLength)))
return OFF_GEL;
// Look and see if we're on the lane headings.
if( (y < GEL_VERT_INSET) && (y > (GEL_VERT_INSET - LANE_HEADER_HEIGHT)))
if( x < GEL_HORZ_INSET)
return OFF_GEL;
else
{
// skip the lane border places.
int remainder = (x - GEL_HORZ_INSET) %
(gel.getLaneWidth() + gel.getLaneBorder());
if(remainder < gel.getLaneBorder())
return OFF_GEL;
else
return LANE_HEAD;
}
// See if we can find a bin at that size
Bin tempBin = gel.binAtSize(sizeLoc);
if(tempBin != null)
{
currentBin = tempBin;
if(x >= GEL_HORZ_INSET)
return BIN;
else
return BIN_HEAD;
}
// Look up and see if we land in a bin. If so, we're on it's bin
// border.
tempBin = gel.binAtSize(findSizeAt(y - 1));
if(tempBin != null)
{
currentBin = tempBin;
if(x >= GEL_HORZ_INSET)
return BIN_BORDER_BOT;
else
return BIN_HEAD;
}
// Look down and see if we land in a bin. If so, we're on it's bin
// border.
tempBin = gel.binAtSize(findSizeAt(y + 1));
if(tempBin != null)
{
currentBin = tempBin;
if(x >= GEL_HORZ_INSET)
return BIN_BORDER_TOP;
else
return BIN_HEAD;
}
// Do another boundry check, but since we already have the bin head
// if it exists, we can make that check closer.
if( (x < GEL_HORZ_INSET) ||
(x >= (GEL_HORZ_INSET + gelWidth)))
return OFF_GEL;
return BLANK;
}
/**
* Watches for the enter key to be pushed in a text field, and then
* refreshes the gel when it is.
*/
public void keyReleased(KeyEvent e)
{
// Assume that a release of the enter key constitutes the typing
// of the enter key. It is very unlikely that it got pushed down
// somewhere else and released here.
// There are only three listeners, so enter the values if the enter
// key is pushed
if (e.getKeyCode() == KeyEvent.VK_ENTER)
{
readDisplay();
refresh(true);
}
}
/**
* Calculates the size in bp that corresponds to the screen location
* given.
*
* @param pos the verticle position to calculate the size in bp for
*
* @return the size
*/
public double findSizeAt(int pos)
{
return (gel.getMaxSize() - (pos - GEL_VERT_INSET)*gel.getSizeInc());
}
/**
* Gives the position in the gel of the lane at the given size. The
* lanes are numbered 1 through n, where 1 is the rightmost lane.
*/
public int findLaneAt(int pos)
{
return 1+(pos - GEL_HORZ_INSET)/(gel.getLaneWidth() + gel.getLaneBorder());
}
/**
* Called when the image on screen should be redrawn. For example, when
* a bin has been added. This will not read in a new image if the
* image has been changed.
*
* @param rebuildGel if this is true, then the gel image will be
* recreated from the data in the gel, using the current
* gel settings.
*
* @see Gel
*/
public void refresh(boolean rebuildGel)
{
if(rebuildGel)
{
rebuild();
// Let the parent know about the changes.
getParent().validate();
}
repaint();
}
/**
* Recreates the gel image from the data. This should be used if the
* size or intensity parameters of the gel have changed. Or if a larger
* image is desired, etc.
*/
public void rebuild()
{
infoLabel.setText("Updating gel image...");
// remake the pixel array
pixel = gel.getPixels(gelLength);
// get the width
gelWidth = pixel.length/gelLength;
// Make the new image and store it.
imgSource = new MemoryImageSource(gelWidth, gelLength, pixel,
0, gelWidth);
gelImage = createImage(imgSource);
// set the size
setSize(gelWidth + 2*GEL_HORZ_INSET, gelLength +2*GEL_VERT_INSET);
infoLabel.setText("Gel image update complete.");
}
/**
* Gives the bin that is currently selected on the gel. This bin should
* be the one that appears highlighted in a different color.
*
* @return the bin
*/
public Bin getCurrentBin()
{
return selectedBin;
}
/**
* Sets the selection to the specified bin. The bin must be valid,
* but no check will be performed. As long as the bin is retrieved
* from the list, there shouldn't be any problem. This is used
* to keep the bin selection synchronized across bins.
*
* @param bin the bin to highlight as selected.
*/
public void setCurrentBin(Bin bin)
{
selectedBin = bin;
refresh(false);
}
/**
* Set the length of the gel image to the specified value. The image will
* be recreated when this is called.
*
* @param length the length in pixels.
*/
public void setGelLength(int length)
{
gelLength = length;
rebuild();
}
/**
* Cleans up before this class is disposed off.
*
* @exception Throwable something bad happened.
*/
public void finalize() throws Throwable
{
gelImage.flush();
super.finalize();
}
// ==================Unused methods required by interfaces=================
/**Unused*/public void keyPressed(KeyEvent e) {}
/**Unused*/public void keyTyped(KeyEvent e) {}
}