Skip to content
Snippets Groups Projects
JamView.java 42.4 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;
    
    import java.awt.Component;
    
    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;
    
    import java.awt.Insets;
    
    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;
    
    import java.awt.event.MouseMotionListener;
    
    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.JPopupMenu;
    
    tjc's avatar
    tjc committed
    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;
    
    import javax.swing.border.Border;
    import javax.swing.border.EmptyBorder;
    
    tjc's avatar
    tjc committed
    
    
    import net.sf.samtools.AlignmentBlock;
    
    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.Selection;
    import uk.ac.sanger.artemis.SelectionChangeEvent;
    import uk.ac.sanger.artemis.SelectionChangeListener;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.SimpleEntryGroup;
    
    import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
    import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.components.EntryFileDialog;
    
    import uk.ac.sanger.artemis.components.FeatureDisplay;
    
    tjc's avatar
    tjc committed
    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.MarkerRange;
    
    tjc's avatar
    tjc committed
    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
    
                         implements Scrollable, DisplayAdjustmentListener, SelectionChangeListener
    
    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;
      
    
      private FeatureDisplay feature_display;
      private Selection selection;
      private JPanel mainPanel;
      private boolean showScale = true;
    
    tjc's avatar
    tjc committed
      private Ruler ruler = new Ruler();
    
    tjc's avatar
    tjc committed
      private int nbasesInView;
    
      
      private int startBase = -1;
      private int endBase   = -1;
    
    tjc's avatar
    tjc committed
      private int laststart;
      private int lastend;
    
      private int maxUnitIncrement = 8;
    
    tjc's avatar
    tjc committed
      private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
      private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
    
      
      private boolean showBaseAlignment = false;
      /** Used to colour the frames. */
      private Color light_grey = new Color(200, 200, 200);
    
    tjc's avatar
    tjc committed
      private int ALIGNMENT_PIX_PER_BASE;
    
    tjc's avatar
    tjc committed
      
    
      private JPopupMenu popup;
    
    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
      
      private boolean painting = 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
    
    
        //Options.getOptions().getFontUIResource();
        //final javax.swing.plaf.FontUIResource font_ui_resource =
        //  new javax.swing.plaf.FontUIResource(getFont());
        final javax.swing.plaf.FontUIResource font_ui_resource =
          Options.getOptions().getFontUIResource();
        
    
    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);
        }
    
        
        setFont(Options.getOptions().getFont());
    
    tjc's avatar
    tjc committed
        FontMetrics fm  = getFontMetrics(getFont());
    
        ALIGNMENT_PIX_PER_BASE = fm.charWidth('M');
        selection = new Selection(null);
    
    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
        System.out.println("readFromBamPicard "+start+".."+end);
    
    tjc's avatar
    tjc committed
      }
      
    
    tjc's avatar
    tjc committed
      /**
       * Override
       */
    
      protected void paintComponent(Graphics g)
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        if(!painting)
          return;
        
    
    tjc's avatar
    tjc committed
    	super.paintComponent(g);
    	Graphics2D g2 = (Graphics2D)g;
    
    	String refName = (String) combo.getSelectedItem();
        int seqLength = seqLengths.get(refName);
    
    tjc's avatar
    tjc committed
    	float pixPerBase = getPixPerBaseByWidth();
    
    tjc's avatar
    tjc committed
    	
    
        int start;
        final int end;
        
        if(startBase > 0)
          start = startBase;
        else
        {
          double x = jspView.getViewport().getViewRect().getX();
    
          start = 1 + (int) (getMaxBasesInPanel(seqLength) * ( (float)x / (float)getPreferredSize().getWidth() ));
    
        }
        
        if(endBase > 0)
          end = endBase;
        else
    
    tjc's avatar
    tjc committed
        {
          int width = jspView.getViewport().getViewRect().width;
          if(jspView.getVerticalScrollBar() != null)
            width += jspView.getVerticalScrollBar().getWidth();
          end   = (int) (start + ((float)width / (float)pixPerBase));
        }
    
    tjc's avatar
    tjc committed
    
    
        //System.out.println("paintComponent "+start+".."+end+"       "+startBase+".."+endBase+
        //   "  pixPerBase="+pixPerBase+"  getPreferredSize().getWidth()="+getPreferredSize().getWidth());
    
    tjc's avatar
    tjc committed
        if(laststart != start ||
    
    tjc's avatar
    tjc committed
           lastend   != end)
        {
          try
          {
    
    tjc's avatar
    tjc committed
            setCursor(cbusy);
    
    tjc's avatar
    tjc committed
            if(PICARD)
              readFromBamPicard(start, end);
            else
              readFromBam(start, end);
    
    
            Collections.sort(readsInView, new SAMRecordComparator());
    
    tjc's avatar
    tjc committed
            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*3 >= ALIGNMENT_PIX_PER_BASE)
    
    tjc's avatar
    tjc committed
    	  drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
    	else
    
    tjc's avatar
    tjc committed
    	  drawLineView(g2, seqLength, pixPerBase, start, end);
    
    tjc's avatar
    tjc committed
      }
      
    
    tjc's avatar
    tjc committed
      
      private float getPixPerBaseByWidth()
      {
        String refName = (String) combo.getSelectedItem();
        int seqLength = seqLengths.get(refName);
    
        return ((float)getPreferredSize().getWidth())/(getMaxBasesInPanel(seqLength));
    
    tjc's avatar
    tjc committed
      }
      
      private float getPixPerBaseByBasesInView()
      {
        int width = jspView.getViewport().getViewRect().width;
        if(jspView.getVerticalScrollBar() != null)
          width += jspView.getVerticalScrollBar().getWidth();
        
        return ((float)width)/(float)(nbasesInView); 
      }
      
    
      private float getMaxBasesInPanel(int seqLength)
      {
        if(feature_display == null)
          return (float)(seqLength);
        else
          return (float)(seqLength+nbasesInView);
      }
    
    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
        ruler.start = start;
        ruler.end = end;
        ruler.repaint();
        
    
        int ypos = 0;
    
    tjc's avatar
    tjc committed
        
    
        String refSeq = null;
    
    tjc's avatar
    tjc committed
        int refSeqStart = start;
    
    tjc's avatar
    tjc committed
        if(bases != null)
        {
          // draw the reference sequence
          ypos+=11;
    
    tjc's avatar
    tjc committed
          try
          {
    
            int seqEnd = end+2;
    
    tjc's avatar
    tjc committed
            if(seqEnd > bases.getLength())
              seqEnd = bases.getLength();
    
    tjc's avatar
    tjc committed
    
            if(refSeqStart < 1)
              refSeqStart = 1;
            refSeq = 
    
              bases.getSubSequence(new Range(refSeqStart, seqEnd), Bases.FORWARD).toUpperCase();
            int xpos = (refSeqStart-1)*ALIGNMENT_PIX_PER_BASE;
            
            g2.setColor(light_grey);
            g2.fillRect(xpos, ypos-11, 
                jspView.getViewport().getWidth()+(ALIGNMENT_PIX_PER_BASE*2), 11);
    
            drawSelectionRange(g2, pixPerBase, start, end);
    
            g2.setColor(Color.black);
            g2.drawString(refSeq, xpos, ypos);
            
            //for(int i=0;i<refSeq.length(); i++)
            //{
              //xpos = ((refSeqStart-1) + i)*ALIGNMENT_PIX_PER_BASE;
              //g2.drawString(refSeq.substring(i, i+1), xpos, ypos);
            //}
    
    tjc's avatar
    tjc committed
          }
          catch (OutOfRangeException e)
          {
            e.printStackTrace();
          }
        }
    
        else
          drawSelectionRange(g2, pixPerBase, start, end);
    
    tjc's avatar
    tjc committed
        
    
    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())
        {
    
    tjc's avatar
    tjc committed
          Dimension d = getPreferredSize();
          d.setSize(getPreferredSize().getWidth(), ypos);
          setPreferredSize(d);
    
    tjc's avatar
    tjc committed
          revalidate();
        }
    
    tjc's avatar
    tjc committed
      }
    
    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, String 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;
    
        String readSeq = samRecord.getReadString();
    
    tjc's avatar
    tjc committed
    
    
        List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
        for(int i=0; i<blocks.size(); i++)
    
    tjc's avatar
    tjc committed
        {
    
          AlignmentBlock block = blocks.get(i);
          for(int j=0; j<block.getLength(); j++)
    
    tjc's avatar
    tjc committed
          {
    
            int readPos = block.getReadStart()-1+j;
    
            xpos  = block.getReferenceStart()-1+j;
            int refPos = xpos-refSeqStart+1;
    
            if(checkBoxSNPs.isSelected() && refSeq != null && refPos > 0 && refPos < refSeq.length())
    
              if(readSeq.charAt(readPos) != refSeq.charAt(refPos))
    
                g2.setColor(Color.red);
              else
                g2.setColor(col);
            }
            g2.drawString(readSeq.substring(readPos, readPos+1), xpos*ALIGNMENT_PIX_PER_BASE, ypos);
    
    tjc's avatar
    tjc committed
          }
    
    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)
    
      {
        drawSelectionRange(g2, pixPerBase,start, end);
        if(isShowScale())
          drawScale(g2, start, end, pixPerBase);
    
    tjc's avatar
    tjc committed
        
    
    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);
    
        int scaleHeight;
        if(isShowScale())
          scaleHeight = 15;
        else
          scaleHeight = 0;
    
    tjc's avatar
    tjc committed
        
        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 &&
    
            Math.abs(samRecord.getMateAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
    
    tjc's avatar
    tjc committed
        {
          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
        
        if (checkBoxSNPs.isSelected())
          showSNPsOnReads(g2, samRecord, pixPerBase, 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, 400);
    
    tjc's avatar
    tjc committed
        else
    
          drawTicks(g2, start, end, pixPerBase, 100);
    
    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);
      }
      
    
      /**
       * Highlight a selected range
       * @param g2
       * @param pixPerBase
       * @param start
       * @param end
       */
      private void drawSelectionRange(Graphics2D g2, float pixPerBase, int start, int end)
      {
        if(getSelection() != null)
        {
          Range selectedRange = getSelection().getSelectionRange();
    
          if(selectedRange != null)
          {
            int rangeStart = selectedRange.getStart();
            int rangeEnd   = selectedRange.getEnd();
            
            if(end < rangeStart || start > rangeEnd)
              return;
            
            int x = (int) (pixPerBase*(rangeStart-1));
            int width = (int) (pixPerBase*(rangeEnd-rangeStart+1));
            
            g2.setColor(Color.pink);
            g2.fillRect(x, 0, width, getHeight());
          }
        }
      }
      
    
    tjc's avatar
    tjc committed
      /**
       * 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();
    
        
        // use alignment blocks of the contiguous alignment of
        // subsets of read bases to a reference sequence
        List<AlignmentBlock> blocks = thisRead.getAlignmentBlocks();
    
    tjc's avatar
    tjc committed
        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();
          g2.setColor(Color.red);
    
    
          for(int i=0; i<blocks.size(); i++)
    
    tjc's avatar
    tjc committed
          {
    
            AlignmentBlock block = blocks.get(i);
            for(int j=0; j<block.getLength(); j++)
    
    tjc's avatar
    tjc committed
            {
    
              int readPos = block.getReadStart()-1+j;
              int refPos  = block.getReferenceStart()+j;
    
              if (Character.toUpperCase(refSeq[refPos-thisStart]) != readSeq[readPos])
              {
                g2.drawLine((int) ((thisStart + i) * pixPerBase), ypos + 2,
                            (int) ((thisStart + i) * pixPerBase), ypos - 2);
              }
    
    tjc's avatar
    tjc committed
            }
    
    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
      }
      
    
      /**
       * Add the alignment view to the supplied <code>JPanel</code> in
       * a <code>JScrollPane</code>.
       * @param mainPanel  panel to add the alignment to
    
       * @param autohide automatically hide the top panel containing the buttons
    
       */
      public void addJamToPanel(final JPanel mainPanel,
    
                                final boolean autohide,
                                final FeatureDisplay feature_display)
    
    tjc's avatar
    tjc committed
      {
    
        this.mainPanel = mainPanel;
    
        
        if(feature_display != null)
        {
          this.feature_display = feature_display;
          this.selection = feature_display.getSelection();
        }
        
        final JPanel topPanel = new JPanel(new GridBagLayout());
    
    tjc's avatar
    tjc committed
        GridBagConstraints gc = new GridBagConstraints();
    
        // auto hide top panel
        final JCheckBox buttonAutoHide = new JCheckBox("Auto-hide", autohide);
        final MouseMotionListener mouseMotionListener = new MouseMotionListener()
    
    tjc's avatar
    tjc committed
        {
    
          public void mouseDragged(MouseEvent event)
    
    tjc's avatar
    tjc committed
          {
    
            handleCanvasMouseDragOrClick(event);
          }
          
          public void mouseMoved(MouseEvent e)
          {
            int thisHgt = HEIGHT;
            if (thisHgt < 5)
              thisHgt = 15;
    
            int y = (int) (e.getY() - jspView.getViewport().getViewRect().getY());
            if (y < thisHgt)
    
              if (!containsComponent(topPanel, mainPanel))
                mainPanel.add(topPanel, BorderLayout.NORTH);
    
            else
            {
              if (buttonAutoHide.isSelected() && containsComponent(topPanel, mainPanel))
                mainPanel.remove(topPanel);
            }
            mainPanel.repaint();
            mainPanel.revalidate();
          }
        };
        addMouseMotionListener(mouseMotionListener);
    
        combo = new JComboBox(seqNames);
        combo.setEditable(false);
    
        combo.addItemListener(new ItemListener()
        {
          public void itemStateChanged(ItemEvent e)
          {
            laststart = -1;
            lastend = -1;
            setZoomLevel(JamView.this.nbasesInView);
          }
        });
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.NORTHWEST;
        topPanel.add(combo, gc);
    
    
    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)
          {
    
            if (checkBoxSNPs.isSelected() && bases == null)
    
            {
              JOptionPane.showMessageDialog(null,
    
                  "No reference sequence supplied to identify SNPs.", "SNPs",
                  JOptionPane.INFORMATION_MESSAGE);
    
    tjc's avatar
    tjc committed
            repaint();
          }
        });
    
        topPanel.add(checkBoxSNPs, gc);
    
        if (feature_display == null)
    
    tjc's avatar
    tjc committed
        {
    
          final JTextField baseText = new JTextField(10);
          JButton goTo = new JButton("GoTo:");
          goTo.addActionListener(new ActionListener()
    
    tjc's avatar
    tjc committed
          {
    
            public void actionPerformed(ActionEvent e)
    
    tjc's avatar
    tjc committed
            {
    
              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);
              }
    
    tjc's avatar
    tjc committed
            }
    
          });
          topPanel.add(goTo, gc);
          topPanel.add(baseText, gc);
    
          JButton zoomIn = new JButton("+");
    
          Insets ins = new Insets(0,0,0,0);
          zoomIn.setMargin(ins);
    
          zoomIn.addActionListener(new ActionListener()
    
    tjc's avatar
    tjc committed
          {
    
            public void actionPerformed(ActionEvent e)
            {
              int startBase = getBaseAtStartOfView();
              setZoomLevel((int) (JamView.this.nbasesInView * 1.1));
              goToBasePosition(startBase);
            }
          });
          topPanel.add(zoomIn, gc);
    
          JButton zoomOut = new JButton("-");
          zoomOut.addActionListener(new ActionListener()
    
    tjc's avatar
    tjc committed
          {
    
            public void actionPerformed(ActionEvent e)
            {
              if (showBaseAlignment)
                return;
              int startBase = getBaseAtStartOfView();
              setZoomLevel((int) (JamView.this.nbasesInView * .9));
              goToBasePosition(startBase);
            }
          });
          topPanel.add(zoomOut, gc);
        }
    
        
        topPanel.add(buttonAutoHide, gc);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        mainPanel.setPreferredSize(new Dimension(900, 400));
    
    tjc's avatar
    tjc committed