Skip to content
Snippets Groups Projects
BamView.java 135 KiB
Newer Older
tjc's avatar
tjc committed
/* BamView
tjc's avatar
tjc committed
 *
 * 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.AlphaComposite;
tjc's avatar
tjc committed
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Composite;
tjc's avatar
tjc committed
import java.awt.Dimension;
tjc's avatar
tjc committed
import java.awt.FlowLayout;
tjc's avatar
tjc committed
import java.awt.FontMetrics;
tjc's avatar
tjc committed
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
tjc's avatar
tjc committed
import java.awt.Point;
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.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
tjc's avatar
tjc committed
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;
import java.awt.image.BufferedImage;
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.FileOutputStream;
import java.io.IOException;
tjc's avatar
tjc committed
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.reflect.Field;
tjc's avatar
tjc committed
import java.net.URL;
tjc's avatar
tjc committed
import java.util.Collections;
tjc's avatar
tjc committed
import java.util.Enumeration;
import java.util.HashMap;
tjc's avatar
tjc committed
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
tjc's avatar
tjc committed
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
tjc's avatar
tjc committed

import javax.swing.BorderFactory;
import javax.swing.Box;
tjc's avatar
tjc committed
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
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.JCheckBoxMenuItem;
import javax.swing.JComponent;
tjc's avatar
tjc committed
import javax.swing.JFrame;
import javax.swing.JMenu;
tjc's avatar
tjc committed
import javax.swing.JMenuBar;
tjc's avatar
tjc committed
import javax.swing.JOptionPane;
tjc's avatar
tjc committed
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
tjc's avatar
tjc committed
import javax.swing.JScrollBar;
tjc's avatar
tjc committed
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
tcarver's avatar
tcarver committed
import javax.swing.JSlider;
tjc's avatar
tjc committed
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
tjc's avatar
tjc committed
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
tcarver's avatar
tcarver committed
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
tjc's avatar
tjc committed

import net.sf.picard.reference.ReferenceSequenceFile;
import net.sf.picard.sam.BuildBamIndex;
import net.sf.samtools.AlignmentBlock;
import net.sf.samtools.SAMException;
tjc's avatar
tjc committed
import net.sf.samtools.SAMFileHeader;
import net.sf.samtools.SAMFileReader;
tcarver's avatar
tcarver committed
import net.sf.samtools.SAMReadGroupRecord;
tjc's avatar
tjc committed
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.FeatureVector;
tjc's avatar
tjc committed
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.circular.TextFieldInt;
import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
import uk.ac.sanger.artemis.components.EntryEdit;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.components.EntryFileDialog;
import uk.ac.sanger.artemis.components.FeatureDisplay;
import uk.ac.sanger.artemis.components.FileViewer;
import uk.ac.sanger.artemis.components.IndexReferenceEvent;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.components.MessageDialog;
import uk.ac.sanger.artemis.components.NonModalDialog;
import uk.ac.sanger.artemis.components.SequenceComboBox;
import uk.ac.sanger.artemis.components.SwingWorker;
import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
tjc's avatar
tjc committed
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.FTPSeekableStream;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.util.OutOfRangeException;

public class BamView extends JPanel
tjc's avatar
tjc committed
                     implements DisplayAdjustmentListener, SelectionChangeListener
tjc's avatar
tjc committed
{
  private static final long serialVersionUID = 1L;
tjc's avatar
tjc committed

  private List<BamViewRecord> readsInView;
tjc's avatar
tjc committed
  private Hashtable<String, SAMFileReader> samFileReaderHash = new Hashtable<String, SAMFileReader>();
tcarver's avatar
tcarver committed
  private List<SAMReadGroupRecord> readGroups = new Vector<SAMReadGroupRecord>();
tjc's avatar
tjc committed

  private HashMap<String, Integer> seqLengths = new HashMap<String, Integer>();
  private HashMap<String, Integer> offsetLengths;
tjc's avatar
tjc committed
  private Vector<String> seqNames = new Vector<String>();
tcarver's avatar
tcarver committed
  protected List<Short> hideBamList = new Vector<Short>();
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  private SAMRecordPredicate samRecordFlagPredicate;
tjc's avatar
tjc committed
  private SAMRecordMapQPredicate samRecordMapQPredicate;
tjc's avatar
tjc committed

  private SAMRecordFilter filterFrame;

tjc's avatar
tjc committed
  private Bases bases;
tjc's avatar
tjc committed
  private JScrollPane jspView;
tjc's avatar
tjc committed
  private JScrollBar scrollBar;
  
  private SequenceComboBox combo;
  private boolean isOrientation = false;
tjc's avatar
tjc committed
  private boolean isSingle = false;
  private boolean isSNPs = false;
  private boolean isCoverage = false;
tjc's avatar
tjc committed
  private boolean isSNPplot = false;
tjc's avatar
tjc committed
  
  private EntryEdit entry_edit;
  private FeatureDisplay feature_display;
  private Selection selection;
tjc's avatar
tjc committed
  private JPanel mainPanel = new JPanel();
  private CoveragePanel coveragePanel;
tjc's avatar
tjc committed
  private SnpPanel snpPanel;
tcarver's avatar
tcarver committed
  protected boolean logScale = false;
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 boolean asynchronous = true;
  private boolean showBaseAlignment = false;
  private JMenu bamFilesMenu = new JMenu("BAM files");
  private JCheckBoxMenuItem logMenuItem = new JCheckBoxMenuItem("Use Log Scale", logScale);
  
  private JCheckBoxMenuItem cbStackView = new JCheckBoxMenuItem("Stack", true);
  private JCheckBoxMenuItem cbPairedStackView = new JCheckBoxMenuItem("Paired Stack");
  private JCheckBoxMenuItem cbStrandStackView = new JCheckBoxMenuItem("Strand Stack");
  private JCheckBoxMenuItem cbIsizeStackView = new JCheckBoxMenuItem("Inferred Size", false);
  private JCheckBoxMenuItem cbCoverageView = new JCheckBoxMenuItem("Coverage", false);
  private JCheckBoxMenuItem cbCoverageStrandView = new JCheckBoxMenuItem("Coverage by Strand", false);
tcarver's avatar
tcarver committed
  private JCheckBoxMenuItem cbCoverageHeatMap = new JCheckBoxMenuItem("Coverage Heat Map", false);
  private JCheckBoxMenuItem cbLastSelected;
  
  private ButtonGroup buttonGroup = new ButtonGroup();
  
tcarver's avatar
tcarver committed
  private JCheckBoxMenuItem colourByReadGrp = new JCheckBoxMenuItem("Read Group");
  private JCheckBoxMenuItem colourByStrandTag = new JCheckBoxMenuItem("RNASeq Strand Specific Tag (XS)");
  private JCheckBoxMenuItem colourByCoverageColour = new JCheckBoxMenuItem("Coverage Plot Colours");
  private JCheckBoxMenuItem baseQualityColour = new JCheckBoxMenuItem("Base Quality");
tjc's avatar
tjc committed
  private JCheckBoxMenuItem markInsertions = new JCheckBoxMenuItem("Mark Insertions", true);
    AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f);
  
tcarver's avatar
tcarver committed
  private ReadGroupsFrame readGrpFrame;
tcarver's avatar
tcarver committed
  private GroupBamFrame groupsFrame = new GroupBamFrame(this, bamFilesMenu);
  private CoveragePanel coverageView = new CoveragePanel();
  
tcarver's avatar
tcarver committed
  protected static String BAM_SUFFIX = ".*\\.(bam|cram)$";
  /** Used to colour the frames. */
  private static Color LIGHT_GREY = new Color(200, 200, 200);
  private static Color DARK_GREEN = new Color(0, 150, 0);
  private static Color DARK_ORANGE = new Color(255,140,0);
  private static Color DEEP_PINK   = new Color(139,10,80);
  private Point lastMousePoint = null;
  private BamViewRecord mouseOverSAMRecord = null;
  private BamViewRecord highlightSAMRecord = null;
  private String mouseOverInsertion;
  // record of where a mouse drag starts
  protected int dragStart = -1;
  private static int MAX_BASES = 26000;
  private int maxHeight = 800;
tjc's avatar
tjc committed
  private boolean concatSequences = false;
tjc's avatar
tjc committed
  private int ALIGNMENT_PIX_PER_BASE;
  private JPopupMenu popup;
  private PopupMessageFrame popFrame = new PopupMessageFrame();
  private PopupMessageFrame waitingFrame = new PopupMessageFrame("waiting...");
  private ExecutorService bamReadTaskExecutor;
  private int MAX_COVERAGE = Integer.MAX_VALUE;
tcarver's avatar
tcarver committed
  private float readLnHgt = 2.0f;
  
tjc's avatar
tjc committed
  public static org.apache.log4j.Logger logger4j = 
    org.apache.log4j.Logger.getLogger(BamView.class);
  
  public BamView(List<String> bamList, 
                String reference,
                int nbasesInView,
                final EntryEdit entry_edit,
                final FeatureDisplay feature_display,
                final Bases bases,
                final JPanel containerPanel,
                final JFrame frame)
  {
    this(bamList, reference, nbasesInView, feature_display, bases, containerPanel, frame);
    this.entry_edit = entry_edit;
  }
  
  public BamView(List<String> bamList, 
tjc's avatar
tjc committed
                 String reference,
tjc's avatar
tjc committed
                 int nbasesInView,
                 final FeatureDisplay feature_display,
                 final Bases bases,
tjc's avatar
tjc committed
                 final JPanel containerPanel,
tjc's avatar
tjc committed
                 final JFrame frame)
tjc's avatar
tjc committed
  {
    super();
    setBackground(Color.white);
    this.bamList = bamList;
tjc's avatar
tjc committed
    this.nbasesInView = nbasesInView;
tjc's avatar
tjc committed
    this.feature_display = feature_display;
    this.bases = bases;
tjc's avatar
tjc committed

tjc's avatar
tjc committed
    containerPanel.setLayout(new BoxLayout(containerPanel, BoxLayout.Y_AXIS));
    containerPanel.add(mainPanel);
    // filter out unmapped reads by default
    setSamRecordFlagPredicate(
        new SAMRecordFlagPredicate(SAMRecordFlagPredicate.READ_UNMAPPED_FLAG));
    
tjc's avatar
tjc committed
    if(reference != null)
    {
      System.setProperty("reference", reference); // for CRAM
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
    
    if(Options.getOptions().getIntegerProperty("bam_read_thread") != null)
    { 
      logger4j.debug("BAM READ THREADS="+Options.getOptions().getIntegerProperty("bam_read_thread"));
      bamReadTaskExecutor = Executors.newFixedThreadPool(
          Options.getOptions().getIntegerProperty("bam_read_thread"));
    }
    else
      bamReadTaskExecutor = Executors.newFixedThreadPool(1);
    
    
    if(Options.getOptions().getIntegerProperty("bam_max_coverage") != null)
    { 
      logger4j.debug("BAM MAX COVERAGE="+Options.getOptions().getIntegerProperty("bam_max_coverage"));
      MAX_COVERAGE = Options.getOptions().getIntegerProperty("bam_max_coverage");
    }  

    try
    {
      readHeaderPicard();
    }
    catch(java.lang.UnsupportedClassVersionError err)
    {
      JOptionPane.showMessageDialog(null, 
          "This requires Java 1.6 or higher.", 
          "Check Java Version", JOptionPane.WARNING_MESSAGE);
    }
tjc's avatar
tjc committed
    catch (IOException e)
    {
      e.printStackTrace();
    }
tjc's avatar
tjc committed

    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);
    }
tjc's avatar
tjc committed

    setFont(Options.getOptions().getFont());
tjc's avatar
tjc committed
    FontMetrics fm  = getFontMetrics(getFont());
    ALIGNMENT_PIX_PER_BASE = fm.charWidth('M');
    BASE_HEIGHT = fm.getMaxAscent();
    selection = new Selection(null);
    
    MultiLineToolTipUI.initialize();
    setToolTipText("");
    
    buttonGroup.add(cbStackView);
    buttonGroup.add(cbPairedStackView);
    buttonGroup.add(cbStrandStackView);
    buttonGroup.add(cbIsizeStackView);
    buttonGroup.add(cbCoverageView);
    buttonGroup.add(cbCoverageStrandView);
tcarver's avatar
tcarver committed
    buttonGroup.add(cbCoverageHeatMap);
tjc's avatar
tjc committed
    addMouseListener(new PopupListener());
tjc's avatar
tjc committed
    jspView = new JScrollPane(this, 
        JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
        JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    
tjc's avatar
tjc committed
    jspView.setViewportBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.DARK_GRAY));
tjc's avatar
tjc committed
    Border empty = new EmptyBorder(0,0,0,0);
    jspView.setBorder(empty);
    jspView.getVerticalScrollBar().setUnitIncrement(8);
