X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FSeqPanel.java;h=827c3150ae2be532410136c86c7a64d12067b6d9;hb=cb8e52fbbc5f725e3f7f48c672cdddb0690bd978;hp=04ceea6c10a567dcf3466486a7674f2e727f9ec8;hpb=09976d9f77b1a3a6486735b0fd9caeb158f1834f;p=jalview.git
diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java
index 04ceea6..827c315 100644
--- a/src/jalview/gui/SeqPanel.java
+++ b/src/jalview/gui/SeqPanel.java
@@ -20,8 +20,31 @@
*/
package jalview.gui;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+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;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JToolTip;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import javax.swing.ToolTipManager;
+
import jalview.api.AlignViewportI;
-import jalview.bin.Cache;
+import jalview.bin.Console;
import jalview.commands.EditCommand;
import jalview.commands.EditCommand.Action;
import jalview.commands.EditCommand.Edit;
@@ -29,6 +52,7 @@ import jalview.datamodel.AlignmentAnnotation;
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;
@@ -50,28 +74,7 @@ import jalview.util.MessageManager;
import jalview.util.Platform;
import jalview.viewmodel.AlignmentViewport;
import jalview.viewmodel.ViewportRanges;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Point;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.MouseMotionListener;
-import java.awt.event.MouseWheelEvent;
-import java.awt.event.MouseWheelListener;
-import java.util.Collections;
-import java.util.List;
-
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JToolTip;
-import javax.swing.SwingUtilities;
-import javax.swing.Timer;
-import javax.swing.ToolTipManager;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
/**
* DOCUMENT ME!
@@ -209,13 +212,21 @@ public class SeqPanel extends JPanel
StringBuffer keyboardNo2;
- java.net.URL linkImageURL;
-
private final SequenceAnnotationReport seqARep;
- StringBuilder tooltipText = new StringBuilder();
+ /*
+ * the last tooltip on mousing over the alignment (or annotation in wrapped mode)
+ * - the tooltip is not set again if unchanged
+ * - this is the tooltip text _before_ formatting as html
+ */
+ private String lastTooltip;
- String tmpString;
+ /*
+ * the last tooltip on mousing over the alignment (or annotation in wrapped mode)
+ * - used to decide where to place the tooltip in getTooltipLocation()
+ * - this is the tooltip text _after_ formatting as html
+ */
+ private String lastFormattedTooltip;
EditCommand editCommand;
@@ -231,8 +242,7 @@ public class SeqPanel extends JPanel
*/
public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel)
{
- linkImageURL = getClass().getResource("/images/link.gif");
- seqARep = new SequenceAnnotationReport(linkImageURL.toString());
+ seqARep = new SequenceAnnotationReport(true);
ToolTipManager.sharedInstance().registerComponent(this);
ToolTipManager.sharedInstance().setInitialDelay(0);
ToolTipManager.sharedInstance().setDismissDelay(10000);
@@ -265,6 +275,9 @@ public class SeqPanel extends JPanel
/**
* Computes the column and sequence row (and possibly annotation row when in
* wrapped mode) for the given mouse position
+ *
+ * Mouse position is not set if in wrapped mode with the cursor either between
+ * sequences, or over the left or right vertical scale.
*
* @param evt
* @return
@@ -321,8 +334,10 @@ public class SeqPanel extends JPanel
}
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);
@@ -330,6 +345,9 @@ public class SeqPanel extends JPanel
/**
* Returns the aligned sequence position (base 0) at the mouse position, or
* the closest visible one
+ *
+ * Returns -1 if in wrapped mode with the mouse over either left or right
+ * vertical scale.
*
* @param evt
* @return
@@ -465,47 +483,80 @@ public class SeqPanel extends JPanel
void moveCursor(int dx, int dy)
{
- seqCanvas.cursorX += dx;
- seqCanvas.cursorY += dy;
-
+ moveCursor(dx, dy,false);
+ }
+ void moveCursor(int dx, int dy, boolean nextWord)
+ {
HiddenColumns hidden = av.getAlignment().getHiddenColumns();
- if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
+ if (nextWord)
{
- int original = seqCanvas.cursorX - dx;
int maxWidth = av.getAlignment().getWidth();
-
- if (!hidden.isVisible(seqCanvas.cursorX))
- {
- int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
- int[] region = hidden.getRegionWithEdgeAtRes(visx);
-
- if (region != null) // just in case
+ int maxHeight=av.getAlignment().getHeight();
+ SequenceI seqAtRow = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
+ // look for next gap or residue
+ boolean isGap = Comparison.isGap(seqAtRow.getCharAt(seqCanvas.cursorX));
+ int p = seqCanvas.cursorX,lastP,r=seqCanvas.cursorY,lastR;
+ do
+ {
+ lastP = p;
+ lastR = r;
+ if (dy != 0)
{
- if (dx == 1)
+ r += dy;
+ if (r < 0)
{
- // moving right
- seqCanvas.cursorX = region[1] + 1;
+ r = 0;
}
- else if (dx == -1)
+ if (r >= maxHeight)
{
- // moving left
- seqCanvas.cursorX = region[0] - 1;
+ r = maxHeight - 1;
}
+ seqAtRow = av.getAlignment().getSequenceAt(r);
}
- seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
- }
+ p = nextVisible(hidden, maxWidth, p, dx);
+ } while ((dx != 0 ? p != lastP : r != lastR)
+ && isGap == Comparison.isGap(seqAtRow.getCharAt(p)));
+ seqCanvas.cursorX=p;
+ seqCanvas.cursorY=r;
+ } else {
+ int maxWidth = av.getAlignment().getWidth();
+ seqCanvas.cursorX = nextVisible(hidden, maxWidth, seqCanvas.cursorX, dx);
+ seqCanvas.cursorY += dy;
+ }
+ scrollToVisible(false);
+ }
- if (seqCanvas.cursorX >= maxWidth
- || !hidden.isVisible(seqCanvas.cursorX))
+ private int nextVisible(HiddenColumns hidden,int maxWidth, int original, int dx)
+ {
+ int newCursorX=original+dx;
+ if (av.hasHiddenColumns() && !hidden.isVisible(newCursorX))
+ {
+ int visx = hidden.absoluteToVisibleColumn(newCursorX - dx);
+ int[] region = hidden.getRegionWithEdgeAtRes(visx);
+
+ if (region != null) // just in case
{
- seqCanvas.cursorX = original;
+ if (dx == 1)
+ {
+ // moving right
+ newCursorX = region[1] + 1;
+ }
+ else if (dx == -1)
+ {
+ // moving left
+ newCursorX = region[0] - 1;
+ }
}
}
-
- scrollToVisible(false);
+ newCursorX = (newCursorX < 0) ? 0 : newCursorX;
+ if (newCursorX >= maxWidth
+ || !hidden.isVisible(newCursorX))
+ {
+ newCursorX = original;
+ }
+ return newCursorX;
}
-
/**
* Scroll to make the cursor visible in the viewport.
*
@@ -817,8 +868,6 @@ public class SeqPanel extends JPanel
String lastMessage;
- private String formattedTooltipText;
-
@Override
public void mouseOverSequence(SequenceI sequence, int index, int pos)
{
@@ -839,11 +888,11 @@ public class SeqPanel extends JPanel
* 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;
@@ -869,6 +918,78 @@ public class SeqPanel extends JPanel
{
setStatusMessage(results);
}
+ return 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
+ *
+ * - results are a single residue in a protein alignment
+ * - there is a mapping to a coding sequence (codon)
+ * - there are one or more SNP variant features on the codon
+ *
+ * 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();
+
+ List matches = results.getResults();
+ int j = matches.size();
+ List infos = new ArrayList<>();
+ for (int i = 0; i < j; i++)
+ {
+ SearchResultMatchI match = matches.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
@@ -908,8 +1029,10 @@ public class SeqPanel extends JPanel
/*
* just a pixel move without change of 'cell'
*/
+ moveTooltip = false;
return;
}
+ moveTooltip = true;
lastMousePosition = mousePos;
if (mousePos.isOverAnnotation())
@@ -925,6 +1048,7 @@ public class SeqPanel extends JPanel
lastMousePosition = null;
setToolTipText(null);
lastTooltip = null;
+ lastFormattedTooltip = null;
ap.alignFrame.setStatus("");
return;
}
@@ -946,7 +1070,7 @@ public class SeqPanel extends JPanel
mouseOverSequence(sequence, column, pos);
}
- tooltipText.setLength(6); // ""
+ StringBuilder tooltipText = new StringBuilder(64);
SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
if (groups != null)
@@ -975,32 +1099,63 @@ public class SeqPanel extends JPanel
* add features that straddle the gap (pos may be the residue before or
* after the gap)
*/
+ int unshownFeatures = 0;
if (av.isShowSequenceFeatures())
{
List features = ap.getFeatureRenderer()
.findFeaturesAtColumn(sequence, column + 1);
- seqARep.appendFeatures(tooltipText, pos, features,
- this.ap.getSeqPanel().seqCanvas.fr);
+ unshownFeatures = seqARep.appendFeatures(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.appendFeatures(tooltipText,
+ pos, mf, fr2, MAX_TOOLTIP_LENGTH);
+ }
+ }
+ }
}
- if (tooltipText.length() == 6) //
+ if (tooltipText.length() == 0) // nothing added
{
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("
").append("... ").append("")
+ .append(MessageManager.formatMessage(
+ "label.features_not_shown", unshownFeatures))
+ .append("");
+ }
String textString = tooltipText.toString();
- if (lastTooltip == null || !lastTooltip.equals(textString))
+ if (!textString.equals(lastTooltip))
{
- formattedTooltipText = JvSwingUtils.wrapTooltip(true,
- textString);
- setToolTipText(formattedTooltipText);
lastTooltip = textString;
+ lastFormattedTooltip = JvSwingUtils.wrapTooltip(true,
+ textString);
+ setToolTipText(lastFormattedTooltip);
}
}
}
@@ -1026,16 +1181,34 @@ public class SeqPanel extends JPanel
String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column,
anns);
- setToolTipText(tooltip);
- lastTooltip = tooltip;
+ if (!tooltip.equals(lastTooltip))
+ {
+ lastTooltip = tooltip;
+ lastFormattedTooltip = tooltip == null ? null
+ : JvSwingUtils.wrapTooltip(true, tooltip);
+ setToolTipText(lastFormattedTooltip);
+ }
String msg = AnnotationPanel.getStatusMessage(av.getAlignment(), column,
anns[rowIndex]);
ap.alignFrame.setStatus(msg);
}
- private Point lastp = null;
+ /*
+ * if Shift key is held down while moving the mouse,
+ * the tooltip location is not changed once shown
+ */
+ private Point lastTooltipLocation = null;
+
+ /*
+ * this flag is false for pixel moves within a residue,
+ * to reduce tooltip flicker
+ */
+ private boolean moveTooltip = true;
+ /*
+ * a dummy tooltip used to estimate where to position tooltips
+ */
private JToolTip tempTip = new JLabel().createToolTip();
/*
@@ -1048,33 +1221,30 @@ public class SeqPanel extends JPanel
{
// BH 2018
- if (tooltipText == null || tooltipText.length() <= 6)
+ if (lastTooltip == null || !moveTooltip)
{
return null;
}
- if (lastp != null && event.isShiftDown())
+ if (lastTooltipLocation != null && event.isShiftDown())
{
- return lastp;
+ return lastTooltipLocation;
}
- Point p = lastp;
int x = event.getX();
int y = event.getY();
int w = getWidth();
- tempTip.setTipText(formattedTooltipText);
+ tempTip.setTipText(lastFormattedTooltip);
int tipWidth = (int) tempTip.getPreferredSize().getWidth();
// was x += (w - x < 200) ? -(w / 2) : 5;
x = (x + tipWidth < w ? x + 10 : w - tipWidth);
- p = new Point(x, y + 20); // BH 2018 was - 20?
+ Point p = new Point(x, y + av.getCharHeight()); // BH 2018 was - 20?
- return lastp = p;
+ return lastTooltipLocation = p;
}
- String lastTooltip;
-
/**
* set when the current UI interaction has resulted in a change that requires
* shading in overviews and structures to be recalculated. this could be
@@ -1111,7 +1281,7 @@ public class SeqPanel extends JPanel
{
char sequenceChar = sequence.getCharAt(column);
int pos = sequence.findPosition(column);
- setStatusMessage(sequence, seqIndex, sequenceChar, pos);
+ setStatusMessage(sequence.getName(), seqIndex, sequenceChar, pos);
return pos;
}
@@ -1127,7 +1297,7 @@ public class SeqPanel extends JPanel
* Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
*
*
- * @param sequence
+ * @param seqName
* @param seqIndex
* sequence position in the alignment (1..)
* @param sequenceChar
@@ -1135,7 +1305,7 @@ public class SeqPanel extends JPanel
* @param residuePos
* the sequence residue position (if not over a gap)
*/
- protected void setStatusMessage(SequenceI sequence, int seqIndex,
+ protected void setStatusMessage(String seqName, int seqIndex,
char sequenceChar, int residuePos)
{
StringBuilder text = new StringBuilder(32);
@@ -1145,7 +1315,7 @@ public class SeqPanel extends JPanel
*/
String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
text.append("Sequence").append(seqno).append(" ID: ")
- .append(sequence.getName());
+ .append(seqName);
String residue = null;
@@ -1190,7 +1360,8 @@ public class SeqPanel extends JPanel
{
return;
}
- SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
+ SequenceI alignedSeq = al.getSequenceAt(sequenceIndex);
+ SequenceI ds = alignedSeq.getDatasetSequence();
for (SearchResultMatchI m : results.getResults())
{
SequenceI seq = m.getSequence();
@@ -1202,8 +1373,8 @@ public class SeqPanel extends JPanel
if (seq == ds)
{
int start = m.getStart();
- setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
- start);
+ setStatusMessage(alignedSeq.getName(), sequenceIndex,
+ seq.getCharAt(start - 1), start);
return;
}
}
@@ -1940,6 +2111,7 @@ public class SeqPanel extends JPanel
@Override
public void mouseExited(MouseEvent e)
{
+ lastMousePosition = null;
ap.alignFrame.setStatus(" ");
if (av.getWrapAlignment())
{
@@ -1967,7 +2139,7 @@ public class SeqPanel extends JPanel
return;
}
- if (evt.getClickCount() > 1)
+ if (evt.getClickCount() > 1 && av.isShowSequenceFeatures())
{
sg = av.getSelectionGroup();
if (sg != null && sg.getSize() == 1
@@ -2168,11 +2340,11 @@ public class SeqPanel extends JPanel
final int column = pos.column;
final int seq = pos.seqIndex;
SequenceI sequence = av.getAlignment().getSequenceAt(seq);
- List 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());
+ }
}
/**
@@ -2405,14 +2577,7 @@ public class SeqPanel extends JPanel
{
scrollThread = new ScrollThread();
scrollThread.setMousePosition(mousePos);
- if (!Platform.isJS())
- {
- /*
- * Java - run in a new thread
- */
- scrollThread.start();
- }
- else
+ if (Platform.isJS())
{
/*
* Javascript - run every 20ms until scrolling stopped
@@ -2444,6 +2609,13 @@ public class SeqPanel extends JPanel
});
t.start();
}
+ else
+ {
+ /*
+ * Java - run in a new thread
+ */
+ scrollThread.start();
+ }
}
}
@@ -2630,7 +2802,7 @@ public class SeqPanel extends JPanel
{
if (av.getAlignment() == null)
{
- Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
+ Console.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
+ " ViewId=" + av.getViewId()
+ " 's alignment is NULL! returning immediately.");
return;
@@ -2730,7 +2902,7 @@ public class SeqPanel extends JPanel
* 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);
/*