1 /* BamView
2  *
3  * created: 2009
4  *
5  * This file is part of Artemis
6  *
7  * Copyright(C) 2009  Genome Research Limited
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or(at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  *
23  */
24 package uk.ac.sanger.artemis.components.alignment;
25 
26 
27 import java.awt.AlphaComposite;
28 import java.awt.BasicStroke;
29 import java.awt.BorderLayout;
30 import java.awt.Color;
31 import java.awt.Component;
32 import java.awt.Composite;
33 import java.awt.Cursor;
34 import java.awt.Dimension;
35 import java.awt.FlowLayout;
36 import java.awt.FontMetrics;
37 import java.awt.Graphics;
38 import java.awt.Graphics2D;
39 import java.awt.GridBagConstraints;
40 import java.awt.GridBagLayout;
41 import java.awt.Insets;
42 import java.awt.Point;
43 import java.awt.Rectangle;
44 import java.awt.Stroke;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ActionListener;
47 import java.awt.event.AdjustmentEvent;
48 import java.awt.event.AdjustmentListener;
49 import java.awt.event.ItemEvent;
50 import java.awt.event.ItemListener;
51 import java.awt.event.KeyAdapter;
52 import java.awt.event.KeyEvent;
53 import java.awt.event.MouseAdapter;
54 import java.awt.event.MouseEvent;
55 import java.awt.event.MouseMotionListener;
56 import java.awt.image.BufferedImage;
57 import java.io.BufferedReader;
58 import java.io.File;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.InputStreamReader;
62 import java.lang.management.ManagementFactory;
63 import java.lang.management.MemoryMXBean;
64 import java.net.URL;
65 import java.nio.BufferOverflowException;
66 import java.nio.BufferUnderflowException;
67 import java.util.ArrayList;
68 import java.util.Collections;
69 import java.util.Enumeration;
70 import java.util.HashMap;
71 import java.util.Hashtable;
72 import java.util.List;
73 import java.util.Vector;
74 import java.util.concurrent.CountDownLatch;
75 import java.util.concurrent.ExecutorService;
76 import java.util.concurrent.Executors;
77 import java.util.concurrent.atomic.AtomicBoolean;
78 
79 import javax.swing.BorderFactory;
80 import javax.swing.Box;
81 import javax.swing.BoxLayout;
82 import javax.swing.ButtonGroup;
83 import javax.swing.ImageIcon;
84 import javax.swing.JButton;
85 import javax.swing.JCheckBox;
86 import javax.swing.JCheckBoxMenuItem;
87 import javax.swing.JComponent;
88 import javax.swing.JFrame;
89 import javax.swing.JLabel;
90 import javax.swing.JMenu;
91 import javax.swing.JMenuBar;
92 import javax.swing.JMenuItem;
93 import javax.swing.JOptionPane;
94 import javax.swing.JPanel;
95 import javax.swing.JPopupMenu;
96 import javax.swing.JRadioButton;
97 import javax.swing.JScrollBar;
98 import javax.swing.JScrollPane;
99 import javax.swing.JSeparator;
100 import javax.swing.JSlider;
101 import javax.swing.JTextField;
102 import javax.swing.SwingUtilities;
103 import javax.swing.UIManager;
104 import javax.swing.border.Border;
105 import javax.swing.border.EmptyBorder;
106 import javax.swing.event.ChangeEvent;
107 import javax.swing.event.ChangeListener;
108 
109 import org.apache.log4j.Level;
110 
111 import htsjdk.samtools.AlignmentBlock;
112 import htsjdk.samtools.SAMException;
113 import htsjdk.samtools.SAMFileHeader;
114 import htsjdk.samtools.SamReader;
115 import htsjdk.samtools.SamReaderFactory;
116 import htsjdk.samtools.ValidationStringency;
117 import htsjdk.samtools.SamReaderFactory.Option;
118 import htsjdk.samtools.cram.ref.ReferenceSource;
119 import htsjdk.samtools.SAMReadGroupRecord;
120 import htsjdk.samtools.SAMRecord;
121 import htsjdk.samtools.SAMSequenceRecord;
122 import htsjdk.samtools.SamInputResource;
123 import htsjdk.samtools.util.CloseableIterator;
124 
125 import uk.ac.sanger.artemis.Entry;
126 import uk.ac.sanger.artemis.EntryGroup;
127 import uk.ac.sanger.artemis.FeatureVector;
128 import uk.ac.sanger.artemis.Options;
129 import uk.ac.sanger.artemis.Selection;
130 import uk.ac.sanger.artemis.SelectionChangeEvent;
131 import uk.ac.sanger.artemis.SelectionChangeListener;
132 import uk.ac.sanger.artemis.circular.TextFieldInt;
133 import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
134 import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
135 import uk.ac.sanger.artemis.components.EntryEdit;
136 import uk.ac.sanger.artemis.components.EntryFileDialog;
137 import uk.ac.sanger.artemis.components.FeatureDisplay;
138 import uk.ac.sanger.artemis.components.FileViewer;
139 import uk.ac.sanger.artemis.components.IndexReferenceEvent;
140 import uk.ac.sanger.artemis.components.MessageDialog;
141 import uk.ac.sanger.artemis.components.NonModalDialog;
142 import uk.ac.sanger.artemis.components.SequenceComboBox;
143 import uk.ac.sanger.artemis.components.SwingWorker;
144 import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
145 import uk.ac.sanger.artemis.io.EntryInformation;
146 import uk.ac.sanger.artemis.io.Range;
147 import uk.ac.sanger.artemis.sequence.Bases;
148 import uk.ac.sanger.artemis.sequence.MarkerRange;
149 import uk.ac.sanger.artemis.sequence.NoSequenceException;
150 import uk.ac.sanger.artemis.util.Document;
151 import uk.ac.sanger.artemis.util.DocumentFactory;
152 import uk.ac.sanger.artemis.util.FTPSeekableStream;
153 import uk.ac.sanger.artemis.util.OutOfRangeException;
154 
155 /**
156  * Handles displaying of BAM and CRAM alignment files.
157  * If an index file is not present it will create one.
158  * Files can be read locally, by http or ftp.
159  *
160  */
161 public class BamView extends JPanel
162                      implements DisplayAdjustmentListener, SelectionChangeListener
163 {
164   private static final long serialVersionUID = 1L;
165 
166   public static String BAM_SUFFIX = ".*\\.(bam|cram)$";
167 
168   /** Whether or not we re running as a standalone BamView panel. */
169   private static boolean standaloneMode = false;
170 
171   private List<BamViewRecord> readsInView;
172   private Hashtable<String, SamReader> samFileReaderHash = new Hashtable<String, SamReader>();
173   private List<SAMReadGroupRecord> readGroups = new Vector<SAMReadGroupRecord>();
174 
175   private HashMap<String, Integer> seqLengths = new HashMap<String, Integer>();
176   private HashMap<String, Integer> offsetLengths;
177   private Vector<String> seqNames = new Vector<String>();
178   protected List<String> bamList;
179   protected List<Short> hideBamList = new Vector<Short>();
180 
181   private SAMRecordPredicate samRecordFlagPredicate;
182   private SAMRecordMapQPredicate samRecordMapQPredicate;
183 
184   private SAMRecordFilter filterFrame;
185 
186   private Bases bases;
187   private JScrollPane jspView;
188   private JScrollBar scrollBar;
189 
190   private SequenceComboBox combo;
191   private boolean isOrientation = false;
192   private boolean isSingle = false;
193   private boolean isSNPs = false;
194 
195   private boolean isCoverage = false;
196   private boolean isSNPplot = false;
197 
198   private EntryEdit entry_edit;
199   private FeatureDisplay feature_display;
200   private Selection selection;
201   private JPanel mainPanel = new JPanel();
202   private CoveragePanel coveragePanel;
203   private SnpPanel snpPanel;
204 
205   private boolean logScale = false;
206   private Ruler ruler;
207   private int nbasesInView;
208 
209   private int startBase = -1;
210   private int endBase   = -1;
211   private int laststart;
212   private int lastend;
213 
214   private boolean asynchronous = true;
215   private boolean showBaseAlignment = false;
216 
217   private JMenu bamFilesMenu = new JMenu("BAM/CRAM files");
218   private JCheckBoxMenuItem logMenuItem = new JCheckBoxMenuItem("Use Log Scale", logScale);
219 
220   private JCheckBoxMenuItem cbStackView = new JCheckBoxMenuItem("Stack", true);
221   private JCheckBoxMenuItem cbPairedStackView = new JCheckBoxMenuItem("Paired Stack");
222   private JCheckBoxMenuItem cbStrandStackView = new JCheckBoxMenuItem("Strand Stack");
223   private JCheckBoxMenuItem cbIsizeStackView = new JCheckBoxMenuItem("Inferred Size", false);
224   private JCheckBoxMenuItem cbCoverageView = new JCheckBoxMenuItem("Coverage", false);
225   private JCheckBoxMenuItem cbCoverageStrandView = new JCheckBoxMenuItem("Coverage by Strand", false);
226   private JCheckBoxMenuItem cbCoverageHeatMap = new JCheckBoxMenuItem("Coverage Heat Map", false);
227   private JCheckBoxMenuItem cbLastSelected;
228 
229   private ButtonGroup buttonGroup = new ButtonGroup();
230 
231   private JCheckBoxMenuItem colourByReadGrp = new JCheckBoxMenuItem("Read Group");
232   private JCheckBoxMenuItem colourByStrandTag = new JCheckBoxMenuItem("RNASeq Strand Specific Tag (XS)");
233   private JCheckBoxMenuItem colourByCoverageColour = new JCheckBoxMenuItem("Coverage Plot Colours");
234   private JCheckBoxMenuItem baseQualityColour = new JCheckBoxMenuItem("Base Quality");
235   private JCheckBoxMenuItem markInsertions = new JCheckBoxMenuItem("Mark Insertions", true);
236 
237   private AlphaComposite translucent =
238     AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f);
239 
240   private ReadGroupsFrame readGrpFrame;
241   private GroupBamFrame groupsFrame = new GroupBamFrame(this, bamFilesMenu);
242   private CoveragePanel coverageView = new CoveragePanel();
243 
244   /** Used to colour the frames. */
245   private static Color LIGHT_GREY = new Color(200, 200, 200);
246   private static Color DARK_GREEN = new Color(0, 150, 0);
247   private static Color DARK_ORANGE = new Color(255,140,0);
248   private static Color DEEP_PINK   = new Color(139,10,80);
249 
250   private static Color NON_SELECTED_READ_HIGHLIGHT_COLOUR = new Color(189,103,107);
251 
252   private Point lastMousePoint = null;
253   private volatile BamViewRecord mouseOverSAMRecord = null;
254   private volatile BamViewRecord highlightSAMRecord = null;
255   private String mouseOverInsertion;
256   // record of where a mouse drag starts
257   protected int dragStart = -1;
258 
259   private static int MAX_BASES = 26000;
260   private int maxHeight = 800;
261 
262   private boolean concatSequences = false;
263   private int ALIGNMENT_PIX_PER_BASE;
264   private int BASE_HEIGHT;
265 
266   private JPopupMenu popup;
267   private PopupMessageFrame popFrame = new PopupMessageFrame();
268   private PopupMessageFrame waitingFrame = new PopupMessageFrame("waiting...");
269   private ExecutorService bamReadTaskExecutor;
270   private int MAX_COVERAGE = Integer.MAX_VALUE;
271 
272   private float readLnHgt = 2.0f;
273 
274   private int ftpSocketTimeout = BamUtils.getFtpSocketTimeout();
275 
276   /** Whether we should strictly validate upfront the input BAM/CRAM file.*/
277   boolean doInputFileValidation = false;
278 
279   /** Whether to use htsjdk file index caching.*/
280   boolean useHtsjdkIndexCaching = false;
281 
282   private volatile AtomicBoolean foundFatalErrors = new AtomicBoolean(false);
283 
284   /** busy cursor */
285   private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
286   /** done cursor */
287   private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
288 
289   /** Reference file name for CRAM */
290   String referenceFilename;
291 
292   /**Default file validation stringency level for use by SamReader - setting this to strict could cause BAM View to shut with a fatal error for some files. */
293   private ValidationStringency validationStringency = ValidationStringency.SILENT;
294 
295   public static org.apache.log4j.Logger logger4j =
296     org.apache.log4j.Logger.getLogger(BamView.class);
297 
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)298   public BamView(List<String> bamList,
299                 String reference,
300                 int nbasesInView,
301                 final EntryEdit entry_edit,
302                 final FeatureDisplay feature_display,
303                 final Bases bases,
304                 final JPanel containerPanel,
305                 final JFrame frame)
306   {
307     this(bamList, reference, nbasesInView, feature_display, bases, containerPanel, frame);
308     this.entry_edit = entry_edit;
309   }
310 
BamView(List<String> bamList, String reference, int nbasesInView, final FeatureDisplay feature_display, final Bases bases, final JPanel containerPanel, final JFrame frame)311   public BamView(List<String> bamList,
312                  String reference,
313                  int nbasesInView,
314                  final FeatureDisplay feature_display,
315                  final Bases bases,
316                  final JPanel containerPanel,
317                  final JFrame frame)
318   {
319     super();
320     setBackground(Color.white);
321     this.bamList = bamList;
322     this.nbasesInView = nbasesInView;
323     this.feature_display = feature_display;
324     this.bases = bases;
325     this.referenceFilename = reference;
326 
327     containerPanel.setLayout(new BoxLayout(containerPanel, BoxLayout.Y_AXIS));
328     containerPanel.add(mainPanel);
329 
330     // filter out unmapped reads by default
331     setSamRecordFlagPredicate(
332         new SAMRecordFlagPredicate(SAMRecordFlagPredicate.READ_UNMAPPED_FLAG));
333 
334     if(Options.getOptions().getProperty("bam_validation_mode") != null)
335     {
336     	  String validationMode = Options.getOptions().getProperty("bam_validation_mode");
337 
338       logger4j.debug("BAM FILE VALIDATION STRINGENCY=" + validationMode);
339 
340       if (validationMode.equalsIgnoreCase("STRICT"))
341       {
342     	  	validationStringency = ValidationStringency.STRICT;
343       }
344       else if (validationMode.equalsIgnoreCase("LENIENT"))
345       {
346   	  	validationStringency = ValidationStringency.LENIENT;
347       }
348       else if (validationMode.equalsIgnoreCase("SILENT"))
349       {
350   	  	validationStringency = ValidationStringency.SILENT;
351       }
352     }
353 
354     doInputFileValidation = Options.getOptions().getPropertyTruthValue("bamview_perform_detailed_validation");
355     logger4j.debug("PERFORM UP-FRONT INPUT FILE VALIDATION=" + doInputFileValidation);
356 
357     if(Options.getOptions().getIntegerProperty("bam_read_thread") != null)
358     {
359       logger4j.debug("BAM READ THREADS="+Options.getOptions().getIntegerProperty("bam_read_thread"));
360       bamReadTaskExecutor = Executors.newFixedThreadPool(
361           Options.getOptions().getIntegerProperty("bam_read_thread"));
362     }
363     else
364       bamReadTaskExecutor = Executors.newFixedThreadPool(1);
365 
366 
367     if(Options.getOptions().getIntegerProperty("bam_max_coverage") != null)
368     {
369       logger4j.debug("BAM MAX COVERAGE="+Options.getOptions().getIntegerProperty("bam_max_coverage"));
370       MAX_COVERAGE = Options.getOptions().getIntegerProperty("bam_max_coverage");
371     }
372 
373     useHtsjdkIndexCaching = Options.getOptions().getPropertyTruthValue("bamview_use_htsjdk_file_index_caching");
374     logger4j.debug("USE HTSJDK BAM FILE INDEX CACHING=" + useHtsjdkIndexCaching);
375 
376     try
377     {
378       readAlignmentFileHeader();
379     }
380     catch(java.lang.UnsupportedClassVersionError err)
381     {
382       JOptionPane.showMessageDialog(null,
383           "This requires Java 1.8 or higher.",
384           "Check Java Version", JOptionPane.WARNING_MESSAGE);
385     }
386     catch (IOException ioe)
387     {
388     		if (standaloneMode)
389 		{
390 			System.err.println(ioe.getMessage());
391 			System.exit(0);
392 		}
393 		else
394 		{
395 			// TODO Why has this exception just been ignored?
396 		    ioe.printStackTrace();
397 		}
398 
399     }
400     catch (RuntimeException e)
401     {
402     		if (standaloneMode)
403     		{
404     			System.err.println(e.getMessage());
405     			System.exit(0);
406     		}
407     		else
408     		{
409     			throw e;
410     		}
411     }
412 
413 
414     final javax.swing.plaf.FontUIResource font_ui_resource =
415       Options.getOptions().getFontUIResource();
416 
417     Enumeration<Object> keys = UIManager.getDefaults().keys();
418     while(keys.hasMoreElements())
419     {
420       Object key = keys.nextElement();
421       Object value = UIManager.get(key);
422       if(value instanceof javax.swing.plaf.FontUIResource)
423         UIManager.put(key, font_ui_resource);
424     }
425 
426     setFont(Options.getOptions().getFont());
427     FontMetrics fm  = getFontMetrics(getFont());
428     ALIGNMENT_PIX_PER_BASE = fm.charWidth('M');
429     BASE_HEIGHT = fm.getMaxAscent();
430     selection = new Selection(null);
431 
432     MultiLineToolTipUI.initialize();
433     setToolTipText("");
434 
435     buttonGroup.add(cbStackView);
436     buttonGroup.add(cbPairedStackView);
437     buttonGroup.add(cbStrandStackView);
438     buttonGroup.add(cbIsizeStackView);
439     buttonGroup.add(cbCoverageView);
440     buttonGroup.add(cbCoverageStrandView);
441     buttonGroup.add(cbCoverageHeatMap);
442     addMouseListener(new PopupListener());
443 
444     jspView = new JScrollPane(this,
445         JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
446         JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
447 
448     jspView.setViewportBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.DARK_GRAY));
449     Border empty = new EmptyBorder(0,0,0,0);
450     jspView.setBorder(empty);
451     jspView.getVerticalScrollBar().setUnitIncrement(8);
452 
453     addBamToPanel(frame);
454 
455     // apply command line options
456     if(System.getProperty("show_snps") != null)
457       isSNPs = true;
458     if(System.getProperty("show_snp_plot") != null)
459     {
460       isSNPplot = true;
461       snpPanel.setVisible(isSNPplot);
462     }
463     if(System.getProperty("show_cov_plot") != null)
464     {
465       isCoverage = true;
466       coveragePanel.setVisible(isCoverage);
467     }
468   }
469 
getToolTipText()470   public String getToolTipText()
471   {
472 		if(isCoverageView(getPixPerBaseByWidth()) && lastMousePoint != null)
473 	      return coverageView.getToolTipText(
474 	          lastMousePoint.y-getJspView().getViewport().getViewPosition().y);
475 
476 	    if(mouseOverSAMRecord == null)
477 	      return null;
478 
479 	    String msg =
480 	        mouseOverSAMRecord.sam.getReadName() + "\n" +
481 	        mouseOverSAMRecord.sam.getAlignmentStart() + ".." +
482 	        mouseOverSAMRecord.sam.getAlignmentEnd() +
483 	       (mouseOverSAMRecord.sam.getReadGroup() != null ? "\nRG="+mouseOverSAMRecord.sam.getReadGroup().getId() : "") +
484 	        "\nisize=" + mouseOverSAMRecord.sam.getInferredInsertSize() +
485 	        "\nmapq=" + mouseOverSAMRecord.sam.getMappingQuality()+
486 	        "\nrname="+ mouseOverSAMRecord.sam.getReferenceName();
487 
488 	    if( mouseOverSAMRecord.sam.getReadPairedFlag() &&
489 	        mouseOverSAMRecord.sam.getProperPairFlag() &&
490 	       !mouseOverSAMRecord.sam.getMateUnmappedFlag())
491 	    {
492 	      msg = msg +
493 	        "\nstrand (read/mate): "+
494 	       (mouseOverSAMRecord.sam.getReadNegativeStrandFlag() ? "-" : "+")+" / "+
495 	       (mouseOverSAMRecord.sam.getMateNegativeStrandFlag() ? "-" : "+");
496 	    }
497 	    else
498 	      msg = msg +
499 	        "\nstrand (read/mate): "+
500 	       (mouseOverSAMRecord.sam.getReadNegativeStrandFlag() ? "-" : "+");
501 
502 	    if(msg != null && mouseOverInsertion != null)
503 	      msg = msg + "\nInsertion at:" +mouseOverInsertion;
504 
505 	    return msg;
506   }
507 
508   /**
509    * Get the SAM file reader.
510    * @param alignmentFile String
511    * @return SamReader
512    * @throws IOException for I/O issue
513    * @throws SAMException
514    */
getSAMFileReader(final String alignmentFile)515   private SamReader getSAMFileReader(final String alignmentFile) throws IOException, SAMException
516   {
517     if(samFileReaderHash.containsKey(alignmentFile))
518       return samFileReaderHash.get(alignmentFile);
519 
520     /*
521      * Try and find the associated index file.
522      * Create one if we can't find it.
523      */
524     	File indexFile = BamUtils.getIndexFile(alignmentFile);
525 
526     final SamReader samFileReader;
527     CRAMReferenceSequenceFile ref = null;
528 
529     // Parsing of the header happens during SamReader construction,
530     // so need to set the default stringency (from properties).
531     //
532     final SamReaderFactory samReaderFactory = SamReaderFactory.makeDefault();
533     if (useHtsjdkIndexCaching)
534     	  samReaderFactory.enable(SamReaderFactory.Option.CACHE_FILE_BASED_INDEXES);
535     else
536       samReaderFactory.disable(SamReaderFactory.Option.CACHE_FILE_BASED_INDEXES);
537 
538     samReaderFactory.disable(Option.EAGERLY_DECODE);
539     samReaderFactory.validationStringency(validationStringency);
540 
541     if ( BamUtils.isCramFile(alignmentFile) )
542     {
543     	  htsjdk.samtools.util.Log.setGlobalLogLevel(
544   	          htsjdk.samtools.util.Log.LogLevel.ERROR);
545 
546     	  if (!BamView.isStandaloneMode())
547     	  {
548 	    	// Will only be executed from Artemis, rather than standalone mode.
549 	    	//
550 	    	ref = new CRAMReferenceSequenceFile(
551 	    	        feature_display.getEntryGroup().getSequenceEntry(), this);
552 
553 	    	samReaderFactory.referenceSource(new ReferenceSource(ref));
554     	  }
555     	  else
556   	  {
557     		// BamView Standalone mode
558     		//
559   	    if (referenceFilename != null && referenceFilename.trim().length() > 0)
560   	    	{
561   	    	  samReaderFactory.referenceSource(new uk.ac.sanger.artemis.cramtools.ref.ReferenceSource(new File(referenceFilename)));
562   	    	}
563   	    	else
564   	    	{
565   	    	  // No ref file provided so the code will search for one.
566   	    	  samReaderFactory.referenceSource(new uk.ac.sanger.artemis.cramtools.ref.ReferenceSource());
567   	    	}
568   	  }
569     }
570 
571     /*
572      * Create the associated Sam File Reader
573      */
574     if(alignmentFile.startsWith("ftp"))
575     {
576       FTPSeekableStream fss = new FTPSeekableStream(new URL(alignmentFile), ftpSocketTimeout);
577       samFileReader = samReaderFactory.open(SamInputResource.of(fss).index(indexFile) );
578     }
579     else if(!alignmentFile.startsWith("http"))
580     {
581       File normalFile = new File(alignmentFile);
582       samFileReader = samReaderFactory.open(SamInputResource.of(normalFile).index(indexFile) );
583     }
584     else
585     {
586       final URL urlFile = new URL(alignmentFile);
587       samFileReader = samReaderFactory.open(SamInputResource.of(urlFile).index(indexFile) );
588     }
589 
590     /*
591      * Do one-off additional up-front validation of file.
592      * May take a little while.
593      */
594     if (doInputFileValidation)
595       BamUtils.validateSAMFile(samFileReader, ref, false);
596 
597     samFileReaderHash.put(alignmentFile, samFileReader);
598 
599     readGroups.addAll(samFileReader.getFileHeader().getReadGroups());
600 
601     return samFileReader;
602   }
603 
readAlignmentFileHeader()604   private void readAlignmentFileHeader() throws IOException
605   {
606     final SamReader inputSam = getSAMFileReader(bamList.get(0));
607     final SAMFileHeader header = inputSam.getFileHeader();
608 
609     for(SAMSequenceRecord seq: header.getSequenceDictionary().getSequences())
610     {
611       seqLengths.put(seq.getSequenceName(),
612                      seq.getSequenceLength());
613       seqNames.add(seq.getSequenceName());
614     }
615   }
616 
617   class BamReadTask implements Runnable
618   {
619     private int start;
620     private int end;
621     private short bamIndex;
622     private float pixPerBase;
623     private CountDownLatch latch;
624 
625     private Exception exception;
626 
BamReadTask(int start, int end, short bamIndex, float pixPerBase, CountDownLatch latch)627     BamReadTask(int start, int end, short bamIndex, float pixPerBase, CountDownLatch latch)
628     {
629       this.start = start;
630       this.end = end;
631       this.bamIndex = bamIndex;
632       this.pixPerBase = pixPerBase;
633       this.latch = latch;
634     }
635 
getException()636     public Exception getException()
637     {
638     	  return exception;
639     }
640 
getBamIndex()641     public short getBamIndex()
642     {
643     	  return bamIndex;
644     }
645 
run()646     public void run()
647     {
648     	  exception = null;
649 
650       try
651       {
652         readFromAlignmentFile(start, end, bamIndex, pixPerBase) ;
653       }
654       catch (OutOfMemoryError ome)
655       {
656     	    throw ome;
657       }
658       catch(Exception e)
659       {
660     	    exception = e;
661       }
662       finally
663       {
664         latch.countDown();
665       }
666     }
667   }
668 
669   /**
670    * Read a BAM or CRAM file.
671    * @throws IOException
672    */
readFromAlignmentFile(int start, int end, short bamIndex, float pixPerBase)673   private void readFromAlignmentFile(int start, int end, short bamIndex, float pixPerBase)
674           throws IOException
675   {
676     // Open the input file.  Automatically detects whether input is BAM or CRAM
677     // and delegates to a reader implementation for the appropriate format.
678     final String bam = bamList.get(bamIndex);
679     final SamReader inputSam = getSAMFileReader(bam);
680 
681     if(isConcatSequences())
682     {
683       for(String seq: seqNames)
684       {
685         int sLen = seqLengths.get(seq);
686         int offset = getSequenceOffset(seq);
687         int sBeg = offset+1;
688         int sEnd = sBeg+sLen-1;
689 
690         if( (sBeg >= start && sBeg <= end) ||
691             (sBeg <= start && sEnd >= start) )
692         {
693           int thisStart = start - offset;
694           if(thisStart < 1)
695             thisStart = 1;
696           int thisEnd   = end - offset;
697           if(thisEnd > sLen)
698             thisEnd = sLen;
699 
700           iterateOverBam(inputSam, seq, thisStart, thisEnd, bamIndex, pixPerBase, bam);
701           //System.out.println("READ "+seq+"  "+thisStart+".."+thisEnd+" "+start+" --- "+offset);
702         }
703       }
704     }
705     else
706     {
707       String refName = (String) combo.getSelectedItem();
708       iterateOverBam(inputSam, refName, start, end, bamIndex, pixPerBase, bam);
709     }
710     //inputSam.close();
711   }
712 
713   /**
714    * Iterate over BAM file and load into the <code>List</code> of
715    * <code>SAMRecord</code>.
716    * @param inputSam
717    * @param refName
718    * @param start
719    * @param end
720    */
iterateOverBam(final SamReader inputSam, final String refName, final int start, final int end, final short bamIndex, final float pixPerBase, final String bam)721   private void iterateOverBam(final SamReader inputSam,
722                              final String refName, final int start, final int end,
723                              final short bamIndex, final float pixPerBase,
724                              final String bam)
725   {
726     final MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
727     final int checkMemAfter = 8000;
728     final int seqOffset = getSequenceOffset(refName);
729     final int offset = seqOffset- getBaseAtStartOfView();
730     final boolean isCoverageView = isCoverageView(pixPerBase);
731 
732     int cnt = 0;
733 
734     int nbins = 800;
735     int binSize = ((end-start)/nbins)+1;
736     if(binSize < 1)
737     {
738       binSize = 1;
739       nbins = end-start+1;
740     }
741     int max = MAX_COVERAGE*binSize;
742     if(max < 1)
743       max = Integer.MAX_VALUE;
744     final int cov[] = new int[nbins];
745     for(int i=0; i<nbins; i++)
746       cov[i] = 0;
747 
748     final CloseableIterator<SAMRecord> it = inputSam.queryOverlapping(refName, start, end);
749     try
750     {
751       while ( it.hasNext() )
752       {
753         try
754         {
755           cnt++;
756           SAMRecord samRecord = it.next();
757 
758           if(readGrpFrame != null && !readGrpFrame.isReadGroupVisible(samRecord.getReadGroup()))
759             continue;
760 
761           if( samRecordFlagPredicate == null ||
762              !samRecordFlagPredicate.testPredicate(samRecord))
763           {
764             if(samRecordMapQPredicate == null ||
765                samRecordMapQPredicate.testPredicate(samRecord))
766             {
767               int abeg = samRecord.getAlignmentStart();
768               int aend = samRecord.getAlignmentEnd();
769               boolean over = false;
770 
771               for(int i=abeg; i<aend; i++)
772               {
773                 int bin = ((i-start)/binSize)-1;
774                 if(bin < 0)
775                   continue;
776                 else if(bin > nbins-1)
777                   bin = nbins-1;
778                 cov[bin]++;
779                 if(cov[bin] > max)
780                 {
781                   over = true;
782                   break;
783                 }
784               }
785 
786               if(over)
787                 continue;
788 
789               if(isCoverageView)
790                 coverageView.addRecord(samRecord, offset, bam, colourByStrandTag.isSelected());
791               if(isCoverage)
792                 coveragePanel.addRecord(samRecord, offset, bam, colourByStrandTag.isSelected());
793               if(isSNPplot)
794                 snpPanel.addRecord(samRecord, seqOffset);
795               if(!isCoverageView)
796                 readsInView.add(new BamViewRecord(samRecord, bamIndex));
797             }
798           }
799 
800           if(cnt > checkMemAfter)
801           {
802             cnt = 0;
803             float heapFraction =
804               (float)((float)memory.getHeapMemoryUsage().getUsed()/
805                       (float)memory.getHeapMemoryUsage().getMax());
806             logger4j.debug("Heap memory usage (used/max): "+heapFraction);
807 
808             if(readsInView.size() > checkMemAfter*2 && !waitingFrame.isVisible())
809               waitingFrame.showWaiting("loading...", mainPanel);
810 
811             if(heapFraction > 0.90)
812             {
813               popFrame.show(
814                 "Using > 90 % of the maximum memory limit:"+
815                 (memory.getHeapMemoryUsage().getMax()/1000000.f)+" Mb.\n"+
816                 "Not all reads in this range have been read in. Zoom in or\n"+
817                 "consider increasing the memory for this application.",
818                 mainPanel,
819                 15000);
820               break;
821             }
822           }
823         }
824         catch(Exception e)
825         {
826           System.err.println(e.getMessage());
827         }
828       }
829     }
830     finally
831     {
832       it.close();
833     }
834   }
835 
getSequenceLength()836   private int getSequenceLength()
837   {
838     if(isConcatSequences())
839     {
840       int len = 0;
841       for(String seq: seqNames)
842         len += seqLengths.get(seq);
843       return len;
844     }
845     else
846       return seqLengths.get((String) combo.getSelectedItem());
847   }
848 
849   /**
850    * For BAM files with multiple references sequences, calculate
851    * the offset from the start of the concatenated sequence for
852    * a given reference.
853    * @param refName
854    * @return
855    */
getSequenceOffset(String refName)856   protected int getSequenceOffset(String refName)
857   {
858     if(!isConcatSequences())
859       return 0;
860 
861     if(offsetLengths == null)
862     {
863       if(feature_display == null)
864       {
865         offsetLengths = new HashMap<String, Integer>(combo.getItemCount());
866         int offset = 0;
867         for(int i=0; i<combo.getItemCount(); i++)
868         {
869           String thisSeqName = (String) combo.getItemAt(i);
870           offsetLengths.put(thisSeqName, offset);
871           offset += seqLengths.get(combo.getItemAt(i));
872         }
873         return offsetLengths.get(refName);
874       }
875 
876       final FeatureVector features = feature_display.getEntryGroup().getAllFeatures();
877       final HashMap<String, Integer> lookup = new HashMap<String, Integer>();
878       for(int i=0; i<features.size(); i++)
879         lookup.put(features.elementAt(i).getIDString(), features.elementAt(i).getFirstBase());
880 
881       offsetLengths = new HashMap<String, Integer>(seqNames.size());
882       for(int i=0; i<seqNames.size(); i++)
883       {
884         final Integer pos = lookup.get(seqNames.get(i));
885         if(pos != null)
886           offsetLengths.put(seqNames.get(i), pos-1);
887        /*final FeatureContigPredicate predicate = new FeatureContigPredicate(seqNames.get(i).trim());
888         for(int j=0; j<features.size(); j++)
889         {
890           if(predicate.testPredicate(features.elementAt(j)))
891           {
892             offsetLengths.put(seqNames.get(i), features.elementAt(j).getFirstBase()-1);
893             break;
894           }
895         }*/
896       }
897 
898       if(offsetLengths.size() != seqNames.size())
899       {
900         System.err.println("Found: "+offsetLengths.size() +" of "+ seqNames.size());
901         SwingUtilities.invokeLater(new Runnable()
902         {
903           public void run()
904           {
905             JOptionPane.showMessageDialog(BamView.this,
906                 "There is a problem matching the reference sequences\n"+
907                 "to the names in the BAM/CRAM file. This may mean the labels\n"+
908                 "on the reference features do not match those in the in\n"+
909                 "the BAM/CRAM file.",
910                 "Problem Found", JOptionPane.WARNING_MESSAGE);
911           }
912         });
913 
914         //concatSequences = false;
915         int offset = 0;
916         for(int i=0; i<combo.getItemCount(); i++)
917         {
918           String thisSeqName = (String) combo.getItemAt(i);
919           offsetLengths.put(thisSeqName, offset);
920           offset += seqLengths.get(combo.getItemAt(i));
921         }
922         //return 0;
923       }
924     }
925     return offsetLengths.get(refName);
926   }
927 
928   @Override
paintComponent(Graphics g)929   protected void paintComponent(Graphics g)
930   {
931 	super.paintComponent(g);
932 	Graphics2D g2 = (Graphics2D)g;
933 
934 	mouseOverSAMRecord = null;
935     final int seqLength = getSequenceLength();
936     int start;
937     int end;
938 
939     if(startBase > 0)
940       start = startBase;
941     else
942       start = getBaseAtStartOfView();
943 
944     if(endBase > 0)
945       end = endBase;
946     else
947     {
948       end   = start + nbasesInView - 1;
949       if(end > seqLength)
950         end = seqLength;
951 
952       if(feature_display != null && nbasesInView < feature_display.getMaxVisibleBases())
953         nbasesInView = feature_display.getMaxVisibleBases();
954     }
955 
956 
957     final float pixPerBase = getPixPerBaseByWidth();
958     boolean changeToStackView = false;
959     MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
960     if(laststart != start ||
961        lastend   != end ||
962        CoveragePanel.isRedraw())
963     {
964       if(isCoverageView(pixPerBase))
965         coverageView.init(this, pixPerBase, start, end);
966       if(isCoverage)
967         coveragePanel.init(this, pixPerBase, start, end);
968       if(isSNPplot)
969         snpPanel.init(this, pixPerBase, start, end);
970 
971       BamView.this.getRootPane().setCursor(cbusy);
972 
973       try
974       {
975 	      synchronized (this)
976 	      {
977 	        try
978 	        {
979 
980 	        	  float heapFractionUsedBefore = (float) ((float) memory.getHeapMemoryUsage().getUsed() /
981 	                                                  (float) memory.getHeapMemoryUsage().getMax());
982 	          if(readsInView == null)
983 	            readsInView = new Vector<BamViewRecord>();
984 	          else
985 	            readsInView.clear();
986 
987 	          final CountDownLatch latch = new CountDownLatch(bamList.size()-hideBamList.size());
988 	          //long ms = System.currentTimeMillis();
989 
990 	          List<BamReadTask> tasks = new ArrayList<BamReadTask>();
991 	          for(short i=0; i<bamList.size(); i++)
992 	          {
993 	            if(!hideBamList.contains(i))
994 	            {
995 	              BamReadTask task = new BamReadTask(start, end, i, pixPerBase, latch);
996 	              tasks.add(task);
997 	              bamReadTaskExecutor.execute(task);
998 	            }
999 	          }
1000 
1001 	          try
1002 	          {
1003 	            latch.await();
1004 	          }
1005 	          catch (InterruptedException e) {}
1006 
1007 	          /*
1008 	           * Check for errors during the bam/cram read process...
1009 	           */
1010 	          String errorText = "Failed to load ";
1011 	          boolean foundErrors = false;
1012 	          for (BamReadTask task : tasks)
1013 	          {
1014 	        	    if (task.getException() != null)
1015 	        	    {
1016 	        	    	  foundErrors = true;
1017 
1018 	        	    	  if (task.getException() instanceof BufferUnderflowException || task.getException() instanceof BufferOverflowException)
1019 	        	    	    errorText = errorText + bamList.get(task.getBamIndex()) + ".\nThis may be due to an invalid index file.";
1020 	        	    	  else
1021 	        	    	    errorText = errorText + bamList.get(task.getBamIndex()) + ".\nError: " + task.getException().getMessage();
1022 
1023 	        	    	  logger4j.error(errorText, task.getException());
1024 
1025 	        	    	  break;
1026 	            }
1027 	          }
1028 
1029 	          if (foundErrors)
1030 	          {
1031 	        	    handleFatalError(errorText);
1032 	          }
1033 
1034 	          //System.out.println("===== NO. THREADS="+
1035 	          //     ((java.util.concurrent.ThreadPoolExecutor)bamReadTaskExecutor).getPoolSize()+" TIME="+(System.currentTimeMillis()-ms));
1036 
1037 	          float heapFractionUsedAfter = (float) ((float) memory.getHeapMemoryUsage().getUsed() /
1038 	                                                 (float) memory.getHeapMemoryUsage().getMax());
1039 
1040 	          // System.out.println("Heap Max  : "+memory.getHeapMemoryUsage().getMax());
1041 	          // System.out.println("Heap Used : "+memory.getHeapMemoryUsage().getUsed());
1042 	          // System.out.println("Heap memory used "+heapFractionUsedAfter);
1043 
1044 	          if ((heapFractionUsedAfter - heapFractionUsedBefore) > 0.06
1045 	              && !isStackView() && heapFractionUsedAfter > 0.8)
1046 	          {
1047 	            cbStackView.setSelected(true);
1048 	            changeToStackView = true;
1049 	          }
1050 
1051 	          if((!isStackView() && !isStrandStackView()) || isBaseAlignmentView(pixPerBase))
1052 	          {
1053 	            Collections.sort(readsInView, new SAMRecordComparator());
1054 	          }
1055 	          else if( (isStackView() || isStrandStackView()) &&
1056 	              bamList.size() > 1)
1057 	          {
1058 	            // merge multiple BAM/CRAM files
1059 	            Collections.sort(readsInView, new SAMRecordPositionComparator(BamView.this));
1060 	          }
1061 
1062 	        }
1063 	        catch (OutOfMemoryError ome)
1064 	        {
1065 	          JOptionPane.showMessageDialog(this, "Out of Memory");
1066 	          readsInView.clear();
1067 	          return;
1068 	        }
1069 	        catch(htsjdk.samtools.util.RuntimeIOException re)
1070 	        {
1071 	          JOptionPane.showMessageDialog(this, re.getMessage());
1072 	        }
1073 	      }
1074       }
1075       finally
1076       {
1077     	    try
1078     	    {
1079     	      BamView.this.getRootPane().setCursor(cdone);
1080     	    }
1081     	    catch (NullPointerException e)
1082     	    {
1083     	    	  // Ignore this as could happen if the panel has been closed.
1084     	    }
1085       }
1086     }
1087 
1088     laststart = start;
1089     lastend   = end;
1090 
1091     // this needs to be synchronized when cloning BAM window
1092     synchronized(this)
1093     {
1094       if(showBaseAlignment)
1095 	    drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
1096 	  else
1097 	  {
1098 	    if(isCoverageView(pixPerBase))
1099 	      drawCoverage(g2,start, end, pixPerBase);
1100   	    else if(isStackView())
1101   	      drawStackView(g2, seqLength, pixPerBase, start, end);
1102 	    else if(isPairedStackView())
1103 	      drawPairedStackView(g2, seqLength, pixPerBase, start, end);
1104 	    else if(isStrandStackView())
1105 	    	  drawStrandStackView(g2, seqLength, pixPerBase, start, end);
1106 	    else
1107 	      drawLineView(g2, seqLength, pixPerBase, start, end);
1108 	  }
1109     }
1110 
1111     if(isCoverage)
1112       coveragePanel.repaint();
1113     if(isSNPplot)
1114       snpPanel.repaint();
1115 
1116 	if(waitingFrame.isVisible())
1117       waitingFrame.hideFrame();
1118 	if(changeToStackView)
1119 	{
1120 	  popFrame.show(
1121           "Note :: Changed to the stack view to save memory.\n"+
1122           "Currently this is using "+
1123           (memory.getHeapMemoryUsage().getUsed()/1000000.f)+" Mb "+
1124           "and the maximum\nmemory limit is "+
1125           (memory.getHeapMemoryUsage().getMax()/1000000.f)+" Mb.",
1126           mainPanel,
1127           15000);
1128 	}
1129   }
1130 
repaintBamView()1131   protected void repaintBamView()
1132   {
1133     laststart = -1;
1134 
1135     repaint();
1136   }
1137 
getPixPerBaseByWidth()1138   private float getPixPerBaseByWidth()
1139   {
1140     return (float)mainPanel.getWidth() / (float)nbasesInView;
1141   }
1142 
1143 
getMaxBasesInPanel(int seqLength)1144   private int getMaxBasesInPanel(int seqLength)
1145   {
1146     if(feature_display == null)
1147       return seqLength+nbasesInView/3;
1148     else
1149       return seqLength+nbasesInView;
1150   }
1151 
1152   /**
1153    * Draw the zoomed-in base view.
1154    * @param g2
1155    * @param seqLength
1156    * @param pixPerBase
1157    * @param start
1158    * @param end
1159    */
drawBaseAlignment(Graphics2D g2, int seqLength, float pixPerBase, final int start, int end)1160   private void drawBaseAlignment(Graphics2D g2,
1161                                  int seqLength,
1162                                  float pixPerBase,
1163                                  final int start,
1164                                  int end)
1165   {
1166     ruler.start = start;
1167     ruler.end = end;
1168 
1169     int ypos = 0;
1170     String refSeq = null;
1171     int refSeqStart = start;
1172 
1173     end = start + ( mainPanel.getWidth() * ALIGNMENT_PIX_PER_BASE );
1174     if(bases != null)
1175     {
1176       try
1177       {
1178         int seqEnd = end+2;
1179         if(seqEnd > bases.getLength())
1180           seqEnd = bases.getLength();
1181 
1182         if(refSeqStart < 1)
1183           refSeqStart = 1;
1184         refSeq =
1185           bases.getSubSequence(new Range(refSeqStart, seqEnd), Bases.FORWARD).toUpperCase();
1186 
1187         ruler.refSeq = refSeq;
1188       }
1189       catch (OutOfRangeException e)
1190       {
1191         e.printStackTrace();
1192       }
1193     }
1194     ruler.repaint();
1195     drawSelectionRange(g2, ALIGNMENT_PIX_PER_BASE, start, end, Color.PINK);
1196 
1197     g2.setStroke(new BasicStroke (2.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
1198 
1199     boolean drawn[] = new boolean[readsInView.size()];
1200     for(int i=0; i<readsInView.size(); i++)
1201       drawn[i] = false;
1202 
1203     Rectangle r = jspView.getViewport().getViewRect();
1204     int nreads = readsInView.size();
1205 
1206     for (int i = 0; i < nreads; i++)
1207     {
1208       try
1209       {
1210         if (!drawn[i])
1211         {
1212           ypos += 11;
1213 
1214           BamViewRecord thisRead = readsInView.get(i);
1215           if (ypos < r.getMaxY() || ypos > r.getMinY())
1216             drawSequence(g2, thisRead, ypos, refSeq, refSeqStart);
1217           drawn[i] = true;
1218 
1219           int thisEnd = thisRead.sam.getAlignmentEnd();
1220           if (thisEnd == 0)
1221             thisEnd = thisRead.sam.getAlignmentStart() + thisRead.sam.getReadLength();
1222 
1223           for (int j = i + 1; j < nreads; j++)
1224           {
1225             if (!drawn[j])
1226             {
1227               BamViewRecord nextRead = readsInView.get(j);
1228               int nextStart = nextRead.sam.getAlignmentStart();
1229               if (nextStart > thisEnd + 1)
1230               {
1231                 if (ypos < r.getMaxY() || ypos > r.getMinY())
1232                   drawSequence(g2, nextRead, ypos, refSeq, refSeqStart);
1233 
1234                 drawn[j] = true;
1235                 thisEnd = nextRead.sam.getAlignmentEnd();
1236                 if (thisEnd == 0)
1237                   thisEnd = nextStart + nextRead.sam.getReadLength();
1238               }
1239               else if (ypos > r.getMaxY() || ypos < r.getMinY())
1240                 break;
1241             }
1242           }
1243         }
1244       }
1245       catch (ArrayIndexOutOfBoundsException ae)
1246       {
1247         System.err.println(readsInView.size()+"  "+nreads);
1248         ae.printStackTrace();
1249       }
1250     }
1251 
1252     if(ypos > getHeight())
1253     {
1254       Dimension d = getPreferredSize();
1255       d.setSize(getPreferredSize().getWidth(), ypos);
1256       setPreferredSize(d);
1257       revalidate();
1258     }
1259   }
1260 
1261 
1262   /**
1263    * Draw the query sequence
1264    * @param g2
1265    * @param read
1266    * @param pixPerBase
1267    * @param ypos
1268    */
drawSequence(final Graphics2D g2, final BamViewRecord bamViewRecord, int ypos, String refSeq, int refSeqStart)1269   private void drawSequence(final Graphics2D g2, final BamViewRecord bamViewRecord,
1270                             int ypos, String refSeq, int refSeqStart)
1271   {
1272     SAMRecord samRecord = bamViewRecord.sam;
1273     if (!samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
1274         samRecord.getMateUnmappedFlag() )  // mate is unmapped )  // mate is unmapped
1275       g2.setColor(Color.black);
1276     else
1277       g2.setColor(Color.blue);
1278 
1279     final Color col = g2.getColor();
1280     int xpos;
1281     int len    = 0;
1282     int refPos = 0;
1283     SAMRecordSequenceString readSeq = new SAMRecordSequenceString(samRecord.getReadString());
1284     final int offset = getSequenceOffset(samRecord.getReferenceName());
1285 
1286     byte[] phredQuality = null;
1287     if(baseQualityColour.isSelected())
1288       phredQuality = samRecord.getBaseQualities();
1289 
1290     Hashtable<Integer, String> insertions = null;
1291     List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
1292     for(int i=0; i<blocks.size(); i++)
1293     {
1294       AlignmentBlock block = blocks.get(i);
1295       int blockStart = block.getReadStart();
1296       len += block.getLength();
1297       for(int j=0; j<block.getLength(); j++)
1298       {
1299         int readPos = blockStart-1+j;
1300         xpos = block.getReferenceStart() - 1 + j + offset;
1301         refPos = xpos - refSeqStart + 1;
1302 
1303         if(phredQuality != null && phredQuality.length > 0)
1304           setColourByBaseQuality(g2, phredQuality[readPos]);
1305 
1306         if(isSNPs && refSeq != null && refPos > 0 && refPos < refSeq.length())
1307         {
1308           if(
1309         		  (!readSeq.isSecondaryAlignment()) &&
1310         		  (Character.toUpperCase(readSeq.charAt(readPos)) != refSeq.charAt(refPos))
1311         	  )
1312             g2.setColor(Color.red);
1313           else
1314             g2.setColor(col);
1315         }
1316 
1317         	g2.drawString(readSeq.substring(readPos, readPos+1),
1318                    refPos*ALIGNMENT_PIX_PER_BASE, ypos);
1319 
1320         if(isSNPs)
1321           g2.setColor(col);
1322       }
1323 
1324       // look for insertions
1325       if(markInsertions.isSelected() && i < blocks.size()-1)
1326       {
1327         int blockEnd = blockStart+block.getLength();
1328         int nextBlockStart = blocks.get(i+1).getReadStart();
1329         int insertSize = nextBlockStart - blockEnd;
1330         if(insertSize > 0)
1331         {
1332           if(insertions == null)
1333             insertions = new Hashtable<Integer, String>();
1334 
1335           g2.setColor(DEEP_PINK);
1336 
1337           int xscreen = (refPos+1)*ALIGNMENT_PIX_PER_BASE;
1338           insertions.put(xscreen,
1339               (refPos+refSeqStart+1)+" "+
1340               readSeq.substring(blockEnd-1, nextBlockStart-1));
1341           g2.drawLine(xscreen, ypos, xscreen, ypos-BASE_HEIGHT);
1342 
1343           // mark on reference sequence as well
1344           if(bases != null)
1345             g2.drawLine(xscreen, 11, xscreen, 11-BASE_HEIGHT);
1346           g2.setColor(col);
1347         }
1348       }
1349 
1350       if(highlightSAMRecord != null &&
1351     	       highlightSAMRecord.sam.getReadName().equals(bamViewRecord.sam.getReadName()))
1352       {
1353         refPos =  block.getReferenceStart() + offset - refSeqStart;
1354         int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
1355         int width  = block.getLength()*ALIGNMENT_PIX_PER_BASE;
1356         Color col1 = g2.getColor();
1357         //g2.setColor(Color.red);
1358 
1359         if (isThisBamRecordHighlighted(bamViewRecord) )
1360 		{
1361 			// Selected read alignment
1362 			g2.setColor(Color.black);
1363 		}
1364 		else
1365 		{
1366 			// For a read with the same name as selected one.
1367 			g2.setColor(NON_SELECTED_READ_HIGHLIGHT_COLOUR);
1368 		}
1369 
1370         g2.drawRect(xstart, ypos-BASE_HEIGHT, width, BASE_HEIGHT);
1371         if(i < blocks.size()-1)
1372         {
1373           int nextStart =
1374             (blocks.get(i+1).getReferenceStart() + offset - refSeqStart)*ALIGNMENT_PIX_PER_BASE;
1375           g2.drawLine(xstart+width, ypos-(BASE_HEIGHT/2), nextStart, ypos-(BASE_HEIGHT/2));
1376         }
1377 
1378         g2.setColor(col1);
1379       }
1380       else if(i < blocks.size()-1)
1381       {
1382         refPos =  block.getReferenceStart() + offset - refSeqStart;
1383         int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
1384         int width  = block.getLength()*ALIGNMENT_PIX_PER_BASE;
1385         int nextStart =
1386           (blocks.get(i+1).getReferenceStart() + offset - refSeqStart)*ALIGNMENT_PIX_PER_BASE;
1387         g2.drawLine(xstart+width, ypos-(BASE_HEIGHT/2), nextStart, ypos-(BASE_HEIGHT/2));
1388       }
1389     }
1390 
1391     if(lastMousePoint != null && blocks.size() > 0)
1392     {
1393       refPos = blocks.get(0).getReferenceStart()+offset-refSeqStart;
1394       int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
1395 
1396       refPos = blocks.get(blocks.size()-1).getReferenceStart()+
1397                blocks.get(blocks.size()-1).getLength()+offset-refSeqStart;
1398       int xend   = (refPos+len)*ALIGNMENT_PIX_PER_BASE;
1399 
1400       if(lastMousePoint.getY() > ypos-11 && lastMousePoint.getY() < ypos)
1401       if(lastMousePoint.getX() > xstart &&
1402          lastMousePoint.getX() < xend)
1403       {
1404         mouseOverSAMRecord = bamViewRecord;
1405 
1406         if(insertions != null)
1407           mouseOverInsertion = insertions.get((int)lastMousePoint.getX());
1408       }
1409     }
1410   }
1411 
1412   /**
1413    * Colour bases on their mapping quality.
1414    * @param g2
1415    * @param baseQuality
1416    */
setColourByBaseQuality(Graphics2D g2, byte baseQuality)1417   private void setColourByBaseQuality(Graphics2D g2, byte baseQuality)
1418   {
1419     if (baseQuality < 10)
1420       g2.setColor(Color.blue);
1421     else if (baseQuality < 20)
1422       g2.setColor(DARK_GREEN);
1423     else if (baseQuality < 30)
1424       g2.setColor(DARK_ORANGE);
1425     else
1426       g2.setColor(Color.black);
1427   }
1428 
1429   /**
1430    * Draw inferred size view.
1431    * @param g2
1432    * @param seqLength
1433    * @param pixPerBase
1434    * @param start
1435    * @param end
1436    */
drawLineView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end)1437   private void drawLineView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end)
1438   {
1439     drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
1440     if(isShowScale())
1441       drawScale(g2, start, end, pixPerBase, getHeight());
1442 
1443     final Stroke stroke =
1444       new BasicStroke (readLnHgt, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
1445     g2.setStroke(stroke);
1446     int ydiff = (int) Math.round(1.5*readLnHgt);
1447 
1448     final int scaleHeight;
1449     if(isShowScale())
1450       scaleHeight = 15;
1451     else
1452       scaleHeight = 0;
1453 
1454     int baseAtStartOfView = getBaseAtStartOfView();
1455     Rectangle r = jspView.getViewport().getViewRect();
1456 
1457     for(int i=0; i<readsInView.size(); i++)
1458     {
1459       BamViewRecord bamViewRecord = readsInView.get(i);
1460       SAMRecord samRecord = bamViewRecord.sam;
1461       BamViewRecord bamViewNextRecord = null;
1462       SAMRecord samNextRecord = null;
1463 
1464       List<Integer> snps = getSNPs(samRecord);
1465 
1466       if( !samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
1467           samRecord.getMateUnmappedFlag() )  // mate is unmapped
1468       {
1469         if(isSingle)
1470         {
1471           int ypos = getYPos(scaleHeight, samRecord.getReadString().length()); // (getHeight() - scaleHeight) - samRecord.getReadString().length();
1472           if(ypos > r.getMaxY() || ypos < r.getMinY())
1473             continue;
1474 
1475           g2.setColor(Color.black);
1476           drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
1477         }
1478         continue;
1479       }
1480 
1481       int ypos = getYPos(scaleHeight, Math.abs(samRecord.getInferredInsertSize()));
1482       if( (ypos > r.getMaxY() || ypos < r.getMinY()) && ypos > 0 )
1483         continue;
1484 
1485       if(i < readsInView.size()-1)
1486       {
1487         bamViewNextRecord = readsInView.get(++i);
1488         samNextRecord = bamViewNextRecord.sam;
1489 
1490         if(samRecord.getReadName().equals(samNextRecord.getReadName()))
1491         {
1492           // draw connection between paired reads
1493           if(samRecord.getAlignmentEnd() < samNextRecord.getAlignmentStart() &&
1494               (samNextRecord.getAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
1495           {
1496         	g2.setColor(Color.LIGHT_GRAY);
1497 
1498             int offset1 = getSequenceOffset(samRecord.getReferenceName());
1499             int end1   = samRecord.getAlignmentEnd()+offset1-baseAtStartOfView;
1500 
1501             int offset2 = getSequenceOffset(samNextRecord.getReferenceName());
1502             int start2  = samNextRecord.getAlignmentStart()+offset2-baseAtStartOfView;
1503 
1504             drawTranslucentLine(g2,
1505                    (int)(end1*pixPerBase), (int)(start2*pixPerBase), ypos);
1506           }
1507 
1508           if(colourByCoverageColour.isSelected())
1509             g2.setColor(getColourByCoverageColour(bamViewRecord));
1510           else if( (samRecord.getReadNegativeStrandFlag() && // strand of the query (1 for reverse)
1511                     samNextRecord.getReadNegativeStrandFlag()) ||
1512                    (!samRecord.getReadNegativeStrandFlag() &&
1513                     !samNextRecord.getReadNegativeStrandFlag()))
1514             g2.setColor(Color.red);
1515           else
1516             g2.setColor(Color.blue);
1517 
1518           drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
1519           drawRead(g2, bamViewNextRecord, pixPerBase, ypos, baseAtStartOfView, getSNPs(samNextRecord), ydiff);
1520         }
1521         else
1522         {
1523           drawLoneRead(g2, bamViewRecord, ypos, pixPerBase, baseAtStartOfView, scaleHeight, snps, ydiff);
1524           i--;
1525         }
1526       }
1527       else
1528       {
1529         drawLoneRead(g2, bamViewRecord, ypos, pixPerBase, baseAtStartOfView, scaleHeight, snps, ydiff);
1530       }
1531     }
1532 
1533     drawYScale(g2, scaleHeight);
1534   }
1535 
getYPos(int scaleHeight, int size)1536   private int getYPos(int scaleHeight, int size)
1537   {
1538     if(!logScale)
1539       return (getHeight() - scaleHeight) - size;
1540     else
1541     {
1542       int logInfSize = (int)( Math.log(size) * 100);
1543       return (getHeight() - scaleHeight) - logInfSize;
1544     }
1545   }
1546 
1547   /**
1548    * Draw the reads as lines in vertical stacks. The reads are colour
1549    * coded as follows:
1550    *
1551    * blue  - reads are unique and are paired with a mapped mate
1552    * black - reads are unique and are not paired or have an unmapped mate
1553    * green - reads are duplicates
1554    *
1555    * @param g2
1556    * @param seqLength
1557    * @param pixPerBase
1558    * @param start
1559    * @param end
1560    */
drawStackView(Graphics2D g2, final int seqLength, final float pixPerBase, final int start, final int end)1561   private void drawStackView(Graphics2D g2,
1562                              final int seqLength,
1563                              final float pixPerBase,
1564                              final int start,
1565                              final int end)
1566   {
1567     drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
1568     if(isShowScale())
1569       drawScale(g2, start, end, pixPerBase, getHeight());
1570 
1571     final BasicStroke stroke = new BasicStroke(
1572         readLnHgt,
1573         BasicStroke.CAP_BUTT,
1574         BasicStroke.JOIN_MITER);
1575     g2.setStroke(stroke);
1576 
1577     final int scaleHeight;
1578     if(isShowScale())
1579       scaleHeight = 15;
1580     else
1581       scaleHeight = 0;
1582 
1583     int ypos = (getHeight() - scaleHeight);
1584     int ydiff = (int) Math.round(1.5*readLnHgt);
1585 
1586     if(isOrientation)
1587       ydiff= 2*ydiff;
1588     int maxEnd = 0;
1589     final int baseAtStartOfView = getBaseAtStartOfView();
1590     g2.setColor(Color.blue);
1591     final Rectangle r = jspView.getViewport().getViewRect();
1592 
1593     for(BamViewRecord bamViewRecord: readsInView)
1594     {
1595       SAMRecord samRecord = bamViewRecord.sam;
1596 
1597       int offset = getSequenceOffset(samRecord.getReferenceName());
1598 
1599       int recordStart = samRecord.getAlignmentStart()+offset;
1600       int recordEnd = samRecord.getAlignmentEnd()+offset;
1601 
1602       List<Integer> snps = getSNPs(samRecord);
1603 
1604       if(colourByStrandTag.isSelected())
1605 	  {
1606 	      if(samRecord.getAttribute("XS") == null)
1607 	        g2.setColor(Color.BLACK);
1608 	      else if( ((Character)samRecord.getAttribute("XS")).equals('+') )
1609 	        g2.setColor(Color.BLUE);
1610 	      else if( ((Character)samRecord.getAttribute("XS")).equals('-') )
1611 	        g2.setColor(Color.RED);
1612 	      else
1613 	        g2.setColor(Color.BLACK);
1614 	  }
1615 	  else if(colourByCoverageColour.isSelected())
1616 	      g2.setColor(getColourByCoverageColour(bamViewRecord));
1617 	  else if(colourByReadGrp.isSelected())
1618 	      g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, samRecord.getReadGroup()));
1619 	  else if (samRecord.getDuplicateReadFlag())
1620 			g2.setColor(DARK_GREEN); // Duplicate
1621 	  else if (!samRecord.getReadPairedFlag() ||   // read is not paired in sequencing
1622 	              samRecord.getMateUnmappedFlag() )  // mate is unmapped )  // mate is unmapped
1623 	      g2.setColor(Color.black);
1624 	  else
1625 	      g2.setColor(Color.blue);
1626 
1627       if(maxEnd < recordStart || ypos < 0)
1628       {
1629         ypos = (getHeight() - scaleHeight)-ydiff;
1630         maxEnd = recordEnd+2;
1631       }
1632       else
1633         ypos = ypos-ydiff;
1634 
1635       if(ypos > r.getMaxY() || ypos < r.getMinY())
1636         continue;
1637 
1638       drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
1639     }
1640   }
1641 
1642   /**
1643    * Draw the reads as lines in vertical stacks. The reads are colour
1644    * coded as follows:
1645    *
1646    * blue  - reads are unique and are paired with a mapped mate
1647    * black - reads are unique and are not paired or have an unmapped mate
1648    * green - reads are duplicates
1649    *
1650    * @param g2
1651    * @param seqLength
1652    * @param pixPerBase
1653    * @param start
1654    * @param end
1655    */
drawStrandStackView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end)1656   private void drawStrandStackView(Graphics2D g2,
1657                                    int seqLength,
1658                                    float pixPerBase,
1659                                    int start,
1660                                    int end)
1661   {
1662     drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
1663     final BasicStroke stroke = new BasicStroke(
1664         readLnHgt,
1665         BasicStroke.CAP_BUTT,
1666         BasicStroke.JOIN_MITER);
1667 
1668     final int scaleHeight = 15;
1669     drawScale(g2, start, end, pixPerBase, ((getHeight()+scaleHeight)/2));
1670 
1671     int ymid = (getHeight()/ 2);
1672     int ydiff = (int) Math.round(1.5*readLnHgt);
1673     if(isOrientation)
1674       ydiff= 2*ydiff;
1675 
1676     g2.setStroke(stroke);
1677     // positive strand
1678     drawStrand(g2, false, scaleHeight, ymid-(scaleHeight/2), -ydiff, pixPerBase);
1679 
1680     // negative strand
1681     drawStrand(g2, true, scaleHeight, ymid+(scaleHeight/2), ydiff, pixPerBase);
1682   }
1683 
1684 
drawStrand(Graphics2D g2, boolean isStrandNegative, int scaleHeight, int ymid, int ystep, float pixPerBase)1685   private void drawStrand(Graphics2D g2,
1686                           boolean isStrandNegative,
1687                           int scaleHeight,
1688                           int ymid,
1689                           int ystep,
1690                           float pixPerBase)
1691   {
1692     int hgt = getHeight();
1693     int ypos = (hgt - scaleHeight);
1694     int maxEnd = 0;
1695     int baseAtStartOfView = getBaseAtStartOfView();
1696     g2.setColor(Color.blue);
1697     Rectangle r = jspView.getViewport().getViewRect();
1698 
1699     for(BamViewRecord bamViewRecord: readsInView)
1700     {
1701       SAMRecord samRecord = bamViewRecord.sam;
1702 
1703       if( isNegativeStrand(samRecord, colourByStrandTag.isSelected()) == isStrandNegative )
1704       {
1705         final int offset = getSequenceOffset(samRecord.getReferenceName());
1706         final int recordStart = samRecord.getAlignmentStart()+offset;
1707         final int recordEnd   = samRecord.getAlignmentEnd()+offset;
1708         List<Integer> snps = getSNPs(samRecord);
1709 
1710         if(colourByStrandTag.isSelected())
1711         {
1712             if(samRecord.getAttribute("XS") == null)
1713               g2.setColor(Color.BLACK);
1714             else if( ((Character)samRecord.getAttribute("XS")).equals('+') )
1715               g2.setColor(Color.BLUE);
1716             else if( ((Character)samRecord.getAttribute("XS")).equals('-') )
1717               g2.setColor(Color.RED);
1718             else
1719               g2.setColor(Color.BLACK);
1720         }
1721         else if(colourByCoverageColour.isSelected())
1722             g2.setColor(getColourByCoverageColour(bamViewRecord));
1723         else if(colourByReadGrp.isSelected())
1724             g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, samRecord.getReadGroup()));
1725         else if (samRecord.getDuplicateReadFlag())
1726 			g2.setColor(DARK_GREEN); // Duplicate
1727         else if (!samRecord.getReadPairedFlag() ||   // read is not paired in sequencing
1728                     samRecord.getMateUnmappedFlag() )  // mate is unmapped
1729             g2.setColor(Color.black);
1730         else
1731             g2.setColor(Color.blue);
1732 
1733         if(maxEnd < recordStart || ypos < 0 || ypos > hgt)
1734         {
1735           ypos = ymid + ystep;
1736           maxEnd = recordEnd+2;
1737         }
1738         else
1739           ypos = ypos + ystep;
1740 
1741         if(ypos > r.getMaxY() || ypos < r.getMinY())
1742           continue;
1743 
1744         drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ystep);
1745       }
1746     }
1747   }
1748 
1749   /**
1750    * Draw paired reads as lines in a vertical stacks.
1751    * @param g2
1752    * @param seqLength
1753    * @param pixPerBase
1754    * @param start
1755    * @param end
1756    */
drawPairedStackView(Graphics2D g2, final int seqLength, final float pixPerBase, final int start, final int end)1757   private void drawPairedStackView(Graphics2D g2,
1758                                    final int seqLength,
1759                                    final float pixPerBase,
1760                                    final int start,
1761                                    final int end)
1762   {
1763     drawSelectionRange(g2, pixPerBase,start, end, Color.PINK);
1764     if(isShowScale())
1765       drawScale(g2, start, end, pixPerBase, getHeight());
1766 
1767     final Vector<PairedRead> pairedReads = new Vector<PairedRead>();
1768     for(int i=0; i<readsInView.size(); i++)
1769     {
1770       BamViewRecord bamViewRecord = readsInView.get(i);
1771       SAMRecord samRecord = bamViewRecord.sam;
1772       if( !samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
1773           samRecord.getMateUnmappedFlag() )  // mate is unmapped
1774         continue;
1775 
1776       BamViewRecord bamViewNextRecord = null;
1777       if(i < readsInView.size()-1)
1778       {
1779         bamViewNextRecord = readsInView.get(++i);
1780         SAMRecord samNextRecord = bamViewNextRecord.sam;
1781 
1782         final PairedRead pr = new PairedRead();
1783         if(samRecord.getReadName().equals(samNextRecord.getReadName()) &&
1784            isFromSameBamFile(bamViewRecord, bamViewNextRecord, bamList))
1785         {
1786           if(samRecord.getAlignmentStart() < samNextRecord.getAlignmentStart())
1787           {
1788             pr.sam1 = bamViewRecord;
1789             pr.sam2 = bamViewNextRecord;
1790           }
1791           else
1792           {
1793             pr.sam2 = bamViewRecord;
1794             pr.sam1 = bamViewNextRecord;
1795           }
1796         }
1797         else
1798         {
1799           --i;
1800           pr.sam1 = bamViewRecord;
1801           pr.sam2 = null;
1802         }
1803 
1804         pairedReads.add(pr);
1805       }
1806     }
1807     Collections.sort(pairedReads, new PairedReadComparator());
1808 
1809     Stroke originalStroke = new BasicStroke (readLnHgt, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
1810 
1811     g2.setStroke( new BasicStroke (1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
1812 
1813     final int scaleHeight;
1814     if(isShowScale())
1815       scaleHeight = 15;
1816     else
1817       scaleHeight = 0;
1818 
1819     int ydiff = (int) Math.round(2.3*readLnHgt);
1820     if(isOrientation)
1821       ydiff= 2*ydiff;
1822     int ypos = getHeight() - scaleHeight - ydiff;
1823     int lastEnd = 0;
1824     int baseAtStartOfView = getBaseAtStartOfView();
1825     Rectangle r = jspView.getViewport().getViewRect();
1826 
1827     for(PairedRead pr: pairedReads)
1828     {
1829       if(pr.sam1.sam.getAlignmentStart() > lastEnd)
1830       {
1831         ypos = getHeight() - scaleHeight - ydiff;
1832         if(pr.sam2 != null)
1833           lastEnd = pr.sam2.sam.getAlignmentEnd();
1834         else
1835           lastEnd = pr.sam1.sam.getAlignmentEnd();
1836       }
1837       else
1838         ypos = ypos - ydiff;
1839 
1840       if(ypos > r.getMaxY() || ypos < r.getMinY())
1841         continue;
1842 
1843       g2.setStroke(originalStroke);
1844 
1845       if( highlightSAMRecord != null &&
1846       		highlightSAMRecord.sam.getReadName().equals(pr.sam1.sam.getReadName()) )
1847         g2.setColor(Color.black);
1848       else
1849         g2.setColor(Color.gray);
1850 
1851       if(pr.sam2 != null)
1852       {
1853         if(!readsOverlap(pr.sam1.sam, pr.sam2.sam))
1854         {
1855           int offset1 = getSequenceOffset(pr.sam1.sam.getReferenceName());
1856           int offset2 = getSequenceOffset(pr.sam2.sam.getReferenceName());
1857           drawTranslucentJointedLine(g2,
1858               (int)((pr.sam1.sam.getAlignmentEnd()+offset1-getBaseAtStartOfView())*pixPerBase),
1859               (int)((pr.sam2.sam.getAlignmentStart()+offset2-getBaseAtStartOfView())*pixPerBase), ypos);
1860         }
1861       }
1862       else if(!pr.sam1.sam.getMateUnmappedFlag() &&
1863                pr.sam1.sam.getProperPairFlag() &&
1864                pr.sam1.sam.getMateReferenceName().equals(pr.sam1.sam.getReferenceName()))
1865       {
1866         final int prStart, prEnd;
1867         if(pr.sam1.sam.getAlignmentStart() > pr.sam1.sam.getMateAlignmentStart())
1868         {
1869           prStart = pr.sam1.sam.getMateAlignmentStart();
1870           prEnd = pr.sam1.sam.getAlignmentStart();
1871         }
1872         else
1873         {
1874           prStart = pr.sam1.sam.getAlignmentEnd();
1875           prEnd = pr.sam1.sam.getMateAlignmentStart();
1876         }
1877 
1878         int offset = getSequenceOffset(pr.sam1.sam.getReferenceName());
1879         drawTranslucentJointedLine(g2,
1880               (int)( (prStart+offset-getBaseAtStartOfView())*pixPerBase),
1881               (int)( (prEnd  +offset-getBaseAtStartOfView())*pixPerBase), ypos);
1882       }
1883 
1884       if(colourByCoverageColour.isSelected())
1885         g2.setColor(getColourByCoverageColour(pr.sam1));
1886       else if(colourByStrandTag.isSelected())
1887       {
1888         if( ((Character)pr.sam1.sam.getAttribute("XS")).equals('+') )
1889           g2.setColor(Color.BLUE);
1890         else if( ((Character)pr.sam1.sam.getAttribute("XS")).equals('-') )
1891           g2.setColor(Color.RED);
1892         else
1893           g2.setColor(Color.BLACK);
1894       }
1895       else if(colourByReadGrp.isSelected())
1896         g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, pr.sam1.sam.getReadGroup()));
1897       else if(   pr.sam2 != null &&
1898               !( pr.sam1.sam.getReadNegativeStrandFlag() ^ pr.sam2.sam.getReadNegativeStrandFlag() ) )
1899         g2.setColor(Color.red);
1900       else
1901         g2.setColor(Color.blue);
1902 
1903       Color c = g2.getColor();
1904       drawRead(g2, pr.sam1, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam1.sam), ydiff);
1905       if(pr.sam2 != null)
1906       {
1907         g2.setColor(c);
1908         drawRead(g2, pr.sam2, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam2.sam), ydiff);
1909       }
1910     }
1911   }
1912 
1913   /**
1914    * Check if a record is on the negative strand. If the RNA strand specific
1915    * checkbox is set then use the RNA strand.
1916    * @param samRecord
1917    * @param useStrandTag - strand specific tag
1918    * @return
1919    */
isNegativeStrand(final SAMRecord samRecord, final boolean useStrandTag)1920   protected static boolean isNegativeStrand(final SAMRecord samRecord,
1921                                             final boolean useStrandTag)
1922   {
1923     if(useStrandTag)
1924     {
1925       if(samRecord.getAttribute("XS") == null)
1926         return samRecord.getReadNegativeStrandFlag();
1927       if( ((Character)samRecord.getAttribute("XS")).equals('+') )
1928         return false;
1929       else
1930         return true;
1931     }
1932     return samRecord.getReadNegativeStrandFlag();
1933   }
1934 
1935   /**
1936    * Check if two records are from the same BAM file
1937    * @param sam1
1938    * @param sam2
1939    * @param bamList
1940    * @return
1941    */
isFromSameBamFile(final BamViewRecord sam1, final BamViewRecord sam2, final List<String> bamList)1942   private boolean isFromSameBamFile(final BamViewRecord sam1,
1943                                     final BamViewRecord sam2,
1944                                     final List<String> bamList)
1945   {
1946     if(bamList == null || bamList.size()<2)
1947       return true;
1948 
1949     final short o1 = sam1.bamIndex;
1950     final short o2 = sam2.bamIndex;
1951     if(o1 != -1 && o2 != -1)
1952       if( o1 != o2 )
1953         return false;
1954 
1955     return true;
1956   }
1957 
1958 
1959   /**
1960    * Check if two records overlap
1961    * @param s1
1962    * @param s2
1963    * @return true id the two reads overlap
1964    */
readsOverlap(final SAMRecord s1, final SAMRecord s2)1965   private boolean readsOverlap(final SAMRecord s1,
1966                                final SAMRecord s2)
1967   {
1968     if( (s2.getAlignmentStart() >= s1.getAlignmentStart() &&
1969          s2.getAlignmentStart() <= s1.getAlignmentEnd()) ||
1970         (s2.getAlignmentEnd()   >= s1.getAlignmentStart() &&
1971          s2.getAlignmentEnd()   <= s1.getAlignmentEnd()) )
1972       return true;
1973 
1974     if( (s1.getAlignmentStart() >= s2.getAlignmentStart() &&
1975          s1.getAlignmentStart() <= s2.getAlignmentEnd()) ||
1976         (s1.getAlignmentEnd()   >= s2.getAlignmentStart() &&
1977          s1.getAlignmentEnd()   <= s2.getAlignmentEnd()) )
1978      return true;
1979     return false;
1980   }
1981 
1982   /**
1983    * Draw the read coverage.
1984    * @param g2
1985    * @param start
1986    * @param end
1987    * @param pixPerBase
1988    */
drawCoverage(Graphics2D g2, int start, int end, float pixPerBase)1989   private void drawCoverage(Graphics2D g2, int start, int end, float pixPerBase)
1990   {
1991     int scaleHeight = 0;
1992     if(isShowScale())
1993     {
1994       drawScale(g2, start, end, pixPerBase, getHeight());
1995       scaleHeight = 15;
1996     }
1997 
1998     int hgt = jspView.getVisibleRect().height-scaleHeight;
1999     if(!cbCoverageStrandView.isSelected() && !coverageView.isPlotHeatMap())
2000     {
2001       try
2002       {
2003         int y = getHeight()-6-( (hgt* MAX_COVERAGE)/(coverageView.max/coverageView.windowSize) );
2004         g2.setColor(Color.YELLOW);
2005         // draw the threshold for the coverage max read cut-off
2006         g2.fillRect(0, y, getWidth(), 8);
2007       }
2008       catch(Exception e){}
2009     }
2010 
2011     g2.translate(0, getJspView().getViewport().getViewPosition().y);
2012 
2013     coverageView.drawSelectionRange(g2, pixPerBase, start, end, getHeight(), Color.PINK);
2014     coverageView.draw(g2, getWidth(), hgt, hideBamList);
2015     if(!coverageView.isPlotHeatMap())
2016       coverageView.drawMax(g2, coverageView.getMaxCoverage());
2017   }
2018 
2019   /**
2020    * Draw a read that apparently has a read mate that is not in view.
2021    * @param g2
2022    * @param thisRead
2023    * @param ypos
2024    * @param pixPerBase
2025    * @param originalStroke
2026    * @param stroke
2027    */
drawLoneRead(Graphics2D g2, BamViewRecord bamViewRecord, int ypos, float pixPerBase, int baseAtStartOfView, int scaleHeight, List<Integer> snps, int ydiff)2028   private void drawLoneRead(Graphics2D g2, BamViewRecord bamViewRecord, int ypos,
2029       float pixPerBase, int baseAtStartOfView, int scaleHeight, List<Integer> snps, int ydiff)
2030   {
2031     SAMRecord samRecord = bamViewRecord.sam;
2032     boolean offTheTop = false;
2033     int offset = getSequenceOffset(samRecord.getReferenceName());
2034     int thisStart = samRecord.getAlignmentStart()+offset;
2035     int thisEnd   = thisStart + samRecord.getReadString().length() -1;
2036 
2037     if(ypos <= 0)
2038     {
2039       offTheTop = true;
2040       ypos = samRecord.getReadString().length();
2041     }
2042 
2043     if(samRecord.getInferredInsertSize() == 0)
2044     {
2045       offTheTop = true;
2046       ypos = getHeight() - scaleHeight - 5;
2047     }
2048 
2049     if(samRecord.getInferredInsertSize() != 0 &&
2050       Math.abs(samRecord.getMateAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
2051     {
2052       g2.setColor(Color.LIGHT_GRAY);
2053 
2054       if(samRecord.getAlignmentEnd() < samRecord.getMateAlignmentStart())
2055       {
2056         int nextStart =
2057           (int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase);
2058         drawTranslucentLine(g2,
2059           (int)((thisEnd-getBaseAtStartOfView())*pixPerBase), nextStart, ypos);
2060       }
2061       else
2062       {
2063         int nextStart =
2064             (int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase);
2065         drawTranslucentLine(g2,
2066             (int)((thisStart-getBaseAtStartOfView())*pixPerBase), nextStart, ypos);
2067       }
2068     }
2069 
2070     if(colourByCoverageColour.isSelected())
2071       g2.setColor(getColourByCoverageColour(bamViewRecord));
2072     else if(offTheTop)
2073       g2.setColor(DARK_ORANGE);
2074     else if(samRecord.getReadNegativeStrandFlag() &&
2075             samRecord.getMateNegativeStrandFlag()) // strand of the query (1 for reverse)
2076       g2.setColor(Color.red);
2077     else
2078       g2.setColor(Color.blue);
2079 
2080     drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff);
2081 
2082     /*if (isSNPs)
2083       showSNPsOnReads(g2, samRecord, pixPerBase, ypos, offset);*/
2084   }
2085 
2086 
drawScale(Graphics2D g2, int start, int end, float pixPerBase, int ypos)2087   private void drawScale(Graphics2D g2, int start, int end, float pixPerBase, int ypos)
2088   {
2089     g2.setColor(Color.black);
2090     g2.drawLine( 0, ypos-14,
2091                  (int)((end - getBaseAtStartOfView())*pixPerBase),   ypos-14);
2092     int interval = end-start;
2093 
2094     if(interval > 256000)
2095       drawTicks(g2, start, end, pixPerBase, 512000, ypos);
2096     else if(interval > 64000)
2097       drawTicks(g2, start, end, pixPerBase, 12800, ypos);
2098     else if(interval > 16000)
2099       drawTicks(g2, start, end, pixPerBase, 3200, ypos);
2100     else if(interval > 4000)
2101       drawTicks(g2, start, end, pixPerBase, 800, ypos);
2102     else if(interval > 1000)
2103       drawTicks(g2, start, end, pixPerBase, 400, ypos);
2104     else
2105       drawTicks(g2, start, end, pixPerBase, 100, ypos);
2106   }
2107 
drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division, int ypos)2108   private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division, int ypos)
2109   {
2110     int markStart = (Math.round(start/division)*division);
2111 
2112     if(markStart < 1)
2113       markStart = 1;
2114 
2115     int sm = markStart-(division/2);
2116     float x;
2117     if(sm > start)
2118     {
2119       x = (sm-getBaseAtStartOfView())*pixPerBase;
2120       g2.drawLine((int)x, ypos-14,(int)x, ypos-12);
2121     }
2122 
2123     for(int m=markStart; m<end; m+=division)
2124     {
2125       x = (m-getBaseAtStartOfView())*pixPerBase;
2126       g2.drawString(Integer.toString(m), x, ypos-1);
2127       g2.drawLine((int)x, ypos-14,(int)x, ypos-11);
2128 
2129       sm = m+(division/2);
2130 
2131       if(sm < end)
2132       {
2133         x = (sm-getBaseAtStartOfView())*pixPerBase;
2134         g2.drawLine((int)x, ypos-14,(int)x, ypos-12);
2135       }
2136 
2137       if(m == 1)
2138         m = 0;
2139     }
2140   }
2141 
2142   /**
2143    * Draw a y-scale for inferred size (isize) of reads.
2144    * @param g2
2145    * @param xScaleHeight
2146    */
drawYScale(Graphics2D g2, int xScaleHeight)2147   private void drawYScale(Graphics2D g2, int xScaleHeight)
2148   {
2149     g2.setColor(Color.black);
2150     int maxY = getPreferredSize().height-xScaleHeight;
2151 
2152     if(logScale)
2153     {
2154       int start = 10;
2155       int count = 0;
2156       int ypos = getYPos(xScaleHeight, start);
2157 
2158       while(ypos > 0 && count < 15 && start > 1)
2159       {
2160         g2.drawLine(0, ypos, 2, ypos);
2161         g2.drawString(Integer.toString(start), 3, ypos);
2162         start = start*5;
2163         ypos = getYPos(xScaleHeight, start);
2164         count++;
2165       }
2166       return;
2167     }
2168 
2169     for(int i=100; i<maxY; i+=100)
2170     {
2171       int ypos = getHeight()-i-xScaleHeight;
2172       g2.drawLine(0, ypos, 2, ypos);
2173       g2.drawString(Integer.toString(i), 3, ypos);
2174     }
2175   }
2176 
2177   /**
2178    * Draw a given read.
2179    * @param g2
2180    * @param thisRead
2181    * @param pixPerBase
2182    * @param ypos
2183    * @param baseAtStartOfView
2184    * @param snps
2185    */
drawRead(Graphics2D g2, final BamViewRecord bamViewRecord, final float pixPerBase, final int ypos, final int baseAtStartOfView, final List<Integer> snps, final int ydiff)2186   private void drawRead(Graphics2D g2,
2187       final BamViewRecord bamViewRecord,
2188       final float pixPerBase,
2189       final int ypos,
2190       final int baseAtStartOfView,
2191       final List<Integer> snps,
2192       final int ydiff)
2193   {
2194     SAMRecord thisRead = bamViewRecord.sam;
2195     int offset = getSequenceOffset(thisRead.getReferenceName());
2196 
2197     int thisStart = thisRead.getAlignmentStart()+offset-baseAtStartOfView;
2198     int thisEnd   = thisRead.getAlignmentEnd()+offset-baseAtStartOfView;
2199 
2200     	if(highlightSAMRecord != null &&
2201     		highlightSAMRecord.sam.getReadName().equals(thisRead.getReadName()))
2202 	{
2203     		Stroke originalStroke = g2.getStroke();
2204 		Stroke stroke =
2205 		  new BasicStroke (readLnHgt*1.6f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
2206 		g2.setStroke(stroke);
2207 		Color c = g2.getColor();
2208 
2209 		if (isThisBamRecordHighlighted(bamViewRecord) )
2210 		{
2211 			// Selected read alignment
2212 			g2.setColor(Color.black);
2213 		}
2214 		else
2215 		{
2216 			// For a read with the same name as selected one.
2217 			g2.setColor(NON_SELECTED_READ_HIGHLIGHT_COLOUR);
2218 		}
2219 
2220 		g2.drawLine((int)( thisStart * pixPerBase), ypos,
2221 		            (int)( thisEnd * pixPerBase), ypos);
2222 		g2.setColor(c);
2223 		g2.setStroke(originalStroke);
2224 	}
2225 
2226 
2227     if(thisRead.getCigar().getCigarElements().size() == 1)
2228       g2.drawLine((int)( thisStart * pixPerBase), ypos,
2229                   (int)( thisEnd * pixPerBase), ypos);
2230     else
2231     {
2232       List<AlignmentBlock> blocks = thisRead.getAlignmentBlocks();
2233       Color c = g2.getColor();
2234       int lastEnd = 0;
2235       for(int i=0; i<blocks.size(); i++)
2236       {
2237         AlignmentBlock block = blocks.get(i);
2238         int blockStart = block.getReferenceStart()+offset-baseAtStartOfView;
2239         int blockEnd = blockStart + block.getLength() - 1;
2240 
2241         g2.drawLine((int)( blockStart * pixPerBase), ypos,
2242                     (int)( blockEnd * pixPerBase), ypos);
2243         if(i > 0 && blockStart != lastEnd)
2244         {
2245           g2.setColor(Color.gray);
2246           g2.drawLine((int)( blockStart * pixPerBase), ypos,
2247                       (int)( lastEnd * pixPerBase), ypos);
2248           g2.setColor(c);
2249         }
2250         lastEnd = blockEnd;
2251       }
2252     }
2253 
2254     if(isOrientation)
2255       drawArrow(g2, thisRead, thisStart, thisEnd, pixPerBase, ypos, ydiff);
2256 
2257     // test if the mouse is over this read
2258     if(lastMousePoint != null)
2259     {
2260 	    	if(lastMousePoint.getY() > ypos-(Math.abs(ydiff)/2) && lastMousePoint.getY() < ypos+(Math.abs(ydiff)/2))
2261 	    	{
2262 		      if(lastMousePoint.getX() > thisStart * pixPerBase &&
2263 		         lastMousePoint.getX() < thisEnd * pixPerBase)
2264 		      {
2265 		    	  	mouseOverSAMRecord = bamViewRecord;
2266 		      }
2267 	    }
2268     }
2269 
2270     if (isSNPs && snps != null && snps.size() > 0)
2271       showSNPsOnReads(snps, g2, pixPerBase, ypos);
2272   }
2273 
2274   /**
2275    * Draw arrow on the read to indicate orientation.
2276    * @param g2
2277    * @param thisRead
2278    * @param thisStart
2279    * @param thisEnd
2280    * @param pixPerBase
2281    * @param ypos
2282    */
drawArrow(final Graphics2D g2, final SAMRecord thisRead, final int thisStart, final int thisEnd, final float pixPerBase, int ypos, int ydiff)2283   private void drawArrow(final Graphics2D g2,
2284       final SAMRecord thisRead,
2285       final int thisStart,
2286       final int thisEnd,
2287       final float pixPerBase,
2288       int ypos,
2289       int ydiff)
2290   {
2291     if(ydiff < 0)
2292       ydiff = -ydiff;
2293 
2294     if(thisRead.getReadNegativeStrandFlag())
2295     {
2296       ypos-=readLnHgt/2;
2297       int apos = ypos + ydiff - 1;
2298       g2.drawLine((int)( (thisStart+5) * pixPerBase), apos,
2299                   (int)( thisStart * pixPerBase), ypos);
2300     }
2301     else
2302     {
2303       ypos+=readLnHgt/2;
2304       int apos = ypos - ydiff + 1;
2305       g2.drawLine((int)( (thisEnd-5) * pixPerBase), apos,
2306                   (int)( thisEnd * pixPerBase), ypos);
2307     }
2308   }
2309 
2310   /**
2311    * Highlight a selected range
2312    * @param g2
2313    * @param pixPerBase
2314    * @param start
2315    * @param end
2316    */
drawSelectionRange(Graphics2D g2, float pixPerBase, int start, int end, Color c)2317   private void drawSelectionRange(Graphics2D g2, float pixPerBase, int start, int end, Color c)
2318   {
2319     if(getSelection() != null)
2320     {
2321       Range selectedRange = getSelection().getSelectionRange();
2322 
2323       if(selectedRange != null)
2324       {
2325         int rangeStart = selectedRange.getStart();
2326         int rangeEnd   = selectedRange.getEnd();
2327 
2328         if(end < rangeStart || start > rangeEnd)
2329           return;
2330 
2331         int x = (int) (pixPerBase*(rangeStart-getBaseAtStartOfView()));
2332         int width = (int) (pixPerBase*(rangeEnd-rangeStart+1));
2333 
2334         g2.setColor(c);
2335         g2.fillRect(x, 0, width, getHeight());
2336       }
2337     }
2338   }
2339 
2340   /**
2341    * Draw a translucent line
2342    * @param g2
2343    * @param start
2344    * @param end
2345    * @param ypos
2346    */
drawTranslucentLine(Graphics2D g2, int start, int end, int ypos)2347   private void drawTranslucentLine(Graphics2D g2, int start, int end, int ypos)
2348   {
2349     Composite origComposite = g2.getComposite();
2350     g2.setComposite(translucent);
2351     g2.drawLine(start, ypos, end, ypos);
2352     g2.setComposite(origComposite);
2353   }
2354 
2355   /**
2356    * Draw a translucent line
2357    * @param g2
2358    * @param start
2359    * @param end
2360    * @param ypos
2361    */
drawTranslucentJointedLine(Graphics2D g2, int start, int end, int ypos)2362   private void drawTranslucentJointedLine(Graphics2D g2, int start, int end, int ypos)
2363   {
2364     Composite origComposite = g2.getComposite();
2365     g2.setComposite(translucent);
2366 
2367     int mid = (int) ((end-start)/2.f)+start;
2368     //g2.drawLine(start, ypos, end, ypos);
2369     g2.drawLine(start, ypos, mid, ypos-5);
2370     g2.drawLine(mid, ypos-5, end, ypos);
2371     g2.setComposite(origComposite);
2372   }
2373 
2374   /**
2375    * Display the SNPs for the given read.
2376    * @param snps
2377    * @param g2
2378    * @param pixPerBase
2379    * @param ypos
2380    */
showSNPsOnReads(final List<Integer> snps, final Graphics2D g2, float pixPerBase, final int ypos)2381   private void showSNPsOnReads(final List<Integer> snps,
2382                                final Graphics2D g2,
2383                                float pixPerBase, final int ypos)
2384   {
2385     final Stroke originalStroke = g2.getStroke();
2386     final BasicStroke stroke = new BasicStroke(
2387         1.3f,
2388         BasicStroke.CAP_BUTT,
2389         BasicStroke.JOIN_MITER);
2390     g2.setStroke(stroke);
2391 
2392     g2.setColor(Color.red);
2393     for(int pos: snps)
2394       g2.drawLine((int) (pos * pixPerBase), ypos + 2,
2395                   (int) (pos * pixPerBase), ypos - 2);
2396     g2.setStroke(originalStroke);
2397   }
2398 
2399 
2400   /**
2401    * Get the SNP positions
2402    * @param samRecord
2403    */
getSNPs(final SAMRecord samRecord)2404   private List<Integer> getSNPs(final SAMRecord samRecord)
2405   {
2406     if(!isSNPs)  // return null if not displaying SNPs
2407       return null;
2408     int rbeg = samRecord.getAlignmentStart();
2409     int rend = samRecord.getAlignmentEnd();
2410     int offset = getSequenceOffset(samRecord.getReferenceName());
2411     ArrayList<Integer> snps = null;
2412 
2413     // use alignment blocks of the contiguous alignment of
2414     // subsets of read bases to a reference sequence
2415     try
2416     {
2417       final char[] refSeq = bases.getSubSequenceC(
2418           new Range(rbeg+offset, rend+offset), Bases.FORWARD);
2419       final byte[] readSeq = samRecord.getReadBases();
2420 
2421       if (readSeq == null || readSeq.length == 0)
2422       {
2423     	  	// Can occur for secondary alignments
2424     	  	return null;
2425       }
2426 
2427       offset = offset - getBaseAtStartOfView();
2428       final List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
2429       for(AlignmentBlock block: blocks)
2430       {
2431         int readStart = block.getReadStart();
2432         int refStart  = block.getReferenceStart();
2433         int len = block.getLength();
2434         for(int j=0; j<len; j++)
2435         {
2436           int readPos = readStart-1+j;
2437           int refPos  = refStart+j;
2438           if (Character.toUpperCase(refSeq[refPos-rbeg]) != Character.toUpperCase( (char)readSeq[readPos]) )
2439           {
2440             if(snps == null)
2441               snps = new ArrayList<Integer>();
2442             snps.add(refPos+offset);
2443           }
2444         }
2445       }
2446     }
2447     catch (OutOfRangeException e)
2448     {
2449       System.err.println(samRecord.getReadName()+" "+e.getMessage());
2450     }
2451     return snps;
2452   }
2453 
2454 
2455   /**
2456    * Add the alignment view to the supplied <code>JPanel</code> in
2457    * a <code>JScrollPane</code>.
2458    * @param mainPanel  panel to add the alignment to
2459    * @param frame
2460    * @param autohide automatically hide the top panel containing the buttons
2461    * @param feature_display
2462    */
addBamToPanel(final JFrame frame)2463   private void addBamToPanel(final JFrame frame)
2464   {
2465     final JComponent topPanel = bamTopPanel(frame);
2466     mainPanel.setPreferredSize(new Dimension(900, 400));
2467 
2468     setDisplay(1, nbasesInView, null);
2469     mainPanel.setLayout(new BorderLayout());
2470 
2471     if(topPanel instanceof JPanel)
2472       mainPanel.add(topPanel, BorderLayout.NORTH);
2473     mainPanel.add(jspView, BorderLayout.CENTER);
2474 
2475     JPanel bottomPanel = new JPanel(new BorderLayout());
2476     coveragePanel = new CoveragePanel(this);
2477     bottomPanel.add(coveragePanel, BorderLayout.CENTER);
2478 
2479     //
2480     snpPanel = new SnpPanel(this, bases);
2481     bottomPanel.add(snpPanel, BorderLayout.NORTH);
2482 
2483     if(feature_display == null)
2484     {
2485       scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 1, nbasesInView, 1,
2486           getMaxBasesInPanel(getSequenceLength()));
2487       scrollBar.setUnitIncrement(nbasesInView/20);
2488       scrollBar.addAdjustmentListener(new AdjustmentListener()
2489       {
2490         public void adjustmentValueChanged(AdjustmentEvent e)
2491         {
2492           repaint();
2493 
2494           if(isSNPplot)
2495             snpPanel.repaint();
2496           if(isCoverage)
2497             coveragePanel.repaint();
2498         }
2499       });
2500       bottomPanel.add(scrollBar, BorderLayout.SOUTH);
2501     }
2502     else
2503     {
2504       if(!isConcatSequences())
2505       {
2506         int seqLen = seqLengths.get((String) combo.getSelectedItem());
2507         int artemisSeqLen = feature_display.getSequenceLength();
2508         if(seqLen != artemisSeqLen)
2509         {
2510           int newIndex = -1;
2511           for(int i=0; i<seqNames.size(); i++)
2512           {
2513             if(seqLengths.get(seqNames.get(i)) == artemisSeqLen)
2514             {
2515               // this looks like the correct sequence
2516               combo.setSelectedIndex(i);
2517               newIndex = i;
2518             }
2519           }
2520 
2521           if(!Options.isBlackBeltMode())
2522           {
2523             String label[] = {
2524                 "The length of the sequence loaded does not match the length of",
2525                 "the default reference sequence in the BAM/CRAM ("+seqNames.get(0)+").",
2526                 (newIndex == -1 ? "" : "The length does match the reference "+
2527                     seqNames.get(newIndex)+" so this has been set as the default.")
2528             };
2529             new NonModalDialog(frame, label);
2530           }
2531         }
2532       }
2533     }
2534 
2535     mainPanel.add(bottomPanel, BorderLayout.SOUTH);
2536     coveragePanel.setPreferredSize(new Dimension(900, 100));
2537     coveragePanel.setVisible(false);
2538     snpPanel.setPreferredSize(new Dimension(900, 100));
2539     snpPanel.setVisible(false);
2540 
2541     mainPanel.revalidate();
2542     jspView.getVerticalScrollBar().setValue(
2543         jspView.getVerticalScrollBar().getMaximum());
2544   }
2545 
addToViewMenu(final short thisBamIndex)2546   private void addToViewMenu(final short thisBamIndex)
2547   {
2548     final File f = new File(bamList.get(thisBamIndex));
2549     final JCheckBoxMenuItem cbBam = new JCheckBoxMenuItem(
2550                                      f.getName(),
2551                                      getImageIcon(getColourByCoverageColour(thisBamIndex)),
2552                                      true);
2553     bamFilesMenu.add(cbBam);
2554     cbBam.addItemListener(new ItemListener() {
2555       public void itemStateChanged(ItemEvent e) {
2556         if(cbBam.isSelected())
2557           hideBamList.remove(new Short(thisBamIndex));
2558         else
2559           hideBamList.add(new Short(thisBamIndex));
2560         laststart = -1;
2561         repaint();
2562       }
2563     });
2564   }
2565 
2566   /**
2567    * Refresh the colour of the icons used to identify the
2568    * BAM/CRAM files.
2569    */
refreshColourOfBamMenu()2570   protected void refreshColourOfBamMenu()
2571   {
2572     final Component cs[] = bamFilesMenu.getMenuComponents();
2573     for(Component c : cs)
2574     {
2575       if(c instanceof JCheckBoxMenuItem)
2576       {
2577         final JCheckBoxMenuItem cbBam = (JCheckBoxMenuItem) c;
2578         final Color col = getColorByJCheckBoxMenuItem(cbBam);
2579         if(col != null)
2580           cbBam.setIcon(getImageIcon(col));
2581       }
2582     }
2583   }
2584 
getColorByJCheckBoxMenuItem(JCheckBoxMenuItem cbBam)2585   protected Color getColorByJCheckBoxMenuItem(JCheckBoxMenuItem cbBam)
2586   {
2587     final String bam = cbBam.getText();
2588     for(short i=0; i<bamList.size(); i++)
2589     {
2590       final File f = new File(bamList.get(i));
2591       if(f.getName().equals(bam))
2592         return getColourByCoverageColour(i);
2593     }
2594     return null;
2595   }
2596 
2597   /**
2598    * Create an icon of a box using the given colour.
2599    * @param c
2600    * @return
2601    */
getImageIcon(Color c)2602   ImageIcon getImageIcon(Color c)
2603   {
2604     BufferedImage image = (BufferedImage)this.createImage(10, 10);
2605     Graphics2D g2 = image.createGraphics();
2606     g2.setColor(c);
2607     g2.fillRect(0, 0, 10, 10);
2608     return new ImageIcon(image);
2609   }
2610 
createMenus(JComponent menu)2611   private void createMenus(JComponent menu)
2612   {
2613     final JMenuItem addBam = new JMenuItem("Add BAM/CRAM ...");
2614     menu.add(addBam);
2615     addBam.addActionListener(new ActionListener()
2616     {
2617       public void actionPerformed(ActionEvent e)
2618       {
2619         FileSelectionDialog bamFileSelection = new FileSelectionDialog(
2620             null, false, "BamView", "BAM/CRAM");
2621         List<String> bamFiles = bamFileSelection.getFiles(BAM_SUFFIX);
2622         short count = (short) bamList.size();
2623 
2624         bamList.addAll(bamFileSelection.getFiles(BAM_SUFFIX));
2625 
2626         for(short i=0; i<bamFiles.size(); i++)
2627           addToViewMenu((short) (i+count));
2628         laststart = -1;
2629         repaint();
2630       }
2631     });
2632 
2633     bamFilesMenu.setFont(addBam.getFont());
2634 
2635     final JMenuItem groupBams = new JMenuItem("Group BAMs/CRAMs ...");
2636     groupBams.addActionListener(new ActionListener(){
2637       public void actionPerformed(ActionEvent arg0)
2638       {
2639         groupsFrame.updateAndDisplay();
2640       }
2641     });
2642     bamFilesMenu.add(groupBams);
2643     bamFilesMenu.addSeparator();
2644     menu.add(bamFilesMenu);
2645 
2646 
2647     final JMenu analyse = new JMenu("Analyse");
2648     menu.add(analyse);
2649     final JMenuItem readCount = new JMenuItem("Read count of selected features ...");
2650     analyse.add(readCount);
2651     if(feature_display == null)
2652       readCount.setEnabled(false);
2653     readCount.addActionListener(new ActionListener()
2654     {
2655       public void actionPerformed(ActionEvent e)
2656       {
2657         FeatureVector features = feature_display.getSelection().getAllFeatures();
2658 
2659         JCheckBox overlap = new JCheckBox("Include all overlapping reads", true);
2660         overlap.setToolTipText("Include reads that partially overlap the feature");
2661         JCheckBox spliced = new JCheckBox("Introns included", true);
2662         Box yBox = Box.createVerticalBox();
2663         yBox.add(overlap);
2664         yBox.add(spliced);
2665 
2666         final ReadCountDialog opts = new ReadCountDialog(new JFrame(),
2667             "Read Count Options", feature_display, yBox);
2668         if(opts.getStatus() == -1)
2669           return;
2670         //JOptionPane.showMessageDialog(null, yBox, "Read Count Option", JOptionPane.INFORMATION_MESSAGE);
2671 
2672         new MappedReads(BamView.this, features,
2673             !overlap.isSelected(), spliced.isSelected(), colourByStrandTag.isSelected());
2674       }
2675     });
2676 
2677     final JMenuItem rpkm = new JMenuItem("RPKM value of selected features ...");
2678     analyse.add(rpkm);
2679     if(feature_display == null)
2680       rpkm.setEnabled(false);
2681     rpkm.addActionListener(new ActionListener()
2682     {
2683       public void actionPerformed(ActionEvent e)
2684       {
2685         final FeatureVector features = feature_display.getSelection().getAllFeatures();
2686 
2687         JCheckBox overlap = new JCheckBox("Include all overlapping reads", true);
2688         overlap.setToolTipText("Include reads that partially overlap the feature");
2689         JCheckBox spliced = new JCheckBox("Introns included", true);
2690         JCheckBox allRefSeqs = new JCheckBox("Use reads mapped to all reference sequences", false);
2691 
2692         Box yBox = Box.createVerticalBox();
2693         yBox.add(overlap);
2694         yBox.add(spliced);
2695 
2696         if(seqLengths.size() > 1)
2697           yBox.add(allRefSeqs);
2698 
2699         final ReadCountDialog opts = new ReadCountDialog(new JFrame(),
2700             "RPKM Options", feature_display, yBox);
2701         if(opts.getStatus() == -1)
2702           return;
2703 
2704         int seqlen = 0;
2705         if(feature_display != null)
2706           seqlen = feature_display.getSequenceLength();
2707         else if(bases != null)
2708           seqlen = bases.getLength();
2709 
2710         new MappedReads(BamView.this, features, seqlen,
2711             !overlap.isSelected(), spliced.isSelected(), allRefSeqs.isSelected(),
2712             colourByStrandTag.isSelected());
2713       }
2714     });
2715 
2716     final JMenuItem createFeatures = new JMenuItem("Create features from coverage peaks ...");
2717     analyse.add(createFeatures);
2718     if(feature_display == null)
2719       createFeatures.setEnabled(false);
2720     createFeatures.addActionListener(new ActionListener()
2721     {
2722       public void actionPerformed(ActionEvent e)
2723       {
2724         if(feature_display == null)
2725           return;
2726         new CreateFeatures(groupsFrame);
2727       }
2728     });
2729 
2730     for(short i=0; i<bamList.size(); i++)
2731       addToViewMenu(i);
2732 
2733     menu.add(new JSeparator());
2734 
2735     JMenu viewMenu = new JMenu("Views");
2736     cbStackView.setFont(viewMenu.getFont());
2737     cbIsizeStackView.setFont(viewMenu.getFont());
2738     cbPairedStackView.setFont(viewMenu.getFont());
2739     cbStrandStackView.setFont(viewMenu.getFont());
2740     cbCoverageView.setFont(viewMenu.getFont());
2741     cbCoverageStrandView.setFont(viewMenu.getFont());
2742     cbCoverageHeatMap.setFont(viewMenu.getFont());
2743 
2744     baseQualityColour.setFont(viewMenu.getFont());
2745     colourByReadGrp.setFont(viewMenu.getFont());
2746     colourByCoverageColour.setFont(viewMenu.getFont());
2747     colourByStrandTag.setFont(viewMenu.getFont());
2748     markInsertions.setFont(viewMenu.getFont());
2749 
2750     cbIsizeStackView.addActionListener(new ActionListener()
2751     {
2752       public void actionPerformed(ActionEvent e)
2753       {
2754         laststart = -1;
2755         logMenuItem.setEnabled(isIsizeStackView());
2756         getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
2757         repaint();
2758       }
2759     });
2760     viewMenu.add(cbIsizeStackView);
2761 
2762 
2763     cbStackView.addActionListener(new ActionListener()
2764     {
2765       public void actionPerformed(ActionEvent e)
2766       {
2767         laststart = -1;
2768         if(cbStackView.isSelected())
2769           logMenuItem.setEnabled(false);
2770         getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
2771         setViewportBtm();
2772         repaint();
2773       }
2774     });
2775     viewMenu.add(cbStackView);
2776 
2777 
2778     cbPairedStackView.addActionListener(new ActionListener()
2779     {
2780       public void actionPerformed(ActionEvent e)
2781       {
2782         laststart = -1;
2783         if(cbPairedStackView.isSelected())
2784           logMenuItem.setEnabled(false);
2785         getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
2786         repaint();
2787       }
2788     });
2789     viewMenu.add(cbPairedStackView);
2790 
2791     cbStrandStackView.addActionListener(new ActionListener()
2792     {
2793       public void actionPerformed(ActionEvent e)
2794       {
2795         laststart = -1;
2796         if(isStrandStackView())
2797           setViewportMidPoint();
2798 
2799         if(cbStrandStackView.isSelected())
2800           logMenuItem.setEnabled(false);
2801         getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
2802         repaint();
2803       }
2804     });
2805     viewMenu.add(cbStrandStackView);
2806 
2807     cbCoverageView.addActionListener(new ActionListener()
2808     {
2809       public void actionPerformed(ActionEvent e)
2810       {
2811         laststart = -1;
2812         if(cbCoverageView.isSelected())
2813         {
2814           logMenuItem.setEnabled(false);
2815           coverageView.setPlotHeatMap(false);
2816           coverageView.setPlotByStrand(false);
2817           setViewportBtm();
2818           getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
2819         }
2820         repaint();
2821       }
2822     });
2823     viewMenu.add(cbCoverageView);
2824 
2825     cbCoverageStrandView.addActionListener(new ActionListener()
2826     {
2827       public void actionPerformed(ActionEvent e)
2828       {
2829         laststart = -1;
2830         if(cbCoverageStrandView.isSelected())
2831         {
2832           logMenuItem.setEnabled(true);
2833           coverageView.setPlotHeatMap(false);
2834           coverageView.setPlotByStrand(true);
2835           setViewportBtm();
2836           getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
2837         }
2838 
2839         repaint();
2840       }
2841     });
2842     viewMenu.add(cbCoverageStrandView);
2843 
2844 
2845     cbCoverageHeatMap.addActionListener(new ActionListener()
2846     {
2847       public void actionPerformed(ActionEvent e)
2848       {
2849         laststart = -1;
2850         if(cbCoverageHeatMap.isSelected())
2851         {
2852           logMenuItem.setEnabled(true);
2853           coverageView.setPlotHeatMap(true);
2854           setViewportBtm();
2855           getJspView().setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
2856         }
2857 
2858         repaint();
2859       }
2860     });
2861     viewMenu.add(cbCoverageHeatMap);
2862 
2863     menu.add(viewMenu);
2864 
2865     final JCheckBoxMenuItem checkBoxSNPs = new JCheckBoxMenuItem("SNP marks", isSNPs);
2866     //
2867     final JMenu colourMenu = new JMenu("Colour By");
2868 
2869     final JCheckBoxMenuItem colourDefault = new JCheckBoxMenuItem ("Default", true);
2870     final ButtonGroup grp = new ButtonGroup();
2871     grp.add(colourByReadGrp);
2872     grp.add(colourByCoverageColour);
2873     grp.add(colourByStrandTag);
2874     grp.add(colourDefault);
2875 
2876     colourMenu.add(colourDefault);
2877     colourDefault.addActionListener(new ActionListener()
2878     {
2879       public void actionPerformed(ActionEvent e)
2880       {
2881         repaintBamView();
2882       }
2883     });
2884 
2885     colourMenu.add(colourByReadGrp);
2886     colourByReadGrp.addActionListener(new ActionListener()
2887     {
2888       public void actionPerformed(ActionEvent e)
2889       {
2890         repaintBamView();
2891       }
2892     });
2893 
2894     colourMenu.add(colourByCoverageColour);
2895     colourByCoverageColour.addActionListener(new ActionListener()
2896     {
2897       public void actionPerformed(ActionEvent e)
2898       {
2899         repaintBamView();
2900       }
2901     });
2902 
2903     colourMenu.add(colourByStrandTag);
2904     colourByStrandTag.addActionListener(new ActionListener()
2905     {
2906       public void actionPerformed(ActionEvent e)
2907       {
2908         repaintBamView();
2909       }
2910     });
2911 
2912 
2913     baseQualityColour.addActionListener(new ActionListener()
2914     {
2915       public void actionPerformed(ActionEvent e)
2916       {
2917         if(baseQualityColour.isSelected())
2918         {
2919           checkBoxSNPs.setSelected(false);
2920           isSNPs = false;
2921         }
2922         repaint();
2923       }
2924     });
2925     colourMenu.addSeparator();
2926     colourMenu.add(baseQualityColour);
2927     menu.add(colourMenu);
2928 
2929     // =============
2930     // Show Menu
2931     // =============
2932 
2933     JMenu showMenu = new JMenu("Show");
2934 
2935     JCheckBoxMenuItem checkBoxOrientation = new JCheckBoxMenuItem("Orientation");
2936     checkBoxOrientation.addActionListener(new ActionListener()
2937     {
2938       public void actionPerformed(ActionEvent e)
2939       {
2940         isOrientation = !isOrientation;
2941         repaint();
2942       }
2943     });
2944     showMenu.add(checkBoxOrientation);
2945 
2946     JCheckBoxMenuItem checkBoxSingle = new JCheckBoxMenuItem("Single Reads");
2947     checkBoxSingle.addActionListener(new ActionListener()
2948     {
2949       public void actionPerformed(ActionEvent e)
2950       {
2951         repaint();
2952         isSingle = !isSingle;
2953       }
2954     });
2955     showMenu.add(checkBoxSingle);
2956 
2957     checkBoxSNPs.addActionListener(new ActionListener()
2958     {
2959       public void actionPerformed(ActionEvent e)
2960       {
2961         if (isSNPs && bases == null)
2962         {
2963           JOptionPane.showMessageDialog(null,
2964               "No reference sequence supplied to identify SNPs.", "SNPs",
2965               JOptionPane.INFORMATION_MESSAGE);
2966         }
2967         isSNPs = !isSNPs;
2968 
2969         if(isSNPs)
2970           baseQualityColour.setSelected(false);
2971         repaint();
2972       }
2973     });
2974     showMenu.add(checkBoxSNPs);
2975 
2976     markInsertions.addActionListener(new ActionListener()
2977     {
2978       public void actionPerformed(ActionEvent e)
2979       {
2980         repaint();
2981       }
2982     });
2983     showMenu.add(markInsertions);
2984     menu.add(showMenu);
2985 
2986     //
2987     JMenu graphMenu = new JMenu("Graph");
2988     JCheckBoxMenuItem checkBoxCoverage = new JCheckBoxMenuItem("Coverage", isCoverage);
2989     checkBoxCoverage.addActionListener(new ActionListener()
2990     {
2991       public void actionPerformed(ActionEvent e)
2992       {
2993         isCoverage = !isCoverage;
2994         coveragePanel.setVisible(isCoverage);
2995 
2996         if( isCoverage &&
2997             !cbCoverageView.isSelected() &&
2998             !cbCoverageStrandView.isSelected() &&
2999             !cbCoverageHeatMap.isSelected())
3000           laststart = -1;
3001         repaint();
3002       }
3003     });
3004     graphMenu.add(checkBoxCoverage);
3005 
3006     JCheckBoxMenuItem checkBoxSNP = new JCheckBoxMenuItem("SNP", isSNPplot);
3007     checkBoxSNP.addActionListener(new ActionListener()
3008     {
3009       public void actionPerformed(ActionEvent e)
3010       {
3011         isSNPplot = !isSNPplot;
3012         snpPanel.setVisible(isSNPplot);
3013         laststart = -1;
3014         repaint();
3015       }
3016     });
3017     graphMenu.add(checkBoxSNP);
3018     menu.add(graphMenu);
3019 
3020 
3021     if(feature_display != null)
3022     {
3023       final JCheckBoxMenuItem checkBoxSync =
3024         new JCheckBoxMenuItem("Asynchronous", asynchronous);
3025       checkBoxSync.addActionListener(new ActionListener()
3026       {
3027         public void actionPerformed(ActionEvent e)
3028         {
3029           asynchronous = checkBoxSync.isSelected();
3030         }
3031       });
3032       menu.add(checkBoxSync);
3033     }
3034 
3035     menu.add(new JSeparator());
3036 
3037     JMenu maxHeightMenu = new JMenu("BamView Height");
3038     menu.add(maxHeightMenu);
3039 
3040     final String hgts[] =
3041        {"500", "800", "1000", "1500", "2500", "5000", "50000"};
3042 
3043     ButtonGroup bgroup = new ButtonGroup();
3044     for(int i=0; i<hgts.length; i++)
3045     {
3046       final String hgt = hgts[i];
3047       final JCheckBoxMenuItem maxHeightMenuItem = new JCheckBoxMenuItem(hgt);
3048       bgroup.add(maxHeightMenuItem);
3049       maxHeightMenuItem.setSelected(hgts[i].equals(Integer.toString(maxHeight)));
3050       maxHeightMenu.add(maxHeightMenuItem);
3051       maxHeightMenuItem.addActionListener(new ActionListener()
3052       {
3053         public void actionPerformed(ActionEvent e)
3054         {
3055           if(maxHeightMenuItem.isSelected())
3056             maxHeight = Integer.parseInt(hgt);
3057           int start = getBaseAtStartOfView();
3058           setDisplay(start, nbasesInView+start, null);
3059         }
3060       });
3061     }
3062 
3063     menu.add(new JSeparator());
3064     logMenuItem.setFont(menu.getFont());
3065     menu.add(logMenuItem);
3066     logMenuItem.setEnabled(isIsizeStackView());
3067 
3068     logMenuItem.addActionListener(new ActionListener()
3069     {
3070       public void actionPerformed(ActionEvent e)
3071       {
3072         logScale = logMenuItem.isSelected();
3073         repaint();
3074       }
3075     });
3076 
3077     final JMenuItem readGroupsMenu = new JMenuItem("Read Groups ...");
3078     readGroupsMenu.addActionListener(new ActionListener(){
3079       public void actionPerformed(ActionEvent arg0)
3080       {
3081         ReadGroupsFrame f = getReadGroupFrame();
3082         f.setVisible(true);
3083       }
3084     });
3085     menu.add(readGroupsMenu);
3086 
3087     JMenuItem filter = new JMenuItem("Filter Reads ...");
3088     menu.add(filter);
3089     filter.addActionListener(new ActionListener()
3090     {
3091       public void actionPerformed(ActionEvent e)
3092       {
3093         if(filterFrame == null)
3094           filterFrame = new SAMRecordFilter(BamView.this);
3095         else
3096           filterFrame.setVisible(true);
3097       }
3098     });
3099 
3100     JMenuItem maxReadCoverage = new JMenuItem("Read Coverage Threshold ...");
3101     menu.add(maxReadCoverage);
3102     maxReadCoverage.addActionListener(new ActionListener()
3103     {
3104       public void actionPerformed(ActionEvent e)
3105       {
3106         final TextFieldInt maxRead = new TextFieldInt();
3107         maxRead.setValue(MAX_COVERAGE);
3108         int status = JOptionPane.showConfirmDialog(null, maxRead,
3109             "Read Coverage Threshold", JOptionPane.OK_CANCEL_OPTION);
3110         if(status == JOptionPane.OK_OPTION &&
3111            maxRead.getValue() != MAX_COVERAGE)
3112         {
3113           MAX_COVERAGE = maxRead.getValue();
3114           if(MAX_COVERAGE < 1)
3115             MAX_COVERAGE = Integer.MAX_VALUE;
3116           laststart = -1;
3117           repaint();
3118         }
3119       }
3120     });
3121 
3122     JMenuItem readList = new JMenuItem("List Reads ...");
3123     menu.add(readList);
3124     readList.addActionListener(new ActionListener()
3125     {
3126       public void actionPerformed(ActionEvent e)
3127       {
3128         new SAMRecordList(BamView.this);
3129       }
3130     });
3131 
3132     final JMenuItem bamSplitter = new JMenuItem("Clone window");
3133     bamSplitter.addActionListener(new ActionListener()
3134     {
3135       public void actionPerformed(ActionEvent e)
3136       {
3137         openBamView(new Vector<String>(bamList));
3138       }
3139     });
3140     menu.add(new JSeparator());
3141     menu.add(bamSplitter);
3142 
3143     //
3144     JMenu coverageMenu = new JMenu("Coverage Options");
3145     coverageView.init(this, 0.f, 0, 0);
3146     coverageView.createMenus(coverageMenu);
3147     viewMenu.add(new JSeparator());
3148     viewMenu.add(coverageMenu);
3149   }
3150 
getReadGroupFrame()3151   private ReadGroupsFrame getReadGroupFrame()
3152   {
3153     if(readGrpFrame == null)
3154       readGrpFrame = new ReadGroupsFrame(readGroups, BamView.this);
3155     return readGrpFrame;
3156   }
3157 
bamTopPanel(final JFrame frame)3158   private JComponent bamTopPanel(final JFrame frame)
3159   {
3160     final JComponent topPanel;
3161     if(frame == null)
3162     {
3163       topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
3164       if(feature_display != null)
3165         this.selection = feature_display.getSelection();
3166     }
3167     else
3168     {
3169       topPanel = new JMenuBar();
3170       frame.setJMenuBar((JMenuBar)topPanel);
3171 
3172       JMenu fileMenu = new JMenu("File");
3173       topPanel.add(fileMenu);
3174 
3175       JMenuItem readBam = new JMenuItem("Open new BamView ...");
3176       fileMenu.add(readBam);
3177       readBam.addActionListener(new ActionListener()
3178       {
3179         public void actionPerformed(ActionEvent e)
3180         {
3181           String[] s = { "NEW-BAMVIEW" };
3182           BamView.main(s);
3183         }
3184       });
3185 
3186 
3187       JMenuItem saveAs = new JMenuItem("Save As Image File (png/jpeg/svg) ...");
3188       fileMenu.add(saveAs);
3189       saveAs.addActionListener(new ActionListener()
3190       {
3191         public void actionPerformed(ActionEvent e)
3192         {
3193           PrintBamView.print((JPanel)mainPanel.getParent());
3194         }
3195       });
3196 
3197 
3198       JMenuItem close = new JMenuItem("Close");
3199       fileMenu.add(close);
3200       close.addActionListener(new ActionListener()
3201       {
3202         public void actionPerformed(ActionEvent e)
3203         {
3204           close();
3205         }
3206       });
3207 
3208       JMenuItem exit = new JMenuItem("Exit");
3209       fileMenu.add(new JSeparator());
3210       fileMenu.add(exit);
3211       exit.addActionListener(new ActionListener()
3212       {
3213         public void actionPerformed(ActionEvent e)
3214         {
3215           int status = JOptionPane.showConfirmDialog(BamView.this,
3216               "Exit BamView?", "Exit",
3217               JOptionPane.OK_CANCEL_OPTION);
3218           if(status != JOptionPane.OK_OPTION)
3219             return;
3220           System.exit(0);
3221         }
3222       });
3223 
3224       addKeyListener(new KeyAdapter()
3225       {
3226         public void keyPressed(final KeyEvent event)
3227         {
3228           switch (event.getKeyCode())
3229           {
3230             case KeyEvent.VK_UP:
3231               setZoomLevel((int) (BamView.this.nbasesInView * 1.1));
3232               break;
3233             case KeyEvent.VK_DOWN:
3234               if (showBaseAlignment)
3235                 break;
3236               setZoomLevel((int) (BamView.this.nbasesInView * .9));
3237               break;
3238             default:
3239               break;
3240           }
3241         }
3242       });
3243     }
3244 
3245     if(seqNames.size() > 1)
3246     {
3247       int len = 0;
3248       for(int i=0; i<seqNames.size(); i++)
3249         len += seqLengths.get(seqNames.get(i));
3250 
3251       if(feature_display != null &&
3252          len == feature_display.getSequenceLength())
3253         concatSequences = true;
3254       else if(bases != null &&
3255           len == bases.getLength() )
3256         concatSequences = true;
3257     }
3258 
3259     // auto hide top panel
3260     final JCheckBox buttonAutoHide = new JCheckBox("Hide", (frame == null));
3261     buttonAutoHide.setToolTipText("Auto-Hide");
3262     final MouseMotionListener mouseMotionListener = new MouseMotionListener()
3263     {
3264       public void mouseDragged(MouseEvent event)
3265       {
3266         handleCanvasMouseDrag(event);
3267       }
3268 
3269       public void mouseMoved(MouseEvent e)
3270       {
3271         lastMousePoint = e.getPoint();
3272         int thisHgt = HEIGHT-2;
3273         if (thisHgt < 5)
3274           thisHgt = 15;
3275 
3276         int y = (int) (e.getY() - jspView.getViewport().getViewRect().getY());
3277         if (y < thisHgt)
3278           topPanel.setVisible(true);
3279         else
3280         {
3281           if (buttonAutoHide.isSelected() && topPanel.isVisible())
3282             topPanel.setVisible(false);
3283         }
3284 
3285         // KJP - added these to make selecting work properly.
3286         mainPanel.repaint();
3287         mainPanel.revalidate();
3288       }
3289     };
3290     addMouseMotionListener(mouseMotionListener);
3291 
3292 
3293     combo = new SequenceComboBox(seqNames){
3294       private static final long serialVersionUID = 1L;
3295       public void update(IndexReferenceEvent event)
3296       {
3297         laststart = -1;
3298         if(feature_display != null)
3299         {
3300           setZoomLevel(feature_display.getMaxVisibleBases());
3301         }
3302         else
3303         {
3304         	  /*
3305         	   * Reset the scroll bar to the left hand side when we change
3306         	   * the combo selection (for bamview). If this is not done
3307         	   * then we can go to the end of one [long] contig, select a new one that's
3308         	   * shorter and we will end up off the end of it in no-man's land
3309         	   * - this eventually leads to an array negative index exception in
3310         	   * iterateOverBam
3311         	   */
3312           startBase = -1;
3313           endBase = -1;
3314           scrollBar.setValues(1, BamView.this.nbasesInView, 1,
3315                getMaxBasesInPanel(getSequenceLength()));
3316           setZoomLevel(BamView.this.nbasesInView);
3317           repaint();
3318         }
3319       }
3320     };
3321     topPanel.add(combo);
3322 
3323     if(feature_display == null)
3324     {
3325       final JTextField baseText = new JTextField(8);
3326       JButton goTo = new JButton("GoTo:");
3327       goTo.setToolTipText("Go to base position");
3328       goTo.addActionListener(new ActionListener()
3329       {
3330         public void actionPerformed(ActionEvent e)
3331         {
3332           try
3333           {
3334             int basePosition = Integer.parseInt(baseText.getText());
3335             scrollBar.setValue(basePosition);
3336           }
3337           catch (NumberFormatException nfe)
3338           {
3339             JOptionPane.showMessageDialog(BamView.this,
3340                 "Expecting a base number!", "Number Format",
3341                 JOptionPane.WARNING_MESSAGE);
3342           }
3343         }
3344       });
3345       topPanel.add(goTo);
3346       topPanel.add(baseText);
3347 
3348       JButton zoomIn = new JButton("-");
3349       zoomIn.setToolTipText("Zoom out (up arrow)");
3350       Insets ins = new Insets(1,1,1,1);
3351       zoomIn.setMargin(ins);
3352       zoomIn.addActionListener(new ActionListener()
3353       {
3354         public void actionPerformed(ActionEvent e)
3355         {
3356           setZoomLevel((int) (BamView.this.nbasesInView * 1.1));
3357         }
3358       });
3359       topPanel.add(zoomIn);
3360 
3361       JButton zoomOut = new JButton("+");
3362       zoomOut.setToolTipText("Zoom in (down arrow)");
3363       zoomOut.setMargin(ins);
3364       zoomOut.addActionListener(new ActionListener()
3365       {
3366         public void actionPerformed(ActionEvent e)
3367         {
3368           if (showBaseAlignment)
3369             return;
3370           setZoomLevel((int) (BamView.this.nbasesInView * .9));
3371         }
3372       });
3373       topPanel.add(zoomOut);
3374     }
3375 
3376     topPanel.add(buttonAutoHide);
3377 
3378 
3379     final JSlider slider = new JSlider(13, 52, (int) (readLnHgt*10));
3380     slider.addChangeListener(new ChangeListener(){
3381       public void stateChanged(ChangeEvent arg0)
3382       {
3383         readLnHgt = (slider.getValue()/10.f);
3384         repaintBamView();
3385       }
3386     });
3387     topPanel.add(new JLabel(" Read Height:"));
3388     topPanel.add(slider);
3389 
3390     if(feature_display != null)
3391     {
3392       JButton close = new JButton("Close");
3393       topPanel.add(close);
3394       close.addActionListener(new ActionListener()
3395       {
3396         public void actionPerformed(ActionEvent e)
3397         {
3398           int status = JOptionPane.showConfirmDialog(frame,
3399               "Close the BAM panel?", "Close",
3400               JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
3401           if(status == JOptionPane.CANCEL_OPTION)
3402             return;
3403 
3404           closeBamPanel();
3405         }
3406       });
3407     }
3408     return topPanel;
3409   }
3410 
setVisible(boolean visible)3411   public void setVisible(boolean visible)
3412   {
3413     super.setVisible(visible);
3414     mainPanel.setVisible(visible);
3415   }
3416 
setViewportMidPoint()3417   private void setViewportMidPoint()
3418   {
3419     Point p = jspView.getViewport().getLocation();
3420     p.y = (getHeight() - jspView.getViewport().getViewRect().height)/2;
3421     jspView.getViewport().setViewPosition(p);
3422   }
3423 
setViewportBtm()3424   private void setViewportBtm()
3425   {
3426     jspView.getVerticalScrollBar().setValue(
3427         jspView.getVerticalScrollBar().getMaximum());
3428   }
3429 
getBaseAtStartOfView()3430   protected int getBaseAtStartOfView()
3431   {
3432     if(feature_display != null)
3433       return feature_display.getForwardBaseAtLeftEdge();
3434     else
3435       return scrollBar.getValue();
3436   }
3437 
3438   /**
3439    * Set the panel size based on the number of bases visible
3440    * and repaint.
3441    * @param nbasesInView
3442    */
setZoomLevel(final int nbasesInView)3443   private void setZoomLevel(final int nbasesInView)
3444   {
3445     int startValue = getBaseAtStartOfView();
3446     this.nbasesInView = nbasesInView;
3447     float pixPerBase = getPixPerBaseByWidth();
3448 
3449     if(isBaseAlignmentView(pixPerBase))
3450     {
3451       pixPerBase = ALIGNMENT_PIX_PER_BASE;
3452       this.nbasesInView = (int)(mainPanel.getWidth()/pixPerBase);
3453       jspView.getVerticalScrollBar().setValue(0);
3454 
3455       if(ruler == null)
3456         ruler = new Ruler();
3457       jspView.setColumnHeaderView(ruler);
3458       showBaseAlignment = true;
3459       baseQualityColour.setEnabled(true);
3460       markInsertions.setEnabled(true);
3461     }
3462     else if(jspView != null)
3463     {
3464       if(!isCoverageView(pixPerBase) && nbasesInView >= MAX_BASES)
3465       {
3466         cbLastSelected = getSelectedCheckBoxMenuItem();
3467         cbCoverageView.setSelected(true);
3468         coverageView.setPlotByStrand(false);
3469       }
3470       else if(isCoverageView(pixPerBase) && nbasesInView < MAX_BASES && cbLastSelected != null)
3471       {
3472         cbLastSelected.setSelected(true);
3473         cbLastSelected = null;
3474       }
3475 
3476       jspView.setColumnHeaderView(null);
3477       if(isCoverageView(pixPerBase))
3478         setViewportBtm();
3479       else if (isStrandStackView())
3480         setViewportMidPoint();
3481       showBaseAlignment = false;
3482       baseQualityColour.setEnabled(false);
3483       markInsertions.setEnabled(false);
3484     }
3485 
3486     if(scrollBar != null)
3487     {
3488       scrollBar.setValues(startValue, nbasesInView, 1,
3489              getMaxBasesInPanel(getSequenceLength()));
3490       scrollBar.setUnitIncrement(nbasesInView/20);
3491       scrollBar.setBlockIncrement(nbasesInView);
3492     }
3493   }
3494 
3495 
3496   /**
3497    * Set the start and end base positions to display.
3498    * @param start
3499    * @param end
3500    * @param event
3501    */
setDisplay(int start, int end, DisplayAdjustmentEvent event)3502   public void setDisplay(int start,
3503                          int end,
3504                          DisplayAdjustmentEvent event)
3505   {
3506     this.startBase = start;
3507     this.endBase   = end;
3508     this.nbasesInView = end-start+1;
3509     lastMousePoint = null;
3510 
3511     float pixPerBase;
3512     if(jspView.getViewport().getViewRect().width > 0)
3513       pixPerBase = getPixPerBaseByWidth();
3514     else
3515     {
3516       if(feature_display == null)
3517         pixPerBase = 1000.f/(float)(end-start+1);
3518       else
3519         pixPerBase = feature_display.getWidth()/(float)(end-start+1);
3520     }
3521 
3522     Dimension d = new Dimension();
3523     d.setSize(nbasesInView*pixPerBase, maxHeight);
3524     setPreferredSize(d);
3525 
3526     if(event == null)
3527     {
3528       this.startBase = -1;
3529       this.endBase   = -1;
3530     }
3531   }
3532 
3533   /**
3534    * Return an Artemis entry from a file
3535    * @param entryFileName
3536    * @param entryGroup
3537    * @return
3538    * @throws NoSequenceException
3539    */
getEntry(final String entryFileName, final EntryGroup entryGroup)3540   private Entry getEntry(final String entryFileName, final EntryGroup entryGroup)
3541                    throws NoSequenceException
3542   {
3543     final Document entry_document = DocumentFactory.makeDocument(entryFileName);
3544     final EntryInformation artemis_entry_information =
3545       Options.getArtemisEntryInformation();
3546 
3547     //System.out.println(entryFileName);
3548     final uk.ac.sanger.artemis.io.Entry new_embl_entry =
3549       EntryFileDialog.getEntryFromFile(null, entry_document,
3550                                        artemis_entry_information,
3551                                        false);
3552 
3553     if(new_embl_entry == null)  // the read failed
3554       return null;
3555 
3556     Entry entry = null;
3557     try
3558     {
3559       if(entryGroup.getSequenceEntry() != null)
3560         bases = entryGroup.getSequenceEntry().getBases();
3561 
3562       if(bases == null)
3563       {
3564         entry = new Entry(new_embl_entry);
3565         bases = entry.getBases();
3566       }
3567       else
3568         entry = new Entry(bases,new_embl_entry);
3569 
3570       entryGroup.add(entry);
3571     }
3572     catch(OutOfRangeException e)
3573     {
3574       new MessageDialog(null, "read failed: one of the features in " +
3575           entryFileName + " has an out of range " +
3576                         "location: " + e.getMessage());
3577     }
3578     return entry;
3579   }
3580 
isShowScale()3581   private boolean isShowScale()
3582   {
3583     return (feature_display == null ? true : false);
3584   }
3585 
getJspView()3586   public JScrollPane getJspView()
3587   {
3588     return jspView;
3589   }
3590 
3591   /**
3592    *  Handle a mouse drag event on the drawing canvas.
3593    **/
handleCanvasMouseDrag(final MouseEvent event)3594   private void handleCanvasMouseDrag(final MouseEvent event)
3595   {
3596     if(event.getButton() == MouseEvent.BUTTON3 || bases == null)
3597       return;
3598 
3599     // KJP: Removed this as it causes selection issues and
3600     // doesn't seem strictly necessary.
3601     // highlightSAMRecord = null;
3602 
3603     if(event.getClickCount() > 1)
3604     {
3605       getSelection().clear();
3606       repaint();
3607       return;
3608     }
3609 
3610     highlightRange(event,
3611         MouseEvent.BUTTON1_DOWN_MASK & MouseEvent.BUTTON2_DOWN_MASK);
3612   }
3613 
3614   /**
3615    *
3616    * @param event
3617    * @param onmask
3618    */
highlightRange(MouseEvent event, int onmask)3619   protected void highlightRange(MouseEvent event, int onmask)
3620   {
3621     int seqLength = getSequenceLength();
3622     float pixPerBase = getPixPerBaseByWidth();
3623     int start = (int) ( ( (event.getPoint().getX())/pixPerBase) + getBaseAtStartOfView() );
3624 
3625     if(start < 1)
3626       start = 1;
3627     if(start > seqLength)
3628       start = seqLength;
3629 
3630     if (dragStart < 0 && (event.getModifiersEx() & onmask) == onmask)
3631       dragStart = start;
3632     else if((event.getModifiersEx() & onmask) != onmask)
3633       dragStart = -1;
3634 
3635     MarkerRange drag_range;
3636     try
3637     {
3638       if(dragStart < 0)
3639         drag_range = new MarkerRange (bases.getForwardStrand(), start, start);
3640       else
3641         drag_range = new MarkerRange (bases.getForwardStrand(), dragStart, start);
3642       getSelection().setMarkerRange(drag_range);
3643       repaint();
3644     }
3645     catch (OutOfRangeException e)
3646     {
3647       e.printStackTrace();
3648     }
3649   }
3650 
3651   /**
3652    * Get the colour for the given read given to it by the coverage plot.
3653    * @param samRecord
3654    * @return
3655    */
getColourByCoverageColour(BamViewRecord samRecord)3656   private Color getColourByCoverageColour(BamViewRecord samRecord)
3657   {
3658     short fileIndex = 0;
3659     if(bamList.size()>1)
3660       fileIndex = samRecord.bamIndex;
3661     return getColourByCoverageColour(fileIndex);
3662   }
3663 
getColourByCoverageColour(final short fileIndex)3664   private Color getColourByCoverageColour(final short fileIndex)
3665   {
3666     LineAttributes lines[] = CoveragePanel.getLineAttributes(bamList.size());
3667     return lines[fileIndex].getLineColour();
3668   }
3669 
3670 
getMaxBases()3671   protected int getMaxBases()
3672   {
3673     return MAX_BASES;
3674   }
3675 
setMaxBases(int max)3676   protected void setMaxBases(int max)
3677   {
3678     MAX_BASES = max;
3679   }
3680 
isStackView()3681   private boolean isStackView()
3682   {
3683     return cbStackView.isSelected();
3684   }
3685 
isPairedStackView()3686   private boolean isPairedStackView()
3687   {
3688     return cbPairedStackView.isSelected();
3689   }
3690 
isStrandStackView()3691   private boolean isStrandStackView()
3692   {
3693     return cbStrandStackView.isSelected();
3694   }
3695 
isCoverageView(float pixPerBase)3696   private boolean isCoverageView(float pixPerBase)
3697   {
3698     if(isBaseAlignmentView(pixPerBase))
3699       return false;
3700     return cbCoverageView.isSelected() || cbCoverageStrandView.isSelected() || cbCoverageHeatMap.isSelected();
3701   }
3702 
isIsizeStackView()3703   private boolean isIsizeStackView()
3704   {
3705     return cbIsizeStackView.isSelected();
3706   }
3707 
isBaseAlignmentView(float pixPerBase)3708   private boolean isBaseAlignmentView(float pixPerBase)
3709   {
3710     if(pixPerBase*1.08f >= ALIGNMENT_PIX_PER_BASE)
3711       return true;
3712     return false;
3713   }
3714 
getSelectedCheckBoxMenuItem()3715   private JCheckBoxMenuItem getSelectedCheckBoxMenuItem()
3716   {
3717     if(isStackView())
3718       return cbStackView;
3719     if(isPairedStackView())
3720       return cbPairedStackView;
3721     if(isStrandStackView())
3722       return cbStrandStackView;
3723     if(isIsizeStackView())
3724       return cbIsizeStackView;
3725     if(cbCoverageView.isSelected())
3726       return cbCoverageView;
3727     if(cbCoverageHeatMap.isSelected())
3728       return cbCoverageHeatMap;
3729     return cbCoverageStrandView;
3730   }
3731 
getSelection()3732   protected Selection getSelection()
3733   {
3734     return selection;
3735   }
3736 
getReadsInView()3737   protected List<BamViewRecord> getReadsInView()
3738   {
3739     return readsInView;
3740   }
3741 
getBasesInView()3742   protected int getBasesInView()
3743   {
3744     return nbasesInView;
3745   }
3746 
setHighlightSAMRecord(BamViewRecord highlightSAMRecord)3747   protected void setHighlightSAMRecord(BamViewRecord highlightSAMRecord)
3748   {
3749     this.highlightSAMRecord = highlightSAMRecord;
3750   }
3751 
getHighlightSAMRecord()3752   protected BamViewRecord getHighlightSAMRecord()
3753   {
3754     return highlightSAMRecord;
3755   }
3756 
getFeatureDisplay()3757   protected FeatureDisplay getFeatureDisplay()
3758   {
3759     return feature_display;
3760   }
3761 
3762   /**
3763    * @return the combo
3764    */
getCombo()3765   public SequenceComboBox getCombo()
3766   {
3767     return combo;
3768   }
3769 
getSamFileReaderHash()3770   protected Hashtable<String, SamReader>  getSamFileReaderHash()
3771   {
3772     return samFileReaderHash;
3773   }
3774 
getSeqNames()3775   protected Vector<String> getSeqNames()
3776   {
3777     return seqNames;
3778   }
3779 
getSeqLengths()3780   protected HashMap<String, Integer> getSeqLengths()
3781   {
3782     return seqLengths;
3783   }
3784 
getOffsetLengths()3785   protected HashMap<String, Integer> getOffsetLengths()
3786   {
3787     return offsetLengths;
3788   }
3789 
getVersion()3790   private String getVersion()
3791   {
3792     final ClassLoader cl = this.getClass().getClassLoader();
3793     try
3794     {
3795       String line;
3796       InputStream in = cl.getResourceAsStream("etc/versions");
3797       BufferedReader reader = new BufferedReader(new InputStreamReader(in));
3798       while((line = reader.readLine()) != null)
3799       {
3800         if(line.startsWith("BamView"))
3801           return line.substring( "BamView".length() ).trim();
3802       }
3803       reader.close();
3804       in.close();
3805     }
3806     catch (Exception ex)
3807     {
3808     }
3809     return null;
3810   }
3811 
3812   /**
3813    * Open another BamView window
3814    */
openBamView(final List<String> bamsList)3815   public void openBamView(final List<String> bamsList)
3816   {
3817     BamView bamView = new BamView(bamsList,
3818         null, nbasesInView, entry_edit,
3819         feature_display, bases, (JPanel) mainPanel.getParent(), null);
3820     bamView.getJspView().getVerticalScrollBar().setValue(
3821         bamView.getJspView().getVerticalScrollBar().getMaximum());
3822     getJspView().getVerticalScrollBar().setValue(
3823         bamView.getJspView().getVerticalScrollBar().getMaximum());
3824 
3825     int start = getBaseAtStartOfView();
3826     setDisplay(start, nbasesInView+start, null);
3827     if(feature_display != null)
3828     {
3829       feature_display.addDisplayAdjustmentListener(bamView);
3830       feature_display.getSelection().addSelectionChangeListener(bamView);
3831 
3832       if(entry_edit != null)
3833         entry_edit.getOneLinePerEntryDisplay().addDisplayAdjustmentListener(bamView);
3834       if(feature_display.getEntryGroup().getSequenceEntry().getEMBLEntry().getSequence()
3835           instanceof uk.ac.sanger.artemis.io.IndexFastaStream)
3836       {
3837         if(entry_edit != null)
3838         {
3839           // add reference sequence selection listeners
3840           entry_edit.getEntryGroupDisplay().getIndexFastaCombo().addIndexReferenceListener(bamView.getCombo());
3841           bamView.getCombo().addIndexReferenceListener(entry_edit.getEntryGroupDisplay().getIndexFastaCombo());
3842         }
3843       }
3844     }
3845   }
3846 
3847   /**
3848    * Artemis event notification
3849    */
displayAdjustmentValueChanged(final DisplayAdjustmentEvent event)3850   public void displayAdjustmentValueChanged(final DisplayAdjustmentEvent event)
3851   {
3852     if(event.getType() == DisplayAdjustmentEvent.REV_COMP_EVENT &&
3853        event.isRevCompDisplay())
3854       JOptionPane.showMessageDialog(this,
3855           "Flipping the display is not supported by BamView.", "Warning",
3856           JOptionPane.WARNING_MESSAGE);
3857 
3858     if(!asynchronous)
3859     {
3860       // if not asynchronous
3861       displayAdjustmentWork(event);
3862       return;
3863     }
3864 
3865     SwingWorker worker = new SwingWorker()
3866     {
3867       public Object construct()
3868       {
3869         try
3870         {
3871           Thread.sleep(500);
3872         }
3873         catch (InterruptedException e)
3874         {
3875           e.printStackTrace();
3876         }
3877 
3878         if(event.getStart() != ((FeatureDisplay)event.getSource()).getForwardBaseAtLeftEdge())
3879         {
3880           waitingFrame.showWaiting("waiting...", mainPanel);
3881           return null;
3882         }
3883 
3884         displayAdjustmentWork(event);
3885         waitingFrame.setVisible(false);
3886         return null;
3887       }
3888     };
3889     worker.start();
3890   }
3891 
3892   /**
3893    * Carry out the display agjustment event action.
3894    * @param event
3895    */
displayAdjustmentWork(final DisplayAdjustmentEvent event)3896   private void displayAdjustmentWork(final DisplayAdjustmentEvent event)
3897   {
3898     if(event.getType() == DisplayAdjustmentEvent.SCALE_ADJUST_EVENT)
3899     {
3900       laststart = -1;
3901 
3902       BamView.this.startBase = event.getStart();
3903       BamView.this.endBase   = event.getEnd();
3904 
3905       int width = feature_display.getMaxVisibleBases();
3906       setZoomLevel(width);
3907       repaint();
3908     }
3909     else
3910     {
3911       setDisplay(event.getStart(),
3912         event.getStart()+feature_display.getMaxVisibleBases(), event);
3913       repaint();
3914     }
3915   }
3916 
selectionChanged(SelectionChangeEvent event)3917   public void selectionChanged(SelectionChangeEvent event)
3918   {
3919     repaint();
3920   }
3921 
3922   private class Ruler extends JPanel
3923   {
3924     private static final long serialVersionUID = 1L;
3925     protected int start;
3926     protected int end;
3927     protected String refSeq;
3928 
Ruler()3929     public Ruler()
3930     {
3931       super();
3932       setPreferredSize(new Dimension(mainPanel.getWidth(), 26));
3933       setBackground(Color.white);
3934     }
3935 
paintComponent(Graphics g)3936     public void paintComponent(Graphics g)
3937     {
3938       super.paintComponent(g);
3939       Graphics2D g2 = (Graphics2D)g;
3940       drawBaseScale(g2, start, end, 12);
3941     }
3942 
drawBaseScale(Graphics2D g2, int start, int end, int ypos)3943     private void drawBaseScale(Graphics2D g2, int start, int end, int ypos)
3944     {
3945       int startMark = (((int)(start/10))*10)+1;
3946       if(end > getSequenceLength())
3947         end = getSequenceLength();
3948 
3949       for(int i=startMark; i<end; i+=20)
3950       {
3951         int xpos = (i-start)*ALIGNMENT_PIX_PER_BASE;
3952         g2.drawString(Integer.toString(i), xpos, ypos);
3953       }
3954 
3955       for(int i=startMark; i<end; i+=10)
3956       {
3957         int xpos = (i-start)*ALIGNMENT_PIX_PER_BASE;
3958         xpos+=(ALIGNMENT_PIX_PER_BASE/2);
3959         g2.drawLine(xpos, ypos+1, xpos, ypos+5);
3960       }
3961 
3962       if(refSeq != null)
3963       {
3964         ypos+=15;
3965         g2.setColor(LIGHT_GREY);
3966         g2.fillRect(0, ypos-11, getWidth(), 11);
3967 
3968         g2.translate(0, 16);
3969         drawSelectionRange(g2, ALIGNMENT_PIX_PER_BASE, start, end, Color.yellow);
3970         g2.translate(0, -16);
3971         g2.setColor(Color.black);
3972         g2.drawString(refSeq, 0, ypos-2);
3973       }
3974     }
3975   }
3976 
3977   /**
3978   * Popup menu listener
3979   */
3980   class PopupListener extends MouseAdapter
3981   {
3982 	private JMenuItem gotoMateMenuItem;
3983 	private JMenuItem showDetails;
3984 	private JMenu coverageMenu;
3985 	private JMenuItem createGroup;
3986 
mouseClicked(MouseEvent e)3987     public void mouseClicked(MouseEvent e)
3988     {
3989       if(e.isPopupTrigger() ||
3990          e.getButton() == MouseEvent.BUTTON3)
3991         return;
3992 
3993       BamView.this.requestFocus();
3994 
3995       if(e.getClickCount() > 1)
3996         getSelection().clear();
3997       else if(e.getButton() == MouseEvent.BUTTON1)
3998       {
3999         if(isCoverageView(getPixPerBaseByWidth()))
4000           coverageView.singleClick(e.isShiftDown(),
4001               e.getPoint().y-getJspView().getViewport().getViewPosition().y);
4002       }
4003       else
4004         highlightRange(e, MouseEvent.BUTTON2_DOWN_MASK);
4005     }
4006 
mousePressed(final MouseEvent e)4007     public void mousePressed(final MouseEvent e)
4008     {
4009       highlightSAMRecord = mouseOverSAMRecord;
4010 
4011     	  repaint();
4012 
4013     	  javax.swing.SwingUtilities.invokeLater(new Runnable() {
4014     	        public void run() {
4015     	        		maybeShowPopup(e);
4016     	        }
4017     	    });
4018 
4019     }
4020 
mouseReleased(MouseEvent e)4021     public void mouseReleased(MouseEvent e)
4022     {
4023       dragStart = -1;
4024       maybeShowPopup(e);
4025     }
4026 
maybeShowPopup(MouseEvent e)4027     private void maybeShowPopup(MouseEvent e)
4028     {
4029       if(e.isPopupTrigger())
4030       {
4031         //
4032         // main menu options
4033         if(popup == null)
4034         {
4035           popup = new JPopupMenu();
4036           createMenus(popup);
4037         }
4038 
4039         //
4040         // coverage heatmap menu options
4041         if(coverageMenu != null)
4042           popup.remove(coverageMenu);
4043         if(isCoverageView(getPixPerBaseByWidth()) && coverageView.isPlotHeatMap())
4044         {
4045           if(coverageMenu == null)
4046           {
4047             coverageMenu = new JMenu("Coverage HeatMap");
4048             coverageView.createMenus(coverageMenu);
4049 
4050             final JCheckBoxMenuItem coverageGrid = new JCheckBoxMenuItem("Show heatmap grid", false);
4051             coverageGrid.addActionListener(new ActionListener()
4052             {
4053               public void actionPerformed(ActionEvent e)
4054               {
4055                 coverageView.showLabels(coverageGrid.isSelected());
4056               }
4057             });
4058             coverageMenu.add(coverageGrid);
4059 
4060             createGroup = new JMenuItem("Create group from selected BAMs/CRAMs");
4061             createGroup.addActionListener(new ActionListener()
4062             {
4063               private int n = 1;
4064               public void actionPerformed(ActionEvent e)
4065               {
4066                 String groupName = "group_"+n;
4067                 groupsFrame.addGroup(groupName);
4068                 final List<String> selected = coverageView.getSelected();
4069                 for(String sel: selected)
4070                   groupsFrame.addToGroup((new File(sel)).getName(), groupName);
4071                 groupsFrame.updateAndDisplay();
4072                 n++;
4073               }
4074             });
4075             coverageMenu.add(createGroup);
4076           }
4077           createGroup.setEnabled(coverageView.hasSelectedBams());
4078           popup.add(coverageMenu);
4079         }
4080 
4081         if(gotoMateMenuItem != null)
4082           popup.remove(gotoMateMenuItem);
4083         if(showDetails != null)
4084           popup.remove(showDetails);
4085 
4086         if( mouseOverSAMRecord != null &&
4087         		mouseOverSAMRecord.sam.getReadPairedFlag() &&
4088            !mouseOverSAMRecord.sam.getMateUnmappedFlag() )
4089         {
4090       	  highlightSAMRecord = mouseOverSAMRecord;
4091 
4092           final BamViewRecord thisSAMRecord = mouseOverSAMRecord;
4093           gotoMateMenuItem = new JMenuItem("Go to mate of : "+
4094               thisSAMRecord.sam.getReadName());
4095           gotoMateMenuItem.addActionListener(new ActionListener()
4096           {
4097 			public void actionPerformed(ActionEvent e)
4098 			{
4099 			  String name = thisSAMRecord.sam.getMateReferenceName();
4100 			  if(name.equals("="))
4101 			    name = thisSAMRecord.sam.getReferenceName();
4102 			  int offset = getSequenceOffset(name);
4103 			  if(feature_display != null)
4104 			    feature_display.makeBaseVisible(
4105 			        thisSAMRecord.sam.getMateAlignmentStart()+offset);
4106 			  else
4107 			    scrollBar.setValue(
4108 			        thisSAMRecord.sam.getMateAlignmentStart()+offset-
4109 			        (nbasesInView/2));
4110 
4111 			  highlightSAMRecord = thisSAMRecord;
4112 			}
4113           });
4114           popup.add(gotoMateMenuItem);
4115         }
4116 
4117         if( mouseOverSAMRecord != null)
4118         {
4119         	  highlightSAMRecord = mouseOverSAMRecord;
4120 
4121           final BamViewRecord thisSAMRecord = mouseOverSAMRecord;
4122           showDetails = new JMenuItem("Show details of : "+
4123               thisSAMRecord.sam.getReadName());
4124           showDetails.addActionListener(new ActionListener()
4125           {
4126         	    public void actionPerformed(ActionEvent e)
4127             {
4128             	  BamView.this.getRootPane().setCursor(cbusy);
4129             	  BamView.this.getRootPane().repaint();
4130             	  BamView.this.getRootPane().revalidate();
4131 
4132             	  SwingUtilities.invokeLater(
4133         	         new Runnable()
4134         	         {
4135      	            public void run()
4136         	            {
4137             	            	try
4138             	            	{
4139             	                openFileViewer(thisSAMRecord.sam, getMate(thisSAMRecord), bamList);
4140             	            }
4141             	            	finally
4142             	            	{
4143             	            		BamView.this.getRootPane().setCursor(cdone);
4144             	            	}
4145         	            }
4146         	         }
4147             	  );
4148 
4149             }
4150           });
4151           popup.add(showDetails);
4152         }
4153         popup.show(e.getComponent(),
4154                 e.getX(), e.getY());
4155       }
4156     }
4157   }
4158 
openFileViewer(SAMRecord readRecord, SAMRecord mateRecord, List<String> bamList)4159   protected static void openFileViewer(SAMRecord readRecord, SAMRecord mateRecord, List<String> bamList)
4160   {
4161     FileViewer viewDetail = new FileViewer(readRecord.getReadName(), true, false, false);
4162     appendToDetailView(readRecord, mateRecord, viewDetail, bamList);
4163   }
4164 
appendToDetailView(final SAMRecord sam, final SAMRecord mate, final FileViewer viewer, final List<String> bamList)4165   private static void appendToDetailView(final SAMRecord sam,
4166                                          final SAMRecord mate,
4167                                          final FileViewer viewer,
4168                                          final List<String> bamList)
4169   {
4170     if(bamList.size() > 1 && sam.getAttribute("FL") != null)
4171     {
4172       int bamIdx = (Integer)sam.getAttribute("FL");
4173       if(bamIdx < bamList.size())
4174         viewer.appendString("File                  "+bamList.get(bamIdx)+"\n\n", Level.INFO);
4175     }
4176 
4177     viewer.appendString("Read Name             "+sam.getReadName()+"\n", Level.INFO);
4178     viewer.appendString("Coordinates           "+sam.getAlignmentStart()+".."+
4179                                                  sam.getAlignmentEnd()+"\n", Level.DEBUG);
4180     if(sam.getReadGroup() != null)
4181       viewer.appendString("Read Group            "+sam.getReadGroup().getId()+"\n", Level.DEBUG);
4182     viewer.appendString("Length                "+sam.getReadLength()+"\n", Level.DEBUG);
4183     viewer.appendString("Reference Name        "+sam.getReferenceName()+"\n", Level.DEBUG);
4184     viewer.appendString("Inferred Size         "+sam.getInferredInsertSize()+"\n", Level.DEBUG);
4185     viewer.appendString("Mapping Quality       "+sam.getMappingQuality()+"\n", Level.DEBUG);
4186     viewer.appendString("Cigar String          "+sam.getCigarString()+"\n", Level.DEBUG);
4187     viewer.appendString("Strand                "+
4188         (sam.getReadNegativeStrandFlag() ? "-\n\n" : "+\n\n"), Level.DEBUG);
4189 
4190     if(sam.getReadPairedFlag())
4191     {
4192       if(mate != null)
4193       {
4194         viewer.appendString("Mate Coordinates      "+mate.getAlignmentStart()+".."+
4195                                                      mate.getAlignmentEnd()+"\n", Level.DEBUG);
4196         viewer.appendString("Mate Length           "+mate.getReadLength()+"\n", Level.DEBUG);
4197         viewer.appendString("Mate Reference Name   "+mate.getReferenceName()+"\n", Level.DEBUG);
4198         viewer.appendString("Mate Inferred Size    "+mate.getInferredInsertSize()+"\n", Level.DEBUG);
4199         viewer.appendString("Mate Mapping Quality  "+mate.getMappingQuality()+"\n", Level.DEBUG);
4200         viewer.appendString("Mate Cigar String     "+mate.getCigarString()+"\n", Level.DEBUG);
4201       }
4202       else
4203       {
4204         viewer.appendString("Mate Start Coordinate "+sam.getMateAlignmentStart()+"\n", Level.DEBUG);
4205         viewer.appendString("Mate Reference Name   "+sam.getMateReferenceName()+"\n", Level.DEBUG);
4206       }
4207       viewer.appendString("Mate Strand           "+
4208           (sam.getMateNegativeStrandFlag() ? "-" : "+"), Level.DEBUG);
4209     }
4210 
4211     viewer.appendString("\n\nFlags:", Level.INFO);
4212     viewer.appendString("\nDuplicate Read        "+
4213         (sam.getDuplicateReadFlag() ? "yes" : "no"), Level.DEBUG);
4214     viewer.appendString("\nSecondary Alignment   "+
4215             (sam.isSecondaryAlignment() ? "yes" : "no"), Level.DEBUG);
4216     viewer.appendString("\nSupplementary Alignment   "+
4217             (sam.getSupplementaryAlignmentFlag() ? "yes" : "no"), Level.DEBUG);
4218 
4219     viewer.appendString("\nRead Paired           "+
4220         (sam.getReadPairedFlag() ? "yes" : "no"), Level.DEBUG);
4221     if(sam.getReadPairedFlag())
4222     {
4223       viewer.appendString("\nFirst of Pair         "+
4224         (sam.getFirstOfPairFlag() ? "yes" : "no"), Level.DEBUG);
4225       viewer.appendString("\nMate Unmapped         "+
4226         (sam.getMateUnmappedFlag() ? "yes" : "no"), Level.DEBUG);
4227       viewer.appendString("\nProper Pair           "+
4228         (sam.getProperPairFlag() ? "yes" : "no"), Level.DEBUG);
4229     }
4230     viewer.appendString("\nRead Fails Vendor\nQuality Check         "+
4231         (sam.getReadFailsVendorQualityCheckFlag() ? "yes" : "no"), Level.DEBUG);
4232     viewer.appendString("\nRead Unmapped         "+
4233         (sam.getReadUnmappedFlag() ? "yes" : "no"), Level.DEBUG);
4234 
4235     if(sam.getReadPairedFlag())
4236       viewer.appendString("\nSecond Of Pair        "+
4237         (sam.getSecondOfPairFlag() ? "yes" : "no"), Level.DEBUG);
4238 
4239     viewer.appendString("\n\nRead Bases:\n", Level.INFO);
4240     wrapReadBases(sam, viewer);
4241 
4242     if(sam.getReadPairedFlag() && mate != null)
4243     {
4244       viewer.appendString("\nMate Read Bases:\n", Level.INFO);
4245       wrapReadBases(mate, viewer);
4246     }
4247   }
4248 
wrapReadBases(final SAMRecord sam, final FileViewer viewer)4249   private static void wrapReadBases(final SAMRecord sam,
4250                              final FileViewer viewer)
4251   {
4252     final String seq = new String(sam.getReadBases());
4253     final int MAX_SEQ_LINE_LENGTH = 100;
4254     for(int i=0; i<=seq.length(); i+=MAX_SEQ_LINE_LENGTH)
4255     {
4256       int iend = i+MAX_SEQ_LINE_LENGTH;
4257       if(iend > seq.length())
4258         iend = seq.length();
4259       viewer.appendString(seq.substring(i, iend)+"\n", Level.DEBUG);
4260     }
4261   }
4262 
4263   /**
4264    * Query for the mate of a read
4265    * @param mate
4266    * @return
4267    */
getMate(BamViewRecord thisSAMRecord)4268   protected SAMRecord getMate(BamViewRecord thisSAMRecord)
4269   {
4270     if(!thisSAMRecord.sam.getReadPairedFlag())  // read is not paired in sequencing
4271       return null;
4272 
4273     SAMRecord mate = null;
4274     try
4275     {
4276       short fileIndex = 0;
4277       if(bamList.size()>1 && thisSAMRecord.bamIndex > 0)
4278         fileIndex = thisSAMRecord.bamIndex;
4279       String bam = bamList.get(fileIndex);
4280       final SamReader inputSam = getSAMFileReader(bam);
4281       mate = inputSam.queryMate(thisSAMRecord.sam);
4282     }
4283     catch (Exception e)
4284     {
4285       logger4j.warn("WARN: BamView.getMate() :: "+e.getMessage());
4286       e.printStackTrace();
4287     }
4288     return mate;
4289   }
4290 
getSamRecordFlagPredicate()4291   protected SAMRecordPredicate getSamRecordFlagPredicate()
4292   {
4293     return samRecordFlagPredicate;
4294   }
4295 
setSamRecordFlagPredicate( SAMRecordPredicate samRecordFlagPredicate)4296   protected void setSamRecordFlagPredicate(
4297       SAMRecordPredicate samRecordFlagPredicate)
4298   {
4299     laststart = -1;
4300     lastend = -1;
4301     this.samRecordFlagPredicate = samRecordFlagPredicate;
4302   }
4303 
getSamRecordMapQPredicate()4304   protected SAMRecordMapQPredicate getSamRecordMapQPredicate()
4305   {
4306     return samRecordMapQPredicate;
4307   }
4308 
setSamRecordMapQPredicate( SAMRecordMapQPredicate samRecordMapQPredicate)4309   protected void setSamRecordMapQPredicate(
4310       SAMRecordMapQPredicate samRecordMapQPredicate)
4311   {
4312     laststart = -1;
4313     lastend = -1;
4314     this.samRecordMapQPredicate = samRecordMapQPredicate;
4315   }
4316 
4317   /**
4318    * @return the concatSequences
4319    */
isConcatSequences()4320   protected boolean isConcatSequences()
4321   {
4322     return concatSequences;
4323   }
4324 
4325   /**
4326    * Check whether the given BAM record is highlighted currently.
4327    * @param bamRec BamViewRecord
4328    * @return boolean - true if highlighted
4329    */
isThisBamRecordHighlighted(BamViewRecord bamRec)4330   protected boolean isThisBamRecordHighlighted(BamViewRecord bamRec)
4331   {
4332 	  if (highlightSAMRecord == null)
4333 	  {
4334 		  return false;
4335 	  }
4336 
4337 	  return BamUtils.samRecordEqualityCheck(highlightSAMRecord.sam, bamRec.sam);
4338   }
4339 
4340   class PairedRead
4341   {
4342     BamViewRecord sam1;
4343     BamViewRecord sam2;
4344   }
4345 
4346   class CreateFeatures
4347   {
CreateFeatures(final GroupBamFrame groupsFrame)4348     CreateFeatures(final GroupBamFrame groupsFrame)
4349     {
4350       final TextFieldInt threshold = new TextFieldInt();
4351       final TextFieldInt minSize = new TextFieldInt();
4352       final TextFieldInt minBams = new TextFieldInt();
4353 
4354       threshold.setValue(6);
4355       minSize.setValue(6);
4356       minBams.setValue( (groupsFrame.getNumberOfGroups() == 1 ?
4357           bamList.size() : groupsFrame.getMaximumBamsInGroup()) );
4358 
4359       final JPanel gridPanel = new JPanel(new GridBagLayout());
4360       GridBagConstraints c = new GridBagConstraints();
4361       c.anchor = GridBagConstraints.WEST;
4362       c.fill = GridBagConstraints.HORIZONTAL;
4363       c.gridx = 0;
4364       c.gridy = 0;
4365 
4366       gridPanel.add(new JLabel("Minimum number of reads:"), c);
4367       c.gridy++;
4368       gridPanel.add(threshold, c);
4369 
4370       c.gridy++;
4371       gridPanel.add(new JSeparator(), c);
4372       c.gridy++;
4373       gridPanel.add(new JLabel("Minimum number of BAMs/CRAMs for reads to be present in:"), c);
4374       c.gridy++;
4375       gridPanel.add(minBams, c);
4376 
4377       JRadioButton useAllBams = new JRadioButton("out of all BAMs/CRAMs", (groupsFrame.getNumberOfGroups() == 1));
4378       JRadioButton useGroup = new JRadioButton("within a group", (groupsFrame.getNumberOfGroups() != 1));
4379 
4380       if(groupsFrame.getNumberOfGroups() == 1)
4381         useGroup.setEnabled(false);
4382 
4383       final ButtonGroup group = new ButtonGroup();
4384       group.add(useAllBams);
4385       group.add(useGroup);
4386 
4387       final Box xBox = Box.createHorizontalBox();
4388       xBox.add(useAllBams);
4389       xBox.add(useGroup);
4390       xBox.add(Box.createHorizontalGlue());
4391       c.gridy++;
4392       gridPanel.add(xBox, c);
4393 
4394       c.gridy++;
4395       gridPanel.add(new JSeparator(), c);
4396       c.gridy++;
4397       gridPanel.add(new JLabel("Minimum feature size:"), c);
4398       c.gridy++;
4399       gridPanel.add(minSize, c);
4400 
4401       final JCheckBox cbOpposite = new JCheckBox("Assume reads on opposite strand", false);
4402       cbOpposite.setToolTipText("for cDNA experiments when the reads are on the opposite strand");
4403       c.gridy++;
4404       gridPanel.add(cbOpposite, c);
4405 
4406       int status =
4407           JOptionPane.showConfirmDialog(feature_display, gridPanel,
4408               "Options", JOptionPane.OK_CANCEL_OPTION);
4409       if(status == JOptionPane.CANCEL_OPTION)
4410         return;
4411 
4412       if(!useGroup.isSelected() && minBams.getValue() > bamList.size())
4413       {
4414         status =
4415             JOptionPane.showConfirmDialog(feature_display,
4416                 "The minimum number of BAMs setting can not be\n"+
4417                 "greater than the total number of BAM files.\n"+
4418                 "Set this to the number of BAMs (i.e. "+bamList.size()+").",
4419                 "Options", JOptionPane.OK_CANCEL_OPTION);
4420         if(status == JOptionPane.CANCEL_OPTION)
4421           return;
4422         minBams.setValue(bamList.size());
4423       }
4424       else if(useGroup.isSelected() && minBams.getValue() > groupsFrame.getMaximumBamsInGroup())
4425       {
4426         status =
4427             JOptionPane.showConfirmDialog(feature_display,
4428                 "Minimum number of BAMs setting can not be greater than\n"+
4429                 "the total number of BAM files found in any of the groups.\n"+
4430                 "Set this to the greatest number of BAM files in any\n"+
4431                 "group (i.e. "+groupsFrame.getMaximumBamsInGroup()+").",
4432                 "Options", JOptionPane.OK_CANCEL_OPTION);
4433         if(status == JOptionPane.CANCEL_OPTION)
4434           return;
4435         minBams.setValue(groupsFrame.getMaximumBamsInGroup());
4436       }
4437 
4438       new MappedReads(BamView.this,
4439           (useGroup.isSelected() ? groupsFrame : null), threshold.getValue(),
4440           minSize.getValue(), minBams.getValue(), cbOpposite.isSelected(), true);
4441     }
4442   }
4443 
4444   /**
4445    * Is BamView being used as a standalone application.
4446    * @return boolean
4447    */
isStandaloneMode()4448   public static boolean isStandaloneMode()
4449   {
4450 	 return standaloneMode;
4451   }
4452 
4453   /**
4454    * Set whether or not BamView is being used as a standalone application.
4455    * @param inStandaloneMode boolean
4456    */
setStandaloneMode(boolean inStandaloneMode)4457   public static void setStandaloneMode(boolean inStandaloneMode)
4458   {
4459 	 standaloneMode = inStandaloneMode;
4460   }
4461 
4462   /**
4463    * Close the application.
4464    */
close()4465   public void close()
4466   {
4467 	  BamView.this.setVisible(false);
4468       Component comp = BamView.this;
4469 
4470       while( !(comp instanceof JFrame) )
4471         comp = comp.getParent();
4472 
4473       ((JFrame)comp).dispose();
4474   }
4475 
4476   /**
4477    * Close the BAM panel.
4478    */
closeBamPanel()4479   public void closeBamPanel()
4480   {
4481 	  BamView.this.getRootPane().setCursor(cdone);
4482 
4483 	  final JPanel containerPanel = (JPanel) mainPanel.getParent();
4484 	  if (feature_display != null)
4485 	  {
4486 	    feature_display.removeDisplayAdjustmentListener(BamView.this);
4487 	    feature_display.getSelection().removeSelectionChangeListener(BamView.this);
4488 	  }
4489       containerPanel.remove(mainPanel);
4490 
4491       if(containerPanel.getComponentCount() > 0)
4492         containerPanel.revalidate();
4493       else
4494       {
4495         if(entry_edit != null)
4496           entry_edit.setNGDivider();
4497         else
4498           containerPanel.setVisible(false);
4499       }
4500   }
4501 
4502   /**
4503    * Called when we encounter a major error while loading a BAM or CRAM file.
4504    * It will display the errors in an option pane and then close the panel,
4505    * exiting completely of BamView is in standalone mode.
4506    * @param errorText String - the error message to display.
4507    */
handleFatalError(final String errorText)4508   protected void handleFatalError(final String errorText)
4509   {
4510 	  if (!foundFatalErrors.get())
4511 	  {
4512 		 foundFatalErrors.set(true);
4513 
4514 		 logger4j.error("BamView errors: " + errorText);
4515 
4516 		 SwingUtilities.invokeLater(new Runnable() {
4517 
4518 		    public void run() {
4519 
4520 		    	  JOptionPane.showMessageDialog(null, errorText);
4521 		    	  readsInView.clear();
4522 		    	  closeBamPanel();
4523 
4524 		    	  if (BamView.isStandaloneMode())
4525 		    	  {
4526 		    	    System.exit(0);
4527 		    	  }
4528 		    }
4529 		 });
4530 	  }
4531   }
4532 
4533   /**
4534    * Class main method.
4535    * @param args
4536    */
main(String[] args)4537   public static void main(String[] args)
4538   {
4539     BamFrame frame = new BamFrame();
4540     if(args.length == 0 && BamFrame.isMac())
4541     {
4542       try
4543       {
4544         Thread.sleep(1000);
4545       }
4546       catch (InterruptedException e1) {}
4547       if(frame.getBamFile() != null)
4548         args = new String[]{ frame.getBamFile() };
4549     }
4550 
4551     List<String> alignmentFileList = new Vector<String>();
4552     String reference = null;
4553     if(args.length == 0 || args[0].equals("NEW-BAMVIEW"))
4554     {
4555       System.setProperty("default_directory", System.getProperty("user.dir"));
4556       FileSelectionDialog fileSelection = new FileSelectionDialog(
4557           null, true, "BamView", "BAM/CRAM");
4558       alignmentFileList = fileSelection.getFiles(BAM_SUFFIX);
4559       reference = fileSelection.getReferenceFile();
4560       if(reference == null || reference.trim().equals(""))
4561         reference = null;
4562 
4563       if(alignmentFileList == null || alignmentFileList.size() < 1)
4564       {
4565         if(args.length > 0 && args[0].equals("NEW-BAMVIEW"))
4566           return;
4567         System.err.println("No files found.");
4568         System.exit(0);
4569       }
4570 
4571     }
4572     else if(!args[0].startsWith("-"))
4573     {
4574       for(int i=0; i< args.length; i++)
4575     	  alignmentFileList.add(args[i]);
4576     }
4577 
4578     int nbasesInView = 2000;
4579     String chr = null;
4580     String vw  = null;
4581     boolean orientation = false;
4582     boolean covPlot     = false;
4583     boolean snpPlot     = false;
4584     int base = 0;
4585 
4586     for(int i=0;i<args.length; i++)
4587     {
4588       if(args[i].equals("-a"))
4589       {
4590         while(i < args.length-1 && !args[++i].startsWith("-"))
4591         {
4592           String filename = args[i];
4593           if(FileSelectionDialog.isListOfFiles(filename))
4594         	  	alignmentFileList.addAll(FileSelectionDialog.getListOfFiles(filename));
4595           else
4596         	  	alignmentFileList.add(filename);
4597         }
4598         --i;
4599       }
4600       else if(args[i].equals("-r"))
4601         reference = args[++i];
4602       else if(args[i].equals("-n"))
4603         nbasesInView = Integer.parseInt(args[++i]);
4604       else if(args[i].equals("-s"))
4605         System.setProperty("samtoolDir", args[++i]);
4606       else if(args[i].equals("-c"))
4607         chr = args[++i].trim();
4608       else if(args[i].equals("-b"))
4609         base = Integer.parseInt(args[++i].trim());
4610       else if(args[i].equals("-v"))
4611         vw = args[++i].trim();
4612       else if(args[i].equals("-o"))
4613         orientation = true;
4614       else if(args[i].equals("-pc"))
4615         covPlot = true;
4616       else if(args[i].equals("-ps"))
4617         snpPlot = true;
4618       else if(args[i].startsWith("-h"))
4619       {
4620         System.out.println("-h\t show help");
4621 
4622         System.out.println("-a\t BAM/CRAM file to display");
4623         System.out.println("-r\t reference file (optional)");
4624         System.out.println("-n\t number of bases to display in the view (optional)");
4625         System.out.println("-c\t chromosome name (optional)");
4626         System.out.println("-v\t view (optional - IS (inferred size), S (stack, default), PS (paired stack), ST (strand), C (coverage))");
4627         System.out.println("-b\t base position (optional)");
4628         System.out.println("-o\t show orientation (optional)");
4629         System.out.println("-pc\t plot coverage (optional)");
4630         System.out.println("-ps\t plot SNP (optional and only with -r)");
4631         System.exit(0);
4632       }
4633     }
4634 
4635     BamView.setStandaloneMode(true);
4636 
4637     /*
4638      * If a reference was specified then make sure that it's a valid file.
4639      */
4640     if (reference != null)
4641 	{
4642 	  File tmpRef = new File(reference);
4643 	  if (!tmpRef.exists()) {
4644 		System.err.println("The specified reference file does not exist.");
4645 		System.exit(0);
4646 	  }
4647 	}
4648 
4649     final BamView view = new BamView(alignmentFileList, reference, nbasesInView, null, null,
4650         (JPanel)frame.getContentPane(), frame);
4651     frame.setTitle("BamView v"+view.getVersion());
4652 
4653     if(chr != null)
4654       view.combo.setSelectedItem(chr);
4655     if(vw != null)
4656     {
4657       if(vw.equalsIgnoreCase("IS"))
4658         view.cbIsizeStackView.setSelected(true);
4659       if(vw.equalsIgnoreCase("PS"))
4660         view.cbPairedStackView.setSelected(true);
4661       if(vw.equalsIgnoreCase("ST"))
4662         view.cbStrandStackView.setSelected(true);
4663       if(vw.equalsIgnoreCase("C"))
4664         view.cbCoverageView.setSelected(true);
4665     }
4666     if(base > 0)
4667       view.scrollBar.setValue(base);
4668     if(orientation)
4669       view.isOrientation = true;
4670     if(covPlot)
4671     {
4672       view.isCoverage = true;
4673       view.coveragePanel.setVisible(true);
4674     }
4675     if(snpPlot)
4676     {
4677       view.isSNPplot = true;
4678       view.snpPanel.setVisible(true);
4679     }
4680 
4681     // translucent
4682     //frame.getRootPane().putClientProperty("Window.alpha", new Float(0.9f));
4683     /*frame.addWindowFocusListener(new WindowFocusListener()
4684     {
4685       public void windowGainedFocus(WindowEvent e)
4686       {
4687         view.requestFocus();
4688       }
4689       public void windowLostFocus(WindowEvent e){}
4690     });*/
4691 
4692     frame.pack();
4693 
4694     view.jspView.getVerticalScrollBar().setValue(
4695         view.jspView.getVerticalScrollBar().getMaximum());
4696     frame.setVisible(true);
4697   }
4698 }
4699