tjc's avatar
tjc committed
    addBamToPanel(frame);

    // apply command line options
    if(System.getProperty("show_snps") != null)
      isSNPs = true;
    if(System.getProperty("show_snp_plot") != null)
    {
      isSNPplot = true;
      snpPanel.setVisible(isSNPplot);
    }
    if(System.getProperty("show_cov_plot") != null)
    {
      isCoverage = true;
      coveragePanel.setVisible(isCoverage);
    }
  }
  
  public String getToolTipText()
  {
    if(isCoverageView(getPixPerBaseByWidth()) && lastMousePoint != null)
      return coverageView.getToolTipText(
          lastMousePoint.y-getJspView().getViewport().getViewPosition().y);
    
    if(mouseOverSAMRecord == null)
      return null;
    
    String msg = 
        mouseOverSAMRecord.sam.getReadName() + "\n" + 
        mouseOverSAMRecord.sam.getAlignmentStart() + ".." +
tcarver's avatar
tcarver committed
        mouseOverSAMRecord.sam.getAlignmentEnd() + 
       (mouseOverSAMRecord.sam.getReadGroup() != null ? "\nRG="+mouseOverSAMRecord.sam.getReadGroup().getId() : "") +
        "\nisize=" + mouseOverSAMRecord.sam.getInferredInsertSize() + 
        "\nmapq=" + mouseOverSAMRecord.sam.getMappingQuality()+
        "\nrname="+ mouseOverSAMRecord.sam.getReferenceName();
    if( mouseOverSAMRecord.sam.getReadPairedFlag() && 
        mouseOverSAMRecord.sam.getProperPairFlag() && 
       !mouseOverSAMRecord.sam.getMateUnmappedFlag())
tjc's avatar
tjc committed
        "\nstrand (read/mate): "+
       (mouseOverSAMRecord.sam.getReadNegativeStrandFlag() ? "-" : "+")+" / "+
       (mouseOverSAMRecord.sam.getMateNegativeStrandFlag() ? "-" : "+");
tjc's avatar
tjc committed
        "\nstrand (read/mate): "+
       (mouseOverSAMRecord.sam.getReadNegativeStrandFlag() ? "-" : "+");
    
    if(msg != null && mouseOverInsertion != null)
      msg = msg + "\nInsertion at:" +mouseOverInsertion;
    
    return msg;
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed
  
  /**
   * Get the BAM index file from the list
   * @param bam
   * @return
   * @throws IOException
   */
  private File getBamIndexFile(String bam) throws IOException
  {
    File bamIndexFile = null;
    if (bam.startsWith("http") || bam.startsWith("ftp"))
tjc's avatar
tjc committed
    {
      final URL urlBamIndexFile = new URL(bam + ".bai");
      InputStream is = urlBamIndexFile.openStream();
tjc's avatar
tjc committed

tjc's avatar
tjc committed
      // Create temp file.
      bamIndexFile = File.createTempFile(urlBamIndexFile.getFile().replaceAll(
          "[\\/\\s]", "_"), ".bai");
      bamIndexFile.deleteOnExit();

      FileOutputStream out = new FileOutputStream(bamIndexFile);
      int c;
      while ((c = is.read()) != -1)
        out.write(c);
      out.flush();
      out.close();
      is.close();

tjc's avatar
tjc committed
      logger4j.debug("create... " + bamIndexFile.getAbsolutePath());
tjc's avatar
tjc committed
    }
    else
tjc's avatar
tjc committed
      bamIndexFile = new File(bam + ".bai");
      if(!bamIndexFile.exists())
tcarver's avatar
tcarver committed
      {
        final File cramIndexFile = new File(bam + ".crai");
        if(cramIndexFile.exists())
tcarver's avatar
tcarver committed
        {
          logger4j.debug(
                 "ERROR: CRAM INDEX FILE ("+cramIndexFile.getName()+ 
                 ") EXPECTING A BAM INDEX FILE (USE THIS OPTION --bam-style-index) ");
tcarver's avatar
tcarver committed
          return cramIndexFile;
tcarver's avatar
tcarver committed
        }
tcarver's avatar
tcarver committed
      }
tjc's avatar
tjc committed

    return bamIndexFile;
  }
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
  /**
   * Get the SAM file reader.
   * @param bam
   * @return
   * @throws IOException
   */
  private SAMFileReader getSAMFileReader(final String bam) throws IOException
  {
    // parsing of the header happens during SAMFileReader construction, 
    // so need to set the default stringency
    SAMFileReader.setDefaultValidationStringency(ValidationStringency.LENIENT);
    
tjc's avatar
tjc committed
    if(samFileReaderHash.containsKey(bam))
      return samFileReaderHash.get(bam);
tjc's avatar
tjc committed
    File bamIndexFile = getBamIndexFile(bam);
      try
      {
        logger4j.warn("Index file not found so using picard to index the BAM.");
        // Use Picard to index the file
        // requires reads to be sorted by coordinate
        new BuildBamIndex().instanceMain(
tcarver's avatar
tcarver committed
          new String[]{ "I="+bam, "O="+bamIndexFile.getAbsolutePath(), "MAX_RECORDS_IN_RAM=50000", "VALIDATION_STRINGENCY=SILENT" });
      }
      catch(SAMException e)
      {
        String ls = System.getProperty("line.separator");
        String msg = 
            "BAM index file is missing. The BAM file needs to be sorted and indexed"+ls+
            "This can be done using samtools (http://samtools.sf.net/):"+ls+ls+
            "samtools sort <in.bam> <out.prefix>"+ls+
            "samtools index <sorted.bam>";
        
        throw new SAMException(msg);
      }
tjc's avatar
tjc committed
    final SAMFileReader samFileReader;
    if(feature_display != null && bam.endsWith("cram"))
    {
tcarver's avatar
tcarver committed
      // set log level
      net.sf.picard.util.Log.setGlobalLogLevel( 
          net.sf.picard.util.Log.LogLevel.ERROR);
      final CRAMReferenceSequenceFile ref = new CRAMReferenceSequenceFile(
        feature_display.getEntryGroup().getSequenceEntry(), this);
      
      final Map<Object, ReferenceSequenceFile> referenceFactory = 
          new HashMap<Object, ReferenceSequenceFile>();
      referenceFactory.put(bamIndexFile, ref);

      try
      {
tcarver's avatar
tcarver committed
        Class<?> cls = getClass().getClassLoader().loadClass("net.sf.samtools.ReferenceDiscovery");
        Field f = cls.getDeclaredField("referenceFactory");
        f.set(null, referenceFactory);
      }
      catch (ClassNotFoundException e)
      {
        System.err.println("Check cramtools.jar is in the CLASSPATH. "+e.getMessage());
      }
      catch (SecurityException e)
      {
        e.printStackTrace();
      }
      catch (NoSuchFieldException e)
      {
        e.printStackTrace();
      }
      catch (IllegalArgumentException e)
      {
        e.printStackTrace();
      }
      catch (IllegalAccessException e)
      {
        e.printStackTrace();
      }

      
      //net.sf.samtools.ReferenceDiscovery.referenceFactory.put(bamIndexFile, ref);
    }
    
    if(bam.startsWith("ftp"))
    {
      FTPSeekableStream fss = new FTPSeekableStream(new URL(bam));
      samFileReader = new SAMFileReader(fss, bamIndexFile, false);
    }
    else if(!bam.startsWith("http"))
tjc's avatar
tjc committed
    {
      File bamFile = new File(bam);
tjc's avatar
tjc committed
      samFileReader = new SAMFileReader(bamFile, bamIndexFile);
tjc's avatar
tjc committed
    }
    else
    {
      final URL urlBamFile = new URL(bam);
      samFileReader = new SAMFileReader(urlBamFile, bamIndexFile, false);
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
    samFileReader.setValidationStringency(ValidationStringency.SILENT);
    samFileReaderHash.put(bam, samFileReader);
tcarver's avatar
tcarver committed

    readGroups.addAll(samFileReader.getFileHeader().getReadGroups());
tjc's avatar
tjc committed
    return samFileReader;
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  private void readHeaderPicard() throws IOException
tjc's avatar
tjc committed
  {
tcarver's avatar
tcarver committed
    final SAMFileReader inputSam = getSAMFileReader(bamList.get(0));
    final SAMFileHeader header = inputSam.getFileHeader();

    for(SAMSequenceRecord seq: header.getSequenceDictionary().getSequences())
tjc's avatar
tjc committed
    {
tcarver's avatar
tcarver committed
      seqLengths.put(seq.getSequenceName(),
                     seq.getSequenceLength());
      seqNames.add(seq.getSequenceName());
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
  }
  
  class BamReadTask implements Runnable 
  {
    private int start; 
    private int end; 
    private short bamIndex; 
    private float pixPerBase;
    private CountDownLatch latch;
    BamReadTask(int start, int end, short bamIndex, float pixPerBase, CountDownLatch latch)  
    {
      this.start = start;
      this.end = end;
      this.bamIndex = bamIndex;
      this.pixPerBase = pixPerBase;
      this.latch = latch;
    }

    public void run() 
    {
      try
      {
        readFromBamPicard(start, end, bamIndex, pixPerBase) ;
      }
      catch (OutOfMemoryError ome)
      {
        throw ome;
      }
      catch(IOException me)
      {
        me.printStackTrace();
      }
      finally
      {
        latch.countDown();
      }
    }
  }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  /**
   * Read a SAM or BAM file.
tjc's avatar
tjc committed
   * @throws IOException 
tjc's avatar
tjc committed
   */
  private void readFromBamPicard(int start, int end, short bamIndex, float pixPerBase) 
tjc's avatar
tjc committed
          throws IOException
tjc's avatar
tjc committed
  {
    // Open the input file.  Automatically detects whether input is SAM or BAM
    // and delegates to a reader implementation for the appropriate format.
tcarver's avatar
tcarver committed
    final String bam = bamList.get(bamIndex);
tjc's avatar
tjc committed
    final SAMFileReader inputSam = getSAMFileReader(bam);
    
    //final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
    if(isConcatSequences())
tjc's avatar
tjc committed
    {
tcarver's avatar
tcarver committed
      for(String seq: seqNames)
tjc's avatar
tjc committed
      {
tcarver's avatar
tcarver committed
        int sLen = seqLengths.get(seq);
        int offset = getSequenceOffset(seq); 
        int sBeg = offset+1;
        int sEnd = sBeg+sLen-1;
tjc's avatar
tjc committed

        if( (sBeg >= start && sBeg <= end) ||
            (sBeg <= start && sEnd >= start) )
tjc's avatar
tjc committed
        {
          int thisStart = start - offset;
          if(thisStart < 1)
            thisStart = 1;
          int thisEnd   = end - offset;
tcarver's avatar
tcarver committed
          iterateOverBam(inputSam, seq, thisStart, thisEnd, bamIndex, pixPerBase, bam);
          //System.out.println("READ "+seq+"  "+thisStart+".."+thisEnd+" "+start+" --- "+offset);
tjc's avatar
tjc committed
        }
      }
    }
    else
    {
      String refName = (String) combo.getSelectedItem();
      iterateOverBam(inputSam, refName, start, end, bamIndex, pixPerBase, bam);
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
    //inputSam.close();
tjc's avatar
tjc committed
  }
  
  /**
   * Iterate over BAM file and load into the <code>List</code> of
   * <code>SAMRecord</code>.
   * @param inputSam
   * @param refName
   * @param start
   * @param end
   */
  private void iterateOverBam(final SAMFileReader inputSam, 
                             final String refName, final int start, final int end,
                             final short bamIndex, final float pixPerBase,
                             final String bam)
  {
    final MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
tcarver's avatar
tcarver committed
    final int checkMemAfter = 8000;
    final int seqOffset = getSequenceOffset(refName);
    final int offset = seqOffset- getBaseAtStartOfView();
    final boolean isCoverageView = isCoverageView(pixPerBase);
    int binSize = ((end-start)/nbins)+1;
    if(binSize < 1)
    {
      binSize = 1;
      nbins = end-start+1;
    }
    int max = MAX_COVERAGE*binSize;
    if(max < 1)
      max = Integer.MAX_VALUE;
    final int cov[] = new int[nbins];
    for(int i=0; i<nbins; i++)
      cov[i] = 0;
tcarver's avatar
tcarver committed

    final CloseableIterator<SAMRecord> it = inputSam.queryOverlapping(refName, start, end);
    try
tjc's avatar
tjc committed
    {
      while ( it.hasNext() )
tjc's avatar
tjc committed
      {
          cnt++;
          SAMRecord samRecord = it.next();

tcarver's avatar
tcarver committed
          if(readGrpFrame != null && !readGrpFrame.isReadGroupVisible(samRecord.getReadGroup()))
            continue;
          
          if( samRecordFlagPredicate == null ||
             !samRecordFlagPredicate.testPredicate(samRecord))
            if(samRecordMapQPredicate == null ||
               samRecordMapQPredicate.testPredicate(samRecord))
            {
              int abeg = samRecord.getAlignmentStart();
              int aend = samRecord.getAlignmentEnd();
              boolean over = false;
              
              for(int i=abeg; i<aend; i++)
              {
                int bin = ((i-start)/binSize)-1;
                if(bin < 0)
tcarver's avatar
tcarver committed
                  continue;
                else if(bin > nbins-1)
                  bin = nbins-1;
                cov[bin]++;
                if(cov[bin] > max)
                {
                  over = true;
                  break;
                }
              }
             
              if(over)
                continue;

              if(isCoverageView)
                coverageView.addRecord(samRecord, offset, bam, colourByStrandTag.isSelected());
                coveragePanel.addRecord(samRecord, offset, bam, colourByStrandTag.isSelected());
              if(isSNPplot)
                snpPanel.addRecord(samRecord, seqOffset);
              if(!isCoverageView)
                readsInView.add(new BamViewRecord(samRecord, bamIndex));
            }
          if(cnt > checkMemAfter)
tjc's avatar
tjc committed
          {
            cnt = 0;
            float heapFraction =
              (float)((float)memory.getHeapMemoryUsage().getUsed()/
                      (float)memory.getHeapMemoryUsage().getMax());
            logger4j.debug("Heap memory usage (used/max): "+heapFraction);
          
            if(readsInView.size() > checkMemAfter*2 && !waitingFrame.isVisible())
              waitingFrame.showWaiting("loading...", mainPanel);

            if(heapFraction > 0.90) 
            {
              popFrame.show(
                "Using > 90 % of the maximum memory limit:"+
                (memory.getHeapMemoryUsage().getMax()/1000000.f)+" Mb.\n"+
                "Not all reads in this range have been read in. Zoom in or\n"+
                "consider increasing the memory for this application.",
                mainPanel,
                15000);
              break;
            }
tjc's avatar
tjc committed
          }
        catch(Exception e)
        {
          System.err.println(e.getMessage());
        }
tjc's avatar
tjc committed
      }
    }
tjc's avatar
tjc committed
  }

  private int getSequenceLength()
  {
    if(isConcatSequences())
tjc's avatar
tjc committed
    {
      int len = 0;
tcarver's avatar
tcarver committed
      for(String seq: seqNames)
        len += seqLengths.get(seq);
tjc's avatar
tjc committed
      return len;
    }
    else
      return seqLengths.get((String) combo.getSelectedItem());
  }
  
tjc's avatar
tjc committed
  /**
   * For BAM files with multiple references sequences, calculate
   * the offset from the start of the concatenated sequence for 
   * a given reference.
   * @param refName
   * @return
   */
tjc's avatar
tjc committed
  protected int getSequenceOffset(String refName)
  {
    if(!isConcatSequences())
tjc's avatar
tjc committed
      return 0;
    {
      if(feature_display == null)
        offsetLengths = new HashMap<String, Integer>(combo.getItemCount());
        int offset = 0;
        for(int i=0; i<combo.getItemCount(); i++)
        {
          String thisSeqName = (String) combo.getItemAt(i);
          offsetLengths.put(thisSeqName, offset);
          offset += seqLengths.get(combo.getItemAt(i));
        }
        return offsetLengths.get(refName);
      }
      final FeatureVector features = feature_display.getEntryGroup().getAllFeatures();
      final HashMap<String, Integer> lookup = new HashMap<String, Integer>();
      for(int i=0; i<features.size(); i++)
        lookup.put(features.elementAt(i).getIDString(), features.elementAt(i).getFirstBase());
        
      offsetLengths = new HashMap<String, Integer>(seqNames.size());
      for(int i=0; i<seqNames.size(); i++)
      {
        final Integer pos = lookup.get(seqNames.get(i));
        if(pos != null)
          offsetLengths.put(seqNames.get(i), pos-1);
       /*final FeatureContigPredicate predicate = new FeatureContigPredicate(seqNames.get(i).trim());
        for(int j=0; j<features.size(); j++)
        {
          if(predicate.testPredicate(features.elementAt(j)))
          {
            offsetLengths.put(seqNames.get(i), features.elementAt(j).getFirstBase()-1);
            break;
          }
      
      if(offsetLengths.size() != seqNames.size())
tjc's avatar
tjc committed
      {
tjc's avatar
tjc committed
        System.err.println("Found: "+offsetLengths.size() +" of "+ seqNames.size());
        SwingUtilities.invokeLater(new Runnable() 
        {
          public void run() 
          {
            JOptionPane.showMessageDialog(BamView.this, 
                "There is a problem matching the reference sequences\n"+
                "to the names in the BAM file. This may mean the labels\n"+
                "on the reference features do not match those in the in\n"+
                "the BAM file.", 
                "Problem Found", JOptionPane.WARNING_MESSAGE);
          }
        });
        //concatSequences = false;
        int offset = 0;
        for(int i=0; i<combo.getItemCount(); i++)
        {
          String thisSeqName = (String) combo.getItemAt(i);
          offsetLengths.put(thisSeqName, offset);
          offset += seqLengths.get(combo.getItemAt(i));
        }
        //return 0;
tjc's avatar
tjc committed
      }
tjc's avatar
tjc committed
    }
    return offsetLengths.get(refName);
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed
  /**
   * Override
   */
  protected void paintComponent(Graphics g)
tjc's avatar
tjc committed
  {
	super.paintComponent(g);
	Graphics2D g2 = (Graphics2D)g;

	mouseOverSAMRecord = null;
tcarver's avatar
tcarver committed
    final int seqLength = getSequenceLength();
    int start;
tjc's avatar
tjc committed
    int end;
    
    if(startBase > 0)
      start = startBase;
    else
tjc's avatar
tjc committed
      start = getBaseAtStartOfView();
    
    if(endBase > 0)
      end = endBase;
    else
tjc's avatar
tjc committed
    {
tjc's avatar
tjc committed
      end   = start + nbasesInView - 1;
      if(end > seqLength)
        end = seqLength;
      
      if(feature_display != null && nbasesInView < feature_display.getMaxVisibleBases())
        nbasesInView = feature_display.getMaxVisibleBases();
tjc's avatar
tjc committed
    }
tcarver's avatar
tcarver committed
    final float pixPerBase = getPixPerBaseByWidth();
    boolean changeToStackView = false;
    MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
tjc's avatar
tjc committed
    if(laststart != start ||
       lastend   != end ||
       CoveragePanel.isRedraw())
tjc's avatar
tjc committed
    {
      if(isCoverageView(pixPerBase))
        coverageView.init(this, pixPerBase, start, end);
      if(isCoverage)
        coveragePanel.init(this, pixPerBase, start, end);
      if(isSNPplot)
        snpPanel.init(this, pixPerBase, start, end);
tjc's avatar
tjc committed
      {
        try
        {
          float heapFractionUsedBefore = (float) ((float) memory.getHeapMemoryUsage().getUsed() / 
                                                  (float) memory.getHeapMemoryUsage().getMax());
          if(readsInView == null)
            readsInView = new Vector<BamViewRecord>();
          else
            readsInView.clear();
tcarver's avatar
tcarver committed
          final CountDownLatch latch = new CountDownLatch(bamList.size()-hideBamList.size());
          //long ms = System.currentTimeMillis();
          for(short i=0; i<bamList.size(); i++)
          {
            if(!hideBamList.contains(i))
              bamReadTaskExecutor.execute(
                  new BamReadTask(start, end, i, pixPerBase, latch));

          try 
          {
            latch.await();
          }
          catch (InterruptedException e) {} // TODO 

          //System.out.println("===== NO. THREADS="+
          //     ((java.util.concurrent.ThreadPoolExecutor)bamReadTaskExecutor).getPoolSize()+" TIME="+(System.currentTimeMillis()-ms));

          float heapFractionUsedAfter = (float) ((float) memory.getHeapMemoryUsage().getUsed() / 
                                                 (float) memory.getHeapMemoryUsage().getMax());

          // System.out.println("Heap Max  : "+memory.getHeapMemoryUsage().getMax());
          // System.out.println("Heap Used : "+memory.getHeapMemoryUsage().getUsed());
          // System.out.println("Heap memory used "+heapFractionUsedAfter);

          if ((heapFractionUsedAfter - heapFractionUsedBefore) > 0.06
              && !isStackView() && heapFractionUsedAfter > 0.8)
            cbStackView.setSelected(true);
          if((!isStackView() && !isStrandStackView()) || isBaseAlignmentView(pixPerBase))
          {
            Collections.sort(readsInView, new SAMRecordComparator());
          }
          else if( (isStackView() || isStrandStackView()) &&
tjc's avatar
tjc committed
              bamList.size() > 1)
          {
            // merge multiple BAM files
            Collections.sort(readsInView, new SAMRecordPositionComparator(BamView.this));
tjc's avatar
tjc committed
          }
        }
        catch (OutOfMemoryError ome)
          JOptionPane.showMessageDialog(this, "Out of Memory");
          readsInView.clear();
          return;
        catch(net.sf.samtools.util.RuntimeIOException re)
        {
          JOptionPane.showMessageDialog(this, re.getMessage());
        }
tjc's avatar
tjc committed
      }
    }
tjc's avatar
tjc committed
    laststart = start;
    lastend   = end;
tcarver's avatar
tcarver committed
    // this needs to be synchronized when cloning BAM window
    synchronized(this)
    {
      if(showBaseAlignment)
	    drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
tcarver's avatar
tcarver committed
	  {
	    if(isCoverageView(pixPerBase))
	      drawCoverage(g2,start, end, pixPerBase);
  	    else if(isStackView())  
	      drawStackView(g2, seqLength, pixPerBase, start, end);
	    else if(isPairedStackView())
	      drawPairedStackView(g2, seqLength, pixPerBase, start, end);
	    else if(isStrandStackView())
	      drawStrandStackView(g2, seqLength, pixPerBase, start, end);
	    else
	      drawLineView(g2, seqLength, pixPerBase, start, end);
	  }
    }
    
    if(isCoverage)
      coveragePanel.repaint();
    if(isSNPplot)
      snpPanel.repaint();
tjc's avatar
tjc committed
	if(waitingFrame.isVisible())
      waitingFrame.hideFrame();
tjc's avatar
tjc committed
	  popFrame.show(
          "Note :: Changed to the stack view to save memory.\n"+
          "Currently this is using "+ 
          (memory.getHeapMemoryUsage().getUsed()/1000000.f)+" Mb "+
          "and the maximum\nmemory limit is "+
          (memory.getHeapMemoryUsage().getMax()/1000000.f)+" Mb.",
tjc's avatar
tjc committed
          mainPanel,
tjc's avatar
tjc committed
  }
  
tcarver's avatar
tcarver committed
  protected void repaintBamView()
  {
    laststart = -1;
    repaint();
  }
tjc's avatar
tjc committed
  
  private float getPixPerBaseByWidth()
  {
    return (float)mainPanel.getWidth() / (float)nbasesInView;
tjc's avatar
tjc committed
  }
  
  
tjc's avatar
tjc committed
  private int getMaxBasesInPanel(int seqLength)
  {
    if(feature_display == null)
tjc's avatar
tjc committed
      return 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
   */
  private void drawBaseAlignment(Graphics2D g2, 
                                 int seqLength, 
                                 float pixPerBase, 
                                 final int start, 
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    ruler.start = start;
    ruler.end = end;
    String refSeq = null;
tjc's avatar
tjc committed
    int refSeqStart = start;
    
    end = start + ( mainPanel.getWidth() * ALIGNMENT_PIX_PER_BASE );
tjc's avatar
tjc committed
    if(bases != null)
    {
      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();
tjc's avatar
tjc committed
        
tjc's avatar
tjc committed
      }
      catch (OutOfRangeException e)
      {
        e.printStackTrace();
      }
    }
    ruler.repaint();
    drawSelectionRange(g2, ALIGNMENT_PIX_PER_BASE, start, end, Color.PINK);
    g2.setStroke(new BasicStroke (2.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
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
    
    Rectangle r = jspView.getViewport().getViewRect();
    int nreads = readsInView.size();
    
tjc's avatar
tjc committed
    for (int i = 0; i < nreads; i++)
tjc's avatar
tjc committed
    {
tjc's avatar
tjc committed
      try
tjc's avatar
tjc committed
      {
tjc's avatar
tjc committed
        if (!drawn[i])
tjc's avatar
tjc committed
        {
tjc's avatar
tjc committed
          ypos += 11;

          BamViewRecord thisRead = readsInView.get(i);
tjc's avatar
tjc committed
          if (ypos < r.getMaxY() || ypos > r.getMinY())
            drawSequence(g2, thisRead, ypos, refSeq, refSeqStart);
          drawn[i] = true;

          int thisEnd = thisRead.sam.getAlignmentEnd();
tjc's avatar
tjc committed
          if (thisEnd == 0)
            thisEnd = thisRead.sam.getAlignmentStart() + thisRead.sam.getReadLength();
tjc's avatar
tjc committed

          for (int j = i + 1; j < nreads; j++)
tjc's avatar
tjc committed
          {
tjc's avatar
tjc committed
            if (!drawn[j])
tjc's avatar
tjc committed
            {
              BamViewRecord nextRead = readsInView.get(j);
              int nextStart = nextRead.sam.getAlignmentStart();
tjc's avatar
tjc committed
              if (nextStart > thisEnd + 1)
              {
                if (ypos < r.getMaxY() || ypos > r.getMinY())
                  drawSequence(g2, nextRead, ypos, refSeq, refSeqStart);

                drawn[j] = true;
                thisEnd = nextRead.sam.getAlignmentEnd();
tjc's avatar
tjc committed
                if (thisEnd == 0)
                  thisEnd = nextStart + nextRead.sam.getReadLength();
tjc's avatar
tjc committed
              }
              else if (ypos > r.getMaxY() || ypos < r.getMinY())
                break;
tjc's avatar
tjc committed
            }
          }
        }
      }
tjc's avatar
tjc committed
      catch (ArrayIndexOutOfBoundsException ae)
      {
        System.err.println(readsInView.size()+"  "+nreads);
        ae.printStackTrace();
      }
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
   */
tcarver's avatar
tcarver committed
  private void drawSequence(final Graphics2D g2, final BamViewRecord bamViewRecord, 
                            int ypos, String refSeq, int refSeqStart)
tjc's avatar
tjc committed
  {
    SAMRecord samRecord = bamViewRecord.sam;
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);
    
    final Color col = g2.getColor();
tjc's avatar
tjc committed
    int xpos;
    int len    = 0;
    int refPos = 0;
tcarver's avatar
tcarver committed
    final String readSeq = samRecord.getReadString();
    final int offset = getSequenceOffset(samRecord.getReferenceName());
    byte[] phredQuality = null;
    if(baseQualityColour.isSelected())
      phredQuality = samRecord.getBaseQualities();

    Hashtable<Integer, String> insertions = null;
    List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
    for(int i=0; i<blocks.size(); i++)
tjc's avatar
tjc committed
    {
      AlignmentBlock block = blocks.get(i);
      int blockStart = block.getReadStart();
      len += block.getLength();
      for(int j=0; j<block.getLength(); j++)
tjc's avatar
tjc committed
      {
        int readPos = blockStart-1+j;
        xpos = block.getReferenceStart() - 1 + j + offset;
        refPos = xpos - refSeqStart + 1;

        if(phredQuality != null)
          setColourByBaseQuality(g2, phredQuality[readPos]);
tjc's avatar
tjc committed
        if(isSNPs && refSeq != null && refPos > 0 && refPos < refSeq.length())
tcarver's avatar
tcarver committed
          if(Character.toUpperCase(readSeq.charAt(readPos)) != refSeq.charAt(refPos))
            g2.setColor(Color.red);
          else
            g2.setColor(col);
        }
tjc's avatar
tjc committed
        g2.drawString(readSeq.substring(readPos, readPos+1), 
                      refPos*ALIGNMENT_PIX_PER_BASE, ypos);
tjc's avatar
tjc committed
      }
      // look for insertions
      if(markInsertions.isSelected() && i < blocks.size()-1)
      {
        int blockEnd = blockStart+block.getLength();
        int nextBlockStart = blocks.get(i+1).getReadStart();
        int insertSize = nextBlockStart - blockEnd;
        if(insertSize > 0)
        {
          if(insertions == null)
            insertions = new Hashtable<Integer, String>();
tjc's avatar
tjc committed

          g2.setColor(DEEP_PINK);
tjc's avatar
tjc committed

          int xscreen = (refPos+1)*ALIGNMENT_PIX_PER_BASE;
          insertions.put(xscreen, 
              readSeq.substring(blockEnd-1, nextBlockStart-1));
          g2.drawLine(xscreen, ypos, xscreen, ypos-BASE_HEIGHT);
          
          // mark on reference sequence as well
          if(bases != null)
            g2.drawLine(xscreen, 11, xscreen, 11-BASE_HEIGHT);
          g2.setColor(col);
        }
      }
      
      // highlight
      if(highlightSAMRecord != null &&
         highlightSAMRecord.sam.getReadName().equals(samRecord.getReadName()))
      {
        refPos =  block.getReferenceStart() + offset - refSeqStart;
        int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
        int width  = block.getLength()*ALIGNMENT_PIX_PER_BASE;
        Color col1 = g2.getColor();
        g2.setColor(Color.red);
        g2.drawRect(xstart, ypos-BASE_HEIGHT, width, BASE_HEIGHT);        
        if(i < blocks.size()-1)
        {
          int nextStart = 
            (blocks.get(i+1).getReferenceStart() + offset - refSeqStart)*ALIGNMENT_PIX_PER_BASE;
          g2.drawLine(xstart+width, ypos-(BASE_HEIGHT/2), nextStart, ypos-(BASE_HEIGHT/2));
        }
        
        g2.setColor(col1);
      }
      else if(i < blocks.size()-1)
      {
        refPos =  block.getReferenceStart() + offset - refSeqStart;
        int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
        int width  = block.getLength()*ALIGNMENT_PIX_PER_BASE;
        int nextStart = 
          (blocks.get(i+1).getReferenceStart() + offset - refSeqStart)*ALIGNMENT_PIX_PER_BASE;
        g2.drawLine(xstart+width, ypos-(BASE_HEIGHT/2), nextStart, ypos-(BASE_HEIGHT/2));
      }
tjc's avatar
tjc committed
    }
    if(lastMousePoint != null && blocks.size() > 0)
      refPos = blocks.get(0).getReferenceStart()+offset-refSeqStart;
tjc's avatar
tjc committed
      int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
      
      refPos = blocks.get(blocks.size()-1).getReferenceStart()+
               blocks.get(blocks.size()-1).getLength()+offset-refSeqStart;
tjc's avatar
tjc committed
      int xend   = (refPos+len)*ALIGNMENT_PIX_PER_BASE;

      if(lastMousePoint.getY() > ypos-11 && lastMousePoint.getY() < ypos)
      if(lastMousePoint.getX() > xstart &&
         lastMousePoint.getX() < xend)
      {
        mouseOverSAMRecord = bamViewRecord;
        if(insertions != null)
          mouseOverInsertion = insertions.get((int)lastMousePoint.getX());
tjc's avatar
tjc committed
  }
  
  /**
   * Colour bases on their mapping quality.
   * @param g2
   * @param baseQuality
   */
  private void setColourByBaseQuality(Graphics2D g2, byte baseQuality)
  {
    if (baseQuality < 10)
      g2.setColor(Color.blue);
    else if (baseQuality < 20)
      g2.setColor(DARK_GREEN);
    else if (baseQuality < 30)
      g2.setColor(DARK_ORANGE);
tjc's avatar
tjc committed
  /**
   * Draw inferred size view.
tjc's avatar
tjc committed
   * @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, Color.PINK);
tjc's avatar
tjc committed
    if(isShowScale())
tjc's avatar
tjc committed
      drawScale(g2, start, end, pixPerBase, getHeight());
tjc's avatar
tjc committed
    
tcarver's avatar
tcarver committed
    final Stroke stroke =
tcarver's avatar
tcarver committed
      new BasicStroke (readLnHgt, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
    g2.setStroke(stroke);
tcarver's avatar
tcarver committed
    int ydiff = (int) Math.round(1.5*readLnHgt);
tcarver's avatar
tcarver committed
    final int scaleHeight;
    if(isShowScale())
      scaleHeight = 15;
    else
      scaleHeight = 0;
tjc's avatar
tjc committed
    
    int baseAtStartOfView = getBaseAtStartOfView();
    Rectangle r = jspView.getViewport().getViewRect();
tjc's avatar
tjc committed
    for(int i=0; i<readsInView.size(); i++)
    {
      BamViewRecord bamViewRecord = readsInView.get(i);
      SAMRecord samRecord = bamViewRecord.sam;
      BamViewRecord bamViewNextRecord = null;
tjc's avatar
tjc committed
      SAMRecord samNextRecord = null;      
tjc's avatar
tjc committed

tcarver's avatar
tcarver committed
      List<Integer> snps = getSNPs(samRecord);
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
      {
tjc's avatar
tjc committed
        if(isSingle)
tjc's avatar
tjc committed
        {
          int ypos = getYPos(scaleHeight, samRecord.getReadString().length()); // (getHeight() - scaleHeight) - samRecord.getReadString().length();
          if(ypos > r.getMaxY() || ypos < r.getMinY())
tjc's avatar
tjc committed
          g2.setColor(Color.black);
tcarver's avatar
tcarver committed
          drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
tjc's avatar
tjc committed
        }
tjc's avatar
tjc committed
        continue;
tjc's avatar
tjc committed
      }
tjc's avatar
tjc committed

      int ypos = getYPos(scaleHeight, Math.abs(samRecord.getInferredInsertSize()));
      if( (ypos > r.getMaxY() || ypos < r.getMinY()) && ypos > 0 )
tjc's avatar
tjc committed
      if(i < readsInView.size()-1)
      {
        bamViewNextRecord = readsInView.get(++i);
        samNextRecord = bamViewNextRecord.sam;
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.setColor(Color.LIGHT_GRAY);
            int offset1 = getSequenceOffset(samRecord.getReferenceName());
            int end1   = samRecord.getAlignmentEnd()+offset1-baseAtStartOfView;
            
            int offset2 = getSequenceOffset(samNextRecord.getReferenceName());
            int start2  = samNextRecord.getAlignmentStart()+offset2-baseAtStartOfView;
            
            drawTranslucentLine(g2, 
                   (int)(end1*pixPerBase), (int)(start2*pixPerBase), ypos);
tjc's avatar
tjc committed
          }
tjc's avatar
tjc committed
          
          if(colourByCoverageColour.isSelected())
            g2.setColor(getColourByCoverageColour(bamViewRecord));
          else if( (samRecord.getReadNegativeStrandFlag() && // strand of the query (1 for reverse)
                    samNextRecord.getReadNegativeStrandFlag()) ||
                   (!samRecord.getReadNegativeStrandFlag() && 
                    !samNextRecord.getReadNegativeStrandFlag()))
tjc's avatar
tjc committed
            g2.setColor(Color.red);
          else
            g2.setColor(Color.blue);
tcarver's avatar
tcarver committed
          drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
          drawRead(g2, bamViewNextRecord, pixPerBase, ypos, baseAtStartOfView, getSNPs(samNextRecord), ydiff);
tjc's avatar
tjc committed
        }
        else
tjc's avatar
tjc committed
        {
tcarver's avatar
tcarver committed
          drawLoneRead(g2, bamViewRecord, ypos, pixPerBase, baseAtStartOfView, scaleHeight, snps, ydiff);
tjc's avatar
tjc committed
          i--;
tjc's avatar
tjc committed
        }
      }
      else
      {
tcarver's avatar
tcarver committed
        drawLoneRead(g2, bamViewRecord, ypos, pixPerBase, baseAtStartOfView, scaleHeight, snps, ydiff);
tjc's avatar
tjc committed
      }
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
    drawYScale(g2, scaleHeight);
tjc's avatar
tjc committed
  }
  
  private int getYPos(int scaleHeight, int size)
  {
    if(!logScale)
tcarver's avatar
tcarver committed
      return (getHeight() - scaleHeight) - size;
    else
    {
      int logInfSize = (int)( Math.log(size) * 100);
tcarver's avatar
tcarver committed
      return (getHeight() - scaleHeight) - logInfSize;
  /**
   * Draw the reads as lines in vertical stacks. The reads are colour 
   * coded as follows:
   * 
   * blue  - reads are unique and are paired with a mapped mate
   * black - reads are unique and are not paired or have an unmapped mate
   * green - reads are duplicates
   * 
   * @param g2
   * @param seqLength
   * @param pixPerBase
   * @param start
   * @param end
   */
  private void drawStackView(Graphics2D g2, 
tcarver's avatar
tcarver committed
                             final int seqLength, 
                             final float pixPerBase, 
                             final int start, 
                             final int end)
    drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
    if(isShowScale())
tjc's avatar
tjc committed
      drawScale(g2, start, end, pixPerBase, getHeight());
tcarver's avatar
tcarver committed
    final BasicStroke stroke = new BasicStroke(
tcarver's avatar
tcarver committed
        readLnHgt,
        BasicStroke.CAP_BUTT, 
        BasicStroke.JOIN_MITER);
    g2.setStroke(stroke);
tcarver's avatar
tcarver committed
    final int scaleHeight;
    if(isShowScale())
      scaleHeight = 15;
    else
      scaleHeight = 0;
    
    int ypos = (getHeight() - scaleHeight);
tcarver's avatar
tcarver committed
    int ydiff = (int) Math.round(1.5*readLnHgt);

    if(isOrientation)
tcarver's avatar
tcarver committed
      ydiff= 2*ydiff;
tcarver's avatar
tcarver committed
    final int baseAtStartOfView = getBaseAtStartOfView();
    g2.setColor(Color.blue);
tcarver's avatar
tcarver committed
    final Rectangle r = jspView.getViewport().getViewRect();
tcarver's avatar
tcarver committed
    for(BamViewRecord bamViewRecord: readsInView)
      SAMRecord samRecord = bamViewRecord.sam;
      int offset = getSequenceOffset(samRecord.getReferenceName());
      int recordStart = samRecord.getAlignmentStart()+offset;
      int recordEnd = samRecord.getAlignmentEnd()+offset;
tcarver's avatar
tcarver committed
      List<Integer> snps = getSNPs(samRecord);
tcarver's avatar
tcarver committed
      if(colourByCoverageColour.isSelected() || 
         colourByStrandTag.isSelected() ||
         colourByReadGrp.isSelected() ||
         lstStart != recordStart || lstEnd != recordEnd || snps != null)
      {
        if(colourByStrandTag.isSelected())
        {
          if(samRecord.getAttribute("XS") == null)
            g2.setColor(Color.BLACK); 
          else if( ((Character)samRecord.getAttribute("XS")).equals('+') )
            g2.setColor(Color.BLUE);
          else if( ((Character)samRecord.getAttribute("XS")).equals('-') )
            g2.setColor(Color.RED);
          else 
            g2.setColor(Color.BLACK); 
        }
        else if(colourByCoverageColour.isSelected())
          g2.setColor(getColourByCoverageColour(bamViewRecord));
tcarver's avatar
tcarver committed
        else if(colourByReadGrp.isSelected())
          g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, samRecord.getReadGroup()));
        else if (!samRecord.getReadPairedFlag() ||   // read is not paired in sequencing
                  samRecord.getMateUnmappedFlag() )  // mate is unmapped )  // mate is unmapped 
        if(maxEnd < recordStart || ypos < 0)
          ypos = (getHeight() - scaleHeight)-ydiff;
          ypos = ypos-ydiff;
        g2.setColor(DARK_GREEN);
      if(snps != null)
        lstStart = -1;
      else
      {
        lstStart = recordStart;
        lstEnd   = recordEnd;
      }
      if(ypos > r.getMaxY() || ypos < r.getMinY())
tcarver's avatar
tcarver committed
      drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
tjc's avatar
tjc committed
  /**
   * Draw the reads as lines in vertical stacks. The reads are colour 
   * coded as follows:
   * 
   * blue  - reads are unique and are paired with a mapped mate
   * black - reads are unique and are not paired or have an unmapped mate
   * green - reads are duplicates
   * 
   * @param g2
   * @param seqLength
   * @param pixPerBase
   * @param start
   * @param end
   */
  private void drawStrandStackView(Graphics2D g2, 
                                   int seqLength, 
                                   float pixPerBase, 
                                   int start, 
                                   int end)
  {
    drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);   
tcarver's avatar
tcarver committed
    final BasicStroke stroke = new BasicStroke(
tcarver's avatar
tcarver committed
        readLnHgt,
tjc's avatar
tjc committed
        BasicStroke.CAP_BUTT, 
        BasicStroke.JOIN_MITER);
    
tcarver's avatar
tcarver committed
    final int scaleHeight = 15;
tjc's avatar
tjc committed
    drawScale(g2, start, end, pixPerBase, ((getHeight()+scaleHeight)/2));

    int ymid = (getHeight()/ 2);
tcarver's avatar
tcarver committed
    int ydiff = (int) Math.round(1.5*readLnHgt);
    if(isOrientation)
tcarver's avatar
tcarver committed
      ydiff= 2*ydiff;
    
    g2.setStroke(stroke);
tjc's avatar
tjc committed
    // positive strand    
tcarver's avatar
tcarver committed
    drawStrand(g2, false, scaleHeight, ymid-(scaleHeight/2), -ydiff, pixPerBase);
tjc's avatar
tjc committed
    
    // negative strand
tcarver's avatar
tcarver committed
    drawStrand(g2, true, scaleHeight, ymid+(scaleHeight/2), ydiff, pixPerBase);
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  private void drawStrand(Graphics2D g2, 
                          boolean isStrandNegative, 
                          int scaleHeight,
                          int ymid,
                          int ystep,
tcarver's avatar
tcarver committed
                          float pixPerBase)
tjc's avatar
tjc committed
  {
    int hgt = getHeight();
    int ypos = (hgt - scaleHeight);
tjc's avatar
tjc committed
    int maxEnd = 0;
    int lstStart = 0;
    int lstEnd = 0;
    int baseAtStartOfView = getBaseAtStartOfView();
tjc's avatar
tjc committed
    g2.setColor(Color.blue);
    Rectangle r = jspView.getViewport().getViewRect();
tjc's avatar
tjc committed
    
tcarver's avatar
tcarver committed
    for(BamViewRecord bamViewRecord: readsInView)
tjc's avatar
tjc committed
    {
      SAMRecord samRecord = bamViewRecord.sam;
      if( isNegativeStrand(samRecord, colourByStrandTag.isSelected()) == isStrandNegative )
tjc's avatar
tjc committed
      {
tcarver's avatar
tcarver committed
        final int offset = getSequenceOffset(samRecord.getReferenceName());
        final int recordStart = samRecord.getAlignmentStart()+offset;
        final int recordEnd   = samRecord.getAlignmentEnd()+offset;
tcarver's avatar
tcarver committed
        List<Integer> snps = getSNPs(samRecord);
tcarver's avatar
tcarver committed
        if(colourByCoverageColour.isSelected() || 
           colourByStrandTag.isSelected() ||
           colourByReadGrp.isSelected() ||
           lstStart != recordStart || lstEnd != recordEnd || snps != null)
        {
          if(colourByStrandTag.isSelected())
          {
            if(samRecord.getAttribute("XS") == null)
              g2.setColor(Color.BLACK); 
            else if( ((Character)samRecord.getAttribute("XS")).equals('+') )
              g2.setColor(Color.BLUE);
            else if( ((Character)samRecord.getAttribute("XS")).equals('-') )
              g2.setColor(Color.RED);
            else 
              g2.setColor(Color.BLACK); 
          }
          else if(colourByCoverageColour.isSelected())
            g2.setColor(getColourByCoverageColour(bamViewRecord));
tcarver's avatar
tcarver committed
          else if(colourByReadGrp.isSelected())
            g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, samRecord.getReadGroup()));
          else if (!samRecord.getReadPairedFlag() ||   // read is not paired in sequencing
                    samRecord.getMateUnmappedFlag() )  // mate is unmapped 
tjc's avatar
tjc committed
            g2.setColor(Color.black);
          else
            g2.setColor(Color.blue);
        
          if(maxEnd < recordStart || ypos < 0 || ypos > hgt)
tjc's avatar
tjc committed
          {
            ypos = ymid + ystep;
            maxEnd = recordEnd+2;
          }
          else
            ypos = ypos + ystep;
        }
        else
          g2.setColor(DARK_GREEN);
tjc's avatar
tjc committed

        if(snps != null)
          lstStart = -1;
        else
        {
          lstStart = recordStart;
          lstEnd   = recordEnd;
        }
        if(ypos > r.getMaxY() || ypos < r.getMinY())
tcarver's avatar
tcarver committed
        drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ystep);
tjc's avatar
tjc committed
      }
    }
  }
   * Draw paired reads as lines in a vertical stacks. 
   * @param g2
   * @param seqLength
   * @param pixPerBase
   * @param start
   * @param end
   */
  private void drawPairedStackView(Graphics2D g2, 
tcarver's avatar
tcarver committed
                                   final int seqLength, 
                                   final float pixPerBase, 
                                   final int start, 
                                   final int end)
    drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
tjc's avatar
tjc committed
      drawScale(g2, start, end, pixPerBase, getHeight());

    final Vector<PairedRead> pairedReads = new Vector<PairedRead>();   
    for(int i=0; i<readsInView.size(); i++)
    {
      BamViewRecord bamViewRecord = readsInView.get(i);
      SAMRecord samRecord = bamViewRecord.sam;
      if( !samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
          samRecord.getMateUnmappedFlag() )  // mate is unmapped
        continue;

      BamViewRecord bamViewNextRecord = null;      
      if(i < readsInView.size()-1)
      {
        bamViewNextRecord = readsInView.get(++i);
        SAMRecord samNextRecord = bamViewNextRecord.sam;
        
        final PairedRead pr = new PairedRead();
        if(samRecord.getReadName().equals(samNextRecord.getReadName()) && 
           isFromSameBamFile(bamViewRecord, bamViewNextRecord, bamList))
        { 
          if(samRecord.getAlignmentStart() < samNextRecord.getAlignmentStart())
          {
            pr.sam1 = bamViewRecord;
            pr.sam2 = bamViewNextRecord;
            pr.sam2 = bamViewRecord;
            pr.sam1 = bamViewNextRecord;
        pairedReads.add(pr);
      }
    }
    Collections.sort(pairedReads, new PairedReadComparator());
    
tcarver's avatar
tcarver committed
    Stroke originalStroke = new BasicStroke (readLnHgt, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); 

    g2.setStroke( new BasicStroke (1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
tcarver's avatar
tcarver committed
    final int scaleHeight;
    if(isShowScale())
      scaleHeight = 15;
    else
      scaleHeight = 0;
    
tcarver's avatar
tcarver committed
    int ydiff = (int) Math.round(2.3*readLnHgt);
    if(isOrientation)
tcarver's avatar
tcarver committed
      ydiff= 2*ydiff;
    int ypos = getHeight() - scaleHeight - ydiff;
    int baseAtStartOfView = getBaseAtStartOfView();
    Rectangle r = jspView.getViewport().getViewRect();
tcarver's avatar
tcarver committed
    for(PairedRead pr: pairedReads)
      if(pr.sam1.sam.getAlignmentStart() > lastEnd)
        ypos = getHeight() - scaleHeight - ydiff;
          lastEnd = pr.sam2.sam.getAlignmentEnd();
          lastEnd = pr.sam1.sam.getAlignmentEnd();
        ypos = ypos - ydiff;
      if(ypos > r.getMaxY() || ypos < r.getMinY())
      g2.setStroke(originalStroke);
      
      if(highlightSAMRecord != null && 
          highlightSAMRecord.sam.getReadName().equals(pr.sam1.sam.getReadName()))
        g2.setColor(Color.black);
      else
        g2.setColor(Color.gray);
        if(!readsOverlap(pr.sam1.sam, pr.sam2.sam))
          int offset1 = getSequenceOffset(pr.sam1.sam.getReferenceName());
          int offset2 = getSequenceOffset(pr.sam2.sam.getReferenceName());
          drawTranslucentJointedLine(g2, 
              (int)((pr.sam1.sam.getAlignmentEnd()+offset1-getBaseAtStartOfView())*pixPerBase),
              (int)((pr.sam2.sam.getAlignmentStart()+offset2-getBaseAtStartOfView())*pixPerBase), ypos);
      else if(!pr.sam1.sam.getMateUnmappedFlag() &&
               pr.sam1.sam.getProperPairFlag() &&
               pr.sam1.sam.getMateReferenceName().equals(pr.sam1.sam.getReferenceName()))
        final int prStart, prEnd;
        if(pr.sam1.sam.getAlignmentStart() > pr.sam1.sam.getMateAlignmentStart())
          prStart = pr.sam1.sam.getMateAlignmentStart();
          prEnd = pr.sam1.sam.getAlignmentStart();
          prStart = pr.sam1.sam.getAlignmentEnd();
          prEnd = pr.sam1.sam.getMateAlignmentStart();
        int offset = getSequenceOffset(pr.sam1.sam.getReferenceName());
        drawTranslucentJointedLine(g2, 
              (int)( (prStart+offset-getBaseAtStartOfView())*pixPerBase),
              (int)( (prEnd  +offset-getBaseAtStartOfView())*pixPerBase), ypos);
      if(colourByCoverageColour.isSelected())
        g2.setColor(getColourByCoverageColour(pr.sam1));
      else if(colourByStrandTag.isSelected())
      {
        if( ((Character)pr.sam1.sam.getAttribute("XS")).equals('+') )
          g2.setColor(Color.BLUE);
        else if( ((Character)pr.sam1.sam.getAttribute("XS")).equals('-') )
          g2.setColor(Color.RED);
        else 
          g2.setColor(Color.BLACK); 
      }
tcarver's avatar
tcarver committed
      else if(colourByReadGrp.isSelected())
        g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, pr.sam1.sam.getReadGroup()));
      else if(   pr.sam2 != null && 
              !( pr.sam1.sam.getReadNegativeStrandFlag() ^ pr.sam2.sam.getReadNegativeStrandFlag() ) )
        g2.setColor(Color.red);
      else
        g2.setColor(Color.blue);
tcarver's avatar
tcarver committed
      drawRead(g2, pr.sam1, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam1.sam), ydiff);
tcarver's avatar
tcarver committed
        drawRead(g2, pr.sam2, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam2.sam), ydiff);
  /**
   * Check if a record is on the negative strand. If the RNA strand specific
   * checkbox is set then use the RNA strand.
   * @param samRecord
   * @param useStrandTag - strand specific tag
  protected static boolean isNegativeStrand(final SAMRecord samRecord, 
                                            final boolean useStrandTag) 
    {
      if(samRecord.getAttribute("XS") == null)
        return samRecord.getReadNegativeStrandFlag();
      if( ((Character)samRecord.getAttribute("XS")).equals('+') )
        return false;
      else
        return true;
    }
    return samRecord.getReadNegativeStrandFlag();
  }
  
  /**
   * Check if two records are from the same BAM file
   * @param sam1
   * @param sam2
   * @param bamList
   * @return
   */
  private boolean isFromSameBamFile(final BamViewRecord sam1, 
                                    final BamViewRecord sam2, 
                                    final List<String> bamList)
  {
    if(bamList == null || bamList.size()<2)
      return true;

    final short o1 = sam1.bamIndex;
    final short o2 = sam2.bamIndex;
    if(o1 != -1 && o2 != -1)
      if( o1 != o2 )
        return false;
    
    return true;
  }
  
  
  /**
   * Check if two records overlap
   * @param s1
   * @param s2
   * @return true id the two reads overlap
   */
  private boolean readsOverlap(final SAMRecord s1, 
                               final SAMRecord s2)
  {
    if( (s2.getAlignmentStart() >= s1.getAlignmentStart() &&
         s2.getAlignmentStart() <= s1.getAlignmentEnd()) ||
        (s2.getAlignmentEnd()   >= s1.getAlignmentStart() &&
         s2.getAlignmentEnd()   <= s1.getAlignmentEnd()) )
      return true;
    
    if( (s1.getAlignmentStart() >= s2.getAlignmentStart() &&
         s1.getAlignmentStart() <= s2.getAlignmentEnd()) ||
        (s1.getAlignmentEnd()   >= s2.getAlignmentStart() &&
         s1.getAlignmentEnd()   <= s2.getAlignmentEnd()) )
     return true;
    return false;
  }
  
  /**
   * Draw the read coverage.
   * @param g2
   * @param start
   * @param end
   * @param pixPerBase
   */
  private void drawCoverage(Graphics2D g2, int start, int end, float pixPerBase)
  {
    int scaleHeight = 0;
    if(isShowScale())
    {
      drawScale(g2, start, end, pixPerBase, getHeight());
      scaleHeight = 15;
    }

    int hgt = jspView.getVisibleRect().height-scaleHeight;
tcarver's avatar
tcarver committed
    if(!cbCoverageStrandView.isSelected() && !coverageView.isPlotHeatMap())
      try
      {
        int y = getHeight()-6-( (hgt* MAX_COVERAGE)/(coverageView.max/coverageView.windowSize) );
        g2.setColor(Color.YELLOW);
        // draw the threshold for the coverage max read cut-off
        g2.fillRect(0, y, getWidth(), 8);
      }
      catch(Exception e){}
tcarver's avatar
tcarver committed
    g2.translate(0, getJspView().getViewport().getViewPosition().y);

    coverageView.drawSelectionRange(g2, pixPerBase, start, end, getHeight(), Color.PINK);
tcarver's avatar
tcarver committed
    coverageView.draw(g2, getWidth(), hgt, hideBamList);
tcarver's avatar
tcarver committed
    if(!coverageView.isPlotHeatMap())
      coverageView.drawMax(g2);  
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
   */
  private void drawLoneRead(Graphics2D g2, BamViewRecord bamViewRecord, int ypos, 
tcarver's avatar
tcarver committed
      float pixPerBase, int baseAtStartOfView, int scaleHeight, List<Integer> snps, int ydiff)
tjc's avatar
tjc committed
  {
    SAMRecord samRecord = bamViewRecord.sam;
    boolean offTheTop = false;
tjc's avatar
tjc committed
    int offset = getSequenceOffset(samRecord.getReferenceName());
    int thisStart = samRecord.getAlignmentStart()+offset;
    int thisEnd   = thisStart + samRecord.getReadString().length() -1;
tjc's avatar
tjc committed
    
    if(ypos <= 0)
    {
      offTheTop = true;
      ypos = samRecord.getReadString().length();
    }
tjc's avatar
tjc committed
    if(samRecord.getInferredInsertSize() == 0)
    {
      offTheTop = true;
      ypos = getHeight() - scaleHeight - 5;
    }
tjc's avatar
tjc committed
    if(samRecord.getInferredInsertSize() != 0 &&
      Math.abs(samRecord.getMateAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
tjc's avatar
tjc committed
    {
      g2.setColor(Color.LIGHT_GRAY);
      
      if(samRecord.getAlignmentEnd() < samRecord.getMateAlignmentStart())
      {
tjc's avatar
tjc committed
        int nextStart = 
          (int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase);
        drawTranslucentLine(g2, 
          (int)((thisEnd-getBaseAtStartOfView())*pixPerBase), nextStart, ypos);
tjc's avatar
tjc committed
      }
      else
      {
tjc's avatar
tjc committed
        int nextStart = 
            (int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase);
        drawTranslucentLine(g2, 
            (int)((thisStart-getBaseAtStartOfView())*pixPerBase), nextStart, ypos);
tjc's avatar
tjc committed
      }
    }
    
    if(colourByCoverageColour.isSelected())
      g2.setColor(getColourByCoverageColour(bamViewRecord));
    else if(offTheTop)
      g2.setColor(DARK_ORANGE); 
    else if(samRecord.getReadNegativeStrandFlag() &&
            samRecord.getMateNegativeStrandFlag()) // strand of the query (1 for reverse)
tjc's avatar
tjc committed
      g2.setColor(Color.red);
    else
      g2.setColor(Color.blue);
tcarver's avatar
tcarver committed
    drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
tjc's avatar
tjc committed
    
    /*if (isSNPs)
      showSNPsOnReads(g2, samRecord, pixPerBase, ypos, offset);*/
tjc's avatar
tjc committed
  }
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, int ypos)
tjc's avatar
tjc committed
  {
    g2.setColor(Color.black);
tjc's avatar
tjc committed
    g2.drawLine( 0, ypos-14,
                 (int)((end - getBaseAtStartOfView())*pixPerBase),   ypos-14);
tjc's avatar
tjc committed
    int interval = end-start;
    
    if(interval > 256000)
tjc's avatar
tjc committed
      drawTicks(g2, start, end, pixPerBase, 512000, ypos);
tjc's avatar
tjc committed
    else if(interval > 64000)
tjc's avatar
tjc committed
      drawTicks(g2, start, end, pixPerBase, 12800, ypos);
tjc's avatar
tjc committed
    else if(interval > 16000)
tjc's avatar
tjc committed
      drawTicks(g2, start, end, pixPerBase, 3200, ypos);
tjc's avatar
tjc committed
    else if(interval > 4000)
tjc's avatar
tjc committed
      drawTicks(g2, start, end, pixPerBase, 800, ypos);
tjc's avatar
tjc committed
    else if(interval > 1000)
tjc's avatar
tjc committed
      drawTicks(g2, start, end, pixPerBase, 400, ypos);
tjc's avatar
tjc committed
    else
tjc's avatar
tjc committed
      drawTicks(g2, start, end, pixPerBase, 100, ypos);
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division, int ypos)
tjc's avatar
tjc committed
  {
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);
tjc's avatar
tjc committed
    float x;
tjc's avatar
tjc committed
    if(sm > start)
tjc's avatar
tjc committed
    {
      x = (sm-getBaseAtStartOfView())*pixPerBase;
tjc's avatar
tjc committed
      g2.drawLine((int)x, ypos-14,(int)x, ypos-12);
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
    
    for(int m=markStart; m<end; m+=division)
    {
tjc's avatar
tjc committed
      x = (m-getBaseAtStartOfView())*pixPerBase;
tjc's avatar
tjc committed
      g2.drawString(Integer.toString(m), x, ypos-1);
      g2.drawLine((int)x, ypos-14,(int)x, ypos-11);
tjc's avatar
tjc committed
      
      sm = m+(division/2);
      
      if(sm < end)
tjc's avatar
tjc committed
      {
        x = (sm-getBaseAtStartOfView())*pixPerBase;
tjc's avatar
tjc committed
        g2.drawLine((int)x, ypos-14,(int)x, ypos-12);
tjc's avatar
tjc committed
      }
tjc's avatar
tjc committed
      
      if(m == 1)
        m = 0;
  /**
   * Draw a y-scale for inferred size (isize) of reads.
   * @param g2
   * @param xScaleHeight
   */
tjc's avatar
tjc committed
  private void drawYScale(Graphics2D g2, int xScaleHeight)
  {
    g2.setColor(Color.black);
    int maxY = getPreferredSize().height-xScaleHeight;
    if(logScale)
    {
      int start = 10;
      int count = 0;
      int ypos = getYPos(xScaleHeight, start);
      
      while(ypos > 0 && count < 15 && start > 1)
      {
        g2.drawLine(0, ypos, 2, ypos);
        g2.drawString(Integer.toString(start), 3, ypos);
        start = start*5;
        ypos = getYPos(xScaleHeight, start);
        count++;
      }
      return;
    }
    
    for(int i=100; i<maxY; i+=100)
    {
      int ypos = getHeight()-i-xScaleHeight;
tjc's avatar
tjc committed
      g2.drawLine(0, ypos, 2, ypos);
      g2.drawString(Integer.toString(i), 3, ypos);
  /**
   * Draw a given read.
   * @param g2
   * @param thisRead
   * @param pixPerBase
   * @param ypos
   * @param baseAtStartOfView
tcarver's avatar
tcarver committed
  private void drawRead(Graphics2D g2, 
      final BamViewRecord bamViewRecord,
tcarver's avatar
tcarver committed
      final float pixPerBase,
      final int ypos,
tcarver's avatar
tcarver committed
      final List<Integer> snps,
      final int ydiff)
tjc's avatar
tjc committed
  {
    SAMRecord thisRead = bamViewRecord.sam;
tjc's avatar
tjc committed
    int offset = getSequenceOffset(thisRead.getReferenceName());

    int thisStart = thisRead.getAlignmentStart()+offset-baseAtStartOfView;
    int thisEnd   = thisRead.getAlignmentEnd()+offset-baseAtStartOfView;
       highlightSAMRecord.sam.getReadName().equals(thisRead.getReadName()))
       Stroke originalStroke = g2.getStroke();
       Stroke stroke =
tcarver's avatar
tcarver committed
         new BasicStroke (readLnHgt*1.6f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
       g2.setStroke(stroke);
       Color c = g2.getColor();
       g2.setColor(Color.black);
       g2.drawLine((int)( thisStart * pixPerBase), ypos,
                   (int)( thisEnd * pixPerBase), ypos);
       g2.setColor(c);
       g2.setStroke(originalStroke);
    if(thisRead.getCigar().getCigarElements().size() == 1)
      g2.drawLine((int)( thisStart * pixPerBase), ypos,
                  (int)( thisEnd * pixPerBase), ypos);
    else
    {
      List<AlignmentBlock> blocks = thisRead.getAlignmentBlocks();
      Color c = g2.getColor();
      int lastEnd = 0;
      for(int i=0; i<blocks.size(); i++)
      {
        AlignmentBlock block = blocks.get(i);
        int blockStart = block.getReferenceStart()+offset-baseAtStartOfView;
        int blockEnd = blockStart + block.getLength() - 1;
        g2.drawLine((int)( blockStart * pixPerBase), ypos,
                    (int)( blockEnd * pixPerBase), ypos);
        if(i > 0 && blockStart != lastEnd)
        {
          g2.setColor(Color.gray);
          g2.drawLine((int)( blockStart * pixPerBase), ypos,
                      (int)( lastEnd * pixPerBase), ypos);
          g2.setColor(c);
        }
        lastEnd = blockEnd;
      }
    }
    
    if(isOrientation)
tcarver's avatar
tcarver committed
      drawArrow(g2, thisRead, thisStart, thisEnd, pixPerBase, ypos, ydiff);
tjc's avatar
tjc committed

    // test if the mouse is over this read
    if(lastMousePoint != null)
    {
      if(lastMousePoint.getY()+2 > ypos && lastMousePoint.getY()-2 < ypos)
      if(lastMousePoint.getX() > thisStart * pixPerBase &&
         lastMousePoint.getX() < thisEnd * pixPerBase)
      {
        mouseOverSAMRecord = bamViewRecord;
    if (isSNPs && snps != null)
      showSNPsOnReads(snps, g2, pixPerBase, ypos);
tjc's avatar
tjc committed
  }
  
  /**
   * Draw arrow on the read to indicate orientation.
   * @param g2
   * @param thisRead
   * @param thisStart
   * @param thisEnd
   * @param pixPerBase
   * @param ypos
tcarver's avatar
tcarver committed
  private void drawArrow(final Graphics2D g2,
      final SAMRecord thisRead, 
      final int thisStart, 
      final int thisEnd, 
      final float pixPerBase, 
      int ypos,
      int ydiff)
tcarver's avatar
tcarver committed
    if(ydiff < 0)
      ydiff = -ydiff;
    
    if(thisRead.getReadNegativeStrandFlag())
    {
tcarver's avatar
tcarver committed
      ypos-=readLnHgt/2;
      int apos = ypos + ydiff - 1;
      g2.drawLine((int)( (thisStart+5) * pixPerBase), apos,
                  (int)( thisStart * pixPerBase), ypos);
    }
    else
    {
tcarver's avatar
tcarver committed
      ypos+=readLnHgt/2;
      int apos = ypos - ydiff + 1;
      g2.drawLine((int)( (thisEnd-5) * pixPerBase), apos,
                  (int)( thisEnd * 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, Color c)
  {
    if(getSelection() != null)
    {
      Range selectedRange = getSelection().getSelectionRange();

      if(selectedRange != null)
      {
        int rangeStart = selectedRange.getStart();
        int rangeEnd   = selectedRange.getEnd();
        
        if(end < rangeStart || start > rangeEnd)
          return;
        
tjc's avatar
tjc committed
        int x = (int) (pixPerBase*(rangeStart-getBaseAtStartOfView()));
        int width = (int) (pixPerBase*(rangeEnd-rangeStart+1));
        
        g2.fillRect(x, 0, width, getHeight());
      }
    }
  }
  
  /**
   * Draw a translucent line
   * @param g2
   * @param start
   * @param end
   * @param ypos
   */
  private void drawTranslucentLine(Graphics2D g2, int start, int end, int ypos)
  {
    Composite origComposite = g2.getComposite();
    g2.setComposite(translucent);
    g2.drawLine(start, ypos, end, ypos);
    g2.setComposite(origComposite);
  }
  
  /**
   * Draw a translucent line
   * @param g2
   * @param start
   * @param end
   * @param ypos
   */
  private void drawTranslucentJointedLine(Graphics2D g2, int start, int end, int ypos)
  {
    Composite origComposite = g2.getComposite();
    g2.setComposite(translucent);
    
    int mid = (int) ((end-start)/2.f)+start;
    //g2.drawLine(start, ypos, end, ypos);
    g2.drawLine(start, ypos, mid, ypos-5);
    g2.drawLine(mid, ypos-5, end, ypos);
    g2.setComposite(origComposite);
  }
  
tjc's avatar
tjc committed
  /**
   * Display the SNPs for the given read.
tjc's avatar
tjc committed
   * @param g2
   * @param pixPerBase
   * @param ypos
   */
tcarver's avatar
tcarver committed
  private void showSNPsOnReads(final List<Integer> snps,
tcarver's avatar
tcarver committed
                               float pixPerBase, final int ypos)
tjc's avatar
tjc committed
  {
tcarver's avatar
tcarver committed
    final Stroke originalStroke = g2.getStroke();
    final BasicStroke stroke = new BasicStroke(
        1.3f,
        BasicStroke.CAP_BUTT, 
        BasicStroke.JOIN_MITER);
    g2.setStroke(stroke);
    
    g2.setColor(Color.red);
    for(int pos: snps)
      g2.drawLine((int) (pos * pixPerBase), ypos + 2,
                  (int) (pos * pixPerBase), ypos - 2);
tcarver's avatar
tcarver committed
    g2.setStroke(originalStroke);
tcarver's avatar
tcarver committed
  private List<Integer> getSNPs(final SAMRecord samRecord)
  {
    if(!isSNPs)  // return null if not displaying SNPs
      return null;
    int rbeg = samRecord.getAlignmentStart();
    int rend = samRecord.getAlignmentEnd();
    int offset = getSequenceOffset(samRecord.getReferenceName());
    ArrayList<Integer> snps = null;
    
    // use alignment blocks of the contiguous alignment of
    // subsets of read bases to a reference sequence
tjc's avatar
tjc committed
    try
tjc's avatar
tjc committed
    {
      final char[] refSeq = bases.getSubSequenceC(
          new Range(rbeg+offset, rend+offset), Bases.FORWARD);
      final byte[] readSeq = samRecord.getReadBases();
      offset = offset - getBaseAtStartOfView();
      final List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
tcarver's avatar
tcarver committed
      for(AlignmentBlock block: blocks)
tjc's avatar
tjc committed
      {
        int readStart = block.getReadStart();
        int refStart  = block.getReferenceStart();
        int len = block.getLength();
        for(int j=0; j<len; j++)
tjc's avatar
tjc committed
        {
          int readPos = readStart-1+j;
          int refPos  = refStart+j;
tcarver's avatar
tcarver committed
          if (Character.toUpperCase(refSeq[refPos-rbeg]) != Character.toUpperCase( (char)readSeq[readPos]) )
            if(snps == null)
              snps = new ArrayList<Integer>();
            snps.add(refPos+offset);
tjc's avatar
tjc committed
        }
      }
tjc's avatar
tjc committed
    }
    catch (OutOfRangeException e)
    {
      System.err.println(samRecord.getReadName()+" "+e.getMessage());
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
tjc's avatar
tjc committed
   * @param frame
   * @param autohide automatically hide the top panel containing the buttons
tjc's avatar
tjc committed
   * @param feature_display
tjc's avatar
tjc committed
  private void addBamToPanel(final JFrame frame)
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    final JComponent topPanel = bamTopPanel(frame);
tjc's avatar
tjc committed
    mainPanel.setPreferredSize(new Dimension(900, 400));
tjc's avatar
tjc committed
    
    setDisplay(1, nbasesInView, null);
    mainPanel.setLayout(new BorderLayout());
tjc's avatar
tjc committed
    if(topPanel instanceof JPanel)
      mainPanel.add(topPanel, BorderLayout.NORTH);
    mainPanel.add(jspView, BorderLayout.CENTER);
tjc's avatar
tjc committed
    JPanel bottomPanel = new JPanel(new BorderLayout());
    coveragePanel = new CoveragePanel(this);
    bottomPanel.add(coveragePanel, BorderLayout.CENTER);

tjc's avatar
tjc committed
    //
    snpPanel = new SnpPanel(this, bases);
    bottomPanel.add(snpPanel, BorderLayout.NORTH);
    
tjc's avatar
tjc committed
    if(feature_display == null)
    {
      scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 1, nbasesInView, 1,
          getMaxBasesInPanel(getSequenceLength()));
      scrollBar.setUnitIncrement(nbasesInView/20);
tjc's avatar
tjc committed
      scrollBar.addAdjustmentListener(new AdjustmentListener()
      {
        public void adjustmentValueChanged(AdjustmentEvent e)
        {
          repaint();

          if(isSNPplot)
            snpPanel.repaint();
          if(isCoverage)
            coveragePanel.repaint();
tjc's avatar
tjc committed
        }
      });
      bottomPanel.add(scrollBar, BorderLayout.SOUTH);
    }
      if(!isConcatSequences())
      {
        int seqLen = seqLengths.get((String) combo.getSelectedItem());
        int artemisSeqLen = feature_display.getSequenceLength();
        if(seqLen != artemisSeqLen)
        {
          int newIndex = -1;
          for(int i=0; i<seqNames.size(); i++)
          {
            if(seqLengths.get(seqNames.get(i)) == artemisSeqLen)
            {
              // this looks like the correct sequence
              combo.setSelectedIndex(i);
              newIndex = i;
            }
          }

          if(!Options.isBlackBeltMode())
          {
            String label[] = {
                "The length of the sequence loaded does not match the length of",
                "the default reference sequence in the BAM ("+seqNames.get(0)+").",
                (newIndex == -1 ? "" : "The length does match the reference "+
tcarver's avatar
tcarver committed
                    seqNames.get(newIndex)+" so this has been set as the default.") 
            };
            new NonModalDialog(frame, label);
          }
tjc's avatar
tjc committed

    mainPanel.add(bottomPanel, BorderLayout.SOUTH);
    coveragePanel.setPreferredSize(new Dimension(900, 100));
    coveragePanel.setVisible(false);
tjc's avatar
tjc committed
    snpPanel.setPreferredSize(new Dimension(900, 100));
    snpPanel.setVisible(false);

tjc's avatar
tjc committed
    mainPanel.revalidate();
tjc's avatar
tjc committed
    jspView.getVerticalScrollBar().setValue(
        jspView.getVerticalScrollBar().getMaximum());
  private void addToViewMenu(final short thisBamIndex)
    final File f = new File(bamList.get(thisBamIndex));
    final JCheckBoxMenuItem cbBam = new JCheckBoxMenuItem(
                                     f.getName(), 
                                     getImageIcon(getColourByCoverageColour(thisBamIndex)), 
                                     true);
    bamFilesMenu.add(cbBam);
tcarver's avatar
tcarver committed
    cbBam.addItemListener(new ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        if(cbBam.isSelected())
tcarver's avatar
tcarver committed
          hideBamList.remove(new Short(thisBamIndex));
tcarver's avatar
tcarver committed
          hideBamList.add(new Short(thisBamIndex));
  /**
   * Refresh the colour of the icons used to identify the
   * BAM files.
   */
  protected void refreshColourOfBamMenu()
  {
    final Component cs[] = bamFilesMenu.getMenuComponents();
    for(Component c : cs)
    {
      if(c instanceof JCheckBoxMenuItem)
      {
        final JCheckBoxMenuItem cbBam = (JCheckBoxMenuItem) c;
tcarver's avatar
tcarver committed
        final Color col = getColorByJCheckBoxMenuItem(cbBam);
        if(col != null)
          cbBam.setIcon(getImageIcon(col));
tcarver's avatar
tcarver committed
  protected Color getColorByJCheckBoxMenuItem(JCheckBoxMenuItem cbBam)
  {
    final String bam = cbBam.getText();
    for(short i=0; i<bamList.size(); i++)
tcarver's avatar
tcarver committed
    {
      final File f = new File(bamList.get(i));
      if(f.getName().equals(bam))
        return getColourByCoverageColour(i);
    }
    return null;
  }
  
  /**
   * Create an icon of a box using the given colour.
   * @param c
   * @return
   */
tcarver's avatar
tcarver committed
  ImageIcon getImageIcon(Color c)
  {
    BufferedImage image = (BufferedImage)this.createImage(10, 10);
    Graphics2D g2 = image.createGraphics();
    g2.setColor(c);
    g2.fillRect(0, 0, 10, 10);
    return new ImageIcon(image);
  }

  private void createMenus(JComponent menu)
    final JMenuItem addBam = new JMenuItem("Add BAM ...");
    menu.add(addBam);
    addBam.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        FileSelectionDialog bamFileSelection = new FileSelectionDialog(
            null, false, "BamView", "BAM");
tcarver's avatar
tcarver committed
        List<String> bamFiles = bamFileSelection.getFiles(BAM_SUFFIX);
        short count = (short) bamList.size();
tcarver's avatar
tcarver committed
        bamList.addAll(bamFileSelection.getFiles(BAM_SUFFIX));
        for(short i=0; i<bamFiles.size(); i++)
          addToViewMenu((short) (i+count));
tjc's avatar
tjc committed
        laststart = -1; 
        repaint();
      } 
    });
    
    bamFilesMenu.setFont(addBam.getFont());
tcarver's avatar
tcarver committed

tcarver's avatar
tcarver committed
    final JMenuItem groupBams = new JMenuItem("Group BAMs ...");
    groupBams.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0)
      {
        groupsFrame.updateAndDisplay();
      }
    });
    bamFilesMenu.add(groupBams);
    bamFilesMenu.addSeparator();
    menu.add(bamFilesMenu);
    final JMenu analyse = new JMenu("Analyse");
    menu.add(analyse);
tjc's avatar
tjc committed
    final JMenuItem readCount = new JMenuItem("Read count of selected features ...");
    analyse.add(readCount);
    if(feature_display == null)
      readCount.setEnabled(false);
    readCount.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        FeatureVector features = feature_display.getSelection().getAllFeatures();
        JCheckBox overlap = new JCheckBox("Include all overlapping reads", true);
        overlap.setToolTipText("Include reads that partially overlap the feature");
        JCheckBox spliced = new JCheckBox("Introns included", true);
        Box yBox = Box.createVerticalBox();
        yBox.add(overlap);
        yBox.add(spliced);
tcarver's avatar
tcarver committed
        
        final ReadCountDialog opts = new ReadCountDialog(new JFrame(),
            "Read Count Options", feature_display, yBox);
        if(opts.getStatus() == -1)
          return;
        //JOptionPane.showMessageDialog(null, yBox, "Read Count Option", JOptionPane.INFORMATION_MESSAGE);
        new MappedReads(features, (String)combo.getSelectedItem(), samFileReaderHash, bamList,
            seqNames, offsetLengths, concatSequences, seqLengths, 
            samRecordFlagPredicate, samRecordMapQPredicate,
            !overlap.isSelected(), spliced.isSelected(), colourByStrandTag.isSelected());
tjc's avatar
tjc committed
      } 
    });
    
    final JMenuItem rpkm = new JMenuItem("RPKM value of selected features ...");
    analyse.add(rpkm);
    if(feature_display == null)
      rpkm.setEnabled(false);
tjc's avatar
tjc committed
    rpkm.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
tcarver's avatar
tcarver committed
        final FeatureVector features = feature_display.getSelection().getAllFeatures();
tjc's avatar
tjc committed
        JCheckBox overlap = new JCheckBox("Include all overlapping reads", true);
        overlap.setToolTipText("Include reads that partially overlap the feature");
        JCheckBox spliced = new JCheckBox("Introns included", true);
        JCheckBox allRefSeqs = new JCheckBox("Use reads mapped to all reference sequences", false);
        
tjc's avatar
tjc committed
        Box yBox = Box.createVerticalBox();
        yBox.add(overlap);
        yBox.add(spliced);
        
        if(seqLengths.size() > 1)
          yBox.add(allRefSeqs);
tcarver's avatar
tcarver committed
     
        final ReadCountDialog opts = new ReadCountDialog(new JFrame(),
            "RPKM Options", feature_display, yBox);
        if(opts.getStatus() == -1)
          return;

tjc's avatar
tjc committed
        int seqlen = 0;
        if(feature_display != null)
          seqlen = feature_display.getSequenceLength();
        else if(bases != null)
          seqlen = bases.getLength();
        
        new MappedReads(features, (String)combo.getSelectedItem(),
            samFileReaderHash, bamList, seqNames, offsetLengths, concatSequences, 
            seqLengths, seqlen, samRecordFlagPredicate, samRecordMapQPredicate,
            !overlap.isSelected(), spliced.isSelected(), allRefSeqs.isSelected(),
            colourByStrandTag.isSelected());
tcarver's avatar
tcarver committed
    
    
    
    final JMenuItem createFeatures = new JMenuItem("Create features from coverage peaks ...");
    analyse.add(createFeatures);
    if(feature_display == null)
      createFeatures.setEnabled(false);
tcarver's avatar
tcarver committed
    createFeatures.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        if(feature_display == null)
          return;
        new CreateFeatures(groupsFrame);
    for(short i=0; i<bamList.size(); i++)
      addToViewMenu(i);
    
    menu.add(new JSeparator());
    
    JMenu viewMenu = new JMenu("Views");
tjc's avatar
tjc committed
    cbStackView.setFont(viewMenu.getFont());
    cbIsizeStackView.setFont(viewMenu.getFont());
    cbPairedStackView.setFont(viewMenu.getFont());
    cbStrandStackView.setFont(viewMenu.getFont());
    cbCoverageView.setFont(viewMenu.getFont());
    cbCoverageStrandView.setFont(viewMenu.getFont());
tcarver's avatar
tcarver committed
    cbCoverageHeatMap.setFont(viewMenu.getFont());
tjc's avatar
tjc committed
    
    baseQualityColour.setFont(viewMenu.getFont());
tcarver's avatar
tcarver committed
    colourByReadGrp.setFont(viewMenu.getFont());
tjc's avatar
tjc committed
    colourByCoverageColour.setFont(viewMenu.getFont());
    colourByStrandTag.setFont(viewMenu.getFont());
tjc's avatar
tjc committed
    markInsertions.setFont(viewMenu.getFont());
    
    cbIsizeStackView.addActionListener(new ActionListener()
tjc's avatar
tjc committed
    {
      public void actionPerformed(ActionEvent e)
      {
        laststart = -1;
        logMenuItem.setEnabled(isIsizeStackView());
tcarver's avatar
tcarver committed
        getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
tjc's avatar
tjc committed
        repaint();
      }
    });
    viewMenu.add(cbIsizeStackView);
    cbStackView.addActionListener(new ActionListener()
tjc's avatar
tjc committed
    {
      public void actionPerformed(ActionEvent e)
      {
        laststart = -1;
tcarver's avatar
tcarver committed
        if(cbStackView.isSelected())
          logMenuItem.setEnabled(false);
        getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
tjc's avatar
tjc committed
        repaint();
      }
    });
    viewMenu.add(cbStackView);
    cbPairedStackView.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        laststart = -1;
tcarver's avatar
tcarver committed
        if(cbPairedStackView.isSelected())
          logMenuItem.setEnabled(false);
        getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
    viewMenu.add(cbPairedStackView);
    cbStrandStackView.addActionListener(new ActionListener()
tjc's avatar
tjc committed
    {
      public void actionPerformed(ActionEvent e)
      {
        laststart = -1;
        if(isStrandStackView())
tjc's avatar
tjc committed
          setViewportMidPoint();
tcarver's avatar
tcarver committed
        if(cbStrandStackView.isSelected())
          logMenuItem.setEnabled(false);
        getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
tjc's avatar
tjc committed
        repaint();
      }
    });
    viewMenu.add(cbStrandStackView);
    
    cbCoverageView.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        laststart = -1;
        if(cbCoverageView.isSelected())
        {
tcarver's avatar
tcarver committed
          logMenuItem.setEnabled(true);
          coverageView.setPlotHeatMap(false);
          coverageView.setPlotByStrand(false);
          setViewportBtm();
tcarver's avatar
tcarver committed
          getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
        repaint();
      }
    });
    viewMenu.add(cbCoverageView);
    
    cbCoverageStrandView.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        laststart = -1;
        if(cbCoverageStrandView.isSelected())
        {
tcarver's avatar
tcarver committed
          logMenuItem.setEnabled(true);
          coverageView.setPlotHeatMap(false);
          coverageView.setPlotByStrand(true);
          setViewportBtm();
tcarver's avatar
tcarver committed
          getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
tcarver's avatar
tcarver committed

        repaint();
      }
    });
    viewMenu.add(cbCoverageStrandView);
    
tcarver's avatar
tcarver committed
    
    cbCoverageHeatMap.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        laststart = -1;
        if(cbCoverageHeatMap.isSelected())
        {
          logMenuItem.setEnabled(true);
          coverageView.setPlotHeatMap(true);
          setViewportBtm();
          getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
        }

        repaint();
      }
    });
    viewMenu.add(cbCoverageHeatMap);
    
    menu.add(viewMenu);
    final JCheckBoxMenuItem checkBoxSNPs = new JCheckBoxMenuItem("SNP marks", isSNPs);
tcarver's avatar
tcarver committed
    final JMenu colourMenu = new JMenu("Colour By");
    
    final JCheckBoxMenuItem colourDefault = new JCheckBoxMenuItem ("Default", true);
    final ButtonGroup grp = new ButtonGroup();
    grp.add(colourByReadGrp);
    grp.add(colourByCoverageColour);
    grp.add(colourByStrandTag);
    grp.add(colourDefault);
    
    colourMenu.add(colourDefault);
    colourDefault.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        repaintBamView();
      }
    });
    
    colourMenu.add(colourByReadGrp);
    colourByReadGrp.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        repaintBamView();
      }
    });
    
    colourMenu.add(colourByCoverageColour);
    colourByCoverageColour.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
tcarver's avatar
tcarver committed
        repaintBamView();
    colourMenu.add(colourByStrandTag);
    colourByStrandTag.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
tcarver's avatar
tcarver committed
        repaintBamView();
    baseQualityColour.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        if(baseQualityColour.isSelected())
        {
          checkBoxSNPs.setSelected(false);
          isSNPs = false;
        }
        repaint();
      }
    });
