import jalview.datamodel.AlignmentI;
import jalview.datamodel.ColumnSelection;
import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.MappedFeatures;
import jalview.datamodel.SearchResultMatchI;
import jalview.datamodel.SearchResults;
import jalview.datamodel.SearchResultsI;
import jalview.util.MessageManager;
import jalview.util.Platform;
import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
}
else
{
- seqIndex = Math.min((y / charHeight) + av.getRanges().getStartSeq(),
+ ViewportRanges ranges = av.getRanges();
+ seqIndex = Math.min((y / charHeight) + ranges.getStartSeq(),
alignmentHeight - 1);
+ seqIndex = Math.min(seqIndex, ranges.getEndSeq());
}
return new MousePos(col, seqIndex, annIndex);
return;
}
- if (!editingSeqs)
+ if (editingSeqs)
+ {
+ endEditing();
+ }
+ else
{
doMouseReleasedDefineMode(evt, didDrag);
- return;
}
-
- endEditing();
}
/**
* the start of the highlighted region.
*/
@Override
- public void highlightSequence(SearchResultsI results)
+ public String highlightSequence(SearchResultsI results)
{
if (results == null || results.equals(lastSearchResults))
{
- return;
+ return null;
}
lastSearchResults = results;
{
setStatusMessage(results);
}
+ // JAL-3303 feature suppressed for now pending review
+ return null; // results.isEmpty() ? null : getHighlightInfo(results);
+ }
+
+ /**
+ * temporary hack: answers a message suitable to show on structure hover
+ * label. This is normally null. It is a peptide variation description if
+ * <ul>
+ * <li>results are a single residue in a protein alignment</li>
+ * <li>there is a mapping to a coding sequence (codon)</li>
+ * <li>there are one or more SNP variant features on the codon</li>
+ * </ul>
+ * in which case the answer is of the format (e.g.) "p.Glu388Asp"
+ *
+ * @param results
+ * @return
+ */
+ private String getHighlightInfo(SearchResultsI results)
+ {
+ /*
+ * ideally, just find mapped CDS (as we don't care about render style here);
+ * for now, go via split frame complement's FeatureRenderer
+ */
+ AlignViewportI complement = ap.getAlignViewport().getCodingComplement();
+ if (complement == null)
+ {
+ return null;
+ }
+ AlignFrame af = Desktop.getAlignFrameFor(complement);
+ FeatureRendererModel fr2 = af.getFeatureRenderer();
+
+ int j = results.getSize();
+ List<String> infos = new ArrayList<>();
+ for (int i = 0; i < j; i++)
+ {
+ SearchResultMatchI match = results.getResults().get(i);
+ int pos = match.getStart();
+ if (pos == match.getEnd())
+ {
+ SequenceI seq = match.getSequence();
+ SequenceI ds = seq.getDatasetSequence() == null ? seq
+ : seq.getDatasetSequence();
+ MappedFeatures mf = fr2
+ .findComplementFeaturesAtResidue(ds, pos);
+ if (mf != null)
+ {
+ for (SequenceFeature sf : mf.features)
+ {
+ String pv = mf.findProteinVariants(sf);
+ if (pv.length() > 0 && !infos.contains(pv))
+ {
+ infos.add(pv);
+ }
+ }
+ }
+ }
+ }
+
+ if (infos.isEmpty())
+ {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (String info : infos)
+ {
+ if (sb.length() > 0)
+ {
+ sb.append("|");
+ }
+ sb.append(info);
+ }
+ return sb.toString();
}
@Override
* add features that straddle the gap (pos may be the residue before or
* after the gap)
*/
+ int unshownFeatures = 0;
if (av.isShowSequenceFeatures())
{
List<SequenceFeature> features = ap.getFeatureRenderer()
.findFeaturesAtColumn(sequence, column + 1);
- seqARep.appendFeatures(tooltipText, pos, features,
- this.ap.getSeqPanel().seqCanvas.fr);
+ unshownFeatures = seqARep.appendFeaturesLengthLimit(tooltipText, pos,
+ features,
+ this.ap.getSeqPanel().seqCanvas.fr, MAX_TOOLTIP_LENGTH);
+
+ /*
+ * add features in CDS/protein complement at the corresponding
+ * position if configured to do so
+ */
+ if (av.isShowComplementFeatures())
+ {
+ if (!Comparison.isGap(sequence.getCharAt(column)))
+ {
+ AlignViewportI complement = ap.getAlignViewport()
+ .getCodingComplement();
+ AlignFrame af = Desktop.getAlignFrameFor(complement);
+ FeatureRendererModel fr2 = af.getFeatureRenderer();
+ MappedFeatures mf = fr2.findComplementFeaturesAtResidue(sequence,
+ pos);
+ if (mf != null)
+ {
+ unshownFeatures = seqARep.appendFeaturesLengthLimit(
+ tooltipText, pos, mf, fr2,
+ MAX_TOOLTIP_LENGTH);
+ }
+ }
+ }
}
- if (tooltipText.length() == 6) // <html>
+ if (tooltipText.length() == 6) // "<html>"
{
setToolTipText(null);
lastTooltip = null;
}
else
{
- if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
+ if (tooltipText.length() > MAX_TOOLTIP_LENGTH)
{
tooltipText.setLength(MAX_TOOLTIP_LENGTH);
tooltipText.append("...");
}
+ if (unshownFeatures > 0)
+ {
+ tooltipText.append("<br/>").append("... ").append("<i>")
+ .append(MessageManager.formatMessage(
+ "label.features_not_shown", unshownFeatures))
+ .append("</i>");
+ }
String textString = tooltipText.toString();
if (lastTooltip == null || !lastTooltip.equals(textString))
{
}
mouseDragging = true;
- if ((scrollThread != null) && (scrollThread.isRunning()))
+ if (scrollThread != null)
{
- scrollThread.setEvent(evt);
+ scrollThread.setMousePosition(evt.getPoint());
}
}
}
/**
- * DOCUMENT ME!
+ * On reentering the panel, stops any scrolling that was started on dragging
+ * out of the panel
*
* @param e
- * DOCUMENT ME!
*/
@Override
public void mouseEntered(MouseEvent e)
{
oldSeq = 0;
}
-
- if ((scrollThread != null) && (scrollThread.isRunning()))
- {
- scrollThread.stopScrolling();
- scrollThread = null;
- }
+ stopScrolling();
}
/**
- * DOCUMENT ME!
+ * On leaving the panel, if the mouse is being dragged, starts a thread to
+ * scroll it until the mouse is released (in unwrapped mode only)
*
* @param e
- * DOCUMENT ME!
*/
@Override
public void mouseExited(MouseEvent e)
{
+ lastMousePosition = null;
ap.alignFrame.setStatus(" ");
if (av.getWrapAlignment())
{
final int column = pos.column;
final int seq = pos.seqIndex;
SequenceI sequence = av.getAlignment().getSequenceAt(seq);
- List<SequenceFeature> features = ap.getFeatureRenderer()
- .findFeaturesAtColumn(sequence, column + 1);
-
- PopupMenu pop = new PopupMenu(ap, null, features);
- pop.show(this, evt.getX(), evt.getY());
+ if (sequence != null)
+ {
+ PopupMenu pop = new PopupMenu(ap, sequence, column);
+ pop.show(this, evt.getX(), evt.getY());
+ }
}
/**
return;
}
- if (res >= av.getAlignment().getWidth())
- {
- res = av.getAlignment().getWidth() - 1;
- }
+ res = Math.min(res, av.getAlignment().getWidth()-1);
if (stretchGroup.getEndRes() == res)
{
mouseDragging = true;
- if ((scrollThread != null) && (scrollThread.isRunning()))
+ if (scrollThread != null)
{
- scrollThread.setEvent(evt);
+ scrollThread.setMousePosition(evt.getPoint());
}
/*
ap.alignFrame.setStatus(status.toString());
}
- void scrollCanvas(MouseEvent evt)
+ /**
+ * Stops the scroll thread if it is running
+ */
+ void stopScrolling()
{
- if (evt == null)
+ if (scrollThread != null)
{
- if ((scrollThread != null) && (scrollThread.isRunning()))
- {
- scrollThread.stopScrolling();
- scrollThread = null;
- }
- mouseDragging = false;
+ scrollThread.stopScrolling();
+ scrollThread = null;
}
- else
- {
- if (scrollThread == null)
- {
- scrollThread = new ScrollThread();
- }
+ mouseDragging = false;
+ }
- mouseDragging = true;
- scrollThread.setEvent(evt);
+ /**
+ * Starts a thread to scroll the alignment, towards a given mouse position
+ * outside the panel bounds
+ *
+ * @param mousePos
+ */
+ void startScrolling(Point mousePos)
+ {
+ if (scrollThread == null)
+ {
+ scrollThread = new ScrollThread();
}
+ mouseDragging = true;
+ scrollThread.setMousePosition(mousePos);
}
- // this class allows scrolling off the bottom of the visible alignment
+ /**
+ * Performs scrolling of the visible alignment left, right, up or down
+ */
class ScrollThread extends Thread
{
- MouseEvent evt;
+ private Point mousePos;
private volatile boolean threadRunning = true;
+ /**
+ * Constructor
+ */
public ScrollThread()
{
+ setName("SeqPanel$ScrollThread");
start();
}
- public void setEvent(MouseEvent e)
+ /**
+ * Sets the position of the mouse that determines the direction of the
+ * scroll to perform
+ *
+ * @param p
+ */
+ public void setMousePosition(Point p)
{
- evt = e;
+ mousePos = p;
}
+ /**
+ * Sets a flag that will cause the thread to exit
+ */
public void stopScrolling()
{
threadRunning = false;
}
- public boolean isRunning()
- {
- return threadRunning;
- }
-
+ /**
+ * Scrolls the alignment left or right, and/or up or down, depending on the
+ * last notified mouse position, until the limit of the alignment is
+ * reached, or a flag is set to stop the scroll
+ */
@Override
public void run()
{
- while (threadRunning)
+ while (threadRunning && mouseDragging)
{
- if (evt != null)
+ if (mousePos != null)
{
- if (mouseDragging && (evt.getY() < 0)
- && (av.getRanges().getStartSeq() > 0))
+ boolean scrolled = false;
+ ViewportRanges ranges = SeqPanel.this.av.getRanges();
+
+ /*
+ * scroll up or down
+ */
+ if (mousePos.y < 0)
{
- av.getRanges().scrollUp(true);
+ // mouse is above this panel - try scroll up
+ scrolled = ranges.scrollUp(true);
}
-
- if (mouseDragging && (evt.getY() >= getHeight()) && (av
- .getAlignment().getHeight() > av.getRanges().getEndSeq()))
+ else if (mousePos.y >= getHeight())
{
- av.getRanges().scrollUp(false);
+ // mouse is below this panel - try scroll down
+ scrolled = ranges.scrollUp(false);
}
- if (mouseDragging && (evt.getX() < 0))
+ /*
+ * scroll left or right
+ */
+ if (mousePos.x < 0)
+ {
+ scrolled |= ranges.scrollRight(false);
+ }
+ else if (mousePos.x >= getWidth())
{
- av.getRanges().scrollRight(false);
+ scrolled |= ranges.scrollRight(true);
}
- else if (mouseDragging && (evt.getX() >= getWidth()))
+ if (!scrolled)
{
- av.getRanges().scrollRight(true);
+ /*
+ * we have reached the limit of the visible alignment - quit
+ */
+ threadRunning = false;
+ SeqPanel.this.ap.repaint();
}
}
* Map sequence selection
*/
SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
- av.setSelectionGroup(sg);
+ av.setSelectionGroup(sg != null && sg.getSize() > 0 ? sg : null);
av.isSelectionGroupChanged(true);
/*