Skip to content
Snippets Groups Projects
JamView.java 30.5 KiB
Newer Older
tjc's avatar
tjc committed
/* JamView
 *
 * created: 2009
 *
 * This file is part of Artemis
 *
 * Copyright(C) 2009  Genome Research Limited
 *
 * 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; either version 2
 * of the License, or(at your option) any later version.
 *
 * 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.
 *
 */
tjc's avatar
tjc committed
package uk.ac.sanger.artemis.components.alignment;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
tjc's avatar
tjc committed
import java.awt.Cursor;
tjc's avatar
tjc committed
import java.awt.Dimension;
tjc's avatar
tjc committed
import java.awt.FontMetrics;
tjc's avatar
tjc committed
import java.awt.Graphics;
import java.awt.Graphics2D;
tjc's avatar
tjc committed
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
tjc's avatar
tjc committed
import java.awt.Point;
tjc's avatar
tjc committed
import java.awt.Rectangle;
tjc's avatar
tjc committed
import java.awt.Stroke;
tjc's avatar
tjc committed
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
tjc's avatar
tjc committed
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
tjc's avatar
tjc committed
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
tjc's avatar
tjc committed
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
tjc's avatar
tjc committed
import java.io.BufferedReader;
tjc's avatar
tjc committed
import java.io.File;
tjc's avatar
tjc committed
import java.io.IOException;
import java.io.StringReader;
import java.util.Collections;
import java.util.Comparator;
tjc's avatar
tjc committed
import java.util.Enumeration;
tjc's avatar
tjc committed
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

tjc's avatar
tjc committed
import javax.swing.JButton;
tjc's avatar
tjc committed
import javax.swing.JCheckBox;
tjc's avatar
tjc committed
import javax.swing.JComboBox;
import javax.swing.JFrame;
tjc's avatar
tjc committed
import javax.swing.JOptionPane;
tjc's avatar
tjc committed
import javax.swing.JPanel;
import javax.swing.JScrollPane;
tjc's avatar
tjc committed
import javax.swing.JTextField;
tjc's avatar
tjc committed
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
tjc's avatar
tjc committed

tjc's avatar
tjc committed
import net.sf.samtools.SAMFileHeader;
import net.sf.samtools.SAMFileReader;
import net.sf.samtools.SAMRecord;
import net.sf.samtools.SAMSequenceRecord;
import net.sf.samtools.SAMFileReader.ValidationStringency;
import net.sf.samtools.util.CloseableIterator;

tjc's avatar
tjc committed
import uk.ac.sanger.artemis.Entry;
import uk.ac.sanger.artemis.EntryGroup;
import uk.ac.sanger.artemis.Options;
import uk.ac.sanger.artemis.SimpleEntryGroup;
import uk.ac.sanger.artemis.components.EntryFileDialog;
import uk.ac.sanger.artemis.components.MessageDialog;
import uk.ac.sanger.artemis.io.EntryInformation;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.io.Range;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.sequence.Bases;
import uk.ac.sanger.artemis.sequence.NoSequenceException;
import uk.ac.sanger.artemis.util.Document;
import uk.ac.sanger.artemis.util.DocumentFactory;
import uk.ac.sanger.artemis.util.OutOfRangeException;

public class JamView extends JPanel
tjc's avatar
tjc committed
                     implements Scrollable
