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