Skip to content
Snippets Groups Projects
JamView.java 29.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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.SAMReadGroupRecord;
    import net.sf.samtools.SAMRecord;
    import net.sf.samtools.SAMRecordQueryNameComparator;
    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
      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
      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
    	{
    	  Collections.sort(readsInView, new ReadComparator());
    	  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;
          
          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);
        
        Stroke originalStroke = g2.getStroke();
        Stroke stroke =
    
    tjc's avatar
    tjc committed
                new BasicStroke (1.2f, 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
              samRecord.getMateUnmappedFlag() )  // mate is unmapped )  // 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
            {
    
    tjc's avatar
    tjc committed
              if( samRecord.getReadNegativeStrandFlag() && // strand of the query (1 for reverse)
                  samNextRecord.getReadNegativeStrandFlag() )
    
    tjc's avatar
    tjc committed
                g2.setColor(Color.red);
              else
                g2.setColor(Color.blue);
    
    tjc's avatar
    tjc committed
     
    
    tjc's avatar
    tjc committed
              drawRead(g2, samRecord, pixPerBase, stroke, ypos);
              drawRead(g2, samNextRecord, pixPerBase, stroke, ypos);
    
    tjc's avatar
    tjc committed
              
    
    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
            }
            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)
      {
        boolean drawLine = true;
    
    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
        int thisStart = samRecord.getAlignmentStart()-1;
        int thisEnd   = thisStart + samRecord.getReadString().length();
        drawRead(g2, samRecord, pixPerBase, stroke, ypos);
    
    tjc's avatar
    tjc committed
        if(drawLine)
        {
          g2.setStroke(originalStroke);
          g2.setColor(Color.LIGHT_GRAY);
    
    tjc's avatar
    tjc committed
          int nextStart = (int) ((samRecord.getMateAlignmentStart()-1)*pixPerBase);
    
    tjc's avatar
    tjc committed
          g2.drawLine((int)(thisEnd*pixPerBase), ypos, nextStart, ypos);
        }
      }
    
    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())
        {
          try
          {
            char[] refSeq = bases.getSubSequenceC(
                new Range(thisStart + 1, thisEnd), Bases.FORWARD);
            byte[] readSeq = thisRead.getReadBases();
    
            Color col = g2.getColor();
    
            g2.setColor(Color.red);
            for (int i = 0; i < refSeq.length && i < readSeq.length; i++)
            {
              if (Character.toUpperCase(refSeq[i]) != readSeq[i])
              {
                g2.drawLine((int) ((thisStart + i) * pixPerBase), ypos + 1,
                    (int) ((thisStart + i) * pixPerBase), ypos - 1);
              }
            }
            g2.setColor(col);
          }
          catch (OutOfRangeException e)
          {
            e.printStackTrace();
          }
        }
    
    tjc's avatar
    tjc committed
      }
      
    
    tjc's avatar
    tjc committed
      public void addToPanel(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
      class Ruler extends JPanel
      {
    
    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 ReadComparator 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
          return pr1.getReadName().compareTo(pr2.getReadName());
    
    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()
      {
        return false;
      }
    
      public boolean getScrollableTracksViewportWidth()
      {
        return false;
      }
    
      public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
                                            int direction)
      {
      //Get the current position.
        int currentPosition = 0;
        if (orientation == SwingConstants.HORIZONTAL) 
            currentPosition = visibleRect.x;
        else 
            currentPosition = visibleRect.y;
    
        //Return the number of pixels between currentPosition
        //and the nearest tick mark in the indicated direction.
        if (direction < 0)
        {
          int newPosition = currentPosition -
                            (currentPosition / maxUnitIncrement)
                             * maxUnitIncrement;
          return (newPosition == 0) ? maxUnitIncrement : newPosition;
        } 
        else 
        {
          return ((currentPosition / maxUnitIncrement) + 1)
                  * maxUnitIncrement
                  - currentPosition;