/* JamView
*
* created: 2009
*
* This file is part of Artemis
*
* Copyright(C) 2009 Genome Research Limited
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or(at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package uk.ac.sanger.artemis.components.alignment;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.io.File;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import net.sf.samtools.AlignmentBlock;
import net.sf.samtools.SAMFileHeader;
import net.sf.samtools.SAMFileReader;
import net.sf.samtools.SAMRecord;
import net.sf.samtools.SAMSequenceRecord;
import net.sf.samtools.SAMFileReader.ValidationStringency;
import net.sf.samtools.util.CloseableIterator;
import uk.ac.sanger.artemis.Entry;
import uk.ac.sanger.artemis.EntryGroup;
import uk.ac.sanger.artemis.Options;
import uk.ac.sanger.artemis.Selection;
import uk.ac.sanger.artemis.SelectionChangeEvent;
import uk.ac.sanger.artemis.SelectionChangeListener;
import uk.ac.sanger.artemis.SimpleEntryGroup;
import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
import uk.ac.sanger.artemis.components.EntryFileDialog;
import uk.ac.sanger.artemis.components.FeatureDisplay;
import uk.ac.sanger.artemis.components.MessageDialog;
import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
import uk.ac.sanger.artemis.io.EntryInformation;
import uk.ac.sanger.artemis.io.Range;
import uk.ac.sanger.artemis.sequence.Bases;
import uk.ac.sanger.artemis.sequence.MarkerRange;
import uk.ac.sanger.artemis.sequence.NoSequenceException;
import uk.ac.sanger.artemis.util.Document;
import uk.ac.sanger.artemis.util.DocumentFactory;
import uk.ac.sanger.artemis.util.OutOfRangeException;
public class JamView extends JPanel
implements Scrollable, DisplayAdjustmentListener, SelectionChangeListener
{
private static final long serialVersionUID = 1L;
private List<SAMRecord> readsInView;
private Hashtable<String, Integer> seqLengths = new Hashtable<String, Integer>();
private Vector<String> seqNames = new Vector<String>();
private String bam;
private Bases bases;
private JScrollPane jspView;
private JScrollBar scrollBar;
private JComboBox combo;
private boolean isSingle = false;
private boolean isSNPs = false;
private boolean isStackView = false;
private boolean isPairedStackView = false;
private boolean isCoverage = false;
private FeatureDisplay feature_display;
private Selection selection;
private JPanel mainPanel;
private CoveragePanel coveragePanel;
private boolean showScale = true;
private Ruler ruler = new Ruler();
private int nbasesInView;
private int startBase = -1;
private int endBase = -1;
private int laststart;
private int lastend;
private int maxUnitIncrement = 8;
private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR);
private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR);
private boolean showBaseAlignment = false;
private AlphaComposite translucent =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f);
/** Used to colour the frames. */
private Color lightGrey = new Color(200, 200, 200);
private Color darkGreen = new Color(0, 150, 0);
private Color darkOrange = new Color(255,140,0);
private Point lastMousePoint = null;
private SAMRecord mouseOverSAMRecord = null;
// record of where a mouse drag starts
private int dragStart = -1;
private int maxHeight = 800;
private boolean concatSequences = false;
private int ALIGNMENT_PIX_PER_BASE;
private JPopupMenu popup;
public JamView(String bam,
String reference,
int nbasesInView)
{
super();
setBackground(Color.white);
this.bam = bam;
this.nbasesInView = nbasesInView;
if(reference != null)
{
EntryGroup entryGroup = new SimpleEntryGroup();
try
{
getEntry(reference,entryGroup);
}
catch (NoSequenceException e)
{
e.printStackTrace();
}
}
readHeaderPicard();
// set font size
//setFont(getFont().deriveFont(12.f));
//Options.getOptions().getFontUIResource();
//final javax.swing.plaf.FontUIResource font_ui_resource =
// new javax.swing.plaf.FontUIResource(getFont());
final javax.swing.plaf.FontUIResource font_ui_resource =
Options.getOptions().getFontUIResource();
Enumeration<Object> keys = UIManager.getDefaults().keys();
while(keys.hasMoreElements())
{
Object key = keys.nextElement();
Object value = UIManager.get(key);
if(value instanceof javax.swing.plaf.FontUIResource)
UIManager.put(key, font_ui_resource);
}
setFont(Options.getOptions().getFont());
FontMetrics fm = getFontMetrics(getFont());
ALIGNMENT_PIX_PER_BASE = fm.charWidth('M');
selection = new Selection(null);
MultiLineToolTipUI.initialize();
setToolTipText("");
}
public String getToolTipText()
{
return ( mouseOverSAMRecord != null ?
mouseOverSAMRecord.getReadName() + "\n" +
mouseOverSAMRecord.getAlignmentStart() + ".." +
mouseOverSAMRecord.getAlignmentEnd() + "\nisize=" +
mouseOverSAMRecord.getInferredInsertSize() + "\nrname=" +
mouseOverSAMRecord.getReferenceName(): null);
}
/*
private void readHeader()
{
String samtoolCmd = "";
if(System.getProperty("samtoolDir") != null)
samtoolCmd = System.getProperty("samtoolDir");
String cmd[] = { samtoolCmd+File.separator+"samtools",
"view", "-H", bam };
RunSamTools samtools = new RunSamTools(cmd, null, null, null);
if(samtools.getProcessStderr() != null)
System.out.println(samtools.getProcessStderr());
String header = samtools.getProcessStdout();
StringReader samReader = new StringReader(header);
BufferedReader buff = new BufferedReader(samReader);
String line;
try
{
while((line = buff.readLine()) != null)
{
if(line.indexOf("LN:") > -1)
{
String parts[] = line.split("\t");
String name = "";
int seqLength = 0;
for(int i=0; i<parts.length; i++)
{
if(parts[i].startsWith("LN:"))
seqLength = Integer.parseInt( parts[i].substring(3) );
else if(parts[i].startsWith("SN:"))
name = parts[i].substring(3);
}
seqLengths.put(name, seqLength);
seqNames.add(name);
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
private void readFromBam(int start, int end)
{
String refName = (String) combo.getSelectedItem();
String samtoolCmd = "";
if(System.getProperty("samtoolDir") != null)
samtoolCmd = System.getProperty("samtoolDir");
String cmd[] = { samtoolCmd+File.separator+"samtools",
"view",
bam, refName+":"+start+"-"+end };
for(int i=0; i<cmd.length;i++)
System.out.print(cmd[i]+" ");
System.out.println();
if(readsInView == null)
readsInView = new Vector<SAMRecord>();
else
readsInView.clear();
RunSamTools samtools = new RunSamTools(cmd, null, null, readsInView);
if(samtools.getProcessStderr() != null)
System.out.println(samtools.getProcessStderr());
samtools.waitForStdout();
}*/
private void readHeaderPicard()
{
File bamFile = new File(bam);
File indexFile = new File(bam+".bai");
final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
SAMFileHeader header = inputSam.getFileHeader();
List<SAMSequenceRecord> readGroups = header.getSequenceDictionary().getSequences();
for(int i=0; i<readGroups.size(); i++)
{
seqLengths.put(readGroups.get(i).getSequenceName(),
readGroups.get(i).getSequenceLength());
seqNames.add(readGroups.get(i).getSequenceName());
}
inputSam.close();
}
/**
* Read a SAM or BAM file.
*/
private void readFromBamPicard(int start, int end)
{
// Open the input file. Automatically detects whether input is SAM or BAM
// and delegates to a reader implementation for the appropriate format.
File bamFile = new File(bam);
File indexFile = new File(bam+".bai");
final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
inputSam.setValidationStringency(ValidationStringency.SILENT);
if(readsInView == null)
readsInView = new Vector<SAMRecord>();
else
readsInView.clear();
if(concatSequences)
{
int len = 0;
int lastLen = 1;
for(int i=0; i<seqNames.size(); i++)
{
int thisLength = seqLengths.get(seqNames.get(i));
len += thisLength;
if( (lastLen >= start && lastLen < end) ||
(len >= start && len < end) ||
(start >= lastLen && start < len) ||
(end >= lastLen && end < len) )
{
int offset = getSequenceOffset(seqNames.get(i));
int thisStart = start - offset;
if(thisStart < 1)
thisStart = 1;
int thisEnd = end - offset;
if(thisEnd > thisLength)
thisEnd = thisLength;
System.out.println("READ "+seqNames.get(i)+" "+thisStart+".."+thisEnd);
iterateOverBam(inputSam, seqNames.get(i), thisStart, thisEnd);
}
lastLen = len;
}
}
else
{
String refName = (String) combo.getSelectedItem();
iterateOverBam(inputSam, refName, start, end);
}
inputSam.close();
//System.out.println("readFromBamPicard "+start+".."+end);
}
/**
* Iterate over BAM file and load into the <code>List</code> of
* <code>SAMRecord</code>.
* @param inputSam
* @param refName
* @param start
* @param end
*/
private void iterateOverBam(final SAMFileReader inputSam,
String refName, int start, int end)
{
CloseableIterator<SAMRecord> it = inputSam.queryOverlapping(refName, start, end);
while ( it.hasNext() )
{
try
{
SAMRecord samRecord = it.next();
readsInView.add(samRecord);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
it.close();
}
private int getSequenceLength()
{
if(concatSequences)
{
int len = 0;
for(int i=0; i<seqNames.size(); i++)
len += seqLengths.get(seqNames.get(i));
return len;
}
else
return seqLengths.get((String) combo.getSelectedItem());
}
protected int getSequenceOffset(String refName)
{
if(!concatSequences)
return 0;
int offset = 0;
for(int i=0; i<combo.getItemCount(); i++)
{
String thisSeqName = (String) combo.getItemAt(i);
if(refName.equals(thisSeqName))
return offset;
offset += seqLengths.get(combo.getItemAt(i));
}
return offset;
}
/**
* Override
*/
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
mouseOverSAMRecord = null;
int seqLength = getSequenceLength();
float pixPerBase = getPixPerBaseByWidth();
int start;
int end;
if(startBase > 0)
start = startBase;
else
start = getBaseAtStartOfView();
if(endBase > 0)
end = endBase;
else
{
end = start + nbasesInView - 1;
if(end > seqLength)
end = seqLength;
}
//System.out.println(start+".."+end+" " +
// "sequence length = "+getSequenceLength()+
// " pixPerBase="+pixPerBase);
if(laststart != start ||
lastend != end)
{
setCursor(cbusy);
try
{
readFromBamPicard(start, end);
if(!isStackView || pixPerBase*3 >= ALIGNMENT_PIX_PER_BASE)
Collections.sort(readsInView, new SAMRecordComparator());
setCursor(cdone);
}
catch(OutOfMemoryError ome)
{
JOptionPane.showMessageDialog(this, "Out of Memory");
return;
}
}
laststart = start;
lastend = end;
if(pixPerBase*3 >= ALIGNMENT_PIX_PER_BASE)
drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
else
{
if(isStackView)
drawStackView(g2, seqLength, pixPerBase, start, end);
else if(isPairedStackView)
drawPairedStackView(g2, seqLength, pixPerBase, start, end);
else
drawLineView(g2, seqLength, pixPerBase, start, end);
if(isCoverage)
{
coveragePanel.setStartAndEnd(start, end);
coveragePanel.setPixPerBase(pixPerBase);
coveragePanel.repaint();
}
}
}
private float getPixPerBaseByWidth()
{
return (float)( getPreferredSize().getWidth() /nbasesInView);
}
private int getMaxBasesInPanel(int seqLength)
{
if(feature_display == null)
return seqLength+nbasesInView/2;
else
return seqLength+nbasesInView;
}
/**
* Draw the zoomed-in base view.
* @param g2
* @param seqLength
* @param pixPerBase
* @param start
* @param end
*/
private void drawBaseAlignment(Graphics2D g2,
int seqLength,
float pixPerBase,
final int start,
final int end)
{
ruler.start = start;
ruler.end = end;
ruler.repaint();
int ypos = 0;
String refSeq = null;
int refSeqStart = start;
if(bases != null)
{
// draw the reference sequence
ypos+=11;
try
{
int seqEnd = end+2;
if(seqEnd > bases.getLength())
seqEnd = bases.getLength();
if(refSeqStart < 1)
refSeqStart = 1;
refSeq =
bases.getSubSequence(new Range(refSeqStart, seqEnd), Bases.FORWARD).toUpperCase();
System.out.println(refSeqStart +" "+ seqEnd+" "+refSeq);
g2.setColor(lightGrey);
g2.fillRect(0, ypos-11, getPreferredSize().width, 11);
drawSelectionRange(g2, pixPerBase, start, end);
g2.setColor(Color.black);
g2.drawString(refSeq, 0, ypos);
}
catch (OutOfRangeException e)
{
e.printStackTrace();
}
}
else
drawSelectionRange(g2, pixPerBase, start, end);
boolean drawn[] = new boolean[readsInView.size()];
for(int i=0; i<readsInView.size(); i++)
drawn[i] = false;
for(int i=0; i<readsInView.size(); i++)
{
if (!drawn[i])
{
SAMRecord thisRead = readsInView.get(i);
ypos+=11;
drawSequence(g2, thisRead, pixPerBase, ypos, refSeq, refSeqStart);
drawn[i] = true;
int thisEnd = thisRead.getAlignmentEnd();
if(thisEnd == 0)
thisEnd = thisRead.getAlignmentStart()+thisRead.getReadLength();
for(int j=i+1; j<readsInView.size(); j++)
{
if (!drawn[j])
{
SAMRecord nextRead = readsInView.get(j);
if(nextRead.getAlignmentStart() > thisEnd+1)
{
drawSequence(g2, nextRead, pixPerBase, ypos, refSeq, refSeqStart);
drawn[j] = true;
thisEnd = nextRead.getAlignmentEnd();
if(thisEnd == 0)
thisEnd = nextRead.getAlignmentStart()+nextRead.getReadLength();
}
}
}
}
}
if(ypos > getHeight())
{
Dimension d = getPreferredSize();
d.setSize(getPreferredSize().getWidth(), ypos);
setPreferredSize(d);
revalidate();
}
}
/**
* Draw the query sequence
* @param g2
* @param read
* @param pixPerBase
* @param ypos
*/
private void drawSequence(Graphics2D g2, SAMRecord samRecord,
float pixPerBase, int ypos, String refSeq, int refSeqStart)
{
if (!samRecord.getReadPairedFlag() || // read is not paired in sequencing
samRecord.getMateUnmappedFlag() ) // mate is unmapped ) // mate is unmapped
g2.setColor(Color.black);
else
g2.setColor(Color.blue);
Color col = g2.getColor();
int xpos;
int len = 0;
String readSeq = samRecord.getReadString();
int offset = getSequenceOffset(samRecord.getReferenceName());
List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
for(int i=0; i<blocks.size(); i++)
{
AlignmentBlock block = blocks.get(i);
len += block.getLength();
for(int j=0; j<block.getLength(); j++)
{
int readPos = block.getReadStart()-1+j;
xpos = block.getReferenceStart()-1+j+offset;
int refPos = xpos-refSeqStart+1;
if(isSNPs && refSeq != null && refPos > 0 && refPos < refSeq.length())
{
if(readSeq.charAt(readPos) != refSeq.charAt(refPos))
g2.setColor(Color.red);
else
g2.setColor(col);
}
g2.drawString(readSeq.substring(readPos, readPos+1),
refPos*ALIGNMENT_PIX_PER_BASE, ypos);
}
}
if(lastMousePoint != null)
{
int refPos = blocks.get(0).getReferenceStart()+offset-refSeqStart;
int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
int xend = (refPos+len)*ALIGNMENT_PIX_PER_BASE;
if(lastMousePoint.getY() > ypos-11 && lastMousePoint.getY() < ypos)
if(lastMousePoint.getX() > xstart &&
lastMousePoint.getX() < xend)
{
mouseOverSAMRecord = samRecord;
}
}
}
/**
* Draw zoomed-out view.
* @param g2
* @param seqLength
* @param pixPerBase
* @param start
* @param end
*/
private void drawLineView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end)
{
drawSelectionRange(g2, pixPerBase,start, end);
if(isShowScale())
drawScale(g2, start, end, pixPerBase);
Stroke originalStroke =
new BasicStroke (1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
Stroke stroke =
new BasicStroke (1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
int scaleHeight;
if(isShowScale())
scaleHeight = 15;
else
scaleHeight = 0;
for(int i=0; i<readsInView.size(); i++)
{
SAMRecord samRecord = readsInView.get(i);
SAMRecord samNextRecord = null;
if( !samRecord.getReadPairedFlag() || // read is not paired in sequencing
samRecord.getMateUnmappedFlag() ) // mate is unmapped
{
if(isSingle)
{
int ypos = (getHeight() - scaleHeight) - samRecord.getReadString().length();
g2.setColor(Color.orange);
drawRead(g2, samRecord, pixPerBase, stroke, ypos);
}
continue;
}
int ypos = (getHeight() - scaleHeight) - ( Math.abs(samRecord.getInferredInsertSize()) );
if(i < readsInView.size()-1)
{
samNextRecord = readsInView.get(++i);
if(samRecord.getReadName().equals(samNextRecord.getReadName()))
{
// draw connection between paired reads
if(samRecord.getAlignmentEnd() < samNextRecord.getAlignmentStart() &&
(samNextRecord.getAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
{
g2.setStroke(originalStroke);
g2.setColor(Color.LIGHT_GRAY);
drawTranslucentLine(g2,
(int)((samRecord.getAlignmentEnd()-getBaseAtStartOfView())*pixPerBase),
(int)((samNextRecord.getAlignmentStart()-getBaseAtStartOfView())*pixPerBase), ypos);
}
if( samRecord.getReadNegativeStrandFlag() && // strand of the query (1 for reverse)
samNextRecord.getReadNegativeStrandFlag() )
g2.setColor(Color.red);
else
g2.setColor(Color.blue);
drawRead(g2, samRecord, pixPerBase, stroke, ypos);
drawRead(g2, samNextRecord, pixPerBase, stroke, ypos);
}
else
{
drawLoneRead(g2, samRecord, ypos, pixPerBase, originalStroke, stroke);
i--;
}
}
else
{
drawLoneRead(g2, samRecord, ypos, pixPerBase, originalStroke, stroke);
}
}
drawYScale(g2, scaleHeight);
}
/**
* Draw the reads as lines in vertical stacks. The reads are colour
* coded as follows:
*
* blue - reads are unique and are paired with a mapped mate
* black - reads are unique and are not paired or have an unmapped mate
* green - reads are duplicates
*
* @param g2
* @param seqLength
* @param pixPerBase
* @param start
* @param end
*/
private void drawStackView(Graphics2D g2,
int seqLength,
float pixPerBase,
int start,
int end)
{
drawSelectionRange(g2, pixPerBase,start, end);
if(isShowScale())
drawScale(g2, start, end, pixPerBase);
BasicStroke stroke = new BasicStroke(
1.3f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
int scaleHeight;
if(isShowScale())
scaleHeight = 15;
else
scaleHeight = 0;
int ypos = (getHeight() - scaleHeight);
int maxEnd = 0;
int lstStart = 0;
int lstEnd = 0;
g2.setColor(Color.blue);
for(int i=0; i<readsInView.size(); i++)
{
SAMRecord samRecord = readsInView.get(i);
int recordStart = samRecord.getAlignmentStart();
int recordEnd = samRecord.getAlignmentEnd();
if(lstStart != recordStart || lstEnd != recordEnd)
{
if (!samRecord.getReadPairedFlag() || // read is not paired in sequencing
samRecord.getMateUnmappedFlag() ) // mate is unmapped ) // mate is unmapped
g2.setColor(Color.black);
else
g2.setColor(Color.blue);
if(maxEnd < recordStart)
{
ypos = (getHeight() - scaleHeight)-2;
maxEnd = recordEnd+2;
}
else
ypos = ypos-2;
}
else
g2.setColor(darkGreen);
lstStart = recordStart;
lstEnd = recordEnd;
drawRead(g2, samRecord, pixPerBase, stroke, ypos);
}
}
/**
* Draw the reads as lines in vertical stacks. The reads are colour
* coded as follows:
*
* blue - reads are unique and are paired with a mapped mate
* black - reads are unique and are not paired or have an unmapped mate
* green - reads are duplicates
*
* @param g2
* @param seqLength
* @param pixPerBase
* @param start
* @param end
*/
private void drawPairedStackView(Graphics2D g2,
int seqLength,
float pixPerBase,
int start,
int end)
{
drawSelectionRange(g2, pixPerBase,start, end);
if(isShowScale())
drawScale(g2, start, end, pixPerBase);
Vector<PairedRead> pairedReads = new Vector<PairedRead>();
for(int i=0; i<readsInView.size(); i++)
{
SAMRecord samRecord = readsInView.get(i);
if( !samRecord.getReadPairedFlag() || // read is not paired in sequencing
samRecord.getMateUnmappedFlag() ) // mate is unmapped
continue;
SAMRecord samNextRecord = null;
if(i < readsInView.size()-1)
{
samNextRecord = readsInView.get(++i);
PairedRead pr = new PairedRead();
if(samRecord.getReadName().equals(samNextRecord.getReadName()))
{
if(samRecord.getAlignmentStart() < samNextRecord.getAlignmentStart())
{
pr.sam1 = samRecord;
pr.sam2 = samNextRecord;
}
else
{
pr.sam2 = samRecord;
pr.sam1 = samNextRecord;
}
}
else
{
--i;
pr.sam1 = samRecord;
pr.sam2 = null;
}
pairedReads.add(pr);
}
}
Collections.sort(pairedReads, new PairedReadComparator());
Stroke originalStroke = new BasicStroke (1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
Stroke stroke =
new BasicStroke (1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
int scaleHeight;
if(isShowScale())
scaleHeight = 15;
else
scaleHeight = 0;
int ypos = getHeight() - scaleHeight - 3;
int lastEnd = 0;
for(int i=0; i<pairedReads.size(); i++)
{
PairedRead pr = pairedReads.get(i);
if(pr.sam1.getAlignmentStart() > lastEnd)
{
ypos = getHeight() - scaleHeight - 3;
if(pr.sam2 != null)
{
lastEnd = pr.sam2.getAlignmentEnd();
}
else
lastEnd = pr.sam1.getAlignmentEnd();
}
else
ypos = ypos - 3;
g2.setStroke(originalStroke);
g2.setColor(Color.LIGHT_GRAY);
if(pr.sam2 != null)
{
drawTranslucentJointedLine(g2,
(int)((pr.sam1.getAlignmentEnd()-getBaseAtStartOfView())*pixPerBase),
(int)((pr.sam2.getAlignmentStart()-getBaseAtStartOfView())*pixPerBase), ypos);
}
else
{
if(!pr.sam1.getMateUnmappedFlag())
{
int prStart;
if(pr.sam1.getAlignmentStart() > pr.sam1.getMateAlignmentStart())
prStart = pr.sam1.getAlignmentEnd();
else
prStart = pr.sam1.getAlignmentStart();
drawTranslucentJointedLine(g2,
(int)( (prStart-getBaseAtStartOfView())*pixPerBase),
(int)( (pr.sam1.getMateAlignmentStart()-getBaseAtStartOfView())*pixPerBase), ypos);
}
}
if( pr.sam1.getReadNegativeStrandFlag() && // strand of the query (1 for reverse)
( pr.sam2 != null && pr.sam2.getReadNegativeStrandFlag() ) )
g2.setColor(Color.red);
else
g2.setColor(Color.blue);
drawRead(g2, pr.sam1, pixPerBase, stroke, ypos);
if(pr.sam2 != null)
drawRead(g2, pr.sam2, pixPerBase, stroke, ypos);
}
}
/**
* Draw a read that apparently has a read mate that is not in view.
* @param g2
* @param thisRead
* @param ypos
* @param pixPerBase
* @param originalStroke
* @param stroke
*/
private void drawLoneRead(Graphics2D g2, SAMRecord samRecord, int ypos,
float pixPerBase, Stroke originalStroke, Stroke stroke)
{
boolean offTheTop = false;
int offset = getSequenceOffset(samRecord.getReferenceName());
int thisStart = samRecord.getAlignmentStart()-1+offset;
int thisEnd = thisStart + samRecord.getReadString().length();
if(ypos <= 0)
{
offTheTop = true;
ypos = samRecord.getReadString().length();
}
if(Math.abs(samRecord.getMateAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f)
{
g2.setStroke(originalStroke);
g2.setColor(Color.LIGHT_GRAY);
if(samRecord.getAlignmentEnd() < samRecord.getMateAlignmentStart())
{
int nextStart =
(int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase);
drawTranslucentLine(g2,
(int)((thisStart-getBaseAtStartOfView())*pixPerBase), nextStart, ypos);
}
else
{
int nextStart =
(int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase);
drawTranslucentLine(g2,
(int)((thisEnd-getBaseAtStartOfView())*pixPerBase), nextStart, ypos);
}
}
if(offTheTop)
g2.setColor(darkOrange);
else if(samRecord.getReadNegativeStrandFlag()) // strand of the query (1 for reverse)
g2.setColor(Color.red);
else
g2.setColor(Color.blue);
drawRead(g2, samRecord, pixPerBase, stroke, ypos);
if (isSNPs)
showSNPsOnReads(g2, samRecord, pixPerBase, ypos, offset);
}
private void drawScale(Graphics2D g2, int start, int end, float pixPerBase)
{
g2.setColor(Color.black);
g2.drawLine( 0, getHeight()-14,
(int)((end - getBaseAtStartOfView())*pixPerBase), getHeight()-14);
int interval = end-start;
if(interval > 256000)
drawTicks(g2, start, end, pixPerBase, 512000);
else if(interval > 64000)
drawTicks(g2, start, end, pixPerBase, 12800);
else if(interval > 16000)
drawTicks(g2, start, end, pixPerBase, 3200);
else if(interval > 4000)
drawTicks(g2, start, end, pixPerBase, 800);
else if(interval > 1000)
drawTicks(g2, start, end, pixPerBase, 400);
else
drawTicks(g2, start, end, pixPerBase, 100);
}
private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division)
{
int markStart = (Math.round(start/division)*division);
if(markStart < 1)
markStart = 1;
int sm = markStart-(division/2);
float x;
if(sm > start)
{
x = (sm-getBaseAtStartOfView())*pixPerBase;
g2.drawLine((int)x, getHeight()-14,(int)x, getHeight()-12);
}
for(int m=markStart; m<end; m+=division)
{
x = (m-getBaseAtStartOfView())*pixPerBase;
g2.drawString(Integer.toString(m), x, getHeight()-1);
g2.drawLine((int)x, getHeight()-14,(int)x, getHeight()-11);
sm = m+(division/2);
if(sm < end)
{
x = (sm-getBaseAtStartOfView())*pixPerBase;
g2.drawLine((int)x, getHeight()-14,(int)x, getHeight()-12);
}
if(m == 1)
m = 0;
}
}
/**
* Draw a y-scale for inferred size (isize) of reads.
* @param g2
* @param xScaleHeight
*/
private void drawYScale(Graphics2D g2, int xScaleHeight)
{
g2.setColor(Color.black);
int maxY = getPreferredSize().height-xScaleHeight;
for(int i=100; i<maxY; i+=100)
{
int ypos = getHeight()-i-xScaleHeight;
g2.drawLine(0, ypos, 2, ypos);
g2.drawString(Integer.toString(i), 3, ypos);
}
}
/**
* Draw a given read.
* @param g2
* @param thisRead
* @param pixPerBase
* @param stroke
* @param ypos
*/
private void drawRead(Graphics2D g2, SAMRecord thisRead,
float pixPerBase, Stroke stroke, int ypos)
{
int offset = getSequenceOffset(thisRead.getReferenceName());
int thisStart = thisRead.getAlignmentStart()+offset-getBaseAtStartOfView();
int thisEnd = thisRead.getAlignmentEnd()+offset-getBaseAtStartOfView();
g2.setStroke(stroke);
g2.drawLine((int)( thisStart * pixPerBase), ypos,
(int)( thisEnd * pixPerBase), ypos);
// test if the mouse is over this read
if(lastMousePoint != null)
{
if(lastMousePoint.getY()+2 > ypos && lastMousePoint.getY()-2 < ypos)
if(lastMousePoint.getX() > thisStart * pixPerBase &&
lastMousePoint.getX() < thisEnd * pixPerBase)
{
mouseOverSAMRecord = thisRead;
}
}
if (isSNPs)
showSNPsOnReads(g2, thisRead, pixPerBase, ypos, offset);
}
/**
* Highlight a selected range
* @param g2
* @param pixPerBase
* @param start
* @param end
*/
private void drawSelectionRange(Graphics2D g2, float pixPerBase, int start, int end)
{
if(getSelection() != null)
{
Range selectedRange = getSelection().getSelectionRange();
if(selectedRange != null)
{
int rangeStart = selectedRange.getStart();
int rangeEnd = selectedRange.getEnd();
if(end < rangeStart || start > rangeEnd)
return;
int x = (int) (pixPerBase*(rangeStart-getBaseAtStartOfView()));
int width = (int) (pixPerBase*(rangeEnd-rangeStart+1));
g2.setColor(Color.pink);
g2.fillRect(x, 0, width, getHeight());
}
}
}
/**
* Draw a translucent line
* @param g2
* @param start
* @param end
* @param ypos
*/
private void drawTranslucentLine(Graphics2D g2, int start, int end, int ypos)
{
Composite origComposite = g2.getComposite();
g2.setComposite(translucent);
g2.drawLine(start, ypos, end, ypos);
g2.setComposite(origComposite);
}
/**
* Draw a translucent line
* @param g2
* @param start
* @param end
* @param ypos
*/
private void drawTranslucentJointedLine(Graphics2D g2, int start, int end, int ypos)
{
Composite origComposite = g2.getComposite();
g2.setComposite(translucent);
int mid = (int) ((end-start)/2.f)+start;
//g2.drawLine(start, ypos, end, ypos);
g2.drawLine(start, ypos, mid, ypos-5);
g2.drawLine(mid, ypos-5, end, ypos);
g2.setComposite(origComposite);
}
/**
* Display the SNPs for the given read.
* @param g2
* @param thisRead
* @param pixPerBase
* @param ypos
*/
private void showSNPsOnReads(Graphics2D g2, SAMRecord thisRead,
float pixPerBase, int ypos, int offset)
{
int thisStart = thisRead.getAlignmentStart();
int thisEnd = thisRead.getAlignmentEnd();
// use alignment blocks of the contiguous alignment of
// subsets of read bases to a reference sequence
List<AlignmentBlock> blocks = thisRead.getAlignmentBlocks();
try
{
char[] refSeq = bases.getSubSequenceC(
new Range(thisStart+offset, thisEnd+offset), Bases.FORWARD);
byte[] readSeq = thisRead.getReadBases();
Color col = g2.getColor();
g2.setColor(Color.red);
for(int i=0; i<blocks.size(); i++)
{
AlignmentBlock block = blocks.get(i);
for(int j=0; j<block.getLength(); j++)
{
int readPos = block.getReadStart()-1+j;
int refPos = block.getReferenceStart()+j;
if (Character.toUpperCase(refSeq[refPos-thisStart]) != readSeq[readPos])
{
g2.drawLine((int) ((refPos+offset-getBaseAtStartOfView()) * pixPerBase), ypos + 2,
(int) ((refPos+offset-getBaseAtStartOfView()) * pixPerBase), ypos - 2);
}
}
}
g2.setColor(col);
}
catch (OutOfRangeException e)
{
e.printStackTrace();
}
}
/**
* Add the alignment view to the supplied <code>JPanel</code> in
* a <code>JScrollPane</code>.
* @param mainPanel panel to add the alignment to
* @param autohide automatically hide the top panel containing the buttons
*/
public void addJamToPanel(final JPanel mainPanel,
final JFrame frame,
final boolean autohide,
final FeatureDisplay feature_display)
{
this.mainPanel = mainPanel;
final JComponent topPanel;
if(feature_display != null)
{
this.feature_display = feature_display;
this.selection = feature_display.getSelection();
topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
if(seqNames.size() > 1)
{
int len = 0;
for(int i=0; i<seqNames.size(); i++)
len += seqLengths.get(seqNames.get(i));
if(len == feature_display.getSequenceLength())
concatSequences = true;
}
}
else
{
topPanel = new JMenuBar();
frame.setJMenuBar((JMenuBar)topPanel);
}
// auto hide top panel
final JCheckBox buttonAutoHide = new JCheckBox("Hide", autohide);
buttonAutoHide.setToolTipText("Auto-Hide");
final MouseMotionListener mouseMotionListener = new MouseMotionListener()
{
public void mouseDragged(MouseEvent event)
{
handleCanvasMouseDragOrClick(event);
}
public void mouseMoved(MouseEvent e)
{
lastMousePoint = e.getPoint();
int thisHgt = HEIGHT;
if (thisHgt < 5)
thisHgt = 15;
int y = (int) (e.getY() - jspView.getViewport().getViewRect().getY());
if (y < thisHgt)
{
topPanel.setVisible(true);
}
else
{
if (buttonAutoHide.isSelected())
topPanel.setVisible(false);
}
mainPanel.repaint();
mainPanel.revalidate();
}
};
addMouseMotionListener(mouseMotionListener);
combo = new JComboBox(seqNames);
combo.setEditable(false);
combo.addItemListener(new ItemListener()
{
public void itemStateChanged(ItemEvent e)
{
laststart = -1;
lastend = -1;
setZoomLevel(JamView.this.nbasesInView);
}
});
topPanel.add(combo);
if(feature_display == null)
{
final JTextField baseText = new JTextField(8);
JButton goTo = new JButton("GoTo:");
goTo.setToolTipText("Go to base position");
goTo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
int basePosition = Integer.parseInt(baseText.getText());
scrollBar.setValue(basePosition);
}
catch (NumberFormatException nfe)
{
JOptionPane.showMessageDialog(JamView.this,
"Expecting a base number!", "Number Format",
JOptionPane.WARNING_MESSAGE);
}
}
});
topPanel.add(goTo);
topPanel.add(baseText);
JButton zoomIn = new JButton("-");
Insets ins = new Insets(1,1,1,1);
zoomIn.setMargin(ins);
zoomIn.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
setZoomLevel((int) (JamView.this.nbasesInView * 1.1));
}
});
topPanel.add(zoomIn);
JButton zoomOut = new JButton("+");
zoomOut.setMargin(ins);
zoomOut.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if (showBaseAlignment)
return;
setZoomLevel((int) (JamView.this.nbasesInView * .9));
}
});
topPanel.add(zoomOut);
}
topPanel.add(buttonAutoHide);
mainPanel.setPreferredSize(new Dimension(900, 400));
jspView = new JScrollPane(this,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
setDisplay(1, nbasesInView, null);
mainPanel.setLayout(new BorderLayout());
if(topPanel instanceof JPanel)
mainPanel.add(topPanel, BorderLayout.NORTH);
mainPanel.add(jspView, BorderLayout.CENTER);
JPanel bottomPanel = new JPanel(new BorderLayout());
coveragePanel = new CoveragePanel(this);
bottomPanel.add(coveragePanel, BorderLayout.CENTER);
if(feature_display == null)
{
scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 1, nbasesInView, 1,
getMaxBasesInPanel(getSequenceLength()));
scrollBar.addAdjustmentListener(new AdjustmentListener()
{
public void adjustmentValueChanged(AdjustmentEvent e)
{
repaint();
}
});
bottomPanel.add(scrollBar, BorderLayout.SOUTH);
}
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
coveragePanel.setPreferredSize(new Dimension(900, 100));
coveragePanel.setVisible(false);
jspView.getVerticalScrollBar().setValue(
jspView.getVerticalScrollBar().getMaximum());
if(feature_display == null)
{
addKeyListener(new KeyAdapter()
{
public void keyPressed(final KeyEvent event)
{
switch (event.getKeyCode())
{
case KeyEvent.VK_UP:
setZoomLevel((int) (JamView.this.nbasesInView * 1.1));
break;
case KeyEvent.VK_DOWN:
if (showBaseAlignment)
break;
setZoomLevel((int) (JamView.this.nbasesInView * .9));
break;
default:
break;
}
}
});
}
addMouseListener(new PopupListener());
setFocusable(true);
requestFocusInWindow();
addFocusListener(new FocusListener()
{
public void focusGained(FocusEvent fe){}
public void focusLost(FocusEvent fe){}
});
}
private void createViewMenu(JComponent view)
{
JCheckBoxMenuItem checkBoxSingle = new JCheckBoxMenuItem("Single Reads");
checkBoxSingle.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
repaint();
isSingle = !isSingle;
}
});
view.add(checkBoxSingle);
JCheckBoxMenuItem checkBoxSNPs = new JCheckBoxMenuItem("SNPs");
checkBoxSNPs.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if (isSNPs && bases == null)
{
JOptionPane.showMessageDialog(null,
"No reference sequence supplied to identify SNPs.", "SNPs",
JOptionPane.INFORMATION_MESSAGE);
}
isSNPs = !isSNPs;
repaint();
}
});
view.add(checkBoxSNPs);
final JCheckBoxMenuItem checkBoxStackView = new JCheckBoxMenuItem("Stack View");
final JCheckBoxMenuItem checkBoxPairedStackView = new JCheckBoxMenuItem("Paired Stack View");
checkBoxStackView.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
laststart = -1;
lastend = -1;
isStackView = !isStackView;
if(isStackView)
{
isPairedStackView = !isStackView;
checkBoxPairedStackView.setSelected(!isStackView);
}
repaint();
}
});
view.add(checkBoxStackView);
checkBoxPairedStackView.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
laststart = -1;
lastend = -1;
isPairedStackView = !isPairedStackView;
if(isPairedStackView)
{
isStackView = !isPairedStackView;
checkBoxStackView.setSelected(!isPairedStackView);
}
repaint();
}
});
view.add(checkBoxPairedStackView);
JCheckBoxMenuItem checkBoxCoverage = new JCheckBoxMenuItem("Coverage");
checkBoxCoverage.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
isCoverage = !isCoverage;
coveragePanel.setVisible(isCoverage);
repaint();
}
});
view.add(checkBoxCoverage);
view.add(new JSeparator());
JMenu maxHeightMenu = new JMenu("Plot Height");
view.add(maxHeightMenu);
final String hgts[] =
{"500", "800", "1000", "1500", "2500", "5000"};
ButtonGroup bgroup = new ButtonGroup();
for(int i=0; i<hgts.length; i++)
{
final String hgt = hgts[i];
final JCheckBoxMenuItem maxHeightMenuItem = new JCheckBoxMenuItem(hgt);
bgroup.add(maxHeightMenuItem);
maxHeightMenuItem.setSelected(hgts[i].equals(Integer.toString(maxHeight)));
maxHeightMenu.add(maxHeightMenuItem);
maxHeightMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if(maxHeightMenuItem.isSelected())
maxHeight = Integer.parseInt(hgt);
int start = getBaseAtStartOfView();
setDisplay(start, nbasesInView+start, null);
}
});
}
}
public void setVisible(boolean visible)
{
super.setVisible(visible);
mainPanel.setVisible(visible);
}
private int getBaseAtStartOfView()
{
if(feature_display != null)
return feature_display.getForwardBaseAtLeftEdge();
else
return scrollBar.getValue();
}
/**
* Set the panel size based on the number of bases visible
* and repaint.
* @param nbasesInView
*/
private void setZoomLevel(final int nbasesInView)
{
int startValue = getBaseAtStartOfView();
this.nbasesInView = nbasesInView;
float pixPerBase = getPixPerBaseByWidth();
if(pixPerBase*3 > ALIGNMENT_PIX_PER_BASE)
{
pixPerBase = ALIGNMENT_PIX_PER_BASE;
this.nbasesInView = (int)(getPreferredSize().getWidth()/pixPerBase);
jspView.getVerticalScrollBar().setValue(0);
jspView.setColumnHeaderView(ruler);
showBaseAlignment = true;
}
else if(jspView != null)
{
jspView.setColumnHeaderView(null);
jspView.getVerticalScrollBar().setValue(
jspView.getVerticalScrollBar().getMaximum());
showBaseAlignment = false;
}
if(scrollBar != null)
scrollBar.setValues(startValue, nbasesInView, 1,
getMaxBasesInPanel(getSequenceLength()));
}
/**
* Set the start and end base positions to display.
* @param start
* @param end
* @param event
*/
public void setDisplay(int start,
int end,
DisplayAdjustmentEvent event)
{
this.startBase = start;
this.endBase = end;
this.nbasesInView = end-start+1;
lastMousePoint = null;
float pixPerBase;
if(jspView.getViewport().getViewRect().width > 0)
pixPerBase = getPixPerBaseByWidth();
else
{
if(feature_display == null)
pixPerBase = 1000.f/(float)(end-start+1);
else
pixPerBase = feature_display.getWidth()/(float)(end-start+1);
}
if(pixPerBase*3 > ALIGNMENT_PIX_PER_BASE)
{
pixPerBase = ALIGNMENT_PIX_PER_BASE;
jspView.getVerticalScrollBar().setValue(0);
jspView.setColumnHeaderView(ruler);
showBaseAlignment = true;
}
else if(jspView != null)
{
jspView.setColumnHeaderView(null);
showBaseAlignment = false;
}
Dimension d = new Dimension();
d.setSize(nbasesInView*pixPerBase, maxHeight);
setPreferredSize(d);
if(event == null)
{
this.startBase = -1;
this.endBase = -1;
}
}
/**
* Return an Artemis entry from a file
* @param entryFileName
* @param entryGroup
* @return
* @throws NoSequenceException
*/
private Entry getEntry(final String entryFileName, final EntryGroup entryGroup)
throws NoSequenceException
{
final Document entry_document = DocumentFactory.makeDocument(entryFileName);
final EntryInformation artemis_entry_information =
Options.getArtemisEntryInformation();
//System.out.println(entryFileName);
final uk.ac.sanger.artemis.io.Entry new_embl_entry =
EntryFileDialog.getEntryFromFile(null, entry_document,
artemis_entry_information,
false);
if(new_embl_entry == null) // the read failed
return null;
Entry entry = null;
try
{
if(entryGroup.getSequenceEntry() != null)
bases = entryGroup.getSequenceEntry().getBases();
if(bases == null)
{
entry = new Entry(new_embl_entry);
bases = entry.getBases();
}
else
entry = new Entry(bases,new_embl_entry);
entryGroup.add(entry);
}
catch(OutOfRangeException e)
{
new MessageDialog(null, "read failed: one of the features in " +
entryFileName + " has an out of range " +
"location: " + e.getMessage());
}
return entry;
}
private boolean isShowScale()
{
return showScale;
}
public void setShowScale(boolean showScale)
{
this.showScale = showScale;
}
public JScrollPane getJspView()
{
return jspView;
}
public void setBases(Bases bases)
{
this.bases = bases;
}
/**
* Remove JScrollPane border
*/
public void removeBorder()
{
Border empty = new EmptyBorder(0,0,0,0);
jspView.setBorder(empty);
}
/**
* Handle a mouse drag event on the drawing canvas.
**/
private void handleCanvasMouseDragOrClick(final MouseEvent event)
{
if(event.isShiftDown() || event.getButton() == MouseEvent.BUTTON3
|| bases == null)
return;
if(event.getClickCount() > 1)
{
getSelection().clear();
repaint();
return;
}
int onmask = MouseEvent.BUTTON1_DOWN_MASK;
int seqLength = getSequenceLength();
float pixPerBase = getPixPerBaseByWidth();
int start = (int) ( ( (event.getPoint().getX())/pixPerBase) + getBaseAtStartOfView() );
if (dragStart < 0 && (event.getModifiersEx() & onmask) == onmask)
dragStart = start;
else if((event.getModifiersEx() & onmask) != onmask)
dragStart = -1;
if(start < 1)
start = 1;
if(start > seqLength)
start = seqLength;
MarkerRange drag_range;
try
{
if(dragStart < 0)
drag_range = new MarkerRange (bases.getForwardStrand(), start, start);
else
drag_range = new MarkerRange (bases.getForwardStrand(), dragStart, start);
getSelection().setMarkerRange(drag_range);
repaint();
}
catch (OutOfRangeException e)
{
e.printStackTrace();
}
}
private Selection getSelection()
{
return selection;
}
public List<SAMRecord> getReadsInView()
{
return readsInView;
}
public Dimension getPreferredScrollableViewportSize()
{
return getPreferredSize();
}
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction)
{
if (orientation == SwingConstants.HORIZONTAL)
return visibleRect.width - maxUnitIncrement;
else
return visibleRect.height - maxUnitIncrement;
}
public boolean getScrollableTracksViewportHeight()
{
return false;
}
public boolean getScrollableTracksViewportWidth()
{
return false;
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
int direction)
{
//Get the current position.
int currentPosition = 0;
if (orientation == SwingConstants.HORIZONTAL)
currentPosition = visibleRect.x;
else
currentPosition = visibleRect.y;
//Return the number of pixels between currentPosition
//and the nearest tick mark in the indicated direction.
if (direction < 0)
{
int newPosition = currentPosition -
(currentPosition / maxUnitIncrement)
* maxUnitIncrement;
return (newPosition == 0) ? maxUnitIncrement : newPosition;
}
else
{
return ((currentPosition / maxUnitIncrement) + 1)
* maxUnitIncrement
- currentPosition;
}
}
/**
* Artemis event notification
*/
public void displayAdjustmentValueChanged(DisplayAdjustmentEvent event)
{
if(event.getType() == DisplayAdjustmentEvent.SCALE_ADJUST_EVENT)
{
laststart = -1;
lastend = -1;
this.startBase = event.getStart();
this.endBase = event.getEnd();
int width = feature_display.getMaxVisibleBases();
setZoomLevel(width);
repaint();
}
else
{
setDisplay(event.getStart(),
event.getStart()+feature_display.getMaxVisibleBases(), event);
repaint();
}
}
public void selectionChanged(SelectionChangeEvent event)
{
repaint();
}
private class Ruler extends JPanel
{
private static final long serialVersionUID = 1L;
int start;
int end;
public Ruler()
{
super();
setPreferredSize(new Dimension(getPreferredSize().width, 15));
setBackground(Color.white);
setFont(getFont().deriveFont(11.f));
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
drawBaseScale(g2, start, end, 12);
}
private void drawBaseScale(Graphics2D g2, int start, int end, int ypos)
{
int startMark = (((int)(start/10))*10)+1;
for(int i=startMark; i<end; i+=10)
{
int xpos = (i-start)*ALIGNMENT_PIX_PER_BASE;
g2.drawString(Integer.toString(i), xpos, ypos);
xpos+=(ALIGNMENT_PIX_PER_BASE/2);
g2.drawLine(xpos, ypos+1, xpos, ypos+5);
}
}
}
/**
* Popup menu listener
*/
class PopupListener extends MouseAdapter
{
public void mouseClicked(MouseEvent e)
{
JamView.this.requestFocus();
handleCanvasMouseDragOrClick(e);
}
public void mousePressed(MouseEvent e)
{
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e)
{
dragStart = -1;
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e)
{
if(e.isPopupTrigger())
{
if(popup == null)
{
popup = new JPopupMenu();
createViewMenu(popup);
}
popup.show(e.getComponent(),
e.getX(), e.getY());
}
}
}
class PairedRead
{
SAMRecord sam1;
SAMRecord sam2;
}
public static void main(String[] args)
{
String bam = args[0];
int nbasesInView = 1000;
String reference = null;
for(int i=0;i<args.length; i++)
{
if(args[i].equals("-a"))
bam = args[++i];
else if(args[i].equals("-r"))
reference = args[++i];
else if(args[i].equals("-v"))
nbasesInView = Integer.parseInt(args[++i]);
else if(args[i].equals("-s"))
System.setProperty("samtoolDir", args[++i]);
else if(args[i].startsWith("-h"))
{
System.out.println("-h\t show help");
System.out.println("-a\t BAM/SAM file to display");
System.out.println("-r\t reference file (optional)");
System.out.println("-v\t number of bases to display in the view (optional)");
/*System.out.println("-s\t samtool directory");*/
System.exit(0);
}
}
final JamView view = new JamView(bam, reference, nbasesInView);
JFrame frame = new JFrame("JAM");
// translucent
//frame.getRootPane().putClientProperty("Window.alpha", new Float(0.9f));
frame.addWindowFocusListener(new WindowFocusListener()
{
public void windowGainedFocus(WindowEvent e)
{
view.requestFocus();
}
public void windowLostFocus(WindowEvent e){}
});
view.addJamToPanel((JPanel)frame.getContentPane(), frame, false, null);
frame.pack();
view.jspView.getVerticalScrollBar().setValue(
view.jspView.getVerticalScrollBar().getMaximum());
frame.setVisible(true);
}
}