Skip to content
Snippets Groups Projects
VCFview.java 45.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • tjc's avatar
    tjc committed
    /* VCFview
     *
     * created: July 2010
     *
     * This file is part of Artemis
     *
     * Copyright(C) 2010  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.
     *
     */
    
    package uk.ac.sanger.artemis.components.variant;
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Dimension;
    
    import java.awt.FlowLayout;
    
    tjc's avatar
    tjc committed
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Insets;
    import java.awt.Point;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.AdjustmentEvent;
    import java.awt.event.AdjustmentListener;
    import java.awt.event.ItemEvent;
    import java.awt.event.ItemListener;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseMotionListener;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileWriter;
    import java.io.IOException;
    
    import java.util.Hashtable;
    
    tjc's avatar
    tjc committed
    import java.util.List;
    import java.util.Vector;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    tjc's avatar
    tjc committed
    
    import javax.swing.JButton;
    
    import javax.swing.JCheckBox;
    
    import javax.swing.JCheckBoxMenuItem;
    
    tjc's avatar
    tjc committed
    import javax.swing.JComboBox;
    
    import javax.swing.JComponent;
    
    tjc's avatar
    tjc committed
    import javax.swing.JFrame;
    import javax.swing.JMenu;
    import javax.swing.JMenuBar;
    import javax.swing.JMenuItem;
    
    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.JScrollBar;
    import javax.swing.JScrollPane;
    
    import javax.swing.border.Border;
    import javax.swing.border.EmptyBorder;
    
    tjc's avatar
    tjc committed
    
    import net.sf.samtools.util.BlockCompressedInputStream;
    
    import org.apache.log4j.Level;
    
    import uk.ac.sanger.artemis.Entry;
    import uk.ac.sanger.artemis.EntryGroup;
    
    import uk.ac.sanger.artemis.Feature;
    import uk.ac.sanger.artemis.FeatureKeyPredicate;
    import uk.ac.sanger.artemis.FeatureVector;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.Options;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.Selection;
    
    import uk.ac.sanger.artemis.SelectionChangeEvent;
    import uk.ac.sanger.artemis.SelectionChangeListener;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.SimpleEntryGroup;
    
    import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
    import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.components.EntryFileDialog;
    
    import uk.ac.sanger.artemis.components.FeatureDisplay;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.components.FileViewer;
    import uk.ac.sanger.artemis.components.MessageDialog;
    import uk.ac.sanger.artemis.components.alignment.FileSelectionDialog;
    import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
    import uk.ac.sanger.artemis.io.EmblStreamFeature;
    import uk.ac.sanger.artemis.io.EntryInformation;
    import uk.ac.sanger.artemis.io.InvalidRelationException;
    import uk.ac.sanger.artemis.io.Key;
    import uk.ac.sanger.artemis.io.Location;
    import uk.ac.sanger.artemis.io.Qualifier;
    import uk.ac.sanger.artemis.io.QualifierVector;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.io.Range;
    
    import uk.ac.sanger.artemis.io.RangeVector;
    
    import uk.ac.sanger.artemis.sequence.AminoAcidSequence;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.sequence.Bases;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.sequence.MarkerRange;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.sequence.NoSequenceException;
    import uk.ac.sanger.artemis.util.Document;
    import uk.ac.sanger.artemis.util.DocumentFactory;
    import uk.ac.sanger.artemis.util.OutOfRangeException;
    
    
    public class VCFview extends JPanel
    
                 implements DisplayAdjustmentListener, SelectionChangeListener
    
    tjc's avatar
    tjc committed
    {
      private static final long serialVersionUID = 1L;
      private JScrollBar scrollBar;
    
      private JPanel vcfPanel;
    
    tjc's avatar
    tjc committed
      private TabixReader tr[];
    
    tjc's avatar
    tjc committed
      private List<String> vcfFiles;
    
    tjc's avatar
    tjc committed
      private String header[];
    
      private FeatureDisplay feature_display;
    
    tjc's avatar
    tjc committed
      private Selection selection;
    
    tjc's avatar
    tjc committed
      private int nbasesInView;
      private int seqLength;
    
      private EntryGroup entryGroup;
    
    tjc's avatar
    tjc committed
      private String chr;
      private String mouseOverVCFline;
      private int mouseOverIndex = -1;
    
    tjc's avatar
    tjc committed
      
      private boolean vcf_v4 = false;
    
    tjc's avatar
    tjc committed
    //record of where a mouse drag starts
      private int dragStart = -1;
    
    tjc's avatar
    tjc committed
      private JPopupMenu popup;
    
    tjc's avatar
    tjc committed
      private int LINE_HEIGHT = 15;
    
      
      private boolean showSynonymous = true;
      private boolean showNonSynonymous = true;
      private boolean showDeletions = true;
      private boolean showInsertions = true;
    
      private boolean showMultiAlleles = true;
    
    tjc's avatar
    tjc committed
      private boolean markAsNewStop = false;
    
      final JCheckBoxMenuItem markNewStops =
    
    tjc's avatar
    tjc committed
        new JCheckBoxMenuItem("Mark new stops within CDS features", true);
    
      // show variants that do not overlap CDS
      private boolean showNonOverlappings = true;
    
    tjc's avatar
    tjc committed
      private float MIN_QUALITY = -10;
    
      Hashtable<String, Integer> offsetLengths = null;
      private boolean concatSequences = false;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
      private Pattern multiAllelePattern = Pattern.compile("^[AGCT]+,[AGCT,]+$");
    
    tjc's avatar
    tjc committed
      protected static Pattern tabPattern = Pattern.compile("\t");
    
    tjc's avatar
    tjc committed
    
    
      public VCFview(final JFrame frame,
                     final JPanel vcfPanel,
    
    tjc's avatar
    tjc committed
                     final List<String> vcfFiles, 
                     final int nbasesInView,
                     final int seqLength,
                     final String chr,
    
                     final String reference,
                     final FeatureDisplay feature_display)
    
    tjc's avatar
    tjc committed
      {
        super();
        
        this.nbasesInView = nbasesInView;
        this.seqLength = seqLength;
        this.chr = chr;
    
        this.feature_display = feature_display;
        this.vcfPanel = vcfPanel;
    
    tjc's avatar
    tjc committed
        this.vcfFiles = vcfFiles;
    
    tjc's avatar
    tjc committed
        
        setBackground(Color.white);
        MultiLineToolTipUI.initialize();
        setToolTipText("");
    
    tjc's avatar
    tjc committed
        vcfPanel.setPreferredSize(new Dimension(900, 
            (vcfFiles.size()+1)*(LINE_HEIGHT+5)));
    
    tjc's avatar
    tjc committed
        
    
        if(feature_display != null)
    
          this.entryGroup = feature_display.getEntryGroup();
    
        else if(reference != null)
    
          this.entryGroup = getReference(reference);
        if(entryGroup != null)
          this.seqLength = entryGroup.getSequenceEntry().getBases().getLength();
    
    tjc's avatar
    tjc committed
        try
        {
          tr = new TabixReader[vcfFiles.size()];
          header = new String[vcfFiles.size()];
          
          for(int i=0; i<vcfFiles.size(); i++)
          {
            header[i] = readHeader(vcfFiles.get(i));
            tr[i] = new TabixReader(vcfFiles.get(i));
          }
        }
    
    tjc's avatar
    tjc committed
        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();
        }
        
        final JScrollPane jspView = new JScrollPane(this, 
            JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        
    
        vcfPanel.setLayout(new BorderLayout());
        vcfPanel.add(jspView, BorderLayout.CENTER);
    
    tjc's avatar
    tjc committed
        
        if(this.nbasesInView > this.seqLength)
          this.nbasesInView = this.seqLength/2;
    
        scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 1, this.nbasesInView, 1, this.seqLength);
        scrollBar.setUnitIncrement(nbasesInView/20);
        scrollBar.addAdjustmentListener(new AdjustmentListener()
        {
          public void adjustmentValueChanged(AdjustmentEvent e)
          {
            repaint();
          }
        });
        
        //
        //
        addMouseListener(new PopupListener());
        
        //
    
        createMenus(frame, jspView);
    
        setDisplay();
        
        if(feature_display == null)
        {
          vcfPanel.add(scrollBar, BorderLayout.SOUTH);
          frame.pack();
          frame.setVisible(true);
    
    tjc's avatar
    tjc committed
          selection = new Selection(null);
    
        }
        else
        {
          Border empty = new EmptyBorder(0,0,0,0);
          jspView.setBorder(empty);
    
    tjc's avatar
    tjc committed
          selection = feature_display.getSelection();
    
      private void createMenus(JFrame frame, final JScrollPane jspView)
    
        final JComponent topPanel;
    
        if(feature_display != null)
          topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
        else
    
    tjc's avatar
    tjc committed
        {
          markNewStops.setSelected(false);
          markNewStops.setEnabled(false);
    
          topPanel = new JMenuBar();
          frame.setJMenuBar((JMenuBar)topPanel);
    
          
          JMenu fileMenu = new JMenu("File");
          topPanel.add(fileMenu);
    
    tjc's avatar
    tjc committed
        
    
          JMenuItem printImage = new JMenuItem("Save As Image Files (png/jpeg)...");
          printImage.addActionListener(new ActionListener()
    
    tjc's avatar
    tjc committed
          {
    
            public void actionPerformed(ActionEvent e)
            {
              PrintVCFview part = new PrintVCFview(VCFview.this);
              part.print();
            }
          });
          fileMenu.add(printImage);
          
          JMenuItem printPS = new JMenuItem("Print...");
          printPS.addActionListener(new ActionListener()
    
    tjc's avatar
    tjc committed
          {
    
            public void actionPerformed(ActionEvent e)
            {
              PrintVCFview part = new PrintVCFview(VCFview.this);
              part.validate();
              part.doPrintActions();
            }
          });
          fileMenu.add(printPS);
          
    
    tjc's avatar
    tjc committed
    
    
          JMenuItem close = new JMenuItem("Close");
          fileMenu.add(close);
          close.addActionListener(new ActionListener()
    
    tjc's avatar
    tjc committed
          {
    
            public void actionPerformed(ActionEvent e)
            {
              VCFview.this.setVisible(false);
              Component comp = VCFview.this;
              
              while( !(comp instanceof JFrame) )
                comp = comp.getParent();
              ((JFrame)comp).dispose();
            } 
          });
          
          JButton zoomIn = new JButton("-");
          Insets ins = new Insets(1,1,1,1);
          zoomIn.setMargin(ins);
          zoomIn.addActionListener(new ActionListener()
    
    tjc's avatar
    tjc committed
          {
    
            public void actionPerformed(ActionEvent e)
            {
              setZoomLevel((int) (VCFview.this.nbasesInView * 1.1));
            }
          });
          topPanel.add(zoomIn);
    
    tjc's avatar
    tjc committed
    
    
          JButton zoomOut = new JButton("+");
          zoomOut.setMargin(ins);
          zoomOut.addActionListener(new ActionListener()
    
    tjc's avatar
    tjc committed
          {
    
            public void actionPerformed(ActionEvent e)
            {
              setZoomLevel((int) (VCFview.this.nbasesInView * .9));
            }
          });
          topPanel.add(zoomOut);
        }
    
    tjc's avatar
    tjc committed
        
        final JComboBox combo = new JComboBox(tr[0].getmSeq());
    
        
        if(tr[0].getmSeq().length > 1)
    
    tjc's avatar
    tjc committed
          combo.addItem("Combine References");
    
    tjc's avatar
    tjc committed
        if(chr == null)
          this.chr = tr[0].getmSeq()[0];
    
    tjc's avatar
    tjc committed
        combo.setSelectedItem(this.chr);
        combo.setEditable(false);
        combo.setMaximumRowCount(20);
        
        combo.addItemListener(new ItemListener()
        {
          public void itemStateChanged(ItemEvent e)
          {
    
    tjc's avatar
    tjc committed
            if(combo.getSelectedItem().equals("Combine References"))
    
              concatSequences = true;
            else 
            {
              VCFview.this.chr = (String) combo.getSelectedItem();
              concatSequences = false;
            }
    
    tjc's avatar
    tjc committed
            repaint();
          }
        });
        topPanel.add(combo);
    
        if(topPanel instanceof JPanel)
          vcfPanel.add(topPanel, BorderLayout.NORTH);
        
        // auto hide top panel
        final JCheckBox buttonAutoHide = new JCheckBox("Hide", true);
        buttonAutoHide.setToolTipText("Auto-Hide");
        topPanel.add(buttonAutoHide);
        final MouseMotionListener mouseMotionListener = new MouseMotionListener()
        {
          public void mouseDragged(MouseEvent event)
          {
            handleCanvasMouseDrag(event);
          }
          
          public void mouseMoved(MouseEvent e)
          {
            findVariantAtPoint(e.getPoint());
    
            int thisHgt = HEIGHT;
            if (thisHgt < 5)
              thisHgt = 15;
    
            int y = (int) (e.getY() - jspView.getViewport().getViewRect().getY());
            if (y < thisHgt)
              topPanel.setVisible(true);
            else
            {
              if (buttonAutoHide.isSelected())
                topPanel.setVisible(false);
            }
    
          }
        };
        addMouseMotionListener(mouseMotionListener);
    
    
        
        // popup menu
        popup = new JPopupMenu();
    
    tjc's avatar
    tjc committed
        JMenuItem addVCFMenu = new JMenuItem("Add VCF ...");
        addVCFMenu.addActionListener(new ActionListener() 
        {
          public void actionPerformed(ActionEvent e)
          {
            FileSelectionDialog fileSelection = new FileSelectionDialog(
                null, true, "VCFview", "VCF");
            List<String> vcfFileList = fileSelection.getFiles(".*\\.vcf(\\.gz)*$");
    
    tjc's avatar
    tjc committed
            vcfFiles.addAll(vcfFileList);
    
    tjc's avatar
    tjc committed
    
            int count = vcfFileList.size();
            int oldSize = tr.length;
            
            TabixReader[] trTmp = new TabixReader[count + tr.length];
            System.arraycopy(tr, 0, trTmp, 0, tr.length);
            tr = trTmp;
            
            String[] hdTmp = new String[count + tr.length];
            System.arraycopy(header, 0, hdTmp, 0, header.length);
            header = hdTmp;
            
            try
            {
              for (int i = 0; i < vcfFileList.size(); i++)
              {
                header[i+oldSize] = readHeader(vcfFileList.get(i));
                tr[i+oldSize] = new TabixReader(vcfFileList.get(i));
              }
            }
            catch (IOException ioe)
            {
              ioe.printStackTrace();
            }
    
            setDisplay();
            repaint();
            jspView.revalidate();
          }
        });
        popup.add(addVCFMenu);
        popup.addSeparator();
        
    
        JMenu showMenu = new JMenu("Show");
        popup.add(showMenu);
        
    
        final JCheckBoxMenuItem showSyn = new JCheckBoxMenuItem(
    
            "Synonymous", showSynonymous);
    
        showSyn.addActionListener(new ActionListener(){
          public void actionPerformed(ActionEvent e)
          {
            showSynonymous = showSyn.isSelected();
            repaint();
          }
        });
    
        showMenu.add(showSyn);
    
        
        final JCheckBoxMenuItem showNonSyn = new JCheckBoxMenuItem(
    
            "Non-synonymous", showNonSynonymous);
    
        showNonSyn.addActionListener(new ActionListener(){
          public void actionPerformed(ActionEvent e)
          {
            showNonSynonymous = showNonSyn.isSelected();
            repaint();
          }
        });
    
        showMenu.add(showNonSyn);
    
        
        
        final JCheckBoxMenuItem showDeletionsMenu = new JCheckBoxMenuItem(
    
            "Deletions", showDeletions);
    
        showDeletionsMenu.addActionListener(new ActionListener(){
          public void actionPerformed(ActionEvent e)
          {
            showDeletions = showDeletionsMenu.isSelected();
            repaint();
          }
        });
    
        showMenu.add(showDeletionsMenu);
    
        
        final JCheckBoxMenuItem showInsertionsMenu = new JCheckBoxMenuItem(
    
            "Insertions", showInsertions);
    
        showInsertionsMenu.addActionListener(new ActionListener(){
          public void actionPerformed(ActionEvent e)
          {
            showInsertions = showInsertionsMenu.isSelected();
            repaint();
          }
        });
    
        showMenu.add(showInsertionsMenu);
    
        
        final JCheckBoxMenuItem showMultiAllelesMenu = new JCheckBoxMenuItem(
    
            "Multiple alleles", showMultiAlleles);
    
        showMultiAllelesMenu.addActionListener(new ActionListener(){
          public void actionPerformed(ActionEvent e)
          {
            showMultiAlleles = showMultiAllelesMenu.isSelected();
            repaint();
          }
        });
    
        showMenu.add(showMultiAllelesMenu);
        
        final JCheckBoxMenuItem showNonOverlappingsMenu = new JCheckBoxMenuItem(
            "Varaints not overlapping CDS", showNonOverlappings);
        showNonOverlappingsMenu.addActionListener(new ActionListener(){
          public void actionPerformed(ActionEvent e)
          {
            showNonOverlappings = showNonOverlappingsMenu.isSelected();
            repaint();
          }
        });
        showMenu.add(showNonOverlappingsMenu);
    
    tjc's avatar
    tjc committed
        
    
        markNewStops.addActionListener(new ActionListener(){
          public void actionPerformed(ActionEvent e)
          {
            if(!markNewStops.isSelected())
              markAsNewStop = false;
            repaint();
          }
        });
        popup.add(markNewStops);
        
    
    tjc's avatar
    tjc committed
        final JMenuItem filterByQuality = new JMenuItem("Filter by quality");
        filterByQuality.addActionListener(new ActionListener(){
          public void actionPerformed(ActionEvent e)
          {
            //
            String inputValue = JOptionPane.showInputDialog(null, 
                "Enter a minimum quality score:", MIN_QUALITY);
            if(inputValue == null)
              return;
            try
            {
              MIN_QUALITY = Float.parseFloat(inputValue);
              repaint();
            }
            catch(NumberFormatException ex)
            {
              JOptionPane.showMessageDialog(null, 
                  "Number "+inputValue+" not recognised.", 
                  "Format Error", JOptionPane.ERROR_MESSAGE);
            }
          }
        });
        popup.add(filterByQuality);
    
    tjc's avatar
    tjc committed
        
        
        final JMenuItem exportVCF = new JMenuItem("Export filtered VCF");
        exportVCF.addActionListener(new ActionListener(){
          public void actionPerformed(ActionEvent e)
          {
            IOUtils.export(entryGroup, vcfFiles, VCFview.this);
          }
        });
        popup.add(exportVCF);
    
    tjc's avatar
    tjc committed
      }
    
    tjc's avatar
    tjc committed
      private static EntryGroup getReference(String reference)
      {
        EntryGroup entryGroup = new SimpleEntryGroup();
        final Document entry_document = DocumentFactory.makeDocument(reference);
        final EntryInformation artemis_entry_information =
          Options.getArtemisEntryInformation();
    
        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
        {
          Entry entry = null;
          Bases bases = null;
          try
          {
            if (entryGroup.getSequenceEntry() != null)
              bases = entryGroup.getSequenceEntry().getBases();
    
            if (bases == null)
            {
              entry = new Entry(new_embl_entry);
              bases = entry.getBases();
            }
            else
              entry = new Entry(bases, new_embl_entry);
            entryGroup.add(entry);
          }
          catch (OutOfRangeException e)
          {
            new MessageDialog(null, "read failed: one of the features in "
                + reference + " has an out of range " + "location: "
                + e.getMessage());
          }
          catch (NoSequenceException e)
          {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
        }
        return entryGroup;
      }
      
      /**
       * Read the vcf header
       * @param fileName
       * @return
       */
      private String readHeader(final String fileName)
      {
        StringBuffer buff = new StringBuffer();
        buff.append(fileName+"\n");
        try
        {
    
    tjc's avatar
    tjc committed
          if(IOUtils.isBCF(fileName))
          {
            JOptionPane.showMessageDialog(null, 
                "Looks like a BCF formated file.\n"+
                "Convert to VCF and use bgzip and tabix\n"+
                "to compress and index respectively.", 
                "Unsupported Format", 
                JOptionPane.WARNING_MESSAGE);
          }
    
    tjc's avatar
    tjc committed
          
    
    tjc's avatar
    tjc committed
          BlockCompressedInputStream is = 
            new BlockCompressedInputStream(new FileInputStream(fileName));
    
    tjc's avatar
    tjc committed
          String line;
    
    tjc's avatar
    tjc committed
          while( (line = TabixReader.readLine(is) ) != null )
    
    tjc's avatar
    tjc committed
          {
            if(!line.startsWith("##"))
              break;
    
    tjc's avatar
    tjc committed
            
            if(line.indexOf("VCFv4") > -1)
              vcf_v4 = true;
            
    
    tjc's avatar
    tjc committed
            buff.append(line+"\n");
          }
        }
        catch (IOException e)
        {
          e.printStackTrace();
        }
        return buff.toString();
      }
      
      /**
       * Set the number of bases being displayed
       * @param nbasesInView
       */
      private void setZoomLevel(final int nbasesInView)
      {
        int startValue = scrollBar.getValue();
        this.nbasesInView = nbasesInView;
        float pixPerBase = getPixPerBaseByWidth(); 
        this.nbasesInView = (int)(getWidth()/pixPerBase);
        
        if(scrollBar != null)
        {
          scrollBar.setValues(startValue, nbasesInView, 1, seqLength);
          scrollBar.setUnitIncrement(nbasesInView/20);
          scrollBar.setBlockIncrement(nbasesInView);
        }
      }
      
      public String getToolTipText()
      {
        if(mouseOverVCFline == null)
          return null;
        
    
    tjc's avatar
    tjc committed
        String parts[] = tabPattern.split(mouseOverVCFline, 0);
    
    tjc's avatar
    tjc committed
        String msg = 
               "Seq: "+parts[0]+"\n";
        msg += "Pos: "+parts[1]+"\n";
        msg += "ID:  "+parts[2]+"\n";
        msg += "Variant: "+parts[3]+" -> "+parts[4]+"\n";
        msg += "Qual: "+parts[5]+"\n";
        
        return msg;
      }
      
    
      
      
      /**
       * For VCF files with multiple references sequences, calculate
       * the offset from the start of the concatenated sequence for 
       * a given reference.
       * @param refName
       * @return
       */
      protected int getSequenceOffset(String refName)
      {
        if(!concatSequences)
          return 0;
        
        if(offsetLengths == null)
        {   
          String[] contigs = tr[0].getmSeq();
          FeatureVector features = entryGroup.getAllFeatures();
          offsetLengths = new Hashtable<String, Integer>(contigs.length);
          for(int i=0; i<contigs.length; i++)
          {
            FeatureContigPredicate predicate = new FeatureContigPredicate(contigs[i]);
            for(int j=0; j<features.size(); j++)
            {
              if(predicate.testPredicate(features.elementAt(j)))
              {
                offsetLengths.put(contigs[i], features.elementAt(j).getFirstBase()-1);
                break;
              }
            }
          }
    
          
          if(offsetLengths.size() != contigs.length)
            JOptionPane.showMessageDialog(this, 
                "There is a problem matching the reference sequences\n"+
                "to the names in the VCF file. This may mean the labels\n"+
                "on the reference features do not match those in the in\n"+
                "the VCF file.", 
                "Problem Found", JOptionPane.WARNING_MESSAGE);
    
        }
        return offsetLengths.get(refName);
      }
      
    
    tjc's avatar
    tjc committed
      protected void paintComponent(Graphics g)
      {
        super.paintComponent(g);
        mouseOverVCFline = null;
    
        float pixPerBase = getPixPerBaseByWidth();
    
        int start = getBaseAtStartOfView();
    
    tjc's avatar
    tjc committed
        int end   = start+nbasesInView;
    
    tjc's avatar
    tjc committed
        drawSelectionRange((Graphics2D)g, pixPerBase, start, end);
    
        FeatureVector features = getCDSFeaturesInRange(start, end);
    
    tjc's avatar
    tjc committed
        for (int i = 0; i < tr.length; i++)
        {
    
          if(concatSequences) 
    
    tjc's avatar
    tjc committed
          {
    
            String[] contigs = tr[0].getmSeq();
            for(int j=0; j<contigs.length; j++)
            {
              int offset = getSequenceOffset(contigs[j]);
              int nextOffset;
              if(j<contigs.length-1)
                nextOffset = getSequenceOffset(contigs[j+1]);
              else
                nextOffset = seqLength;
              
              if( (offset >= start && offset < end) ||
                  (offset < start && start < nextOffset) )
              {
                int thisStart = start - offset;
                if(thisStart < 1)
                  thisStart = 1;
                int thisEnd   = end - offset;
                
                drawRegion(g, contigs[j]+":"+thisStart+"-"+thisEnd, i, start, pixPerBase, features); 
              }
            }
    
          } 
          else
    
          {
            int thisStart = start;
            if(thisStart < 1)
              thisStart = 1;
            drawRegion(g, chr+":"+thisStart+"-"+end, i, start, pixPerBase, features); 
          }
    
    tjc's avatar
    tjc committed
        }
    
    tjc's avatar
    tjc committed
    
        if(feature_display == null)
          drawScale((Graphics2D)g, start, end, pixPerBase, getHeight());
      }
      
    
      private void drawRegion(Graphics g, 
                              String region,
                              int i, 
                              int start, 
                              float pixPerBase, 
                              FeatureVector features) 
      {
        String s;
        TabixReader.Iterator iter = tr[i].query(region); // get the iterator
        if (iter == null)
          return;
        try
        {
          while ((s = iter.next()) != null)
            drawVariantCall(g, s, start, i, pixPerBase, features);
        }
        catch (IOException e)
        {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }  
      }
      
    
      private FeatureVector getCDSFeaturesInRange(int start, int end)
      {
    
    tjc's avatar
    tjc committed
        if(entryGroup == null)
          return null;
    
        try
        {
          Range range = new Range(start, end);
          FeatureVector features = entryGroup.getFeaturesInRange(range);
               
          FeatureKeyPredicate predicate = new FeatureKeyPredicate(Key.CDS);
          final FeatureVector cdsFeatures = new FeatureVector();
    
          for(int i=0; i<features.size(); i++)
          {
            final Feature this_feature = features.elementAt(i);
            if(predicate.testPredicate(this_feature))
              cdsFeatures.add(this_feature);
          }
          return cdsFeatures;
        }
        catch (OutOfRangeException e1)
        {
          e1.printStackTrace();
        }
        return null;
      }
      
    
    tjc's avatar
    tjc committed
      /**
       * Highlight a selected range
       * @param g2
       * @param pixPerBase
       * @param start
       * @param end
       */
      private void drawSelectionRange(Graphics2D g2, float pixPerBase, int start, int end)
      {
        if(getSelection() != null)
        {
          Range selectedRange = getSelection().getSelectionRange();
    
          if(selectedRange != null)
          {
            int rangeStart = selectedRange.getStart();
            int rangeEnd   = selectedRange.getEnd();
            
            if(end < rangeStart || start > rangeEnd)
              return;
            
            int x = (int) (pixPerBase*(rangeStart-getBaseAtStartOfView()));
            int width = (int) (pixPerBase*(rangeEnd-rangeStart+1));
    
            g2.setColor(Color.pink);
            g2.fillRect(x, 0, width, getHeight());
          }
        }
      }
      
      private Selection getSelection()
      {
        return selection;
    
    tjc's avatar
    tjc committed
      }
      
    
      protected int getBaseAtStartOfView()
      {
        if(feature_display != null)
          return feature_display.getForwardBaseAtLeftEdge();
        else
          return scrollBar.getValue();
      }
      
    
      /**
       * Is this a deletion type.
       * @param variant
       * @return
       */
    
    tjc's avatar
    tjc committed
      private boolean isDeletion(String ref, String variant)
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        if(vcf_v4)
        {
          if( variant.length() < ref.length() && !(variant.indexOf(",") > -1) )
            return true;
        }
        else if(variant.indexOf("D")>-1)
    
          return true;
        return false;
      }
      
      /**
       * Is this an insertion type.
       * @param variant
       * @return
       */
    
    tjc's avatar
    tjc committed
      private boolean isInsertion(String ref, String variant)
    
    tjc's avatar
    tjc committed
        if(vcf_v4)
        {
          if( variant.length() > ref.length() && !(variant.indexOf(",") > -1) )
            return true;
        }
        else if(variant.indexOf("I")>-1)
    
    tjc's avatar
    tjc committed
      protected boolean showVariant(String ref, String variant, FeatureVector features, int basePosition, String quality)
    
    tjc's avatar
    tjc committed
      {  
    
    tjc's avatar
    tjc committed
        if(!showDeletions && isDeletion(ref, variant))
    
    tjc's avatar
    tjc committed
        if(!showInsertions && isInsertion(ref, variant))
    
    tjc's avatar
    tjc committed
        try
        {
          if(Float.parseFloat(quality) < MIN_QUALITY)
            return false;
        }
        catch(NumberFormatException e)
        {
          System.err.println(e.getMessage()); 
        }
        
    
        if(!showNonOverlappings && !isOverlappingFeature(features, basePosition))
            return false;
        
    
        int isSyn = -1;
        if(markNewStops.isSelected() &&
           !isDeletion(ref, variant) && 
           !isInsertion(ref, variant) && 
            variant.length() == 1 && 
            ref.length() == 1)
        {
          isSyn = isSynonymous(features, basePosition, variant.toLowerCase().charAt(0));
          if(isSyn == 2)
            markAsNewStop = true;
          else
            markAsNewStop = false;
        }
        
    
        if( (!showSynonymous || !showNonSynonymous) &&
    
             !isDeletion(ref, variant) && 
             !isInsertion(ref, variant) && 
             variant.length() == 1 && 
             ref.length() == 1)
    
          if(isSyn == -1)
            isSyn = isSynonymous(features, basePosition, variant.toLowerCase().charAt(0));
    
          if( (!showSynonymous && isSyn == 1) ||
              (!showNonSynonymous && isSyn != 1 ) )
    
        if(!showMultiAlleles && multiAllelePattern.matcher(variant).matches())
          return false;
        
        return true;
      }
      
    
      private boolean isOverlappingFeature(FeatureVector features, int basePosition)
      {
        for(int i = 0; i<features.size(); i++)
        {
          Feature feature = features.elementAt(i);
          if(feature.getRawFirstBase() < basePosition && feature.getRawLastBase() > basePosition)
          {
            RangeVector ranges = feature.getLocation().getRanges();
            for(int j=0; j< ranges.size(); j++)
            {
              Range range = (Range) ranges.get(j);
              if(range.getStart() < basePosition && range.getEnd() > basePosition)
                return true;
            }
          }
        }
        return false;
      }
      
    
    tjc's avatar
    tjc committed
      
    
      private void drawVariantCall(Graphics g, String line, int start, int index, float pixPerBase, FeatureVector features)
      {
    
    tjc's avatar
    tjc committed
        //String parts[] = line.split("\\t");
        String parts[] = tabPattern.split(line, 0);
    
        
        int basePosition = Integer.parseInt(parts[1]) + getSequenceOffset(parts[0]);
    
    tjc's avatar
    tjc committed
       
    
    tjc's avatar
    tjc committed
        if( !showVariant(parts[3], parts[4], features, basePosition, parts[5]) )
    
          return;
        
        int pos[] = getScreenPosition(basePosition, pixPerBase, start, index);
    
    tjc's avatar
    tjc committed
    
        if(isDeletion(parts[3], parts[4]))
          g.setColor(Color.gray);
        else if(isInsertion(parts[3], parts[4]))
          g.setColor(Color.yellow);
        else if(parts[4].equals("C") && parts[3].length() == 1)
    
    tjc's avatar
    tjc committed
          g.setColor(Color.red);
    
    tjc's avatar
    tjc committed
        else if(parts[4].equals("A") && parts[3].length() == 1)
    
    tjc's avatar
    tjc committed
          g.setColor(Color.green);
    
    tjc's avatar
    tjc committed
        else if(parts[4].equals("G") && parts[3].length() == 1)
    
    tjc's avatar
    tjc committed
          g.setColor(Color.blue);
    
    tjc's avatar
    tjc committed
        else if(parts[4].equals("T") && parts[3].length() == 1)
    
    tjc's avatar
    tjc committed
          g.setColor(Color.black);
        else
    
          Matcher m = multiAllelePattern.matcher(parts[4]);
          if(m.matches())
          {
            g.setColor(Color.orange);
            g.fillArc(pos[0]-3, pos[1]-LINE_HEIGHT-3, 6, 6, 0, 360);