X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FSeqPanel.java;h=454a73051a77a8330cb266217aae401a950931b7;hb=9d2408483e451285fd555c3cd6e0273977acbaa7;hp=d7d4af038de27a9b4f2f91f61a728d5523c1dc9a;hpb=c1e22e204c48fe7fc30dbb95be46e5a04808898a;p=jalview.git
diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java
index d7d4af0..454a730 100644
--- a/src/jalview/gui/SeqPanel.java
+++ b/src/jalview/gui/SeqPanel.java
@@ -25,6 +25,8 @@ 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;
@@ -34,8 +36,11 @@ 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;
@@ -209,9 +214,19 @@ public class SeqPanel extends JPanel
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,6 +246,8 @@ public class SeqPanel extends JPanel
ToolTipManager.sharedInstance().registerComponent(this);
ToolTipManager.sharedInstance().setInitialDelay(0);
ToolTipManager.sharedInstance().setDismissDelay(10000);
+
+
this.av = viewport;
setBackground(Color.white);
@@ -258,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
@@ -325,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
@@ -460,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.
*
@@ -857,8 +913,8 @@ public class SeqPanel extends JPanel
ap.setToScrollComplementPanel(true);
}
- boolean noFastPaint = wasScrolled && av.getWrapAlignment();
- if (seqCanvas.highlightSearchResults(results, noFastPaint))
+ boolean fastPaint = !(wasScrolled && av.getWrapAlignment());
+ if (seqCanvas.highlightSearchResults(results, fastPaint))
{
setStatusMessage(results);
}
@@ -892,11 +948,12 @@ public class SeqPanel extends JPanel
AlignFrame af = Desktop.getAlignFrameFor(complement);
FeatureRendererModel fr2 = af.getFeatureRenderer();
- int j = results.getSize();
+ List matches = results.getResults();
+ int j = matches.size();
List infos = new ArrayList<>();
for (int i = 0; i < j; i++)
{
- SearchResultMatchI match = results.getResults().get(i);
+ SearchResultMatchI match = matches.get(i);
int pos = match.getStart();
if (pos == match.getEnd())
{
@@ -972,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())
@@ -989,6 +1048,7 @@ public class SeqPanel extends JPanel
lastMousePosition = null;
setToolTipText(null);
lastTooltip = null;
+ lastFormattedTooltip = null;
ap.alignFrame.setStatus("");
return;
}
@@ -1010,7 +1070,7 @@ public class SeqPanel extends JPanel
mouseOverSequence(sequence, column, pos);
}
- tooltipText.setLength(6); // Cuts the buffer back to
+ StringBuilder tooltipText = new StringBuilder(64);
SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
if (groups != null)
@@ -1064,13 +1124,13 @@ public class SeqPanel extends JPanel
pos);
if (mf != null)
{
- unshownFeatures = seqARep.appendFeatures(tooltipText,
+ unshownFeatures += seqARep.appendFeatures(tooltipText,
pos, mf, fr2, MAX_TOOLTIP_LENGTH);
}
}
}
}
- if (tooltipText.length() == 6) // ""
+ if (tooltipText.length() == 0) // nothing added
{
setToolTipText(null);
lastTooltip = null;
@@ -1090,12 +1150,12 @@ public class SeqPanel extends JPanel
.append("");
}
String textString = tooltipText.toString();
- if (lastTooltip == null || !lastTooltip.equals(textString))
+ if (!textString.equals(lastTooltip))
{
- String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
- textString);
- setToolTipText(formattedTooltipText);
lastTooltip = textString;
+ lastFormattedTooltip = JvSwingUtils.wrapTooltip(true,
+ textString);
+ setToolTipText(lastFormattedTooltip);
}
}
}
@@ -1121,15 +1181,35 @@ 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();
/*
* (non-Javadoc)
@@ -1139,29 +1219,31 @@ public class SeqPanel extends JPanel
@Override
public Point getToolTipLocation(MouseEvent event)
{
- if (tooltipText == null || tooltipText.length() <= 6)
+ // BH 2018
+
+ if (lastTooltip == null || !moveTooltip)
{
- lastp = null;
return null;
}
- int x = event.getX();
- int w = getWidth();
- // switch sides when tooltip is too close to edge
- int wdth = (w - x < 200) ? -(w / 2) : 5;
- Point p = lastp;
- if (!event.isShiftDown() || p == null)
+ if (lastTooltipLocation != null && event.isShiftDown())
{
- p = new Point(event.getX() + wdth, event.getY() - 20);
- lastp = p;
+ return lastTooltipLocation;
}
- /*
- * TODO: try to set position so region is not obscured by tooltip
- */
- return p;
- }
- String lastTooltip;
+ int x = event.getX();
+ int y = event.getY();
+ int w = getWidth();
+
+ 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);
+ Point p = new Point(x, y + av.getCharHeight()); // BH 2018 was - 20?
+
+ return lastTooltipLocation = p;
+ }
/**
* set when the current UI interaction has resulted in a change that requires
@@ -2038,7 +2120,7 @@ public class SeqPanel extends JPanel
if (mouseDragging && scrollThread == null)
{
- scrollThread = new ScrollThread();
+ startScrolling(e.getPoint());
}
}
@@ -2084,17 +2166,13 @@ public class SeqPanel extends JPanel
SearchResultsI highlight = new SearchResults();
highlight.addResult(sequence, features.get(0).getBegin(), features
.get(0).getEnd());
- seqCanvas.highlightSearchResults(highlight, false);
+ seqCanvas.highlightSearchResults(highlight, true);
/*
- * open the Amend Features dialog; clear highlighting afterwards,
- * whether changes were made or not
+ * open the Amend Features dialog
*/
- List seqs = Collections.singletonList(sequence);
- seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
- ap);
- av.setSearchResults(null); // clear highlighting
- seqCanvas.repaint(); // draw new/amended features
+ new FeatureEditor(ap, Collections.singletonList(sequence), features,
+ false).showDialog();
}
}
}
@@ -2176,19 +2254,19 @@ public class SeqPanel extends JPanel
}
}
- if (evt.isPopupTrigger()) // Mac: mousePressed
- {
- showPopupMenu(evt, pos);
- return;
- }
-
/*
* defer right-mouse click handling to mouseReleased on Windows
* (where isPopupTrigger() will answer true)
* NB isRightMouseButton is also true for Cmd-click on Mac
*/
- if (SwingUtilities.isRightMouseButton(evt) && !Platform.isAMac())
+ if (Platform.isWinRightButton(evt))
+ {
+ return;
+ }
+
+ if (evt.isPopupTrigger()) // Mac: mousePressed
{
+ showPopupMenu(evt, pos);
return;
}
@@ -2484,29 +2562,73 @@ public class SeqPanel extends JPanel
/**
* Starts a thread to scroll the alignment, towards a given mouse position
- * outside the panel bounds
+ * outside the panel bounds, unless the alignment is in wrapped mode
*
* @param mousePos
*/
void startScrolling(Point mousePos)
{
- if (scrollThread == null)
+ /*
+ * set this.mouseDragging in case this was called from
+ * a drag in ScalePanel or AnnotationPanel
+ */
+ mouseDragging = true;
+ if (!av.getWrapAlignment() && scrollThread == null)
{
scrollThread = new ScrollThread();
+ scrollThread.setMousePosition(mousePos);
+ if (Platform.isJS())
+ {
+ /*
+ * Javascript - run every 20ms until scrolling stopped
+ * or reaches the limit of scrollable alignment
+ */
+ Timer t = new Timer(20, new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ if (scrollThread != null)
+ {
+ // if (!scrollOnce() {t.stop();}) gives compiler error :-(
+ scrollThread.scrollOnce();
+ }
+ }
+ });
+ t.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ if (scrollThread == null)
+ {
+ // SeqPanel.stopScrolling called
+ t.stop();
+ }
+ }
+ });
+ t.start();
+ }
+ else
+ {
+ /*
+ * Java - run in a new thread
+ */
+ scrollThread.start();
+ }
}
-
- mouseDragging = true;
- scrollThread.setMousePosition(mousePos);
}
/**
- * Performs scrolling of the visible alignment left, right, up or down
+ * Performs scrolling of the visible alignment left, right, up or down, until
+ * scrolling is stopped by calling stopScrolling, mouse drag is ended, or the
+ * limit of the alignment is reached
*/
class ScrollThread extends Thread
{
private Point mousePos;
- private volatile boolean threadRunning = true;
+ private volatile boolean keepRunning = true;
/**
* Constructor
@@ -2514,12 +2636,14 @@ public class SeqPanel extends JPanel
public ScrollThread()
{
setName("SeqPanel$ScrollThread");
- start();
}
/**
* Sets the position of the mouse that determines the direction of the
- * scroll to perform
+ * scroll to perform. If this is called as the mouse moves, scrolling should
+ * respond accordingly. For example, if the mouse is dragged right, scroll
+ * right should start; if the drag continues down, scroll down should also
+ * happen.
*
* @param p
*/
@@ -2533,7 +2657,7 @@ public class SeqPanel extends JPanel
*/
public void stopScrolling()
{
- threadRunning = false;
+ keepRunning = false;
}
/**
@@ -2544,48 +2668,12 @@ public class SeqPanel extends JPanel
@Override
public void run()
{
- while (threadRunning && mouseDragging)
+ while (keepRunning)
{
if (mousePos != null)
{
- boolean scrolled = false;
- ViewportRanges ranges = SeqPanel.this.av.getRanges();
-
- /*
- * scroll up or down
- */
- if (mousePos.y < 0)
- {
- // mouse is above this panel - try scroll up
- scrolled = ranges.scrollUp(true);
- }
- else if (mousePos.y >= getHeight())
- {
- // mouse is below this panel - try scroll down
- scrolled = ranges.scrollUp(false);
- }
-
- /*
- * scroll left or right
- */
- if (mousePos.x < 0)
- {
- scrolled |= ranges.scrollRight(false);
- }
- else if (mousePos.x >= getWidth())
- {
- scrolled |= ranges.scrollRight(true);
- }
- if (!scrolled)
- {
- /*
- * we have reached the limit of the visible alignment - quit
- */
- threadRunning = false;
- SeqPanel.this.ap.repaint();
- }
+ keepRunning = scrollOnce();
}
-
try
{
Thread.sleep(20);
@@ -2593,6 +2681,60 @@ public class SeqPanel extends JPanel
{
}
}
+ SeqPanel.this.scrollThread = null;
+ }
+
+ /**
+ * Scrolls
+ *
+ * - one row up, if the mouse is above the panel
+ * - one row down, if the mouse is below the panel
+ * - one column left, if the mouse is left of the panel
+ * - one column right, if the mouse is right of the panel
+ *
+ * Answers true if a scroll was performed, false if not - meaning either
+ * that the mouse position is within the panel, or the edge of the alignment
+ * has been reached.
+ */
+ boolean scrollOnce()
+ {
+ /*
+ * quit after mouseUp ensures interrupt in JalviewJS
+ */
+ if (!mouseDragging)
+ {
+ return false;
+ }
+
+ boolean scrolled = false;
+ ViewportRanges ranges = SeqPanel.this.av.getRanges();
+
+ /*
+ * scroll up or down
+ */
+ if (mousePos.y < 0)
+ {
+ // mouse is above this panel - try scroll up
+ scrolled = ranges.scrollUp(true);
+ }
+ else if (mousePos.y >= getHeight())
+ {
+ // mouse is below this panel - try scroll down
+ scrolled = ranges.scrollUp(false);
+ }
+
+ /*
+ * scroll left or right
+ */
+ if (mousePos.x < 0)
+ {
+ scrolled |= ranges.scrollRight(false);
+ }
+ else if (mousePos.x >= getWidth())
+ {
+ scrolled |= ranges.scrollRight(true);
+ }
+ return scrolled;
}
}