tjc's avatar
tjc committed
{
  private static final long serialVersionUID = 1L;
tjc's avatar
tjc committed
  private List<SAMRecord> readsInView;
tjc's avatar
tjc committed
  private Hashtable<String, Integer> seqLengths = new Hashtable<String, Integer>();
  private Vector<String> seqNames = new Vector<String>();
  private String bam;
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  private Bases bases;
tjc's avatar
tjc committed
  private JScrollPane jspView;
  private JComboBox combo;
tjc's avatar
tjc committed
  private JCheckBox checkBoxSingle;
tjc's avatar
tjc committed
  private JCheckBox checkBoxSNPs;
  
tjc's avatar
tjc committed
  private Ruler ruler = new Ruler();
tjc's avatar
tjc committed
  private int nbasesInView;
tjc's avatar
tjc committed
  private int laststart;
  private int lastend;
  private int maxUnitIncrement = 4;
tjc's avatar
tjc committed
  private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
  private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
tjc's avatar
tjc committed
  private int ALIGNMENT_PIX_PER_BASE;
tjc's avatar
tjc committed
  
  // use the picard library otherwise call samtools
tjc's avatar
tjc committed
  public static boolean PICARD = true;
tjc's avatar
tjc committed

tjc's avatar
tjc committed
 
  public JamView(String bam, 
                 String reference,
                 int nbasesInView)
  {
    super();
    setBackground(Color.white);
    this.bam = bam;
    this.nbasesInView = nbasesInView;
    
    if(reference != null)
    {
tjc's avatar
tjc committed
      EntryGroup entryGroup = new SimpleEntryGroup();
tjc's avatar
tjc committed
      try
      {
        getEntry(reference,entryGroup);
      }
      catch (NoSequenceException e)
      {
        e.printStackTrace();
      }
    }
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
    if(PICARD)
      readHeaderPicard();
    else
      readHeader();
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
    // set font size
    setFont(getFont().deriveFont(12.f));
tjc's avatar
tjc committed
    final javax.swing.plaf.FontUIResource font_ui_resource =
tjc's avatar
tjc committed
      new javax.swing.plaf.FontUIResource(getFont());
   //  Options.getOptions().getFontUIResource();
tjc's avatar
tjc committed

tjc's avatar
tjc committed
    Enumeration<Object> keys = UIManager.getDefaults().keys();
tjc's avatar
tjc committed
    while(keys.hasMoreElements()) 
    {
      Object key = keys.nextElement();
      Object value = UIManager.get(key);
      if(value instanceof javax.swing.plaf.FontUIResource) 
        UIManager.put(key, font_ui_resource);
    }
    FontMetrics fm  = getFontMetrics(getFont());
tjc's avatar
tjc committed
    ALIGNMENT_PIX_PER_BASE  = (int) (fm.stringWidth("A")*1.1);
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  /**
   * Read the BAM/SAM header
   */
tjc's avatar
tjc committed
  private void readHeader()
  {
tjc's avatar
tjc committed
    String samtoolCmd = "";
    if(System.getProperty("samtoolDir") != null)
      samtoolCmd = System.getProperty("samtoolDir");
    String cmd[] = { samtoolCmd+File.separator+"samtools",  
tjc's avatar
tjc committed
			     "view", "-H", bam };
	
tjc's avatar
tjc committed
    RunSamTools samtools = new RunSamTools(cmd, null, null, null);
tjc's avatar
tjc committed
	
    if(samtools.getProcessStderr() != null)
      System.out.println(samtools.getProcessStderr());
 
    String header = samtools.getProcessStdout();
    
    StringReader samReader = new StringReader(header);
	BufferedReader buff = new BufferedReader(samReader);
    
	String line;
	try 
	{
	  while((line = buff.readLine()) != null)
	  {
	    if(line.indexOf("LN:") > -1)
	    {
	      String parts[] = line.split("\t");
	      String name = "";
	      int seqLength = 0;
	      for(int i=0; i<parts.length; i++)
	      {
	        if(parts[i].startsWith("LN:"))
	          seqLength = Integer.parseInt( parts[i].substring(3) );
	        else if(parts[i].startsWith("SN:"))
	          name = parts[i].substring(3);
	      }
	      seqLengths.put(name, seqLength);
	      seqNames.add(name);
	    }
	  }
	} 
	catch (IOException e) 
	{
	  e.printStackTrace();
	}
  }
tjc's avatar
tjc committed

  private void readHeaderPicard()
  {
    File bamFile = new File(bam);
    File indexFile = new File(bam+".bai");
    final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
    SAMFileHeader header = inputSam.getFileHeader();
    List<SAMSequenceRecord> readGroups = header.getSequenceDictionary().getSequences();
    
    for(int i=0; i<readGroups.size(); i++)
    {
      seqLengths.put(readGroups.get(i).getSequenceName(),
                     readGroups.get(i).getSequenceLength());
      seqNames.add(readGroups.get(i).getSequenceName());
    }
    inputSam.close();
  }

tjc's avatar
tjc committed
  /**
   * Read data from BAM/SAM file for a region.
   * @param start
   * @param end
   * @param pair_sort
   */
  private void readFromBam(int start, int end)
tjc's avatar
tjc committed
  {
    String refName = (String) combo.getSelectedItem();
tjc's avatar
tjc committed
    
    String samtoolCmd = "";
    if(System.getProperty("samtoolDir") != null)
      samtoolCmd = System.getProperty("samtoolDir");
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
	String cmd[] = { samtoolCmd+File.separator+"samtools",  
tjc's avatar
tjc committed
				     "view", 
				     bam, refName+":"+start+"-"+end };
tjc's avatar
tjc committed
		
	for(int i=0; i<cmd.length;i++)
	  System.out.print(cmd[i]+" ");
	System.out.println();
tjc's avatar
tjc committed
	
    if(readsInView == null)
tjc's avatar
tjc committed
      readsInView = new Vector<SAMRecord>();
tjc's avatar
tjc committed
    else
      readsInView.clear();
	RunSamTools samtools = new RunSamTools(cmd, null, null, readsInView);
tjc's avatar
tjc committed
		
	if(samtools.getProcessStderr() != null)
      System.out.println(samtools.getProcessStderr());
	    
tjc's avatar
tjc committed
	samtools.waitForStdout();
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  /**
   * Read a SAM or BAM file.
   */
  private void readFromBamPicard(int start, int end)
  {
    // Open the input file.  Automatically detects whether input is SAM or BAM
    // and delegates to a reader implementation for the appropriate format.
    File bamFile = new File(bam);
    File indexFile = new File(bam+".bai");
    final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
    inputSam.setValidationStringency(ValidationStringency.SILENT);
    
    if(readsInView == null)
      readsInView = new Vector<SAMRecord>();
    else
      readsInView.clear();
    String refName = (String) combo.getSelectedItem();
    CloseableIterator<SAMRecord> it = inputSam.queryOverlapping(refName, start, end);
    while ( it.hasNext() )
    {
      try
      {
        SAMRecord samRecord = it.next();
        readsInView.add(samRecord);
      }
      catch(Exception e)
      {
        System.out.println(e.getMessage());
      }
    }
    inputSam.close();
  }
  
tjc's avatar
tjc committed
  /**
   * Override
   */
  public void paintComponent(Graphics g)
  {
	super.paintComponent(g);
	Graphics2D g2 = (Graphics2D)g;

	String refName = (String) combo.getSelectedItem();
    int seqLength = seqLengths.get(refName);

	float pixPerBase = ((float)getWidth())/(float)(seqLength);
	
tjc's avatar
tjc committed
    double x = jspView.getViewport().getViewRect().getX();
    int start = (int) (seqLength * ( (float)x / (float)getWidth()));
    int end   = (int) (start + ((float)jspView.getViewport().getWidth() / 
                                (float)pixPerBase));
tjc's avatar
tjc committed

tjc's avatar
tjc committed
    if(laststart != start ||
tjc's avatar
tjc committed
       lastend   != end)
    {
      try
      {
tjc's avatar
tjc committed
        setCursor(cbusy);
        
        if(PICARD)
          readFromBamPicard(start, end);
        else
          readFromBam(start, end);
        setCursor(cdone);
tjc's avatar
tjc committed
      }
      catch(OutOfMemoryError ome)
      {
        JOptionPane.showMessageDialog(this, "Out of Memory");
        return;
      }
    }
tjc's avatar
tjc committed
    
    laststart = start;
    lastend   = end;
tjc's avatar
tjc committed
	if(pixPerBase >= ALIGNMENT_PIX_PER_BASE)
	  drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
	else
tjc's avatar
tjc committed
	{
tjc's avatar
tjc committed
	  Collections.sort(readsInView, new SAMRecordComparator());
tjc's avatar
tjc committed
	  drawLineView(g2, seqLength, pixPerBase, start, end);
	}
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  /**
   * Draw the zoomed-in base view.
   * @param g2
   * @param seqLength
   * @param pixPerBase
   * @param start
   * @param end
   */
tjc's avatar
tjc committed
  private void drawBaseAlignment(Graphics2D g2, int seqLength, 
tjc's avatar
tjc committed
                                 float pixPerBase, final int start, final int end)
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    int ypos = 0;
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
    ruler.start = start;
    ruler.end = end;
    ruler.repaint();
    
tjc's avatar
tjc committed
    ypos+=6;
tjc's avatar
tjc committed
    
    char[] refSeq = null;
    int refSeqStart = start;
tjc's avatar
tjc committed
    if(bases != null)
    {
      // draw the reference sequence
      ypos+=11;
      try
      {
        int seqEnd = end+1;
        if(seqEnd > bases.getLength())
          seqEnd = bases.getLength();
tjc's avatar
tjc committed

        if(refSeqStart < 1)
          refSeqStart = 1;
        refSeq = 
          bases.getSubSequenceC(new Range(refSeqStart, seqEnd), Bases.FORWARD);
tjc's avatar
tjc committed
        int xpos;

tjc's avatar
tjc committed
        for(int i=0;i<refSeq.length; i++)
tjc's avatar
tjc committed
        {
tjc's avatar
tjc committed
          xpos = ((refSeqStart-1) + i)*ALIGNMENT_PIX_PER_BASE;
          refSeq[i] = Character.toUpperCase(refSeq[i]);
          g2.drawChars(refSeq, i, 1, xpos, ypos);
tjc's avatar
tjc committed
        }
      }
      catch (OutOfRangeException e)
      {
        e.printStackTrace();
      }
    }
    
tjc's avatar
tjc committed
    boolean drawn[] = new boolean[readsInView.size()];
tjc's avatar
tjc committed
    for(int i=0; i<readsInView.size(); i++)
tjc's avatar
tjc committed
      drawn[i] = false;
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
    for(int i=0; i<readsInView.size(); i++)
    {
tjc's avatar
tjc committed
      if (!drawn[i])
tjc's avatar
tjc committed
      {
tjc's avatar
tjc committed
        SAMRecord thisRead = readsInView.get(i);
tjc's avatar
tjc committed
        ypos+=11;
tjc's avatar
tjc committed

tjc's avatar
tjc committed
        drawSequence(g2, thisRead, pixPerBase, ypos, refSeq, refSeqStart);
        drawn[i] = true;
        
        int thisEnd = thisRead.getAlignmentEnd();
        if(thisEnd == 0)
          thisEnd = thisRead.getAlignmentStart()+thisRead.getReadLength();
tjc's avatar
tjc committed
        
        for(int j=i+1; j<readsInView.size(); j++)
        {
tjc's avatar
tjc committed
          if (!drawn[j])
tjc's avatar
tjc committed
          {
tjc's avatar
tjc committed
            SAMRecord nextRead = readsInView.get(j);
            if(nextRead.getAlignmentStart() > thisEnd+1)
tjc's avatar
tjc committed
            {
tjc's avatar
tjc committed
              drawSequence(g2, nextRead, pixPerBase, ypos, refSeq, refSeqStart);
              drawn[j] = true;
              thisEnd = nextRead.getAlignmentEnd();
              if(thisEnd == 0)
                thisEnd = nextRead.getAlignmentStart()+nextRead.getReadLength();
tjc's avatar
tjc committed
            }
          }
        }
      }
    }
tjc's avatar
tjc committed
    
    if(ypos > getHeight())
    {
      setPreferredSize(new Dimension(getWidth(), ypos));
      revalidate();
    }
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  /**
   * Draw the query sequence
   * @param g2
   * @param read
   * @param pixPerBase
   * @param ypos
   */
tjc's avatar
tjc committed
  private void drawSequence(Graphics2D g2, SAMRecord samRecord, 
                            float pixPerBase, int ypos, char[] refSeq, int refSeqStart)
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    if (!samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
        samRecord.getMateUnmappedFlag() )  // mate is unmapped )  // mate is unmapped 
tjc's avatar
tjc committed
      g2.setColor(Color.black);
    else
      g2.setColor(Color.blue);
    
tjc's avatar
tjc committed
    Color col = g2.getColor();
tjc's avatar
tjc committed
    int xpos;
tjc's avatar
tjc committed
    String seq = samRecord.getReadString();

    for(int i=0;i<seq.length(); i++)
tjc's avatar
tjc committed
    {
tjc's avatar
tjc committed
      xpos = ((samRecord.getAlignmentStart()-1) + i)*ALIGNMENT_PIX_PER_BASE;
      
tjc's avatar
tjc committed
      // colour SNPs red
tjc's avatar
tjc committed
      if(checkBoxSNPs.isSelected() && refSeq != null)
      {
        int refPos = samRecord.getAlignmentStart()-refSeqStart+i;
        if(refPos >= 0 && refPos < refSeq.length && seq.charAt(i) != refSeq[refPos])
          g2.setColor(Color.red);
        else
          g2.setColor(col);
      }
      g2.drawString(seq.substring(i, i+1), xpos, ypos);
tjc's avatar
tjc committed
    }
  }
  
tjc's avatar
tjc committed
  /**
   * Draw zoomed-out view.
   * @param g2
   * @param seqLength
   * @param pixPerBase
   * @param start
   * @param end
   */
tjc's avatar
tjc committed
  private void drawLineView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end)
  {   
tjc's avatar
tjc committed
    drawScale(g2, start, end, pixPerBase);
    
tjc's avatar
tjc committed
    Stroke originalStroke = new BasicStroke (1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); 
tjc's avatar
tjc committed
    Stroke stroke =
tjc's avatar
tjc committed
            new BasicStroke (1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
tjc's avatar
tjc committed
    int scaleHeight = 15;
    
    for(int i=0; i<readsInView.size(); i++)
    {
tjc's avatar
tjc committed
      SAMRecord samRecord = readsInView.get(i);
      SAMRecord samNextRecord = null;      
tjc's avatar
tjc committed

tjc's avatar
tjc committed
      if( !samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
tjc's avatar
tjc committed
          samRecord.getMateUnmappedFlag() )  // mate is unmapped
tjc's avatar
tjc committed
      {
        if(checkBoxSingle.isSelected())
        {
tjc's avatar
tjc committed
          int ypos = (getHeight() - scaleHeight) - samRecord.getReadString().length();
tjc's avatar
tjc committed
          g2.setColor(Color.orange);
tjc's avatar
tjc committed
          drawRead(g2, samRecord, pixPerBase, stroke, ypos);
tjc's avatar
tjc committed
        }
tjc's avatar
tjc committed
        continue;
tjc's avatar
tjc committed
      }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
      int ypos = (getHeight() - scaleHeight) - ( Math.abs(samRecord.getInferredInsertSize()) );
tjc's avatar
tjc committed
      if(i < readsInView.size()-1)
      {
tjc's avatar
tjc committed
        samNextRecord = readsInView.get(++i);
tjc's avatar
tjc committed

tjc's avatar
tjc committed
        if(samRecord.getReadName().equals(samNextRecord.getReadName()))
tjc's avatar
tjc committed
        { 
          // draw connection between paired reads
tjc's avatar
tjc committed
          if(samRecord.getAlignmentEnd() < samNextRecord.getAlignmentStart() && 
              (samNextRecord.getAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
tjc's avatar
tjc committed
          {
            g2.setStroke(originalStroke);
            g2.setColor(Color.LIGHT_GRAY);
tjc's avatar
tjc committed
            g2.drawLine((int)(samRecord.getAlignmentEnd()*pixPerBase), ypos, 
                        (int)(samNextRecord.getAlignmentStart()*pixPerBase), ypos);
tjc's avatar
tjc committed
          }
tjc's avatar
tjc committed
          
          if( samRecord.getReadNegativeStrandFlag() && // strand of the query (1 for reverse)
              samNextRecord.getReadNegativeStrandFlag() )
            g2.setColor(Color.red);
          else
            g2.setColor(Color.blue);
          
          drawRead(g2, samRecord, pixPerBase, stroke, ypos);
          drawRead(g2, samNextRecord, pixPerBase, stroke, ypos);
tjc's avatar
tjc committed
        }
        else
tjc's avatar
tjc committed
        {
tjc's avatar
tjc committed
          drawLoneRead(g2, samRecord, ypos, pixPerBase, originalStroke, stroke);
tjc's avatar
tjc committed
          i--;
tjc's avatar
tjc committed
        }
      }
      else
      {
tjc's avatar
tjc committed
        drawLoneRead(g2, samRecord, ypos, pixPerBase, originalStroke, stroke);
tjc's avatar
tjc committed
      }
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  /**
   * Draw a read that apparently has a read mate that is not in view.
   * @param g2
   * @param thisRead
   * @param ypos
   * @param pixPerBase
   * @param originalStroke
   * @param stroke
   */
tjc's avatar
tjc committed
  private void drawLoneRead(Graphics2D g2, SAMRecord samRecord, int ypos, 
tjc's avatar
tjc committed
      float pixPerBase, Stroke originalStroke, Stroke stroke)
  {
tjc's avatar
tjc committed
    boolean drawLine = true;   
    int thisStart = samRecord.getAlignmentStart()-1;
    int thisEnd   = thisStart + samRecord.getReadString().length();
    
    if(drawLine &&
        (samRecord.getMateAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
    {
      g2.setStroke(originalStroke);
      g2.setColor(Color.LIGHT_GRAY);
      
      if(samRecord.getAlignmentEnd() < samRecord.getMateAlignmentStart())
      {
        int nextStart = (int) ((samRecord.getMateAlignmentStart()-1)*pixPerBase);
        g2.drawLine((int)(thisStart*pixPerBase), ypos, nextStart, ypos);
      }
      else
      {
        int nextStart = (int) ((samRecord.getMateAlignmentStart()-1)*pixPerBase);
        g2.drawLine((int)(thisEnd*pixPerBase), ypos, nextStart, ypos);
      }
    }
    
tjc's avatar
tjc committed
    if(samRecord.getReadNegativeStrandFlag()) // strand of the query (1 for reverse)
tjc's avatar
tjc committed
      g2.setColor(Color.red);
    else
      g2.setColor(Color.blue);
    
tjc's avatar
tjc committed
    if(ypos <= 0)
    {
tjc's avatar
tjc committed
      ypos = samRecord.getReadString().length();
tjc's avatar
tjc committed
      drawLine = false;
      g2.setColor(Color.orange); 
tjc's avatar
tjc committed
    }  
tjc's avatar
tjc committed
    drawRead(g2, samRecord, pixPerBase, stroke, ypos);
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  
  private void drawScale(Graphics2D g2, int start, int end, float pixPerBase)
  {
    g2.setColor(Color.black);
tjc's avatar
tjc committed
    g2.drawLine( (int)(start*pixPerBase), getHeight()-14,
                 (int)(end*pixPerBase),   getHeight()-14);
tjc's avatar
tjc committed
    int interval = end-start;
    
    if(interval > 256000)
      drawTicks(g2, start, end, pixPerBase, 512000);
    else if(interval > 64000)
      drawTicks(g2, start, end, pixPerBase, 12800);
    else if(interval > 16000)
      drawTicks(g2, start, end, pixPerBase, 3200);
    else if(interval > 4000)
      drawTicks(g2, start, end, pixPerBase, 800);
    else if(interval > 1000)
      drawTicks(g2, start, end, pixPerBase, 200);
tjc's avatar
tjc committed
    else
      drawTicks(g2, start, end, pixPerBase, 50);
tjc's avatar
tjc committed
  }
  
  private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division)
  {
tjc's avatar
tjc committed
    int markStart = (Math.round(start/division)*division);
    
    if(markStart < 1)
      markStart = 1;
    
tjc's avatar
tjc committed
    int sm = markStart-(division/2);
    
    if(sm > start)
      g2.drawLine((int)(sm*pixPerBase), getHeight()-14,(int)(sm*pixPerBase), getHeight()-12);
    
    for(int m=markStart; m<end; m+=division)
    {
      g2.drawString(Integer.toString(m), m*pixPerBase, getHeight()-1);
      g2.drawLine((int)(m*pixPerBase), getHeight()-14,(int)(m*pixPerBase), getHeight()-11);
      
      sm = m+(division/2);
      
      if(sm < end)
        g2.drawLine((int)(sm*pixPerBase), getHeight()-14,(int)(sm*pixPerBase), getHeight()-12);
tjc's avatar
tjc committed
      
      if(m == 1)
        m = 0;
tjc's avatar
tjc committed
    }
  }
  
tjc's avatar
tjc committed
  private void drawRead(Graphics2D g2, SAMRecord thisRead,
tjc's avatar
tjc committed
		                float pixPerBase, Stroke stroke, int ypos)
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    int thisStart = thisRead.getAlignmentStart()-1;
    int thisEnd   = thisRead.getAlignmentEnd();
tjc's avatar
tjc committed
    g2.setStroke(stroke);
tjc's avatar
tjc committed
    g2.drawLine((int) (thisStart * pixPerBase), ypos,
                (int) (thisEnd * pixPerBase), ypos);

    if (checkBoxSNPs.isSelected())
tjc's avatar
tjc committed
      showSNPsOnReads(g2, thisRead, pixPerBase, ypos);
  }
  
  /**
   * Display the SNPs for the given read.
   * @param g2
   * @param thisRead
   * @param pixPerBase
   * @param ypos
   */
  private void showSNPsOnReads(Graphics2D g2, SAMRecord thisRead,
                               float pixPerBase, int ypos)
  {
    int thisStart = thisRead.getAlignmentStart();
    int thisEnd   = thisRead.getAlignmentEnd();
    try
tjc's avatar
tjc committed
    {
tjc's avatar
tjc committed
      char[] refSeq = bases.getSubSequenceC(
          new Range(thisStart, thisEnd), Bases.FORWARD);
      byte[] readSeq = thisRead.getReadBases();
tjc's avatar
tjc committed

tjc's avatar
tjc committed
      Color col = g2.getColor();
tjc's avatar
tjc committed

tjc's avatar
tjc committed
      g2.setColor(Color.red);
      for (int i = 0; i < refSeq.length && i < readSeq.length; i++)
      {
        if (Character.toUpperCase(refSeq[i]) != readSeq[i])
tjc's avatar
tjc committed
        {
tjc's avatar
tjc committed
          g2.drawLine((int) ((thisStart + i) * pixPerBase), ypos + 2,
                      (int) ((thisStart + i) * pixPerBase), ypos - 2);
tjc's avatar
tjc committed
        }
      }
tjc's avatar
tjc committed
      g2.setColor(col);
    }
    catch (OutOfRangeException e)
    {
      e.printStackTrace();
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  public void addJamToPanel(final JPanel panel)
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    JPanel topPanel = new JPanel(new GridBagLayout());
    GridBagConstraints gc = new GridBagConstraints();
    
tjc's avatar
tjc committed
    combo = new JComboBox(seqNames);
    combo.setEditable(false);
    combo.addItemListener(new ItemListener()
    {
      public void itemStateChanged(ItemEvent e)
      {
tjc's avatar
tjc committed
        laststart = -1;
        lastend   = -1;
tjc's avatar
tjc committed
        setZoomLevel(JamView.this.nbasesInView);
      }
    });
tjc's avatar
tjc committed
    gc.fill = GridBagConstraints.NONE;
tjc's avatar
tjc committed
    gc.anchor = GridBagConstraints.NORTHWEST;
tjc's avatar
tjc committed
    topPanel.add(combo, gc);
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
    checkBoxSingle = new JCheckBox("Single Reads");
    checkBoxSingle.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        repaint();
      }
    });
    topPanel.add(checkBoxSingle, gc);
    
tjc's avatar
tjc committed
    
    checkBoxSNPs = new JCheckBox("SNPs");
    checkBoxSNPs.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        repaint();
      }
    });
    topPanel.add(checkBoxSNPs, gc);
    
    final JTextField baseText = new JTextField(10);
    JButton goTo = new JButton("GoTo:");
    goTo.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        try
        {
          int basePosition = Integer.parseInt(baseText.getText());
          goToBasePosition(basePosition);
        }
        catch(NumberFormatException nfe)
        {
          JOptionPane.showMessageDialog(JamView.this, 
                 "Expecting a base number!", 
                 "Number Format", JOptionPane.WARNING_MESSAGE);
        }
      }
    });
    topPanel.add(goTo, gc);
    topPanel.add(baseText, gc);

tjc's avatar
tjc committed
    panel.setPreferredSize(new Dimension(1000,500));
tjc's avatar
tjc committed
    setLength(nbasesInView);
tjc's avatar
tjc committed

    jspView = new JScrollPane(this, 
        JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
        JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

tjc's avatar
tjc committed
    panel.setLayout(new BorderLayout());
tjc's avatar
tjc committed
    panel.add(topPanel, BorderLayout.NORTH);
tjc's avatar
tjc committed
    panel.add(jspView, BorderLayout.CENTER);
tjc's avatar
tjc committed

    jspView.getVerticalScrollBar().setValue(
        jspView.getVerticalScrollBar().getMaximum());
tjc's avatar
tjc committed

tjc's avatar
tjc committed
    addMouseListener(new MouseAdapter()
    {
      public void mouseClicked(MouseEvent e)
      {
        JamView.this.requestFocus();
      }
    });
    
tjc's avatar
tjc committed
    addKeyListener(new KeyAdapter()
    {
      public void keyPressed(final KeyEvent event)
      {
        switch(event.getKeyCode())
        {
          case KeyEvent.VK_UP:
tjc's avatar
tjc committed
            int startBase = getBaseAtStartOfView();
tjc's avatar
tjc committed
            setZoomLevel( (int) (JamView.this.nbasesInView*1.1) );
tjc's avatar
tjc committed
            goToBasePosition(startBase);
tjc's avatar
tjc committed
            repaint();
            break;
          case KeyEvent.VK_DOWN:
tjc's avatar
tjc committed
            startBase = getBaseAtStartOfView();
tjc's avatar
tjc committed
            setZoomLevel( (int) (JamView.this.nbasesInView*.9) );
tjc's avatar
tjc committed
            goToBasePosition(startBase);
tjc's avatar
tjc committed
            repaint();
            break;
          default:
            break;
        }
      }
    });
    
tjc's avatar
tjc committed
    setFocusable(true);
    requestFocusInWindow();
tjc's avatar
tjc committed
    addFocusListener(new FocusListener() 
    {
      public void focusGained(FocusEvent fe) {}
      public void focusLost(FocusEvent fe) {}
    });
  }
  
tjc's avatar
tjc committed
  private int getBaseAtStartOfView()
  {
    String refName = (String) combo.getSelectedItem();
    int seqLength = seqLengths.get(refName);
    double x = jspView.getViewport().getViewRect().getX();
    return (int) (seqLength * ( x / getWidth()));
  }
  
  /**
   * Set the panel size based on the number of bases visible
   * and repaint.
   * @param nbasesInView
   */
tjc's avatar
tjc committed
  private void setZoomLevel(final int nbasesInView)
  {
    this.nbasesInView = nbasesInView;
    setLength(this.nbasesInView);
    revalidate();
    repaint();
tjc's avatar
tjc committed
  }
  
  /**
   * Set the ViewPort so it starts at the given base position.
   * @param base
   */
tjc's avatar
tjc committed
  private void goToBasePosition(int base)
tjc's avatar
tjc committed
  {
    Point p = jspView.getViewport().getViewPosition();
tjc's avatar
tjc committed
    
    String refName = (String) combo.getSelectedItem();
    int seqLength = seqLengths.get(refName);
tjc's avatar
tjc committed
    p.x = (int) ((getPreferredSize().width)*(((float)base)/(float)seqLength));
tjc's avatar
tjc committed
    jspView.getViewport().setViewPosition(p);
  }

tjc's avatar
tjc committed
  /**
   * Set the panel size based on the number of bases visible.
   * @param nbasesInView
   */
tjc's avatar
tjc committed
  private void setLength(int basesToShow)
  {
    String refName = (String) combo.getSelectedItem();
    int seqLength = seqLengths.get(refName);
tjc's avatar
tjc committed
    double pixPerBase = 1000.d/(double)(basesToShow);
    
    System.out.println(pixPerBase+"  "+ALIGNMENT_PIX_PER_BASE);
    if(pixPerBase > ALIGNMENT_PIX_PER_BASE)
tjc's avatar
tjc committed
    {
tjc's avatar
tjc committed
      pixPerBase = ALIGNMENT_PIX_PER_BASE;
tjc's avatar
tjc committed
      checkBoxSingle.setVisible(false);
tjc's avatar
tjc committed
      jspView.getVerticalScrollBar().setValue(0);
tjc's avatar
tjc committed
      jspView.setColumnHeaderView(ruler);
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
    else if(jspView != null)
tjc's avatar
tjc committed
    {
      checkBoxSingle.setVisible(true);
tjc's avatar
tjc committed
      jspView.setColumnHeaderView(null);
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
    Dimension d = new Dimension();
    d.setSize((seqLength*pixPerBase), 800.d);
    setPreferredSize(d);
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  /**
   * Return an Artemis entry from a file 
   * @param entryFileName
   * @param entryGroup
   * @return
   * @throws NoSequenceException
   */
  private Entry getEntry(final String entryFileName, final EntryGroup entryGroup) 
                   throws NoSequenceException
  {
    final Document entry_document = DocumentFactory.makeDocument(entryFileName);
    final EntryInformation artemis_entry_information =
      Options.getArtemisEntryInformation();
    
    System.out.println(entryFileName);
    final uk.ac.sanger.artemis.io.Entry new_embl_entry =
      EntryFileDialog.getEntryFromFile(null, entry_document,
                                       artemis_entry_information,
                                       false);

    if(new_embl_entry == null)  // the read failed
      return null;

    Entry entry = null;
    try
    {
      if(entryGroup.getSequenceEntry() != null)
        bases = entryGroup.getSequenceEntry().getBases();
tjc's avatar
tjc committed
      
tjc's avatar
tjc committed
      if(bases == null)
tjc's avatar
tjc committed
      {
tjc's avatar
tjc committed
        entry = new Entry(new_embl_entry);
tjc's avatar
tjc committed
        bases = entry.getBases();
      }
tjc's avatar
tjc committed
      else
        entry = new Entry(bases,new_embl_entry);
      
      entryGroup.add(entry);
    } 
    catch(OutOfRangeException e) 
    {
      new MessageDialog(null, "read failed: one of the features in " +
          entryFileName + " has an out of range " +
                        "location: " + e.getMessage());
    }
    return entry;
  }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  private class Ruler extends JPanel
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    private static final long serialVersionUID = 1L;
tjc's avatar
tjc committed
    int start;
    int end;
tjc's avatar
tjc committed

tjc's avatar
tjc committed
    public Ruler()
    {
      super();
      setPreferredSize(new Dimension(getPreferredSize().width, 15));
      setBackground(Color.white);
      setFont(getFont().deriveFont(11.f));
    }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
    public void paintComponent(Graphics g)
    {
      super.paintComponent(g);
      Graphics2D g2 = (Graphics2D)g;
      drawBaseScale(g2, start, end, 12);
    }
tjc's avatar
tjc committed

    private void drawBaseScale(Graphics2D g2, int start, int end, int ypos)
    {
      int startMark = (((int)(start/10))*10)+1;

      for(int i=startMark; i<end; i+=10)
      {
        int xpos = (i-1-start)*ALIGNMENT_PIX_PER_BASE;
        g2.drawString(Integer.toString(i), xpos, ypos);
        
        xpos+=(ALIGNMENT_PIX_PER_BASE/2);
        g2.drawLine(xpos, ypos+1, xpos, ypos+5);
      }
    }
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  class SAMRecordComparator implements Comparator<Object>
tjc's avatar
tjc committed
  {
    public int compare(Object o1, Object o2) 
    {
tjc's avatar
tjc committed
      SAMRecord pr1 = (SAMRecord) o1;
      SAMRecord pr2 = (SAMRecord) o2;
tjc's avatar
tjc committed
      
tjc's avatar
tjc committed
      int cmp = pr1.getReadName().compareTo(pr2.getReadName());
      
      if(cmp == 0)
      {
        if(pr1.getAlignmentStart() < pr2.getAlignmentStart())
          return -1;
        else
          return 1;
      }
      return cmp;
tjc's avatar
tjc committed
    }
  }
tjc's avatar
tjc committed

tjc's avatar
tjc committed

tjc's avatar
tjc committed
  public Dimension getPreferredScrollableViewportSize()
  {
    return getPreferredSize();
  }

  public int getScrollableBlockIncrement(Rectangle visibleRect,
      int orientation, int direction)
  {
    if (orientation == SwingConstants.HORIZONTAL)
      return visibleRect.width - maxUnitIncrement;
    else 
      return visibleRect.height - maxUnitIncrement;
  }

  public boolean getScrollableTracksViewportHeight()