tcarver's avatar
tcarver committed
    colourMenu.addSeparator();
    colourMenu.add(baseQualityColour);
    menu.add(colourMenu);
    
    //
    JMenu showMenu = new JMenu("Show");
    JCheckBoxMenuItem checkBoxOrientation = new JCheckBoxMenuItem("Orientation");
    checkBoxOrientation.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        isOrientation = !isOrientation;
        repaint();
      }
    });
    showMenu.add(checkBoxOrientation);
    
    JCheckBoxMenuItem checkBoxSingle = new JCheckBoxMenuItem("Single Reads");
    checkBoxSingle.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        repaint();
        isSingle = !isSingle;
      }
    });
    showMenu.add(checkBoxSingle);
    checkBoxSNPs.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        if (isSNPs && bases == null)
        {
          JOptionPane.showMessageDialog(null,
              "No reference sequence supplied to identify SNPs.", "SNPs",
              JOptionPane.INFORMATION_MESSAGE);
        }
        isSNPs = !isSNPs;
        
        if(isSNPs)
          baseQualityColour.setSelected(false);
        repaint();
      }
    });
    showMenu.add(checkBoxSNPs);
    
    markInsertions.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        repaint();
      }
    });
    showMenu.add(markInsertions);
tjc's avatar
tjc committed
    menu.add(showMenu);
