/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2015 Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.broad.igv.sam;
import org.apache.log4j.Logger;
import org.broad.igv.Globals;
import org.broad.igv.feature.FeatureUtils;
import org.broad.igv.feature.Locus;
import org.broad.igv.feature.Range;
import org.broad.igv.feature.Strand;
import org.broad.igv.feature.genome.ChromosomeNameComparator;
import org.broad.igv.feature.genome.Genome;
import org.broad.igv.lists.GeneList;
import org.broad.igv.prefs.Constants;
import org.broad.igv.prefs.IGVPreferences;
import org.broad.igv.prefs.PreferenceEditorFX;
import org.broad.igv.prefs.PreferencesManager;
import org.broad.igv.renderer.GraphicUtils;
import org.broad.igv.session.IGVSessionReader;
import org.broad.igv.session.Session;
import org.broad.igv.session.SubtlyImportant;
import org.broad.igv.tools.PFMExporter;
import org.broad.igv.track.*;
import org.broad.igv.ui.FontManager;
import org.broad.igv.ui.IGV;
import org.broad.igv.ui.InsertSizeSettingsDialog;
import org.broad.igv.ui.SashimiPlot;
import org.broad.igv.ui.color.ColorTable;
import org.broad.igv.ui.color.ColorUtilities;
import org.broad.igv.ui.color.PaletteColorTable;
import org.broad.igv.event.AlignmentTrackEvent;
import org.broad.igv.event.IGVEventBus;
import org.broad.igv.event.IGVEventObserver;
import org.broad.igv.ui.panel.DataPanel;
import org.broad.igv.ui.panel.FrameManager;
import org.broad.igv.ui.panel.IGVPopupMenu;
import org.broad.igv.ui.panel.ReferenceFrame;
import org.broad.igv.ui.util.MessageUtils;
import org.broad.igv.ui.util.UIUtilities;
import org.broad.igv.util.Pair;
import org.broad.igv.util.ResourceLocator;
import org.broad.igv.util.StringUtils;
import org.broad.igv.util.Utilities;
import org.broad.igv.util.blat.BlatClient;
import org.broad.igv.util.collections.CollUtils;
import org.broad.igv.util.extview.ExtendViewClient;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.swing.*;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.text.NumberFormat;
import java.util.*;
import java.util.List;
import static org.broad.igv.prefs.Constants.*;
/**
* @author jrobinso
*/
@XmlType(factoryMethod = "getNextTrack")
@XmlSeeAlso(AlignmentTrack.RenderOptions.class)
public class AlignmentTrack extends AbstractTrack implements IGVEventObserver {
private static Logger log = Logger.getLogger(AlignmentTrack.class);
public static final int GROUP_LABEL_HEIGHT = 10;
static final int GROUP_MARGIN = 5;
static final int TOP_MARGIN = 20;
static final int DS_MARGIN_0 = 2;
static final int DOWNAMPLED_ROW_HEIGHT = 3;
static final int INSERTION_ROW_HEIGHT = 9;
static final int DS_MARGIN_2 = 5;
private final AlignmentRenderer renderer;
private boolean removed = false;
private RenderRollback renderRollback;
private boolean showGroupLine;
private Map<ReferenceFrame, List<InsertionInterval>> insertionIntervalsMap;
public enum ShadeBasesOption {
NONE, QUALITY
}
enum ColorOption {
INSERT_SIZE, READ_STRAND, FIRST_OF_PAIR_STRAND, PAIR_ORIENTATION, SAMPLE, READ_GROUP, LIBRARY, BISULFITE, NOMESEQ,
TAG, NONE, UNEXPECTED_PAIR, MAPPED_SIZE, LINK_STRAND
}
public enum SortOption {
START, STRAND, NUCLEOTIDE, QUALITY, SAMPLE, READ_GROUP, INSERT_SIZE, FIRST_OF_PAIR_STRAND, MATE_CHR, TAG, SUPPLEMENTARY, NONE;
}
public enum GroupOption {
STRAND, SAMPLE, READ_GROUP, LIBRARY, FIRST_OF_PAIR_STRAND, TAG, PAIR_ORIENTATION, MATE_CHROMOSOME, NONE, SUPPLEMENTARY, BASE_AT_POS
}
public enum BisulfiteContext {
CG, CHH, CHG, HCG, GCH, WCG, NONE
}
enum OrientationType {
RR, LL, RL, LR, UNKNOWN
}
protected static final Map<BisulfiteContext, String> bisulfiteContextToPubString = new HashMap<BisulfiteContext, String>();
static {
bisulfiteContextToPubString.put(BisulfiteContext.CG, "CG");
bisulfiteContextToPubString.put(BisulfiteContext.CHH, "CHH");
bisulfiteContextToPubString.put(BisulfiteContext.CHG, "CHG");
bisulfiteContextToPubString.put(BisulfiteContext.HCG, "HCG");
bisulfiteContextToPubString.put(BisulfiteContext.GCH, "GCH");
bisulfiteContextToPubString.put(BisulfiteContext.WCG, "WCG");
bisulfiteContextToPubString.put(BisulfiteContext.NONE, "None");
}
protected static final Map<BisulfiteContext, Pair<byte[], byte[]>> bisulfiteContextToContextString = new HashMap<BisulfiteContext, Pair<byte[], byte[]>>();
static {
bisulfiteContextToContextString.put(BisulfiteContext.CG, new Pair<byte[], byte[]>(new byte[]{}, new byte[]{'G'}));
bisulfiteContextToContextString.put(BisulfiteContext.CHH, new Pair<byte[], byte[]>(new byte[]{}, new byte[]{'H', 'H'}));
bisulfiteContextToContextString.put(BisulfiteContext.CHG, new Pair<byte[], byte[]>(new byte[]{}, new byte[]{'H', 'G'}));
bisulfiteContextToContextString.put(BisulfiteContext.HCG, new Pair<byte[], byte[]>(new byte[]{'H'}, new byte[]{'G'}));
bisulfiteContextToContextString.put(BisulfiteContext.GCH, new Pair<byte[], byte[]>(new byte[]{'G'}, new byte[]{'H'}));
bisulfiteContextToContextString.put(BisulfiteContext.WCG, new Pair<byte[], byte[]>(new byte[]{'W'}, new byte[]{'G'}));
}
static final BisulfiteContext DEFAULT_BISULFITE_CONTEXT = BisulfiteContext.CG;
private SequenceTrack sequenceTrack;
private CoverageTrack coverageTrack;
private SpliceJunctionTrack spliceJunctionTrack;
private RenderOptions renderOptions = new RenderOptions(AlignmentDataManager.ExperimentType.OTHER);
private int expandedHeight = 14;
private int collapsedHeight = 9;
private int maxSquishedHeight = 5;
private int squishedHeight = maxSquishedHeight;
private int minHeight = 50;
private AlignmentDataManager dataManager;
private Rectangle alignmentsRect;
private Rectangle downsampleRect;
private Rectangle insertionRect;
private ColorTable readNamePalette;
// Dynamic fields
// The "DataPanel" containing the track. This field might be null at any given time. It is updated each repaint.
private JComponent dataPanel;
private HashMap<String, Color> selectedReadNames = new HashMap();
private HashMap<Rectangle, String> groupNames = new HashMap<>();
public static void sortAlignmentTracks(SortOption option, String tag) {
IGV.getInstance().sortAlignmentTracks(option, tag);
Collection<IGVPreferences> allPrefs = PreferencesManager.getAllPreferences();
for (IGVPreferences prefs : allPrefs) {
prefs.put(SAM_SORT_OPTION, option.toString());
prefs.put(SAM_SORT_BY_TAG, tag);
}
refresh();
}
/**
* Create a new alignment track
*
* @param locator
* @param dataManager
* @param genome
*/
public AlignmentTrack(ResourceLocator locator, AlignmentDataManager dataManager, Genome genome) {
super(locator);
this.dataManager = dataManager;
minimumHeight = 50;
maximumHeight = Integer.MAX_VALUE;
IGVPreferences prefs = getPreferences();
renderer = new AlignmentRenderer(this);
showGroupLine = getPreferences().getAsBoolean(SAM_SHOW_GROUP_SEPARATOR);
setDisplayMode(DisplayMode.EXPANDED);
if (prefs.getAsBoolean(SAM_SHOW_REF_SEQ)) {
sequenceTrack = new SequenceTrack("Reference sequence");
sequenceTrack.setHeight(14);
}
readNamePalette = new PaletteColorTable(ColorUtilities.getDefaultPalette());
this.insertionIntervalsMap = Collections.synchronizedMap(new HashMap<>());
IGVEventBus.getInstance().subscribe(FrameManager.ChangeEvent.class, this);
IGVEventBus.getInstance().subscribe(ExperimentTypeChangeEvent.class, this);
IGVEventBus.getInstance().subscribe(AlignmentTrackEvent.class, this);
}
@Override
public void receiveEvent(Object event) {
// Trim insertionInterval map to current frames
if (event instanceof FrameManager.ChangeEvent) {
Map<ReferenceFrame, List<InsertionInterval>> newMap = Collections.synchronizedMap(new HashMap<>());
for (ReferenceFrame frame : ((FrameManager.ChangeEvent) event).getFrames()) {
if (insertionIntervalsMap.containsKey(frame)) {
newMap.put(frame, insertionIntervalsMap.get(frame));
}
}
insertionIntervalsMap = newMap;
} else if (event instanceof ExperimentTypeChangeEvent) {
if (((ExperimentTypeChangeEvent) event).source == dataManager) {
log.info("Experiment type = " + ((ExperimentTypeChangeEvent) event).type);
renderOptions = new RenderOptions(((ExperimentTypeChangeEvent) event).type);
boolean showJunction = getPreferences().getAsBoolean(Constants.SAM_SHOW_JUNCTION_TRACK);
if (showJunction != spliceJunctionTrack.isVisible()) {
spliceJunctionTrack.setVisible(showJunction);
IGV.getInstance().revalidateTrackPanels();
}
}
} else if (event instanceof AlignmentTrackEvent) {
AlignmentTrackEvent e = (AlignmentTrackEvent) event;
AlignmentTrackEvent.Type eventType = e.getType();
switch (eventType) {
case ALLELE_THRESHOLD:
dataManager.alleleThresholdChanged();
break;
case RELOAD:
clearCaches();
case REFRESH:
setRenderOptions(new RenderOptions(dataManager.getType()));
refresh();
break;
}
}
}
public void setCoverageTrack(CoverageTrack coverageTrack) {
this.coverageTrack = coverageTrack;
this.coverageTrack.setRenderOptions(this.renderOptions);
}
@Override
public void setVisible(boolean visible) {
if (visible != this.isVisible()) {
super.setVisible(visible);
if (dataManager != null) {
dataManager.setShowAlignments(visible);
}
if(IGV.hasInstance()) IGV.getInstance().getMainPanel().revalidate();
}
}
@XmlElement(name = RenderOptions.NAME)
private void setRenderOptions(RenderOptions renderOptions) {
this.renderOptions = renderOptions;
if (this.coverageTrack != null) {
this.coverageTrack.setRenderOptions(this.renderOptions);
}
if(this.spliceJunctionTrack != null) {
this.spliceJunctionTrack.setRenderOptions(this.renderOptions);
}
}
@SubtlyImportant
private RenderOptions getRenderOptions() {
return this.renderOptions;
}
public CoverageTrack getCoverageTrack() {
return coverageTrack;
}
public void setSpliceJunctionTrack(SpliceJunctionTrack spliceJunctionTrack) {
this.spliceJunctionTrack = spliceJunctionTrack;
}
public SpliceJunctionTrack getSpliceJunctionTrack() {
return spliceJunctionTrack;
}
@Override
public IGVPopupMenu getPopupMenu(TrackClickEvent te) {
Alignment alignment = getAlignment(te);
if (alignment != null && alignment.getInsertions() != null) {
for (AlignmentBlock block : alignment.getInsertions()) {
if (block.containsPixel(te.getMouseEvent().getX())) {
return new InsertionMenu(block);
}
}
}
return new PopupMenu(te);
}
@Override
public void setHeight(int preferredHeight) {
super.setHeight(preferredHeight);
minimumHeight = preferredHeight;
}
@Override
public int getHeight() {
if (dataPanel != null
&& (dataPanel instanceof DataPanel && ((DataPanel) dataPanel).getFrame().getScale() > dataManager.getMinVisibleScale())) {
return minimumHeight;
}
int nGroups = dataManager.getMaxGroupCount();
int h = Math.max(minHeight, getNLevels() * getRowHeight() + nGroups * GROUP_MARGIN + TOP_MARGIN
+ DS_MARGIN_0 + DOWNAMPLED_ROW_HEIGHT + DS_MARGIN_2);
//if (insertionRect != null) { // TODO - replace with expand insertions preference
h += INSERTION_ROW_HEIGHT + DS_MARGIN_0;
//}
h = Math.min(maximumHeight, h);
return h;
}
private int getRowHeight() {
if (getDisplayMode() == DisplayMode.EXPANDED) {
return expandedHeight;
} else if (getDisplayMode() == DisplayMode.COLLAPSED) {
return collapsedHeight;
} else {
return squishedHeight;
}
}
private int getNLevels() {
return dataManager.getNLevels();
}
@Override
public boolean isReadyToPaint(ReferenceFrame frame) {
if (frame.getScale() > dataManager.getMinVisibleScale()) {
return true; // Nothing to paint
} else {
}
List<InsertionInterval> insertionIntervals = getInsertionIntervals(frame);
insertionIntervals.clear();
return dataManager.isLoaded(frame);
}
@Override
public void load(ReferenceFrame referenceFrame) {
dataManager.load(referenceFrame, renderOptions, true);
}
public void render(RenderContext context, Rectangle rect) {
Graphics2D g = context.getGraphics2D("LABEL");
g.setFont(FontManager.getFont(GROUP_LABEL_HEIGHT));
dataPanel = context.getPanel();
// Split track rectangle into sections.
int seqHeight = sequenceTrack == null ? 0 : sequenceTrack.getHeight();
if (seqHeight > 0) {
Rectangle seqRect = new Rectangle(rect);
seqRect.height = seqHeight;
sequenceTrack.render(context, seqRect);
}
// Top gap.
rect.y += DS_MARGIN_0;
if (context.getScale() > dataManager.getMinVisibleScale()) {
Rectangle visibleRect = context.getVisibleRect().intersection(rect);
Graphics2D g2 = context.getGraphic2DForColor(Color.gray);
GraphicUtils.drawCenteredText("Zoom in to see alignments.", visibleRect, g2);
return;
}
downsampleRect = new Rectangle(rect);
downsampleRect.height = DOWNAMPLED_ROW_HEIGHT;
renderDownsampledIntervals(context, downsampleRect);
if (renderOptions.drawInsertionIntervals) {
insertionRect = new Rectangle(rect);
insertionRect.y += DOWNAMPLED_ROW_HEIGHT + DS_MARGIN_0;
insertionRect.height = INSERTION_ROW_HEIGHT;
renderInsertionIntervals(context, insertionRect);
rect.y = insertionRect.y + insertionRect.height;
}
alignmentsRect = new Rectangle(rect);
alignmentsRect.y += 2;
alignmentsRect.height -= (alignmentsRect.y - rect.y);
renderAlignments(context, alignmentsRect);
}
private void renderDownsampledIntervals(RenderContext context, Rectangle downsampleRect) {
// Might be offscreen
if (!context.getVisibleRect().intersects(downsampleRect)) return;
final AlignmentInterval loadedInterval = dataManager.getLoadedInterval(context.getReferenceFrame());
if (loadedInterval == null) return;
Graphics2D g = context.getGraphic2DForColor(Color.black);
List<DownsampledInterval> intervals = loadedInterval.getDownsampledIntervals();
for (DownsampledInterval interval : intervals) {
final double scale = context.getScale();
final double origin = context.getOrigin();
int x0 = (int) ((interval.getStart() - origin) / scale);
int x1 = (int) ((interval.getEnd() - origin) / scale);
int w = Math.max(1, x1 - x0);
// If there is room, leave a gap on one side
if (w > 5) w--;
// Greyscale from 0 -> 100 downsampled
//int gray = 200 - interval.getCount();
//Color color = (gray <= 0 ? Color.black : ColorUtilities.getGrayscaleColor(gray));
g.fillRect(x0, downsampleRect.y, w, downsampleRect.height);
}
}
private List<InsertionInterval> getInsertionIntervals(ReferenceFrame frame) {
List<InsertionInterval> insertionIntervals = insertionIntervalsMap.get(frame);
if (insertionIntervals == null) {
insertionIntervals = new ArrayList<>();
insertionIntervalsMap.put(frame, insertionIntervals);
}
return insertionIntervals;
}
private void renderInsertionIntervals(RenderContext context, Rectangle rect) {
// Might be offscreen
if (!context.getVisibleRect().intersects(rect)) return;
List<InsertionMarker> intervals = context.getInsertionMarkers();
if (intervals == null) return;
InsertionMarker selected = InsertionManager.getInstance().getSelectedInsertion(context.getChr());
int w = (int) ((1.41 * rect.height) / 2);
boolean hideSmallIndex = getPreferences().getAsBoolean(SAM_HIDE_SMALL_INDEL);
int smallIndelThreshold = getPreferences().getAsInt(SAM_SMALL_INDEL_BP_THRESHOLD);
List<InsertionInterval> insertionIntervals = getInsertionIntervals(context.getReferenceFrame());
insertionIntervals.clear();
for (InsertionMarker insertionMarker : intervals) {
if (hideSmallIndex && insertionMarker.size < smallIndelThreshold) continue;
final double scale = context.getScale();
final double origin = context.getOrigin();
int midpoint = (int) ((insertionMarker.position - origin) / scale);
int x0 = midpoint - w;
int x1 = midpoint + w;
Rectangle iRect = new Rectangle(x0 + context.translateX, rect.y, 2 * w, rect.height);
insertionIntervals.add(new InsertionInterval(iRect, insertionMarker));
Color c = (selected != null && selected.position == insertionMarker.position) ? Color.red : AlignmentRenderer.purple;
Graphics2D g = context.getGraphic2DForColor(c);
g.fillPolygon(new Polygon(new int[]{x0, x1, midpoint},
new int[]{rect.y, rect.y, rect.y + rect.height}, 3));
}
}
private void renderAlignments(RenderContext context, Rectangle inputRect) {
groupNames.clear();
//log.debug("Render features");
PackedAlignments groups = dataManager.getGroups(context, renderOptions);
if (groups == null) {
//Assume we are still loading.
//This might not always be true
return;
}
Map<String, PEStats> peStats = dataManager.getPEStats();
if (peStats != null) {
renderOptions.peStats = peStats;
}
Rectangle visibleRect = context.getVisibleRect();
final boolean leaveMargin = (getDisplayMode() != DisplayMode.SQUISHED);
maximumHeight = Integer.MAX_VALUE;
// Divide rectangle into equal height levels
double y = inputRect.getY();
double h;
if (getDisplayMode() == DisplayMode.EXPANDED) {
h = expandedHeight;
} else if (getDisplayMode() == DisplayMode.COLLAPSED) {
h = collapsedHeight;
} else {
int visHeight = visibleRect.height;
int depth = dataManager.getNLevels();
if (depth == 0) {
squishedHeight = Math.min(maxSquishedHeight, Math.max(1, expandedHeight));
} else {
squishedHeight = Math.min(maxSquishedHeight, Math.max(1, Math.min(expandedHeight, visHeight / depth)));
}
h = squishedHeight;
}
// Loop through groups
Graphics2D groupBorderGraphics = context.getGraphic2DForColor(AlignmentRenderer.GROUP_DIVIDER_COLOR);
int nGroups = groups.size();
int groupNumber = 0;
GroupOption groupOption = renderOptions.getGroupByOption();
String groupByTag = renderOptions.getGroupByTag();
for (Map.Entry<String, List<Row>> entry : groups.entrySet()) {
groupNumber++;
double yGroup = y; // Remember this for label
// Loop through the alignment rows for this group
List<Row> rows = entry.getValue();
for (Row row : rows) {
if ((visibleRect != null && y > visibleRect.getMaxY())) {
return;
}
if (y + h > visibleRect.getY()) {
Rectangle rowRectangle = new Rectangle(inputRect.x, (int) y, inputRect.width, (int) h);
AlignmentCounts alignmentCounts = dataManager.getLoadedInterval(context.getReferenceFrame()).getCounts();
renderer.renderAlignments(row.alignments, context, rowRectangle,
inputRect, renderOptions, leaveMargin, selectedReadNames, alignmentCounts, getPreferences());
row.y = y;
row.h = h;
}
y += h;
}
if (groupOption != GroupOption.NONE) {
// Draw a subtle divider line between groups
if (showGroupLine) {
if (groupNumber < nGroups) {
int borderY = (int) y + GROUP_MARGIN / 2;
GraphicUtils.drawDottedDashLine(groupBorderGraphics, inputRect.x, borderY, inputRect.width, borderY);
}
}
// Label the group, if there is room
double groupHeight = rows.size() * h;
if (groupHeight > GROUP_LABEL_HEIGHT + 2) {
String groupName = entry.getKey();
Graphics2D g = context.getGraphics2D("LABEL");
FontMetrics fm = g.getFontMetrics();
Rectangle2D stringBouds = fm.getStringBounds(groupName, g);
Rectangle rect = new Rectangle(inputRect.x, (int) yGroup, (int) stringBouds.getWidth() + 10, (int) stringBouds.getHeight());
GraphicUtils.drawVerticallyCenteredText(
groupName, 5, rect, context.getGraphics2D("LABEL"), false, true);
groupNames.put(new Rectangle(inputRect.x, (int) yGroup, inputRect.width, (int) (y - yGroup)), groupName);
}
}
y += GROUP_MARGIN;
}
final int bottom = inputRect.y + inputRect.height;
groupBorderGraphics.drawLine(inputRect.x, bottom, inputRect.width, bottom);
}
public void renderExpandedInsertion(InsertionMarker insertionMarker, RenderContext context, Rectangle inputRect) {
boolean leaveMargin = getDisplayMode() != DisplayMode.COLLAPSED.SQUISHED;
// Insertion interval
Graphics2D g = context.getGraphic2DForColor(Color.red);
Rectangle iRect = new Rectangle(inputRect.x, insertionRect.y, inputRect.width, insertionRect.height);
g.fill(iRect);
List<InsertionInterval> insertionIntervals = getInsertionIntervals(context.getReferenceFrame());
iRect.x += context.translateX;
insertionIntervals.add(new InsertionInterval(iRect, insertionMarker));
inputRect.y += DS_MARGIN_0 + DOWNAMPLED_ROW_HEIGHT + DS_MARGIN_0 + INSERTION_ROW_HEIGHT + DS_MARGIN_2;
//log.debug("Render features");
PackedAlignments groups = dataManager.getGroups(context, renderOptions);
if (groups == null) {
//Assume we are still loading.
//This might not always be true
return;
}
Rectangle visibleRect = context.getVisibleRect();
maximumHeight = Integer.MAX_VALUE;
// Divide rectangle into equal height levels
double y = inputRect.getY();
double h;
if (getDisplayMode() == DisplayMode.EXPANDED) {
h = expandedHeight;
} else {
int visHeight = visibleRect.height;
int depth = dataManager.getNLevels();
if (depth == 0) {
squishedHeight = Math.min(maxSquishedHeight, Math.max(1, expandedHeight));
} else {
squishedHeight = Math.min(maxSquishedHeight, Math.max(1, Math.min(expandedHeight, visHeight / depth)));
}
h = squishedHeight;
}
for (Map.Entry<String, List<Row>> entry : groups.entrySet()) {
// Loop through the alignment rows for this group
List<Row> rows = entry.getValue();
for (Row row : rows) {
if ((visibleRect != null && y > visibleRect.getMaxY())) {
return;
}
if (y + h > visibleRect.getY()) {
Rectangle rowRectangle = new Rectangle(inputRect.x, (int) y, inputRect.width, (int) h);
renderer.renderExpandedInsertion(insertionMarker, row.alignments, context, rowRectangle, leaveMargin);
row.y = y;
row.h = h;
}
y += h;
}
y += GROUP_MARGIN;
}
}
//
// public void renderExpandedInsertions(RenderContext context, Rectangle inputRect) {
//
//
// boolean leaveMargin = getDisplayMode() != DisplayMode.COLLAPSED.SQUISHED;
//
// inputRect.y += DOWNAMPLED_ROW_HEIGHT + DS_MARGIN_2;
//
// //log.debug("Render features");
// PackedAlignments groups = dataManager.getGroups(context, renderOptions);
// if (groups == null) {
// //Assume we are still loading.
// //This might not always be true
// return;
// }
//
// Rectangle visibleRect = context.getVisibleRect();
//
//
// maximumHeight = Integer.MAX_VALUE;
//
// // Divide rectangle into equal height levels
// double y = inputRect.getY();
// double h;
// if (getDisplayMode() == DisplayMode.EXPANDED) {
// h = expandedHeight;
// } else {
//
// int visHeight = visibleRect.height;
// int depth = dataManager.getNLevels();
// if (depth == 0) {
// squishedHeight = Math.min(maxSquishedHeight, Math.max(1, expandedHeight));
// } else {
// squishedHeight = Math.min(maxSquishedHeight, Math.max(1, Math.min(expandedHeight, visHeight / depth)));
// }
// h = squishedHeight;
// }
//
//
// for (Map.Entry<String, List<Row>> entry : groups.entrySet()) {
//
//
// // Loop through the alignment rows for this group
// List<Row> rows = entry.getValue();
// for (Row row : rows) {
// if ((visibleRect != null && y > visibleRect.getMaxY())) {
// return;
// }
//
// if (y + h > visibleRect.getY()) {
// Rectangle rowRectangle = new Rectangle(inputRect.x, (int) y, inputRect.width, (int) h);
// renderer.renderExpandedInsertions(row.alignments, context, rowRectangle, leaveMargin);
// row.y = y;
// row.h = h;
// }
// y += h;
// }
//
// y += GROUP_MARGIN;
//
//
// }
// }
/**
* Sort alignment rows based on alignments that intersect location
*
* @return Whether sorting was performed. If data is still loading, this will return false
*/
public boolean sortRows(SortOption option, ReferenceFrame referenceFrame, double location, String tag) {
return dataManager.sortRows(option, referenceFrame, location, tag);
}
/**
* Visually regroup alignments by the provided {@code GroupOption}.
*
* @param option
* @see AlignmentDataManager#packAlignments
*/
public void groupAlignments(GroupOption option, String tag, Range pos) {
if (option == GroupOption.TAG && tag != null) {
renderOptions.setGroupByTag(tag);
}
if (option == GroupOption.BASE_AT_POS && pos != null) {
renderOptions.setGroupByPos(pos);
}
renderOptions.groupByOption = (option == GroupOption.NONE ? null : option);
dataManager.packAlignments(renderOptions);
}
public void packAlignments() {
dataManager.packAlignments(renderOptions);
}
/**
* Copy the contents of the popup text to the system clipboard.
*/
public void copyToClipboard(final TrackClickEvent e, Alignment alignment, double location, int mouseX) {
if (alignment != null) {
StringBuffer buf = new StringBuffer();
buf.append(alignment.getValueString(location, mouseX, null).replace("<br>", "\n"));
buf.append("\n");
buf.append("Alignment start position = " + alignment.getChr() + ":" + (alignment.getAlignmentStart() + 1));
buf.append("\n");
buf.append(alignment.getReadSequence());
StringSelection stringSelection = new StringSelection(buf.toString());
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(stringSelection, null);
}
}
/**
* Jump to the mate region
*/
public void gotoMate(final TrackClickEvent te, Alignment alignment) {
if (alignment != null) {
ReadMate mate = alignment.getMate();
if (mate != null && mate.isMapped()) {
setSelected(alignment);
String chr = mate.getChr();
int start = mate.start - 1;
// Don't change scale
double range = te.getFrame().getEnd() - te.getFrame().getOrigin();
int newStart = (int) Math.max(0, (start + (alignment.getEnd() - alignment.getStart()) / 2 - range / 2));
int newEnd = newStart + (int) range;
te.getFrame().jumpTo(chr, newStart, newEnd);
te.getFrame().recordHistory();
} else {
MessageUtils.showMessage("Alignment does not have mate, or it is not mapped.");
}
}
}
/**
* Split the screen so the current view and mate region are side by side.
* Need a better name for this method.
*/
public void splitScreenMate(final TrackClickEvent te, Alignment alignment) {
if (alignment != null) {
ReadMate mate = alignment.getMate();
if (mate != null && mate.isMapped()) {
setSelected(alignment);
String mateChr = mate.getChr();
int mateStart = mate.start - 1;
ReferenceFrame frame = te.getFrame();
String locus1 = frame.getFormattedLocusString();
// Generate a locus string for the read mate. Keep the window width (in base pairs) == to the current range
Range range = frame.getCurrentRange();
int length = range.getLength();
int s2 = Math.max(0, mateStart - length / 2);
int e2 = s2 + length;
String startStr = NumberFormat.getInstance().format(s2);
String endStr = NumberFormat.getInstance().format(e2);
String mateLocus = mateChr + ":" + startStr + "-" + endStr;
Session currentSession = IGV.getInstance().getSession();
List<String> loci = null;
if (FrameManager.isGeneListMode()) {
loci = new ArrayList<>(FrameManager.getFrames().size());
for (ReferenceFrame ref : FrameManager.getFrames()) {
//If the frame-name is a locus, we use it unaltered
//Don't want to reprocess, easy to get off-by-one
String name = ref.getName();
if (Locus.fromString(name) != null) {
loci.add(name);
} else {
loci.add(ref.getFormattedLocusString());
}
}
loci.add(mateLocus);
} else {
loci = Arrays.asList(locus1, mateLocus);
}
StringBuffer listName = new StringBuffer();
for (String s : loci) {
listName.append(s + " ");
}
GeneList geneList = new GeneList(listName.toString(), loci, false);
currentSession.setCurrentGeneList(geneList);
Comparator<String> geneListComparator = new Comparator<String>() {
@Override
public int compare(String n0, String n1) {
ReferenceFrame f0 = FrameManager.getFrame(n0);
ReferenceFrame f1 = FrameManager.getFrame(n1);
String chr0 = f0 == null ? "" : f0.getChrName();
String chr1 = f1 == null ? "" : f1.getChrName();
int s0 = f0 == null ? 0 : f0.getCurrentRange().getStart();
int s1 = f1 == null ? 0 : f1.getCurrentRange().getStart();
int chrComp = ChromosomeNameComparator.get().compare(chr0, chr1);
if (chrComp != 0) return chrComp;
return s0 - s1;
}
};
//Need to sort the frames by position
currentSession.sortGeneList(geneListComparator);
IGV.getInstance().resetFrames();
} else {
MessageUtils.showMessage("Alignment does not have mate, or it is not mapped.");
}
}
}
public boolean isLogNormalized() {
return false;
}
public float getRegionScore(String chr, int start, int end, int zoom, RegionScoreType type, String frameName) {
return 0.0f;
}
public AlignmentDataManager getDataManager() {
return dataManager;
}
public String getValueStringAt(String chr, double position, int mouseX, int mouseY, ReferenceFrame frame) {
if (downsampleRect != null && mouseY > downsampleRect.y && mouseY <= downsampleRect.y + downsampleRect.height) {
AlignmentInterval loadedInterval = dataManager.getLoadedInterval(frame);
if (loadedInterval == null) {
return null;
} else {
List<DownsampledInterval> intervals = loadedInterval.getDownsampledIntervals();
DownsampledInterval interval = (DownsampledInterval) FeatureUtils.getFeatureAt(position, 0, intervals);
if (interval != null) {
return interval.getValueString();
}
return null;
}
} else {
InsertionInterval insertionInterval = getInsertionInterval(frame, mouseX, mouseY);
if (insertionInterval != null) {
return "Insertions (" + insertionInterval.insertionMarker.size + " bases)";
} else {
Alignment feature = getAlignmentAt(position, mouseY, frame);
if (feature != null) {
return feature.getValueString(position, mouseX, getWindowFunction());
} else {
for (Map.Entry<Rectangle, String> groupNameEntry : groupNames.entrySet()) {
Rectangle r = groupNameEntry.getKey();
if (mouseY >= r.y && mouseY < r.y + r.height) {
return groupNameEntry.getValue();
}
}
}
}
}
return null;
}
private Alignment getAlignment(final TrackClickEvent te) {
MouseEvent e = te.getMouseEvent();
final ReferenceFrame frame = te.getFrame();
if (frame == null) {
return null;
}
final double location = frame.getChromosomePosition(e.getX());
return getAlignmentAt(location, e.getY(), frame);
}
private Alignment getAlignmentAt(double position, int y, ReferenceFrame frame) {
if (alignmentsRect == null) {
return null; // <= not loaded yet
}
PackedAlignments groups = dataManager.getGroupedAlignmentsContaining(position, frame);
if (groups == null || groups.isEmpty()) {
return null;
}
for (List<Row> rows : groups.values()) {
for (Row row : rows) {
if (y >= row.y && y <= row.y + row.h) {
List<Alignment> features = row.alignments;
// No buffer for alignments, you must zoom in far enough for them to be visible
int buffer = 0;
return FeatureUtils.getFeatureAt(position, buffer, features);
}
}
}
return null;
}
/**
* Get the most "specific" alignment at the specified location. Specificity refers to the smallest alignemnt
* in a group that contains the location (i.e. if a group of linked alignments overlap take the smallest one).
*
* @param te
* @return
*/
private Alignment getSpecficAlignment(TrackClickEvent te) {
Alignment alignment = getAlignment(te);
if (alignment != null) {
final ReferenceFrame frame = te.getFrame();
MouseEvent e = te.getMouseEvent();
final double location = frame.getChromosomePosition(e.getX());
if (alignment instanceof LinkedAlignment) {
Alignment sa = null;
for (Alignment a : ((LinkedAlignment) alignment).alignments) {
if (a.contains(location)) {
if (sa == null || (a.getAlignmentEnd() - a.getAlignmentStart() < sa.getAlignmentEnd() - sa.getAlignmentStart())) {
sa = a;
}
}
}
alignment = sa;
} else if (alignment instanceof PairedAlignment) {
Alignment sa = null;
if (((PairedAlignment) alignment).firstAlignment.contains(location)) {
sa = ((PairedAlignment) alignment).firstAlignment;
} else if (((PairedAlignment) alignment).secondAlignment.contains(location)) {
sa = ((PairedAlignment) alignment).secondAlignment;
}
alignment = sa;
}
}
return alignment;
}
@Override
public boolean handleDataClick(TrackClickEvent te) {
MouseEvent e = te.getMouseEvent();
if (Globals.IS_MAC && e.isMetaDown() || (!Globals.IS_MAC && e.isControlDown())) {
// Selection
final ReferenceFrame frame = te.getFrame();
if (frame != null) {
selectAlignment(e, frame);
if (dataPanel != null) {
dataPanel.repaint();
}
return true;
}
}
InsertionInterval insertionInterval = getInsertionInterval(te.getFrame(), te.getMouseEvent().getX(), te.getMouseEvent().getY());
if (insertionInterval != null) {
final String chrName = te.getFrame().getChrName();
InsertionMarker currentSelection = InsertionManager.getInstance().getSelectedInsertion(chrName);
if (currentSelection != null && currentSelection.position == insertionInterval.insertionMarker.position) {
InsertionManager.getInstance().clearSelected();
} else {
InsertionManager.getInstance().setSelected(chrName, insertionInterval.insertionMarker.position);
}
IGVEventBus.getInstance().post(new InsertionSelectionEvent(insertionInterval.insertionMarker));
return true;
}
if (IGV.getInstance().isShowDetailsOnClick()) {
openTooltipWindow(te);
return true;
}
return false;
}
private void selectAlignment(MouseEvent e, ReferenceFrame frame) {
double location = frame.getChromosomePosition(e.getX());
Alignment alignment = this.getAlignmentAt(location, e.getY(), frame);
if (alignment != null) {
if (selectedReadNames.containsKey(alignment.getReadName())) {
selectedReadNames.remove(alignment.getReadName());
} else {
setSelected(alignment);
}
}
}
private InsertionInterval getInsertionInterval(ReferenceFrame frame, int x, int y) {
List<InsertionInterval> insertionIntervals = getInsertionIntervals(frame);
for (InsertionInterval i : insertionIntervals) {
if (i.rect.contains(x, y)) return i;
}
return null;
}
private void setSelected(Alignment alignment) {
Color c = readNamePalette.get(alignment.getReadName());
selectedReadNames.put(alignment.getReadName(), c);
}
public void clearCaches() {
if (dataManager != null) dataManager.clear();
if (spliceJunctionTrack != null) spliceJunctionTrack.clear();
}
public static void refresh() {
IGV.getInstance().getContentPane().getMainPanel().invalidate();
IGV.getInstance().revalidateTrackPanels();
}
public static boolean isBisulfiteColorType(ColorOption o) {
return (o.equals(ColorOption.BISULFITE) || o.equals(ColorOption.NOMESEQ));
}
public static String getBisulfiteContextPubStr(BisulfiteContext item) {
return bisulfiteContextToPubString.get(item);
}
public static byte[] getBisulfiteContextPreContext(BisulfiteContext item) {
Pair<byte[], byte[]> pair = AlignmentTrack.bisulfiteContextToContextString.get(item);
return pair.getFirst();
}
public static byte[] getBisulfiteContextPostContext(BisulfiteContext item) {
Pair<byte[], byte[]> pair = AlignmentTrack.bisulfiteContextToContextString.get(item);
return pair.getSecond();
}
@Override
public void restorePersistentState(Node node, int version) throws JAXBException {
super.restorePersistentState(node, version);
//For legacy sessions (<= v4. RenderOptions used to be stuffed in
//with Track tag, now it's a sub element
boolean hasRenderSubTag = false;
try {
if (node.hasChildNodes()) {
NodeList list = node.getChildNodes();
for (int ii = 0; ii < list.getLength(); ii++) {
Node item = list.item(ii);
if (item.getNodeName().equals(RenderOptions.NAME)) {
hasRenderSubTag = true;
break;
}
}
}
if (hasRenderSubTag) return;
RenderOptions ro = IGVSessionReader.getJAXBContext().createUnmarshaller().unmarshal(node, RenderOptions.class).getValue();
String shadeBasesKey = "shadeBases";
String value = Utilities.getNullSafe(node.getAttributes(), shadeBasesKey); // For older sessions
if (value != null) {
if (value.equals("false")) {
ro.shadeBasesOption = ShadeBasesOption.NONE;
} else if (value.equals("true")) {
ro.shadeBasesOption = ShadeBasesOption.QUALITY;
}
}
this.setRenderOptions(ro);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
public void setViewAsPairs(boolean vAP) {
// TODO -- generalize this test to all incompatible pairings
if (vAP && renderOptions.groupByOption == GroupOption.STRAND) {
boolean ungroup = MessageUtils.confirm("\"View as pairs\" is incompatible with \"Group by strand\". Ungroup?");
if (ungroup) {
renderOptions.groupByOption = null;
} else {
return;
}
}
dataManager.setViewAsPairs(vAP, renderOptions);
refresh();
}
class RenderRollback {
ColorOption colorOption;
GroupOption groupByOption;
String groupByTag;
String colorByTag;
String linkByTag;
DisplayMode displayMode;
int expandedHeight;
boolean showGroupLine;
RenderRollback(RenderOptions renderOptions, DisplayMode displayMode) {
this.colorOption = renderOptions.colorOption;
this.groupByOption = renderOptions.groupByOption;
this.colorByTag = renderOptions.colorByTag;
this.groupByTag = renderOptions.groupByTag;
this.displayMode = displayMode;
this.expandedHeight = AlignmentTrack.this.expandedHeight;
this.showGroupLine = AlignmentTrack.this.showGroupLine;
this.linkByTag = renderOptions.linkByTag;
}
void restore(RenderOptions renderOptions) {
renderOptions.colorOption = this.colorOption;
renderOptions.groupByOption = this.groupByOption;
renderOptions.colorByTag = this.colorByTag;
renderOptions.groupByTag = this.groupByTag;
renderOptions.linkByTag = this.linkByTag;
AlignmentTrack.this.expandedHeight = this.expandedHeight;
AlignmentTrack.this.showGroupLine = this.showGroupLine;
AlignmentTrack.this.setDisplayMode(this.displayMode);
}
}
public boolean isRemoved() {
return removed;
}
IGVPreferences getPreferences() {
return getPreferences(dataManager.getType());
}
private static IGVPreferences getPreferences(AlignmentDataManager.ExperimentType type) {
String prefKey = Constants.NULL_CATEGORY;
if (type == AlignmentDataManager.ExperimentType.THIRD_GEN) {
prefKey = Constants.THIRD_GEN;
} else if (type == AlignmentDataManager.ExperimentType.RNA) {
prefKey = Constants.RNA;
}
return PreferencesManager.getPreferences(prefKey);
}
@Override
public void dispose() {
super.dispose();
clearCaches();
if (dataManager != null) {
dataManager.dumpAlignments();
IGVEventBus.getInstance().unsubscribe(dataManager);
}
dataManager = null;
removed = true;
setVisible(false);
}
@XmlType(name = RenderOptions.NAME)
@XmlAccessorType(XmlAccessType.NONE)
public static class RenderOptions implements Cloneable {
public static final String NAME = "RenderOptions";
AlignmentDataManager.ExperimentType experimentType;
@XmlAttribute
ShadeBasesOption shadeBasesOption;
@XmlAttribute
boolean shadeCenters;
@XmlAttribute
boolean flagUnmappedPairs;
@XmlAttribute
boolean showAllBases;
boolean showMismatches = true;
private boolean computeIsizes;
@XmlAttribute
private int minInsertSize;
@XmlAttribute
private int maxInsertSize;
private double minInsertSizePercentile;
private double maxInsertSizePercentile;
@XmlAttribute
private ColorOption colorOption;
@XmlAttribute
GroupOption groupByOption = null;
//ContinuousColorScale insertSizeColorScale;
@XmlAttribute
private boolean viewPairs = false;
private boolean pairedArcView = false;
public boolean flagZeroQualityAlignments = true;
Map<String, PEStats> peStats;
@XmlAttribute
private String colorByTag;
@XmlAttribute
private String groupByTag;
@XmlAttribute
private String sortByTag;
@XmlAttribute
private String linkByTag;
@XmlAttribute
private boolean linkedReads;
@XmlAttribute
boolean quickConsensusMode;
BisulfiteContext bisulfiteContext;
private Range groupByPos = null;
public boolean drawInsertionIntervals = false;
public RenderOptions() {
this(AlignmentDataManager.ExperimentType.OTHER);
}
RenderOptions(AlignmentDataManager.ExperimentType experimentType) {
IGVPreferences prefs = getPreferences(experimentType);
this.experimentType = experimentType;
String shadeOptionString = prefs.get(SAM_SHADE_BASES);
if (shadeOptionString.equals("false")) {
shadeBasesOption = ShadeBasesOption.NONE;
} else if (shadeOptionString.equals("true")) {
shadeBasesOption = ShadeBasesOption.QUALITY;
} else {
shadeBasesOption = ShadeBasesOption.valueOf(shadeOptionString);
}
drawInsertionIntervals = prefs.getAsBoolean(SAM_SHOW_INSERTION_MARKERS);
shadeCenters = prefs.getAsBoolean(SAM_SHADE_CENTER);
flagUnmappedPairs = prefs.getAsBoolean(SAM_FLAG_UNMAPPED_PAIR);
computeIsizes = prefs.getAsBoolean(SAM_COMPUTE_ISIZES);
minInsertSize = prefs.getAsInt(SAM_MIN_INSERT_SIZE_THRESHOLD);
maxInsertSize = prefs.getAsInt(SAM_MAX_INSERT_SIZE_THRESHOLD);
minInsertSizePercentile = prefs.getAsFloat(SAM_MIN_INSERT_SIZE_PERCENTILE);
maxInsertSizePercentile = prefs.getAsFloat(SAM_MAX_INSERT_SIZE_PERCENTILE);
showAllBases = prefs.getAsBoolean(SAM_SHOW_ALL_BASES);
quickConsensusMode = prefs.getAsBoolean(SAM_QUICK_CONSENSUS_MODE);
colorOption = CollUtils.valueOf(ColorOption.class, prefs.get(SAM_COLOR_BY), ColorOption.NONE);
groupByOption = CollUtils.valueOf(GroupOption.class, prefs.get(SAM_GROUP_OPTION), GroupOption.NONE);
flagZeroQualityAlignments = prefs.getAsBoolean(SAM_FLAG_ZERO_QUALITY);
bisulfiteContext = DEFAULT_BISULFITE_CONTEXT;
colorByTag = prefs.get(SAM_COLOR_BY_TAG);
sortByTag = prefs.get(SAM_SORT_BY_TAG);
groupByTag = prefs.get(SAM_GROUP_BY_TAG);
setGroupByPos(prefs.get(SAM_GROUP_BY_POS));
//updateColorScale();
peStats = new HashMap<String, PEStats>();
linkedReads = prefs.getAsBoolean(SAM_LINK_READS);
linkByTag = prefs.get(SAM_LINK_TAG);
}
private <T extends Enum<T>> T getFromMap(Map<String, String> attributes, String key, Class<T> clazz, T defaultValue) {
String value = attributes.get(key);
if (value == null) {
return defaultValue;
}
return CollUtils.<T>valueOf(clazz, value, defaultValue);
}
private String getFromMap(Map<String, String> attributes, String key, String defaultValue) {
String value = attributes.get(key);
if (value == null) {
return defaultValue;
}
return value;
}
public void setShowAllBases(boolean showAllBases) {
this.showAllBases = showAllBases;
if (showAllBases) this.showMismatches = false;
}
public void setShowMismatches(boolean showMismatches) {
this.showMismatches = showMismatches;
if (showMismatches) this.showAllBases = false;
}
public int getMinInsertSize() {
return minInsertSize;
}
public void setMinInsertSize(int minInsertSize) {
this.minInsertSize = minInsertSize;
//updateColorScale();
}
public int getMaxInsertSize() {
return maxInsertSize;
}
public boolean isViewPairs() {
return viewPairs;
}
public void setViewPairs(boolean viewPairs) {
this.viewPairs = viewPairs;
}
public boolean isComputeIsizes() {
return computeIsizes;
}
public void setComputeIsizes(boolean computeIsizes) {
this.computeIsizes = computeIsizes;
}
public double getMinInsertSizePercentile() {
return minInsertSizePercentile;
}
public void setMinInsertSizePercentile(double minInsertSizePercentile) {
this.minInsertSizePercentile = minInsertSizePercentile;
}
public double getMaxInsertSizePercentile() {
return maxInsertSizePercentile;
}
public void setMaxInsertSizePercentile(double maxInsertSizePercentile) {
this.maxInsertSizePercentile = maxInsertSizePercentile;
}
public void setMaxInsertSize(int maxInsertSize) {
this.maxInsertSize = maxInsertSize;
}
public ColorOption getColorOption() {
return colorOption;
}
public void setColorOption(ColorOption colorOption) {
this.colorOption = colorOption;
}
public void setColorByTag(String colorByTag) {
this.colorByTag = colorByTag;
getPreferences(experimentType).put(SAM_COLOR_BY_TAG, colorByTag);
}
public String getColorByTag() {
return colorByTag;
}
public String getSortByTag() {
return sortByTag;
}
public void setSortByTag(String sortByTag) {
this.sortByTag = sortByTag;
}
public String getGroupByTag() {
return groupByTag;
}
public void setGroupByTag(String groupByTag) {
this.groupByTag = groupByTag;
}
public Range getGroupByPos() {
return groupByPos;
}
public void setGroupByPos(Range groupByPos) {
this.groupByPos = groupByPos;
}
public void setGroupByPos(String pos) {
if (pos == null) {
this.groupByPos = null;
} else {
String[] posParts = pos.split(" ");
if (posParts.length != 2) {
this.groupByPos = null;
} else {
int posChromStart = Integer.valueOf(posParts[1]);
this.groupByPos = new Range(posParts[0], posChromStart, posChromStart + 1);
}
}
}
public String getLinkByTag() {
return linkByTag;
}
public void setLinkByTag(String linkByTag) {
this.linkByTag = linkByTag;
}
public GroupOption getGroupByOption() {
return groupByOption;
}
public boolean isLinkedReads() {
return linkedReads;
}
public void setLinkedReads(boolean linkedReads) {
this.linkedReads = linkedReads;
}
public boolean isQuickConsensusMode() {
return quickConsensusMode;
}
public void setQuickConsensusMode(boolean quickConsensusMode) {
this.quickConsensusMode = quickConsensusMode;
}
}
public boolean isLinkedReads() {
return renderOptions.linkedReads;
}
public void setLinkedReads(boolean linkedReads, String tag) {
renderOptions.linkedReads = linkedReads;
if (linkedReads == true) {
this.renderRollback = new RenderRollback(renderOptions, getDisplayMode());
renderOptions.setLinkByTag(tag);
if ("READNAME".equals(tag)) {
renderOptions.setColorOption(ColorOption.LINK_STRAND);
} else {
// TenX -- ditto
renderOptions.setColorOption(ColorOption.TAG);
renderOptions.setColorByTag(tag);
if (dataManager.isPhased()) {
renderOptions.groupByOption = GroupOption.TAG;
renderOptions.setGroupByTag("HP");
}
expandedHeight = 10;
showGroupLine = false;
setDisplayMode(DisplayMode.SQUISHED);
}
} else {
if (this.renderRollback != null) {
this.renderRollback.restore(renderOptions);
}
}
dataManager.packAlignments(renderOptions);
refresh();
}
/**
* Listener for deselecting one component when another is selected
*/
private static class Deselector implements ActionListener {
private JMenuItem toDeselect;
private JMenuItem parent;
Deselector(JMenuItem parent, JMenuItem toDeselect) {
this.parent = parent;
this.toDeselect = toDeselect;
}
@Override
public void actionPerformed(ActionEvent e) {
if (this.parent.isSelected()) {
this.toDeselect.setSelected(false);
}
}
}
@SubtlyImportant
private static AlignmentTrack getNextTrack() {
return (AlignmentTrack) IGVSessionReader.getNextTrack();
}
private static class InsertionInterval {
Rectangle rect;
InsertionMarker insertionMarker;
public InsertionInterval(Rectangle rect, InsertionMarker insertionMarker) {
this.rect = rect;
this.insertionMarker = insertionMarker;
}
}
class PopupMenu extends IGVPopupMenu {
PopupMenu(final TrackClickEvent e) {
Collection<Track> tracks = new ArrayList();
tracks.add(AlignmentTrack.this);
JLabel popupTitle = new JLabel(" " + AlignmentTrack.this.getName(), JLabel.CENTER);
Font newFont = getFont().deriveFont(Font.BOLD, 12);
popupTitle.setFont(newFont);
if (popupTitle != null) {
add(popupTitle);
}
addSeparator();
add(TrackMenuUtils.getTrackRenameItem(tracks));
addCopyToClipboardItem(e);
// addSeparator();
// addExpandInsertions();
if (dataManager.isTenX()) {
addTenXItems();
} else {
addSupplItems(); // Are SA tags mutually exlcusive with 10X?
}
addSeparator();
addGroupMenuItem(e);
addSortMenuItem();
addColorByMenuItem();
addPackMenuItem();
addSeparator();
addShadeBaseByMenuItem();
JMenuItem misMatchesItem = addShowMismatchesMenuItem();
JMenuItem showAllItem = addShowAllBasesMenuItem();
misMatchesItem.addActionListener(new Deselector(misMatchesItem, showAllItem));
showAllItem.addActionListener(new Deselector(showAllItem, misMatchesItem));
addQuickConsensusModeItem();
addSeparator();
addViewAsPairsMenuItem();
addGoToMate(e);
showMateRegion(e);
addInsertSizeMenuItem();
addSeparator();
TrackMenuUtils.addDisplayModeItems(tracks, this);
addSeparator();
addSelectByNameItem();
addClearSelectionsMenuItem();
addSeparator();
addCopySequenceItem(e);
if(PreferencesManager.getPreferences().get(Constants.EXTVIEW_URL) != null) {
addExtViewItem(e);
}
addBlatItem(e);
addConsensusSequence(e);
boolean showSashimi = true;//Globals.isDevelopment();
if (showSashimi) {
addSeparator();
JMenuItem sashimi = new JMenuItem("Sashimi Plot");
sashimi.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SashimiPlot.getSashimiPlot(null);
}
});
add(sashimi);
}
addSeparator();
addShowItems();
}
public JMenuItem addExpandInsertions() {
final JMenuItem item = new JCheckBoxMenuItem("Expand insertions");
final Session session = IGV.getInstance().getSession();
item.setSelected(session.expandInsertions);
item.addActionListener(aEvt -> {
session.expandInsertions = !session.expandInsertions;
refresh();
});
add(item);
return item;
}
/**
* Item for exporting "consensus" sequence of region, based
* on loaded alignments.
*
* @param e
*/
private void addConsensusSequence(TrackClickEvent e) {
//Export consensus sequence
JMenuItem item = new JMenuItem("Copy consensus sequence");
final ReferenceFrame frame;
if (e.getFrame() == null && FrameManager.getFrames().size() == 1) {
frame = FrameManager.getFrames().get(0);
} else {
frame = e.getFrame();
}
item.setEnabled(frame != null);
add(item);
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
//This shouldn't ever be true, but just in case it's more user-friendly
if (frame == null) {
MessageUtils.showMessage("Unknown region bounds, cannot export consensus");
return;
}
final int start = (int) frame.getOrigin();
final int end = (int) frame.getEnd();
if ((end - start) > 1000000) {
MessageUtils.showMessage("Cannot export region more than 1 Megabase");
return;
}
AlignmentInterval interval = dataManager.getLoadedInterval(frame);
AlignmentCounts counts = interval.getCounts();
String text = PFMExporter.createPFMText(counts, frame.getChrName(), start, end);
StringUtils.copyTextToClipboard(text);
}
});
}
private JMenu getBisulfiteContextMenuItem(ButtonGroup group) {
// Change track height by attribute
//JMenu bisulfiteContextMenu = new JMenu("Bisulfite Contexts");
JMenu bisulfiteContextMenu = new JMenu("bisulfite mode");
JRadioButtonMenuItem nomeESeqOption = null;
boolean showNomeESeq = getPreferences().getAsBoolean(SAM_NOMESEQ_ENABLED);
if (showNomeESeq) {
nomeESeqOption = new JRadioButtonMenuItem("NOMe-seq bisulfite mode");
nomeESeqOption.setSelected(renderOptions.colorOption == ColorOption.NOMESEQ);
nomeESeqOption.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
setColorOption(ColorOption.NOMESEQ);
refresh();
}
});
group.add(nomeESeqOption);
}
for (final BisulfiteContext item : BisulfiteContext.values()) {
String optionStr = getBisulfiteContextPubStr(item);
JRadioButtonMenuItem m1 = new JRadioButtonMenuItem(optionStr);
m1.setSelected(renderOptions.bisulfiteContext == item);
m1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
setColorOption(ColorOption.BISULFITE);
setBisulfiteContext(item);
refresh();
}
});
bisulfiteContextMenu.add(m1);
group.add(m1);
}
if (nomeESeqOption != null) {
bisulfiteContextMenu.add(nomeESeqOption);
}
return bisulfiteContextMenu;
}
public void addSelectByNameItem() {
// Change track height by attribute
JMenuItem item = new JMenuItem("Select by name...");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
String val = MessageUtils.showInputDialog("Enter read name: ");
if (val != null && val.trim().length() > 0) {
selectedReadNames.put(val, readNamePalette.get(val));
refresh();
}
}
});
add(item);
}
private JCheckBoxMenuItem getGroupMenuItem(String label, final GroupOption option) {
JCheckBoxMenuItem mi = new JCheckBoxMenuItem(label);
mi.setSelected(renderOptions.groupByOption == option);
if (option == GroupOption.NONE) {
mi.setSelected(renderOptions.groupByOption == null);
}
mi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
IGV.getInstance().groupAlignmentTracks(option, null, null);
refresh();
}
});
return mi;
}
public void addGroupMenuItem(final TrackClickEvent te) {//ReferenceFrame frame) {
final MouseEvent me = te.getMouseEvent();
ReferenceFrame frame = te.getFrame();
if (frame == null) {
frame = FrameManager.getDefaultFrame(); // Clicked over name panel, not a specific frame
}
final Range range = frame.getCurrentRange();
final String chrom = range.getChr();
final int chromStart = (int) frame.getChromosomePosition(me.getX());
// Change track height by attribute
JMenu groupMenu = new JMenu("Group alignments by");
ButtonGroup group = new ButtonGroup();
Map<String, GroupOption> mappings = new LinkedHashMap<String, GroupOption>();
mappings.put("none", GroupOption.NONE);
mappings.put("read strand", GroupOption.STRAND);
mappings.put("first-in-pair strand", GroupOption.FIRST_OF_PAIR_STRAND);
mappings.put("sample", GroupOption.SAMPLE);
mappings.put("library", GroupOption.LIBRARY);
mappings.put("read group", GroupOption.READ_GROUP);
mappings.put("chromosome of mate", GroupOption.MATE_CHROMOSOME);
mappings.put("pair orientation", GroupOption.PAIR_ORIENTATION);
mappings.put("supplementary flag", GroupOption.SUPPLEMENTARY);
for (Map.Entry<String, GroupOption> el : mappings.entrySet()) {
JCheckBoxMenuItem mi = getGroupMenuItem(el.getKey(), el.getValue());
groupMenu.add(mi);
group.add(mi);
}
JCheckBoxMenuItem tagOption = new JCheckBoxMenuItem("tag");
tagOption.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
String tag = MessageUtils.showInputDialog("Enter tag", renderOptions.getGroupByTag());
if (tag != null && tag.trim().length() > 0) {
IGV.getInstance().groupAlignmentTracks(GroupOption.TAG, tag, null);
}
}
});
tagOption.setSelected(renderOptions.groupByOption == GroupOption.TAG);
groupMenu.add(tagOption);
group.add(tagOption);
Range oldGroupByPos = renderOptions.getGroupByPos();
if (renderOptions.groupByOption == GroupOption.BASE_AT_POS) { // already sorted by the base at a position
JCheckBoxMenuItem oldGroupByPosOption = new JCheckBoxMenuItem("base at " + oldGroupByPos.getChr() +
":" + Globals.DECIMAL_FORMAT.format(1 + oldGroupByPos.getStart()));
groupMenu.add(oldGroupByPosOption);
oldGroupByPosOption.setSelected(true);
}
if (renderOptions.groupByOption != GroupOption.BASE_AT_POS || oldGroupByPos == null ||
!oldGroupByPos.getChr().equals(chrom) || oldGroupByPos.getStart() != chromStart) { // not already sorted by this position
JCheckBoxMenuItem newGroupByPosOption = new JCheckBoxMenuItem("base at " + chrom +
":" + Globals.DECIMAL_FORMAT.format(1 + chromStart));
newGroupByPosOption.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
Range groupByPos = new Range(chrom, chromStart, chromStart + 1);
IGV.getInstance().groupAlignmentTracks(GroupOption.BASE_AT_POS, null, groupByPos);
}
});
groupMenu.add(newGroupByPosOption);
group.add(newGroupByPosOption);
}
add(groupMenu);
}
private JMenuItem getSortMenuItem(String label, final SortOption option) {
JMenuItem mi = new JMenuItem(label);
mi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
sortAlignmentTracks(option, null);
}
});
return mi;
}
/**
* Sort menu
*/
public void addSortMenuItem() {
JMenu sortMenu = new JMenu("Sort alignments by");
//LinkedHashMap is supposed to preserve order of insertion for iteration
Map<String, SortOption> mappings = new LinkedHashMap<String, SortOption>();
mappings.put("start location", SortOption.START);
mappings.put("read strand", SortOption.STRAND);
mappings.put("first-of-pair strand", SortOption.FIRST_OF_PAIR_STRAND);
mappings.put("base", SortOption.NUCLEOTIDE);
mappings.put("mapping quality", SortOption.QUALITY);
mappings.put("sample", SortOption.SAMPLE);
mappings.put("read group", SortOption.READ_GROUP);
if (dataManager.isPairedEnd()) {
mappings.put("insert size", SortOption.INSERT_SIZE);
mappings.put("chromosome of mate", SortOption.MATE_CHR);
}
// mappings.put("supplementary flag", SortOption.SUPPLEMENTARY);
for (Map.Entry<String, SortOption> el : mappings.entrySet()) {
sortMenu.add(getSortMenuItem(el.getKey(), el.getValue()));
}
JMenuItem tagOption = new JMenuItem("tag");
tagOption.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
String tag = MessageUtils.showInputDialog("Enter tag", renderOptions.getSortByTag());
if (tag != null && tag.trim().length() > 0) {
renderOptions.setSortByTag(tag);
sortAlignmentTracks(SortOption.TAG, tag);
}
}
});
sortMenu.add(tagOption);
add(sortMenu);
}
private void setBisulfiteContext(BisulfiteContext option) {
renderOptions.bisulfiteContext = option;
getPreferences().put(SAM_BISULFITE_CONTEXT, option.toString());
}
private void setColorOption(ColorOption option) {
renderOptions.colorOption = option;
getPreferences().put(SAM_COLOR_BY, option.toString());
}
private JRadioButtonMenuItem getColorMenuItem(String label, final ColorOption option) {
JRadioButtonMenuItem mi = new JRadioButtonMenuItem(label);
mi.setSelected(renderOptions.colorOption == option);
mi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
setColorOption(option);
refresh();
}
});
return mi;
}
public void addColorByMenuItem() {
// Change track height by attribute
JMenu colorMenu = new JMenu("Color alignments by");
ButtonGroup group = new ButtonGroup();
Map<String, ColorOption> mappings = new LinkedHashMap<String, ColorOption>();
mappings.put("no color", ColorOption.NONE);
if (dataManager.isPairedEnd()) {
mappings.put("insert size", ColorOption.INSERT_SIZE);
mappings.put("pair orientation", ColorOption.PAIR_ORIENTATION);
mappings.put("insert size and pair orientation", ColorOption.UNEXPECTED_PAIR);
}
mappings.put("read strand", ColorOption.READ_STRAND);
if (dataManager.isPairedEnd()) {
mappings.put("first-of-pair strand", ColorOption.FIRST_OF_PAIR_STRAND);
}
mappings.put("read group", ColorOption.READ_GROUP);
mappings.put("sample", ColorOption.SAMPLE);
mappings.put("library", ColorOption.LIBRARY);
for (Map.Entry<String, ColorOption> el : mappings.entrySet()) {
JRadioButtonMenuItem mi = getColorMenuItem(el.getKey(), el.getValue());
colorMenu.add(mi);
group.add(mi);
}
JRadioButtonMenuItem tagOption = new JRadioButtonMenuItem("tag");
tagOption.setSelected(renderOptions.colorOption == ColorOption.TAG);
tagOption.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
setColorOption(ColorOption.TAG);
String tag = MessageUtils.showInputDialog("Enter tag", renderOptions.getColorByTag());
if (tag != null && tag.trim().length() > 0) {
renderOptions.setColorByTag(tag);
refresh();
}
}
});
colorMenu.add(tagOption);
group.add(tagOption);
colorMenu.add(getBisulfiteContextMenuItem(group));
add(colorMenu);
}
public void addPackMenuItem() {
// Change track height by attribute
JMenuItem item = new JMenuItem("Re-pack alignments");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
UIUtilities.invokeOnEventThread(new Runnable() {
public void run() {
IGV.getInstance().packAlignmentTracks();
refresh();
}
});
}
});
add(item);
}
public void addCopyToClipboardItem(final TrackClickEvent te) {
final MouseEvent me = te.getMouseEvent();
JMenuItem item = new JMenuItem("Copy read details to clipboard");
final ReferenceFrame frame = te.getFrame();
if (frame == null) {
item.setEnabled(false);
} else {
final double location = frame.getChromosomePosition(me.getX());
final Alignment alignment = getAlignmentAt(location, me.getY(), frame);
// Change track height by attribute
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
copyToClipboard(te, alignment, location, me.getX());
}
});
if (alignment == null) {
item.setEnabled(false);
}
}
add(item);
}
public void addViewAsPairsMenuItem() {
final JMenuItem item = new JCheckBoxMenuItem("View as pairs");
item.setSelected(renderOptions.isViewPairs());
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
boolean viewAsPairs = item.isSelected();
setViewAsPairs(viewAsPairs);
}
});
item.setEnabled(dataManager.isPairedEnd());
add(item);
}
public void addGoToMate(final TrackClickEvent te) {
// Change track height by attribute
JMenuItem item = new JMenuItem("Go to mate");
MouseEvent e = te.getMouseEvent();
final ReferenceFrame frame = te.getFrame();
if (frame == null) {
item.setEnabled(false);
} else {
double location = frame.getChromosomePosition(e.getX());
final Alignment alignment = getAlignmentAt(location, e.getY(), frame);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
gotoMate(te, alignment);
}
});
if (alignment == null || !alignment.isPaired() || !alignment.getMate().isMapped()) {
item.setEnabled(false);
}
}
add(item);
}
public void showMateRegion(final TrackClickEvent te) {
// Change track height by attribute
JMenuItem item = new JMenuItem("View mate region in split screen");
MouseEvent e = te.getMouseEvent();
final ReferenceFrame frame = te.getFrame();
if (frame == null) {
item.setEnabled(false);
} else {
double location = frame.getChromosomePosition(e.getX());
Alignment clickedAlignment = getAlignmentAt(location, e.getY(), frame);
if (clickedAlignment instanceof PairedAlignment) {
Alignment first = ((PairedAlignment) clickedAlignment).getFirstAlignment();
Alignment second = ((PairedAlignment) clickedAlignment).getSecondAlignment();
if (first.contains(location)) {
clickedAlignment = first;
} else if (second.contains(location)) {
clickedAlignment = second;
} else {
clickedAlignment = null;
}
}
final Alignment alignment = clickedAlignment;
item.addActionListener(aEvt -> splitScreenMate(te, alignment));
if (alignment == null || !alignment.isPaired() || !alignment.getMate().isMapped()) {
item.setEnabled(false);
}
}
add(item);
}
public void addClearSelectionsMenuItem() {
// Change track height by attribute
JMenuItem item = new JMenuItem("Clear selections");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
selectedReadNames.clear();
refresh();
}
});
add(item);
}
public JMenuItem addShowAllBasesMenuItem() {
// Change track height by attribute
final JMenuItem item = new JCheckBoxMenuItem("Show all bases");
if (renderOptions.colorOption == ColorOption.BISULFITE || renderOptions.colorOption == ColorOption.NOMESEQ) {
// item.setEnabled(false);
} else {
item.setSelected(renderOptions.showAllBases);
}
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
renderOptions.setShowAllBases(item.isSelected());
refresh();
}
});
add(item);
return item;
}
public JMenuItem addQuickConsensusModeItem() {
// Change track height by attribute
final JMenuItem item = new JCheckBoxMenuItem("Quick consensus mode");
item.setSelected(renderOptions.quickConsensusMode);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
renderOptions.setQuickConsensusMode(item.isSelected());
refresh();
}
});
add(item);
return item;
}
public JMenuItem addShowMismatchesMenuItem() {
// Change track height by attribute
final JMenuItem item = new JCheckBoxMenuItem("Show mismatched bases");
item.setSelected(renderOptions.showMismatches);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
renderOptions.setShowMismatches(item.isSelected());
refresh();
}
});
add(item);
return item;
}
public void addInsertSizeMenuItem() {
// Change track height by attribute
final JMenuItem item = new JCheckBoxMenuItem("Set insert size options ...");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
InsertSizeSettingsDialog dlg = new InsertSizeSettingsDialog(IGV.getMainFrame(), renderOptions);
dlg.setModal(true);
dlg.setVisible(true);
if (!dlg.isCanceled()) {
renderOptions.setComputeIsizes(dlg.isComputeIsize());
renderOptions.setMinInsertSizePercentile(dlg.getMinPercentile());
renderOptions.setMaxInsertSizePercentile(dlg.getMaxPercentile());
if (renderOptions.isComputeIsizes()) {
dataManager.updatePEStats(renderOptions);
}
renderOptions.setMinInsertSize(dlg.getMinThreshold());
renderOptions.setMaxInsertSize(dlg.getMaxThreshold());
refresh();
}
}
});
item.setEnabled(dataManager.isPairedEnd());
add(item);
}
public void addShadeBaseByMenuItem() {
final JMenuItem item = new JCheckBoxMenuItem("Shade base by quality");
item.setSelected(renderOptions.shadeBasesOption == ShadeBasesOption.QUALITY);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
UIUtilities.invokeOnEventThread(new Runnable() {
public void run() {
if (item.isSelected()) {
renderOptions.shadeBasesOption = ShadeBasesOption.QUALITY;
} else {
renderOptions.shadeBasesOption = ShadeBasesOption.NONE;
}
refresh();
}
});
}
});
add(item);
}
public void addShowItems() {
if (AlignmentTrack.this.coverageTrack != null) {
final JMenuItem item = new JCheckBoxMenuItem("Show Coverage Track");
item.setSelected(AlignmentTrack.this.coverageTrack.isVisible());
item.setEnabled(!AlignmentTrack.this.coverageTrack.isRemoved());
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
UIUtilities.invokeOnEventThread(new Runnable() {
public void run() {
if (getCoverageTrack() != null) {
getCoverageTrack().setVisible(item.isSelected());
IGV.getInstance().getMainPanel().revalidate();
}
}
});
}
});
add(item);
}
if (AlignmentTrack.this.spliceJunctionTrack != null) {
final JMenuItem item = new JCheckBoxMenuItem("Show Splice Junction Track");
item.setSelected(AlignmentTrack.this.spliceJunctionTrack.isVisible());
item.setEnabled(!AlignmentTrack.this.spliceJunctionTrack.isRemoved());
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
UIUtilities.invokeOnEventThread(new Runnable() {
public void run() {
if (AlignmentTrack.this.spliceJunctionTrack != null) {
AlignmentTrack.this.spliceJunctionTrack.setVisible(item.isSelected());
}
}
});
}
});
add(item);
}
final JMenuItem alignmentItem = new JMenuItem("Hide Track");
alignmentItem.setEnabled(!AlignmentTrack.this.isRemoved());
alignmentItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
AlignmentTrack.this.setVisible(false);
}
});
add(alignmentItem);
}
public void addCopySequenceItem(final TrackClickEvent te) {
// Change track height by attribute
final JMenuItem item = new JMenuItem("Copy read sequence");
add(item);
final Alignment alignment = getAlignment(te);
if (alignment == null) {
item.setEnabled(false);
return;
}
final String seq = alignment.getReadSequence();
if (seq == null) {
item.setEnabled(false);
return;
}
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
StringUtils.copyTextToClipboard(seq);
}
});
}
public void addBlatItem(final TrackClickEvent te) {
// Change track height by attribute
final JMenuItem item = new JMenuItem("Blat read sequence");
add(item);
final Alignment alignment = getSpecficAlignment(te);
if (alignment == null) {
item.setEnabled(false);
return;
}
final String seq = alignment.getReadSequence();
if (seq == null) {
item.setEnabled(false);
return;
}
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
String blatSeq = alignment.getReadStrand() == Strand.NEGATIVE ?
SequenceTrack.getReverseComplement(seq) : seq;
BlatClient.doBlatQuery(blatSeq);
}
});
}
public void addExtViewItem(final TrackClickEvent te) {
// Change track height by attribute
final JMenuItem item = new JMenuItem("ExtView");
add(item);
final Alignment alignment = getAlignment(te);
if (alignment == null) {
item.setEnabled(false);
return;
}
final String seq = alignment.getReadSequence();
if (seq == null) {
item.setEnabled(false);
return;
}
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
ExtendViewClient.postExtendView(alignment);
}
});
}
public void addTenXItems() {
addSeparator();
final JMenuItem bxItem = new JCheckBoxMenuItem("View linked reads (BX)");
final JMenuItem miItem = new JCheckBoxMenuItem("View linked reads (MI)");
if (isLinkedReads()) {
bxItem.setSelected("BX".equals(renderOptions.linkByTag));
miItem.setSelected("MI".equals(renderOptions.linkByTag));
} else {
bxItem.setSelected(false);
miItem.setSelected(false);
}
bxItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
boolean linkedReads = bxItem.isSelected();
setLinkedReads(linkedReads, "BX");
}
});
add(bxItem);
miItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
boolean linkedReads = miItem.isSelected();
setLinkedReads(linkedReads, "MI");
}
});
add(miItem);
}
public void addSupplItems() {
addSeparator();
final JMenuItem bxItem = new JCheckBoxMenuItem("Link supplementary alignments");
if (isLinkedReads()) {
bxItem.setSelected("READNAME".equals(renderOptions.linkByTag));
} else {
bxItem.setSelected(false);
}
bxItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent aEvt) {
boolean linkedReads = bxItem.isSelected();
setLinkedReads(linkedReads, "READNAME");
}
});
add(bxItem);
}
}
static class InsertionMenu extends IGVPopupMenu {
AlignmentBlock insertion;
InsertionMenu(AlignmentBlock insertion) {
this.insertion = insertion;
addCopySequenceItem();
if (insertion.getBases() != null && insertion.getBases().length > 10) {
addBlatItem();
}
}
public void addCopySequenceItem() {
// Change track height by attribute
final JMenuItem item = new JMenuItem("Copy insert sequence");
add(item);
item.addActionListener(aEvt -> StringUtils.copyTextToClipboard(new String(insertion.getBases())));
}
public void addBlatItem() {
// Change track height by attribute
final JMenuItem item = new JMenuItem("Blat insert sequence");
add(item);
item.addActionListener(aEvt -> {
String blatSeq = new String(insertion.getBases());
BlatClient.doBlatQuery(blatSeq);
});
}
@Override
public boolean includeStandardItems() {
return false;
}
}
}