tjc's avatar
tjc committed
    //
    JMenu graphMenu = new JMenu("Graph");
    JCheckBoxMenuItem checkBoxCoverage = new JCheckBoxMenuItem("Coverage", isCoverage);
    checkBoxCoverage.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        isCoverage = !isCoverage;
        coveragePanel.setVisible(isCoverage);
tjc's avatar
tjc committed
        
        if( isCoverage && 
            !cbCoverageView.isSelected() && 
tcarver's avatar
tcarver committed
            !cbCoverageStrandView.isSelected() &&
            !cbCoverageHeatMap.isSelected())
tjc's avatar
tjc committed
          laststart = -1;
        repaint();
      }
    });
tjc's avatar
tjc committed
    graphMenu.add(checkBoxCoverage);
    
    JCheckBoxMenuItem checkBoxSNP = new JCheckBoxMenuItem("SNP", isSNPplot);
tjc's avatar
tjc committed
    checkBoxSNP.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        isSNPplot = !isSNPplot;
        snpPanel.setVisible(isSNPplot);
        laststart = -1;
tjc's avatar
tjc committed
        repaint();
      }
    });
    graphMenu.add(checkBoxSNP);
    menu.add(graphMenu);
    
    if(feature_display != null)
      final JCheckBoxMenuItem checkBoxSync =
        new JCheckBoxMenuItem("Asynchronous", asynchronous);
      checkBoxSync.addActionListener(new ActionListener()
        public void actionPerformed(ActionEvent e)
        {
          asynchronous = checkBoxSync.isSelected();
        }
      });
      menu.add(checkBoxSync);
    menu.add(new JSeparator());
    JMenu maxHeightMenu = new JMenu("BamView Height");
    menu.add(maxHeightMenu);
    final String hgts[] =
       {"500", "800", "1000", "1500", "2500", "5000", "50000"};
    
    ButtonGroup bgroup = new ButtonGroup();
    for(int i=0; i<hgts.length; i++)
    {
      final String hgt = hgts[i];
      final JCheckBoxMenuItem maxHeightMenuItem = new JCheckBoxMenuItem(hgt);
      bgroup.add(maxHeightMenuItem);
      maxHeightMenuItem.setSelected(hgts[i].equals(Integer.toString(maxHeight)));
      maxHeightMenu.add(maxHeightMenuItem);
      maxHeightMenuItem.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          if(maxHeightMenuItem.isSelected())
            maxHeight = Integer.parseInt(hgt);
          int start = getBaseAtStartOfView();
          setDisplay(start, nbasesInView+start, null);
        }
      });
    }
    menu.add(new JSeparator());
tjc's avatar
tjc committed
    logMenuItem.setFont(menu.getFont());
    menu.add(logMenuItem);
    logMenuItem.setEnabled(isIsizeStackView());
    
    logMenuItem.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        logScale = logMenuItem.isSelected();
        repaint();
      }
    });
    
tcarver's avatar
tcarver committed
    final JMenuItem readGroupsMenu = new JMenuItem("Read Groups ...");
    readGroupsMenu.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0)
      {
tcarver's avatar
tcarver committed
        ReadGroupsFrame f = getReadGroupFrame();
tcarver's avatar
tcarver committed
        f.setVisible(true);
      }
    });
    menu.add(readGroupsMenu);
    
tjc's avatar
tjc committed
    JMenuItem filter = new JMenuItem("Filter Reads ...");
    menu.add(filter);
    filter.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
tjc's avatar
tjc committed
        if(filterFrame == null)
          filterFrame = new SAMRecordFilter(BamView.this);
        else
          filterFrame.setVisible(true);
    JMenuItem maxReadCoverage = new JMenuItem("Read Coverage Threshold ...");
    menu.add(maxReadCoverage);
    maxReadCoverage.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        final TextFieldInt maxRead = new TextFieldInt();
        maxRead.setValue(MAX_COVERAGE);
        int status = JOptionPane.showConfirmDialog(null, maxRead, 
            "Read Coverage Threshold", JOptionPane.OK_CANCEL_OPTION);
        if(status == JOptionPane.OK_OPTION &&
           maxRead.getValue() != MAX_COVERAGE)
        {
          MAX_COVERAGE = maxRead.getValue();
          if(MAX_COVERAGE < 1)
            MAX_COVERAGE = Integer.MAX_VALUE;
          laststart = -1;
          repaint();
        }
      } 
    });
    
tcarver's avatar
tcarver committed
    JMenuItem readList = new JMenuItem("List Reads ...");
tcarver's avatar
tcarver committed
    menu.add(readList);
    readList.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
tcarver's avatar
tcarver committed
        new SAMRecordList(BamView.this);
tcarver's avatar
tcarver committed
      }
tcarver's avatar
tcarver committed
    });
tjc's avatar
tjc committed
    final JMenuItem bamSplitter = new JMenuItem("Clone window");
    bamSplitter.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        openBamView(new Vector<String>(bamList));
tjc's avatar
tjc committed
      } 
    });
    menu.add(new JSeparator());
    menu.add(bamSplitter);

    //
    JMenu coverageMenu = new JMenu("Coverage Options");
    coverageView.init(this, 0.f, 0, 0);
    coverageView.createMenus(coverageMenu);
    viewMenu.add(new JSeparator());
    viewMenu.add(coverageMenu);
tcarver's avatar
tcarver committed
  private ReadGroupsFrame getReadGroupFrame()
tcarver's avatar
tcarver committed
  {
    if(readGrpFrame == null)
tcarver's avatar
tcarver committed
      readGrpFrame = new ReadGroupsFrame(readGroups, BamView.this);
tcarver's avatar
tcarver committed
    return readGrpFrame;
  }
  
tjc's avatar
tjc committed
  private JComponent bamTopPanel(final JFrame frame)
  {
    final JComponent topPanel;
tjc's avatar
tjc committed
    if(frame == null)
tjc's avatar
tjc committed
    {
      topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
      if(feature_display != null)
        this.selection = feature_display.getSelection(); 
tjc's avatar
tjc committed
    }
    else
    { 
      topPanel = new JMenuBar();
      frame.setJMenuBar((JMenuBar)topPanel);
      
      JMenu fileMenu = new JMenu("File");
      topPanel.add(fileMenu);

      JMenuItem readBam = new JMenuItem("Open new BamView ...");
      fileMenu.add(readBam);
      readBam.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          String[] s = { "NEW-BAMVIEW" };
tjc's avatar
tjc committed
          BamView.main(s);
        } 
      });
      
tcarver's avatar
tcarver committed
      
      JMenuItem saveAs = new JMenuItem("Save As Image File (png/jpeg/svg) ...");
      fileMenu.add(saveAs);
      saveAs.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          PrintBamView.print((JPanel)mainPanel.getParent()); 
        }
      });

      
tjc's avatar
tjc committed
      JMenuItem close = new JMenuItem("Close");
      fileMenu.add(close);
      close.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          BamView.this.setVisible(false);
          Component comp = BamView.this;
          
          while( !(comp instanceof JFrame) )
            comp = comp.getParent();
          ((JFrame)comp).dispose();
        } 
      });
      
      JMenuItem exit = new JMenuItem("Exit");
      fileMenu.add(new JSeparator());
      fileMenu.add(exit);
      exit.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          int status = JOptionPane.showConfirmDialog(BamView.this, 
              "Exit BamView?", "Exit", 
              JOptionPane.OK_CANCEL_OPTION);
          if(status != JOptionPane.OK_OPTION)
            return;
          System.exit(0);
        } 
      });
      
      addKeyListener(new KeyAdapter()
      {
        public void keyPressed(final KeyEvent event)
        {
          switch (event.getKeyCode())
          {
            case KeyEvent.VK_UP:
              setZoomLevel((int) (BamView.this.nbasesInView * 1.1));
              break;
            case KeyEvent.VK_DOWN:
              if (showBaseAlignment)
                break;
              setZoomLevel((int) (BamView.this.nbasesInView * .9));
              break;
            default:
              break;
          }
        }
      });
    }
    
    if(seqNames.size() > 1)
    {
      int len = 0;
      for(int i=0; i<seqNames.size(); i++)
        len += seqLengths.get(seqNames.get(i));
      
      if(feature_display != null &&
         len == feature_display.getSequenceLength())
        concatSequences = true;
      else if(bases != null &&
          len == bases.getLength() )
        concatSequences = true;
    }

    // auto hide top panel
tjc's avatar
tjc committed
    final JCheckBox buttonAutoHide = new JCheckBox("Hide", (frame == null));
tjc's avatar
tjc committed
    buttonAutoHide.setToolTipText("Auto-Hide");
    final MouseMotionListener mouseMotionListener = new MouseMotionListener()
    {
      public void mouseDragged(MouseEvent event)
      {
        handleCanvasMouseDrag(event);
      }
      
      public void mouseMoved(MouseEvent e)
      {
        lastMousePoint = e.getPoint();
        
tcarver's avatar
tcarver committed
        int thisHgt = HEIGHT-2;
tjc's avatar
tjc committed
        if (thisHgt < 5)
          thisHgt = 15;

        int y = (int) (e.getY() - jspView.getViewport().getViewRect().getY());
tcarver's avatar
tcarver committed
        Point p = jspView.getViewport().getViewPosition();
        boolean isVis = topPanel.isVisible();
        
tjc's avatar
tjc committed
        if (y < thisHgt)
        {
          topPanel.setVisible(true);
tcarver's avatar
tcarver committed
          if(!isVis)
            p.y += topPanel.getHeight();
tjc's avatar
tjc committed
        }
        else
        {
          if (buttonAutoHide.isSelected())
tcarver's avatar
tcarver committed
            topPanel.setVisible(false); 
tcarver's avatar
tcarver committed
        if(!showBaseAlignment && topPanel.isVisible())
tcarver's avatar
tcarver committed
          jspView.getViewport().setViewPosition(p);
tjc's avatar
tjc committed
        mainPanel.repaint();
tcarver's avatar
tcarver committed
        //mainPanel.revalidate();
tjc's avatar
tjc committed
      }
    };
    addMouseMotionListener(mouseMotionListener);

    
    combo = new SequenceComboBox(seqNames){
      private static final long serialVersionUID = 1L;
      public void update(IndexReferenceEvent event)
tjc's avatar
tjc committed
      {
        laststart = -1;
        if(feature_display != null)
          setZoomLevel(feature_display.getMaxVisibleBases());
        else
          setZoomLevel(BamView.this.nbasesInView);
      }
tjc's avatar
tjc committed
    topPanel.add(combo);

    if(feature_display == null)
    {
      final JTextField baseText = new JTextField(8);
      JButton goTo = new JButton("GoTo:");
      goTo.setToolTipText("Go to base position");
      goTo.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          try
          {
            int basePosition = Integer.parseInt(baseText.getText());
            scrollBar.setValue(basePosition);
          }
          catch (NumberFormatException nfe)
          {
            JOptionPane.showMessageDialog(BamView.this,
                "Expecting a base number!", "Number Format",
                JOptionPane.WARNING_MESSAGE);
          }
        }
      });
      topPanel.add(goTo);
      topPanel.add(baseText);

      JButton zoomIn = new JButton("-");
      zoomIn.setToolTipText("Zoom out (up arrow)");
tjc's avatar
tjc committed
      Insets ins = new Insets(1,1,1,1);
      zoomIn.setMargin(ins);
      zoomIn.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          setZoomLevel((int) (BamView.this.nbasesInView * 1.1));
        }
      });
      topPanel.add(zoomIn);

      JButton zoomOut = new JButton("+");
      zoomOut.setToolTipText("Zoom in (down arrow)");
tjc's avatar
tjc committed
      zoomOut.setMargin(ins);
      zoomOut.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
          if (showBaseAlignment)
            return;
          setZoomLevel((int) (BamView.this.nbasesInView * .9));
        }
      });
      topPanel.add(zoomOut);
    }
    
    topPanel.add(buttonAutoHide);
tcarver's avatar
tcarver committed
    
    final JSlider slider = new JSlider(13, 52, (int) (readLnHgt*10));
    slider.addChangeListener(new ChangeListener(){
      public void stateChanged(ChangeEvent arg0)
      {
        readLnHgt = (slider.getValue()/10.f);
        repaintBamView();
      }
    });
tcarver's avatar
tcarver committed
    topPanel.add(new JLabel(" Read Height:"));
tcarver's avatar
tcarver committed
    topPanel.add(slider);
    
    if(feature_display != null)
    {
      JButton close = new JButton("Close");
      topPanel.add(close);
      close.addActionListener(new ActionListener()
      {
        public void actionPerformed(ActionEvent e)
        {
tcarver's avatar
tcarver committed
          int status = JOptionPane.showConfirmDialog(frame, 
              "Close the BAM panel?", "Close", 
              JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
          if(status == JOptionPane.CANCEL_OPTION)
            return;
          
          final JPanel containerPanel = (JPanel) mainPanel.getParent();
          feature_display.removeDisplayAdjustmentListener(BamView.this);
          feature_display.getSelection().removeSelectionChangeListener(BamView.this);
          containerPanel.remove(mainPanel);
          
          if(containerPanel.getComponentCount() > 0)
            containerPanel.revalidate();
          else
          {
            if(entry_edit != null)
              entry_edit.setNGDivider();
            else
              containerPanel.setVisible(false);
          }
        }
      });
    }
tjc's avatar
tjc committed
    return topPanel;
  }
  
  public void setVisible(boolean visible)
  {
    super.setVisible(visible);
    mainPanel.setVisible(visible);
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  private void setViewportMidPoint()
  {
    Point p = jspView.getViewport().getLocation();
    p.y = (getHeight() - jspView.getViewport().getViewRect().height)/2;
    jspView.getViewport().setViewPosition(p);
  }
  
  private void setViewportBtm()
  {
    jspView.getVerticalScrollBar().setValue(
        jspView.getVerticalScrollBar().getMaximum());
  }
  
tjc's avatar
tjc committed
  protected int getBaseAtStartOfView()
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    if(feature_display != null)
      return feature_display.getForwardBaseAtLeftEdge();
    else
      return scrollBar.getValue();
tjc's avatar
tjc committed
  }
  
  /**
   * 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)
  {
tjc's avatar
tjc committed
    int startValue = getBaseAtStartOfView();
tjc's avatar
tjc committed
    this.nbasesInView = nbasesInView;
tjc's avatar
tjc committed
    float pixPerBase = getPixPerBaseByWidth(); 
tjc's avatar
tjc committed

    if(isBaseAlignmentView(pixPerBase))
tjc's avatar
tjc committed
    {
      pixPerBase = ALIGNMENT_PIX_PER_BASE;
      this.nbasesInView = (int)(mainPanel.getWidth()/pixPerBase);
tjc's avatar
tjc committed
      jspView.getVerticalScrollBar().setValue(0);
      
      if(ruler == null)
        ruler = new Ruler();
tjc's avatar
tjc committed
      jspView.setColumnHeaderView(ruler);
      showBaseAlignment = true;
      baseQualityColour.setEnabled(true);
      markInsertions.setEnabled(true);
tjc's avatar
tjc committed
    }
    else if(jspView != null)
    {
      if(!isCoverageView(pixPerBase) && nbasesInView >= MAX_BASES)
      {
        cbLastSelected = getSelectedCheckBoxMenuItem();
        cbCoverageView.setSelected(true);
        coverageView.setPlotByStrand(false);
tcarver's avatar
tcarver committed
      else if(isCoverageView(pixPerBase) && nbasesInView < MAX_BASES && cbLastSelected != null)
      {
        cbLastSelected.setSelected(true);
        cbLastSelected = null;
      }
      
tjc's avatar
tjc committed
      jspView.setColumnHeaderView(null);
tjc's avatar
tjc committed
      
      if(!isStrandStackView())
        setViewportBtm();
tjc's avatar
tjc committed
      showBaseAlignment = false;
      baseQualityColour.setEnabled(false);
      markInsertions.setEnabled(false);
tjc's avatar
tjc committed
    }
    
tjc's avatar
tjc committed
    if(scrollBar != null)
tjc's avatar
tjc committed
      scrollBar.setValues(startValue, nbasesInView, 1, 
             getMaxBasesInPanel(getSequenceLength()));
      scrollBar.setUnitIncrement(nbasesInView/20);
      scrollBar.setBlockIncrement(nbasesInView);
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  /**
   * Set the start and end base positions to display.
   * @param start
   * @param end
   * @param event
tjc's avatar
tjc committed
   */
  public void setDisplay(int start,
                         int end,
                         DisplayAdjustmentEvent event)
tjc's avatar
tjc committed
  {
    this.startBase = start;
    this.endBase   = end;
tjc's avatar
tjc committed
    this.nbasesInView = end-start+1;
    lastMousePoint = null;
tjc's avatar
tjc committed
    float pixPerBase;
    if(jspView.getViewport().getViewRect().width > 0)
tjc's avatar
tjc committed
      pixPerBase = getPixPerBaseByWidth();
tjc's avatar
tjc committed
    else
    {
      if(feature_display == null)
        pixPerBase = 1000.f/(float)(end-start+1);
      else
        pixPerBase = feature_display.getWidth()/(float)(end-start+1);
    }
tjc's avatar
tjc committed
    Dimension d = new Dimension();
tjc's avatar
tjc committed
    d.setSize(nbasesInView*pixPerBase, maxHeight);
tjc's avatar
tjc committed
    setPreferredSize(d);
tjc's avatar
tjc committed
    if(event == null)
    {
      this.startBase = -1;
      this.endBase   = -1;
    }
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);
tjc's avatar
tjc committed
    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;
  }
  
  private boolean isShowScale()
  {
tjc's avatar
tjc committed
    return (feature_display == null ? true : false);
  }

  public JScrollPane getJspView()
  {
    return jspView;
  }
  
  /**
   *  Handle a mouse drag event on the drawing canvas.
   **/
tjc's avatar
tjc committed
  private void handleCanvasMouseDrag(final MouseEvent event)
    if(event.getButton() == MouseEvent.BUTTON3 || bases == null) 
    if(event.getClickCount() > 1)
    {
      getSelection().clear();
      repaint();
      return;  
    }
    
    highlightRange(event, 
        MouseEvent.BUTTON1_DOWN_MASK & MouseEvent.BUTTON2_DOWN_MASK);
  }
  
  /**
   * 
   * @param event
   * @param onmask
   */
  protected void highlightRange(MouseEvent event, int onmask)
tjc's avatar
tjc committed
    int seqLength = getSequenceLength();
tjc's avatar
tjc committed
    float pixPerBase = getPixPerBaseByWidth();
tjc's avatar
tjc committed
    int start = (int) ( ( (event.getPoint().getX())/pixPerBase) + getBaseAtStartOfView() );
    if(start < 1)
      start = 1;
    if(start > seqLength)
      start = seqLength;
    if (dragStart < 0 && (event.getModifiersEx() & onmask) == onmask)
      dragStart = start;
    else if((event.getModifiersEx() & onmask) != onmask)
      dragStart = -1;
    
    MarkerRange drag_range;
    try
      if(dragStart < 0)
        drag_range = new MarkerRange (bases.getForwardStrand(), start, start);
      else
        drag_range = new MarkerRange (bases.getForwardStrand(), dragStart, start);
      getSelection().setMarkerRange(drag_range);
      repaint();
    catch (OutOfRangeException e)
      e.printStackTrace();
  /**
   * Get the colour for the given read given to it by the coverage plot.
   * @param samRecord
   * @return
   */
  private Color getColourByCoverageColour(BamViewRecord samRecord)
    if(bamList.size()>1)
      fileIndex = samRecord.bamIndex;
    return getColourByCoverageColour(fileIndex); 
  }
  
  private Color getColourByCoverageColour(final short fileIndex)
  {
    LineAttributes lines[] = CoveragePanel.getLineAttributes(bamList.size());
    return lines[fileIndex].getLineColour(); 
  }

  protected int getMaxBases()
  {
    return MAX_BASES;
  }
  
  protected void setMaxBases(int max)
  {
    MAX_BASES = max;
  }
  
  private boolean isStackView()
  {
    return cbStackView.isSelected();  
  }
  
  private boolean isPairedStackView()
  {
    return cbPairedStackView.isSelected();
  }
  
  private boolean isStrandStackView()
  {
    return cbStrandStackView.isSelected();
  }
  
  private boolean isCoverageView(float pixPerBase)
  {
    if(isBaseAlignmentView(pixPerBase))
      return false;
tcarver's avatar
tcarver committed
    return cbCoverageView.isSelected() || cbCoverageStrandView.isSelected() || cbCoverageHeatMap.isSelected();
  }
  
  private boolean isIsizeStackView()
  {
    return cbIsizeStackView.isSelected();
  }
  
  private boolean isBaseAlignmentView(float pixPerBase)
  {
    if(pixPerBase*1.08f >= ALIGNMENT_PIX_PER_BASE)
      return true;
    return false;
  }
  
  private JCheckBoxMenuItem getSelectedCheckBoxMenuItem()
  {
    if(isStackView())
      return cbStackView;
    if(isPairedStackView())
      return cbPairedStackView;
    if(isStrandStackView())
      return cbStrandStackView;
    if(isIsizeStackView())
      return cbIsizeStackView;
    if(cbCoverageView.isSelected())
      return cbCoverageView;
tcarver's avatar
tcarver committed
    if(cbCoverageHeatMap.isSelected())
      return cbCoverageHeatMap;
    return cbCoverageStrandView;
  protected Selection getSelection()
  protected List<BamViewRecord> getReadsInView()
tjc's avatar
tjc committed
    return readsInView;
  }
  
tjc's avatar
tjc committed
  protected int getBasesInView()
  {
    return nbasesInView;
  }
  
  protected void setHighlightSAMRecord(BamViewRecord highlightSAMRecord)
tcarver's avatar
tcarver committed
  {
    this.highlightSAMRecord = highlightSAMRecord;
  }
  
  protected BamViewRecord getHighlightSAMRecord()
tcarver's avatar
tcarver committed
  {
    return highlightSAMRecord;
  }
  
tcarver's avatar
tcarver committed
  protected FeatureDisplay getFeatureDisplay()
  {
    return feature_display;
  }
  
  /**
   * @return the combo
   */
  public SequenceComboBox getCombo()
  {
    return combo;
  }
  
tjc's avatar
tjc committed
  private String getVersion()
  {
    final ClassLoader cl = this.getClass().getClassLoader();
    try
    {
      String line;
      InputStream in = cl.getResourceAsStream("etc/versions");
      BufferedReader reader = new BufferedReader(new InputStreamReader(in));
      while((line = reader.readLine()) != null)
      {
        if(line.startsWith("BamView"))
          return line.substring( "BamView".length() ).trim();
      }
      reader.close();
      in.close();
    }
    catch (Exception ex)
    {
    }
    return null;
  }
  
tcarver's avatar
tcarver committed
  /**
   * Open another BamView window
   */
  public void openBamView(final List<String> bamsList)
tcarver's avatar
tcarver committed
  {
    BamView bamView = new BamView(bamsList, 
tcarver's avatar
tcarver committed
        null, nbasesInView, entry_edit,
        feature_display, bases, (JPanel) mainPanel.getParent(), null);
    bamView.getJspView().getVerticalScrollBar().setValue(
        bamView.getJspView().getVerticalScrollBar().getMaximum());
    getJspView().getVerticalScrollBar().setValue(
        bamView.getJspView().getVerticalScrollBar().getMaximum());

    int start = getBaseAtStartOfView();
    setDisplay(start, nbasesInView+start, null);
    if(feature_display != null)
    {
      feature_display.addDisplayAdjustmentListener(bamView);
      feature_display.getSelection().addSelectionChangeListener(bamView);
tcarver's avatar
tcarver committed
      if(entry_edit != null)
        entry_edit.getOneLinePerEntryDisplay().addDisplayAdjustmentListener(bamView);
      if(feature_display.getEntryGroup().getSequenceEntry().getEMBLEntry().getSequence() 
          instanceof uk.ac.sanger.artemis.io.IndexFastaStream)
      {
        if(entry_edit != null)
        {
          // add reference sequence selection listeners
          entry_edit.getEntryGroupDisplay().getIndexFastaCombo().addIndexReferenceListener(bamView.getCombo());
          bamView.getCombo().addIndexReferenceListener(entry_edit.getEntryGroupDisplay().getIndexFastaCombo());
tjc's avatar
tjc committed
  /**
   * Artemis event notification
   */
  public void displayAdjustmentValueChanged(final DisplayAdjustmentEvent event)
    if(event.getType() == DisplayAdjustmentEvent.REV_COMP_EVENT &&
       event.isRevCompDisplay())
      JOptionPane.showMessageDialog(this, 
          "Flipping the display is not supported by BamView.", "Warning", 
          JOptionPane.WARNING_MESSAGE);

    if(!asynchronous)
    {
      // if not asynchronous
      displayAdjustmentWork(event);
      return;
    }
    
    SwingWorker worker = new SwingWorker()
tjc's avatar
tjc committed
    {
      public Object construct()
      {
        try
        {
          Thread.sleep(500);
        }
        catch (InterruptedException e)
        {
          e.printStackTrace();
        }
        
        if(event.getStart() != ((FeatureDisplay)event.getSource()).getForwardBaseAtLeftEdge())
        {
          waitingFrame.showWaiting("waiting...", mainPanel);
        displayAdjustmentWork(event);
        waitingFrame.setVisible(false);
        return null;
      }
    };
    worker.start();
  /**
   * Carry out the display agjustment event action.
   * @param event
   */
  private void displayAdjustmentWork(final DisplayAdjustmentEvent event)
  {
    if(event.getType() == DisplayAdjustmentEvent.SCALE_ADJUST_EVENT)
    {
      laststart = -1;
tjc's avatar
tjc committed

      BamView.this.startBase = event.getStart();
      BamView.this.endBase   = event.getEnd();

      int width = feature_display.getMaxVisibleBases();
      setZoomLevel(width);
      repaint();
    }
    else
    {
      setDisplay(event.getStart(), 
        event.getStart()+feature_display.getMaxVisibleBases(), event);
      repaint();
    }
  }
  
tjc's avatar
tjc committed
  public void selectionChanged(SelectionChangeEvent event)
  {
    repaint();
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;
    protected int start;
    protected int end;
    protected String refSeq;
tjc's avatar
tjc committed

tjc's avatar
tjc committed
    public Ruler()
    {
      super();
      setPreferredSize(new Dimension(mainPanel.getWidth(), 26));
tjc's avatar
tjc committed
      setBackground(Color.white);
    }
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;
      if(end > getSequenceLength())
        end = getSequenceLength();

      for(int i=startMark; i<end; i+=20)
tjc's avatar
tjc committed
      {
tjc's avatar
tjc committed
        int xpos = (i-start)*ALIGNMENT_PIX_PER_BASE;
tjc's avatar
tjc committed
        g2.drawString(Integer.toString(i), xpos, ypos);
tjc's avatar
tjc committed
        
      for(int i=startMark; i<end; i+=10)
      {
        int xpos = (i-start)*ALIGNMENT_PIX_PER_BASE;
tjc's avatar
tjc committed
        xpos+=(ALIGNMENT_PIX_PER_BASE/2);
        g2.drawLine(xpos, ypos+1, xpos, ypos+5);
      }
      
      if(refSeq != null)
      {
        ypos+=15;
        g2.setColor(LIGHT_GREY);
        g2.fillRect(0, ypos-11, getWidth(), 11);

        g2.translate(0, 16);
        drawSelectionRange(g2, ALIGNMENT_PIX_PER_BASE, start, end, Color.yellow);
        g2.translate(0, -16);
        g2.setColor(Color.black);
        g2.drawString(refSeq, 0, ypos-2);
      }
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
  }
  
  /**
  * Popup menu listener
  */
  class PopupListener extends MouseAdapter
  {
tcarver's avatar
tcarver committed
	private JMenuItem gotoMateMenuItem;
	private JMenuItem showDetails;
	private JMenu coverageMenu;
	private JMenuItem createGroup;
    public void mouseClicked(MouseEvent e)
    {
      if(e.isPopupTrigger() ||
         e.getButton() == MouseEvent.BUTTON3)
        return;
      
      
      if(e.getClickCount() > 1)
        getSelection().clear(); 
      else if(e.getButton() == MouseEvent.BUTTON1)
tcarver's avatar
tcarver committed
      {
        if(isCoverageView(getPixPerBaseByWidth()))
          coverageView.singleClick(e.isShiftDown(),
              e.getPoint().y-getJspView().getViewport().getViewPosition().y);
        else
          highlightSAMRecord = mouseOverSAMRecord;
      }
      else
        highlightRange(e, MouseEvent.BUTTON2_DOWN_MASK);
    }
    
    public void mousePressed(MouseEvent e)
    {
      maybeShowPopup(e);
    }

    public void mouseReleased(MouseEvent e)
    {
      maybeShowPopup(e);
    }

    private void maybeShowPopup(MouseEvent e)
    {
      if(e.isPopupTrigger())
tcarver's avatar
tcarver committed
      {       
        //
        // main menu options
tjc's avatar
tjc committed
        if(popup == null)
        {
          popup = new JPopupMenu();
          createMenus(popup);
tjc's avatar
tjc committed
        }
tcarver's avatar
tcarver committed

        //
        // coverage heatmap menu options
        if(coverageMenu != null)
          popup.remove(coverageMenu);
        if(isCoverageView(getPixPerBaseByWidth()) && coverageView.isPlotHeatMap())
        {
          if(coverageMenu == null)
          {
            coverageMenu = new JMenu("Coverage HeatMap");
tcarver's avatar
tcarver committed
            coverageView.createMenus(coverageMenu);
            
tcarver's avatar
tcarver committed
            final JCheckBoxMenuItem coverageGrid = new JCheckBoxMenuItem("Show heatmap grid", false);
tcarver's avatar
tcarver committed
            coverageGrid.addActionListener(new ActionListener()
            {
              public void actionPerformed(ActionEvent e) 
              {
                coverageView.showLabels(coverageGrid.isSelected());
              }
            });
            coverageMenu.add(coverageGrid);

            createGroup = new JMenuItem("Create group from selected BAMs");
            createGroup.addActionListener(new ActionListener()
            {
tcarver's avatar
tcarver committed
              private int n = 1;
tcarver's avatar
tcarver committed
              public void actionPerformed(ActionEvent e) 
              {
tcarver's avatar
tcarver committed
                String groupName = "group_"+n;
                groupsFrame.addGroup(groupName);
                final List<String> selected = coverageView.getSelected();
                for(String sel: selected)
                  groupsFrame.addToGroup((new File(sel)).getName(), groupName);
                groupsFrame.updateAndDisplay();
                n++;
tcarver's avatar
tcarver committed
              }
            });
            coverageMenu.add(createGroup);
          }
          createGroup.setEnabled(coverageView.hasSelectedBams());
          popup.add(coverageMenu);
        }

        if(gotoMateMenuItem != null)
          popup.remove(gotoMateMenuItem);
        if(showDetails != null)
          popup.remove(showDetails);
            mouseOverSAMRecord.sam.getReadPairedFlag() &&
           !mouseOverSAMRecord.sam.getMateUnmappedFlag() )
          final BamViewRecord thisSAMRecord = mouseOverSAMRecord;
          gotoMateMenuItem = new JMenuItem("Go to mate of : "+
              thisSAMRecord.sam.getReadName());
          gotoMateMenuItem.addActionListener(new ActionListener()
          {
			public void actionPerformed(ActionEvent e) 
			{
			  String name = thisSAMRecord.sam.getMateReferenceName();
			    name = thisSAMRecord.sam.getReferenceName();
			  int offset = getSequenceOffset(name);
			  if(feature_display != null)
			    feature_display.makeBaseVisible(
			        thisSAMRecord.sam.getMateAlignmentStart()+offset);
			        thisSAMRecord.sam.getMateAlignmentStart()+offset-
			        (nbasesInView/2));
			  
			  highlightSAMRecord = thisSAMRecord; 
			}  
          });
          popup.add(gotoMateMenuItem);
          final BamViewRecord thisSAMRecord = mouseOverSAMRecord;
          showDetails = new JMenuItem("Show details of : "+
              thisSAMRecord.sam.getReadName());
          showDetails.addActionListener(new ActionListener()
          {
            public void actionPerformed(ActionEvent e) 
            {
              openFileViewer(thisSAMRecord.sam, getMate(thisSAMRecord), bamList);
tcarver's avatar
tcarver committed
            }
        popup.show(e.getComponent(),
                e.getX(), e.getY());
tjc's avatar
tjc committed
      }
  protected static void openFileViewer(SAMRecord readRecord, SAMRecord mateRecord, List<String> bamList)
tcarver's avatar
tcarver committed
  {
    FileViewer viewDetail = new FileViewer(readRecord.getReadName(), true, false, false);
    appendToDetailView(readRecord, mateRecord, viewDetail, bamList);
  private static void appendToDetailView(final SAMRecord sam, 
                                         final SAMRecord mate, 
                                         final FileViewer viewer, 
                                         final List<String> bamList)
    if(bamList.size() > 1 && sam.getAttribute("FL") != null)
      int bamIdx = (Integer)sam.getAttribute("FL");
      if(bamIdx < bamList.size())
        viewer.appendString("File                  "+bamList.get(bamIdx)+"\n\n", Level.INFO);
    viewer.appendString("Read Name             "+sam.getReadName()+"\n", Level.INFO);
    viewer.appendString("Coordinates           "+sam.getAlignmentStart()+".."+
                                                 sam.getAlignmentEnd()+"\n", Level.DEBUG);
tcarver's avatar
tcarver committed
    if(sam.getReadGroup() != null)
      viewer.appendString("Read Group            "+sam.getReadGroup().getId()+"\n", Level.DEBUG);
    viewer.appendString("Length                "+sam.getReadLength()+"\n", Level.DEBUG);
    viewer.appendString("Reference Name        "+sam.getReferenceName()+"\n", Level.DEBUG);
    viewer.appendString("Inferred Size         "+sam.getInferredInsertSize()+"\n", Level.DEBUG);
    viewer.appendString("Mapping Quality       "+sam.getMappingQuality()+"\n", Level.DEBUG);
    viewer.appendString("Cigar String          "+sam.getCigarString()+"\n", Level.DEBUG);
    viewer.appendString("Strand                "+
        (sam.getReadNegativeStrandFlag() ? "-\n\n" : "+\n\n"), Level.DEBUG);
    if(sam.getReadPairedFlag())
      if(mate != null)
        viewer.appendString("Mate Coordinates      "+mate.getAlignmentStart()+".."+
                                                     mate.getAlignmentEnd()+"\n", Level.DEBUG);
        viewer.appendString("Mate Length           "+mate.getReadLength()+"\n", Level.DEBUG);
        viewer.appendString("Mate Reference Name   "+mate.getReferenceName()+"\n", Level.DEBUG);
        viewer.appendString("Mate Inferred Size    "+mate.getInferredInsertSize()+"\n", Level.DEBUG);
        viewer.appendString("Mate Mapping Quality  "+mate.getMappingQuality()+"\n", Level.DEBUG);
        viewer.appendString("Mate Cigar String     "+mate.getCigarString()+"\n", Level.DEBUG);
        viewer.appendString("Mate Start Coordinate "+sam.getMateAlignmentStart()+"\n", Level.DEBUG);
        viewer.appendString("Mate Reference Name   "+sam.getMateReferenceName()+"\n", Level.DEBUG);
      viewer.appendString("Mate Strand           "+
          (sam.getMateNegativeStrandFlag() ? "-" : "+"), Level.DEBUG);

    viewer.appendString("\n\nFlags:", Level.INFO);
    viewer.appendString("\nDuplicate Read    "+
        (sam.getDuplicateReadFlag() ? "yes" : "no"), Level.DEBUG);
    viewer.appendString("\nRead Paired       "+
        (sam.getReadPairedFlag() ? "yes" : "no"), Level.DEBUG);
    if(sam.getReadPairedFlag())
      viewer.appendString("\nFirst of Pair     "+
        (sam.getFirstOfPairFlag() ? "yes" : "no"), Level.DEBUG);
      viewer.appendString("\nMate Unmapped     "+
        (sam.getMateUnmappedFlag() ? "yes" : "no"), Level.DEBUG);  
      viewer.appendString("\nProper Pair       "+
        (sam.getProperPairFlag() ? "yes" : "no"), Level.DEBUG);
    viewer.appendString("\nRead Fails Vendor\nQuality Check     "+
        (sam.getReadFailsVendorQualityCheckFlag() ? "yes" : "no"), Level.DEBUG);
    viewer.appendString("\nRead Unmapped     "+
        (sam.getReadUnmappedFlag() ? "yes" : "no"), Level.DEBUG);
    if(sam.getReadPairedFlag())
      viewer.appendString("\nSecond Of Pair    "+
        (sam.getSecondOfPairFlag() ? "yes" : "no"), Level.DEBUG);
    viewer.appendString("\n\nRead Bases:\n", Level.INFO);
    wrapReadBases(sam, viewer);
    if(sam.getReadPairedFlag() && mate != null)
    {
      viewer.appendString("\nMate Read Bases:\n", Level.INFO);
      wrapReadBases(mate, viewer);
    }
  }
  
  private static void wrapReadBases(final SAMRecord sam, 
                             final FileViewer viewer)
  {
    final String seq = new String(sam.getReadBases());
    final int MAX_SEQ_LINE_LENGTH = 100;
    for(int i=0; i<=seq.length(); i+=MAX_SEQ_LINE_LENGTH)
    {
      int iend = i+MAX_SEQ_LINE_LENGTH;
      if(iend > seq.length())
        iend = seq.length();
      viewer.appendString(seq.substring(i, iend)+"\n", Level.DEBUG);
  /**
   * Query for the mate of a read
   * @param mate
   * @return
   */
  protected SAMRecord getMate(BamViewRecord thisSAMRecord)
    if(!thisSAMRecord.sam.getReadPairedFlag())  // read is not paired in sequencing
tcarver's avatar
tcarver committed
      return null;
    
    SAMRecord mate = null;
    try
    {
      short fileIndex = 0;
      if(bamList.size()>1 && thisSAMRecord.bamIndex > 0)
        fileIndex = thisSAMRecord.bamIndex;
      String bam = bamList.get(fileIndex);  
      final SAMFileReader inputSam = getSAMFileReader(bam);
      mate = inputSam.queryMate(thisSAMRecord.sam);
    }
    catch (Exception e)
    {
tcarver's avatar
tcarver committed
      logger4j.warn("WARN: BamView.getMate() :: "+e.getMessage());
      e.printStackTrace();
    }
    return mate;
  }
  
tjc's avatar
tjc committed
  protected SAMRecordPredicate getSamRecordFlagPredicate()
  {
    return samRecordFlagPredicate;
  }

  protected void setSamRecordFlagPredicate(
tjc's avatar
tjc committed
      SAMRecordPredicate samRecordFlagPredicate)
  {
    laststart = -1;
    lastend = -1;
    this.samRecordFlagPredicate = samRecordFlagPredicate;
  }
tjc's avatar
tjc committed
  protected SAMRecordMapQPredicate getSamRecordMapQPredicate()
  {
    return samRecordMapQPredicate;
  }

  protected void setSamRecordMapQPredicate(
      SAMRecordMapQPredicate samRecordMapQPredicate)
  {
    laststart = -1;
    lastend = -1;
    this.samRecordMapQPredicate = samRecordMapQPredicate;
  }
  
  /**
   * @return the concatSequences
   */
  protected boolean isConcatSequences()
  {
    return concatSequences;
  }

    BamViewRecord sam1;
    BamViewRecord sam2;
    CreateFeatures(final GroupBamFrame groupsFrame)
    {
      final TextFieldInt threshold = new TextFieldInt();
      final TextFieldInt minSize = new TextFieldInt();
      final TextFieldInt minBams = new TextFieldInt();
      
      threshold.setValue(6);
      minSize.setValue(6);
      minBams.setValue( (groupsFrame.getNumberOfGroups() == 1 ?
          bamList.size() : groupsFrame.getMaximumBamsInGroup()) );
      
      final JPanel gridPanel = new JPanel(new GridBagLayout());
      GridBagConstraints c = new GridBagConstraints();
      c.anchor = GridBagConstraints.WEST;
      c.fill = GridBagConstraints.HORIZONTAL;
      c.gridx = 0;
      c.gridy = 0;
      
      gridPanel.add(new JLabel("Minimum number of reads:"), c);
      c.gridy++;
      gridPanel.add(threshold, c);
      
      c.gridy++;
      gridPanel.add(new JSeparator(), c);
      c.gridy++;
      gridPanel.add(new JLabel("Minimum number of BAMs for reads to be present in:"), c);
      c.gridy++;
      gridPanel.add(minBams, c);
      
      JRadioButton useAllBams = new JRadioButton("out of all BAMs", (groupsFrame.getNumberOfGroups() == 1));
      JRadioButton useGroup = new JRadioButton("within a group", (groupsFrame.getNumberOfGroups() != 1));
      
      if(groupsFrame.getNumberOfGroups() == 1)
        useGroup.setEnabled(false);
      
      final ButtonGroup group = new ButtonGroup();
      group.add(useAllBams);
      group.add(useGroup);

      final Box xBox = Box.createHorizontalBox();
      xBox.add(useAllBams);
      xBox.add(useGroup);
      xBox.add(Box.createHorizontalGlue());
      c.gridy++;
      gridPanel.add(xBox, c);
      c.gridy++;
      gridPanel.add(new JSeparator(), c);
      c.gridy++;
      gridPanel.add(new JLabel("Minimum feature size:"), c);
      c.gridy++;
      gridPanel.add(minSize, c);

      final JCheckBox cbOpposite = new JCheckBox("Assume reads on opposite strand", false);
      cbOpposite.setToolTipText("for cDNA experiments when the reads are on the opposite strand");
      c.gridy++;
      gridPanel.add(cbOpposite, c);

      int status =
          JOptionPane.showConfirmDialog(feature_display, gridPanel, 
              "Options", JOptionPane.OK_CANCEL_OPTION);
      if(status == JOptionPane.CANCEL_OPTION)
        return;
      
      if(!useGroup.isSelected() && minBams.getValue() > bamList.size())
      {
        status =
            JOptionPane.showConfirmDialog(feature_display, 
                "The minimum number of BAMs setting can not be\n"+
                "greater than the total number of BAM files.\n"+
                "Set this to the number of BAMs (i.e. "+bamList.size()+").",
                "Options", JOptionPane.OK_CANCEL_OPTION);
        if(status == JOptionPane.CANCEL_OPTION)
          return;
        minBams.setValue(bamList.size());
      }
      else if(useGroup.isSelected() && minBams.getValue() > groupsFrame.getMaximumBamsInGroup())
      {
        status =
            JOptionPane.showConfirmDialog(feature_display, 
                "Minimum number of BAMs setting can not be greater than\n"+
                "the total number of BAM files found in any of the groups.\n"+
                "Set this to the greatest number of BAM files in any\n"+
                "group (i.e. "+groupsFrame.getMaximumBamsInGroup()+").",
                "Options", JOptionPane.OK_CANCEL_OPTION);
        if(status == JOptionPane.CANCEL_OPTION)
          return;
        minBams.setValue(groupsFrame.getMaximumBamsInGroup());
      }

      new MappedReads((String)combo.getSelectedItem(),BamView.this, samFileReaderHash,
          seqNames, offsetLengths, concatSequences, seqLengths, 
          (useGroup.isSelected() ? groupsFrame : null), threshold.getValue(), 
          minSize.getValue(), minBams.getValue(), cbOpposite.isSelected(), true);
tjc's avatar
tjc committed
  public static void main(String[] args)
  {
tjc's avatar
tjc committed
    BamFrame frame = new BamFrame();
tjc's avatar
tjc committed
    if(args.length == 0 && BamFrame.isMac())
    {
      try
      {
        Thread.sleep(1000);
      }
      catch (InterruptedException e1) {}
tjc's avatar
tjc committed
      if(frame.getBamFile() != null)
tjc's avatar
tjc committed
        args = new String[]{ frame.getBamFile() };
    }
tjc's avatar
tjc committed
      
    List<String> bam = new Vector<String>();
tjc's avatar
tjc committed
    String reference = null;
    if(args.length == 0 || args[0].equals("NEW-BAMVIEW"))
tjc's avatar
tjc committed
      System.setProperty("default_directory", System.getProperty("user.dir"));
      FileSelectionDialog fileSelection = new FileSelectionDialog(
          null, true, "BamView", "BAM");
tcarver's avatar
tcarver committed
      bam = fileSelection.getFiles(BAM_SUFFIX); 
      reference = fileSelection.getReferenceFile();
      if(reference == null || reference.equals(""))
        reference = null;
tjc's avatar
tjc committed
      
      if(bam == null || bam.size() < 1)
        if(args.length > 0 && args[0].equals("NEW-BAMVIEW"))
          return;
        System.err.println("No files found.");
tjc's avatar
tjc committed
        System.exit(0);
    else if(!args[0].startsWith("-"))
    {
      for(int i=0; i< args.length; i++)
        bam.add(args[i]);
    }
    int nbasesInView = 2000;
tjc's avatar
tjc committed
    String chr = null;
    String vw  = null;
    boolean orientation = false;
    boolean covPlot     = false;
    boolean snpPlot     = false;
    int base = 0;
    
tjc's avatar
tjc committed
    for(int i=0;i<args.length; i++)
    {
      if(args[i].equals("-a"))
      {
        while(i < args.length-1 && !args[++i].startsWith("-"))
        {
          String filename = args[i];
          if(FileSelectionDialog.isListOfFiles(filename))
            bam.addAll(FileSelectionDialog.getListOfFiles(filename));
          else
            bam.add(filename);
        }
tjc's avatar
tjc committed
      else if(args[i].equals("-r"))
        reference = args[++i];
tjc's avatar
tjc committed
      else if(args[i].equals("-n"))
tjc's avatar
tjc committed
        nbasesInView = Integer.parseInt(args[++i]);
      else if(args[i].equals("-s"))
        System.setProperty("samtoolDir", args[++i]);
tjc's avatar
tjc committed
      else if(args[i].equals("-c"))
        chr = args[++i].trim();
      else if(args[i].equals("-b"))
        base = Integer.parseInt(args[++i].trim());
      else if(args[i].equals("-v"))
        vw = args[++i].trim();
      else if(args[i].equals("-o"))
        orientation = true;
      else if(args[i].equals("-pc"))
        covPlot = true;
      else if(args[i].equals("-ps"))
        snpPlot = true;
tjc's avatar
tjc committed
      else if(args[i].startsWith("-h"))
      { 
        System.out.println("-h\t show help");
        
        System.out.println("-a\t BAM/SAM file to display");
        System.out.println("-r\t reference file (optional)");
tjc's avatar
tjc committed
        System.out.println("-n\t number of bases to display in the view (optional)");
        System.out.println("-c\t chromosome name (optional)");
        System.out.println("-v\t view (optional - IS (inferred size), S (stack, default), PS (paired stack), ST (strand), C (coverage))");
        System.out.println("-b\t base position (optional)");
        System.out.println("-o\t show orientation (optional)");
        System.out.println("-pc\t plot coverage (optional)");
        System.out.println("-ps\t plot SNP (optional and only with -r)");
tjc's avatar
tjc committed
        System.exit(0);
      }
    }

tjc's avatar
tjc committed
    final BamView view = new BamView(bam, reference, nbasesInView, null, null,
        (JPanel)frame.getContentPane(), frame);
tjc's avatar
tjc committed
    frame.setTitle("BamView v"+view.getVersion());
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
    if(chr != null)
      view.combo.setSelectedItem(chr);
    if(vw != null)
    {
      if(vw.equalsIgnoreCase("IS"))
        view.cbIsizeStackView.setSelected(true);
      if(vw.equalsIgnoreCase("PS"))
        view.cbPairedStackView.setSelected(true);
      if(vw.equalsIgnoreCase("ST"))
        view.cbStrandStackView.setSelected(true);
      if(vw.equalsIgnoreCase("C"))
        view.cbCoverageView.setSelected(true);
    }
    if(base > 0)
      view.scrollBar.setValue(base);
    if(orientation)
      view.isOrientation = true;
    if(covPlot)
    {
      view.isCoverage = true;
      view.coveragePanel.setVisible(true);
    }
    if(snpPlot)
    {
      view.isSNPplot = true;
      view.snpPanel.setVisible(true);
    }

    // translucent
    //frame.getRootPane().putClientProperty("Window.alpha", new Float(0.9f));
    /*frame.addWindowFocusListener(new WindowFocusListener()
tjc's avatar
tjc committed
    {
      public void windowGainedFocus(WindowEvent e)
      {
        view.requestFocus();
      }
      public void windowLostFocus(WindowEvent e){}
tjc's avatar
tjc committed
    frame.pack();
tjc's avatar
tjc committed
    view.jspView.getVerticalScrollBar().setValue(
        view.jspView.getVerticalScrollBar().getMaximum());
    frame.setVisible(true);
tjc's avatar
tjc committed
  }
}