JAL-3071 corrections to IdPanel$ScrollThread
[jalview.git] / src / jalview / gui / SeqPanel.java
index 7abbd7d..04ceea6 100644 (file)
@@ -49,12 +49,15 @@ import jalview.util.MappingUtils;
 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;
@@ -63,8 +66,11 @@ 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;
 
 /**
@@ -230,6 +236,8 @@ public class SeqPanel extends JPanel
     ToolTipManager.sharedInstance().registerComponent(this);
     ToolTipManager.sharedInstance().setInitialDelay(0);
     ToolTipManager.sharedInstance().setDismissDelay(10000);
+    
+    
     this.av = viewport;
     setBackground(Color.white);
 
@@ -742,13 +750,14 @@ public class SeqPanel extends JPanel
       return;
     }
 
-    if (!editingSeqs)
+    if (editingSeqs)
+    {
+      endEditing();
+    }
+    else
     {
       doMouseReleasedDefineMode(evt, didDrag);
-      return;
     }
-
-    endEditing();
   }
 
   /**
@@ -808,6 +817,8 @@ public class SeqPanel extends JPanel
 
   String lastMessage;
 
+  private String formattedTooltipText;
+
   @Override
   public void mouseOverSequence(SequenceI sequence, int index, int pos)
   {
@@ -853,8 +864,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);
     }
@@ -935,7 +946,7 @@ public class SeqPanel extends JPanel
       mouseOverSequence(sequence, column, pos);
     }
 
-    tooltipText.setLength(6); // Cuts the buffer back to <html>
+    tooltipText.setLength(6); // "<html>"
 
     SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
     if (groups != null)
@@ -986,9 +997,9 @@ public class SeqPanel extends JPanel
       String textString = tooltipText.toString();
       if (lastTooltip == null || !lastTooltip.equals(textString))
       {
-        String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
+        formattedTooltipText = JvSwingUtils.wrapTooltip(true,
                 textString);
-        setToolTipText(formattedTooltipText);
+        setToolTipText(formattedTooltipText);        
         lastTooltip = textString;
       }
     }
@@ -1025,6 +1036,8 @@ public class SeqPanel extends JPanel
 
   private Point lastp = null;
 
+  private JToolTip tempTip = new JLabel().createToolTip();
+
   /*
    * (non-Javadoc)
    * 
@@ -1033,26 +1046,31 @@ public class SeqPanel extends JPanel
   @Override
   public Point getToolTipLocation(MouseEvent event)
   {
+    // BH 2018
+
     if (tooltipText == null || tooltipText.length() <= 6)
     {
-      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 (lastp != null && event.isShiftDown())
     {
-      p = new Point(event.getX() + wdth, event.getY() - 20);
-      lastp = p;
+      return lastp;
     }
-    /*
-     * TODO: try to set position so region is not obscured by tooltip
-     */
-    return p;
+
+    Point p = lastp;
+    int x = event.getX();
+    int y = event.getY();
+    int w = getWidth();
+
+    tempTip.setTipText(formattedTooltipText);
+    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?
+
+    return lastp = p;
   }
 
   String lastTooltip;
@@ -1325,9 +1343,9 @@ public class SeqPanel extends JPanel
     }
 
     mouseDragging = true;
-    if ((scrollThread != null) && (scrollThread.isRunning()))
+    if (scrollThread != null)
     {
-      scrollThread.setEvent(evt);
+      scrollThread.setMousePosition(evt.getPoint());
     }
   }
 
@@ -1898,10 +1916,10 @@ public class SeqPanel extends JPanel
   }
 
   /**
-   * 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)
@@ -1910,19 +1928,14 @@ public class SeqPanel extends JPanel
     {
       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)
@@ -1935,7 +1948,7 @@ public class SeqPanel extends JPanel
 
     if (mouseDragging && scrollThread == null)
     {
-      scrollThread = new ScrollThread();
+      startScrolling(e.getPoint());
     }
   }
 
@@ -1981,17 +1994,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<SequenceI> 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();
       }
     }
   }
@@ -2073,22 +2082,22 @@ 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;
+    }
+
     if (av.cursorMode)
     {
       seqCanvas.cursorX = res;
@@ -2248,10 +2257,7 @@ public class SeqPanel extends JPanel
       return;
     }
 
-    if (res >= av.getAlignment().getWidth())
-    {
-      res = av.getAlignment().getWidth() - 1;
-    }
+    res = Math.min(res, av.getAlignment().getWidth()-1);
 
     if (stretchGroup.getEndRes() == res)
     {
@@ -2337,9 +2343,9 @@ public class SeqPanel extends JPanel
 
     mouseDragging = true;
 
-    if ((scrollThread != null) && (scrollThread.isRunning()))
+    if (scrollThread != null)
     {
-      scrollThread.setEvent(evt);
+      scrollThread.setMousePosition(evt.getPoint());
     }
 
     /*
@@ -2369,86 +2375,133 @@ public class SeqPanel extends JPanel
     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
+    mouseDragging = false;
+  }
+
+  /**
+   * Starts a thread to scroll the alignment, towards a given mouse position
+   * outside the panel bounds, unless the alignment is in wrapped mode
+   * 
+   * @param mousePos
+   */
+  void startScrolling(Point mousePos)
+  {
+    /*
+     * set this.mouseDragging in case this was called from 
+     * a drag in ScalePanel or AnnotationPanel
+     */
+    mouseDragging = true;
+    if (!av.getWrapAlignment() && scrollThread == null)
     {
-      if (scrollThread == null)
+      scrollThread = new ScrollThread();
+      scrollThread.setMousePosition(mousePos);
+      if (!Platform.isJS())
       {
-        scrollThread = new ScrollThread();
+        /*
+         * Java - run in a new thread
+         */
+        scrollThread.start();
+      }
+      else
+      {
+        /*
+         * 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();
       }
-
-      mouseDragging = true;
-      scrollThread.setEvent(evt);
     }
-
   }
 
-  // this class allows scrolling off the bottom of the visible alignment
+  /**
+   * 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
   {
-    MouseEvent evt;
+    private Point mousePos;
 
-    private volatile boolean threadRunning = true;
+    private volatile boolean keepRunning = true;
 
+    /**
+     * Constructor
+     */
     public ScrollThread()
     {
-      start();
+      setName("SeqPanel$ScrollThread");
     }
 
-    public void setEvent(MouseEvent e)
+    /**
+     * Sets the position of the mouse that determines the direction of the
+     * 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
+     */
+    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;
+      keepRunning = false;
     }
 
+    /**
+     * 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 (keepRunning)
       {
-        if (evt != null)
+        if (mousePos != null)
         {
-          if (mouseDragging && (evt.getY() < 0)
-                  && (av.getRanges().getStartSeq() > 0))
-          {
-            av.getRanges().scrollUp(true);
-          }
-
-          if (mouseDragging && (evt.getY() >= getHeight()) && (av
-                  .getAlignment().getHeight() > av.getRanges().getEndSeq()))
-          {
-            av.getRanges().scrollUp(false);
-          }
-
-          if (mouseDragging && (evt.getX() < 0))
-          {
-            av.getRanges().scrollRight(false);
-          }
-          else if (mouseDragging && (evt.getX() >= getWidth()))
-          {
-            av.getRanges().scrollRight(true);
-          }
+          keepRunning = scrollOnce();
         }
-
         try
         {
           Thread.sleep(20);
@@ -2456,6 +2509,60 @@ public class SeqPanel extends JPanel
         {
         }
       }
+      SeqPanel.this.scrollThread = null;
+    }
+
+    /**
+     * Scrolls
+     * <ul>
+     * <li>one row up, if the mouse is above the panel</li>
+     * <li>one row down, if the mouse is below the panel</li>
+     * <li>one column left, if the mouse is left of the panel</li>
+     * <li>one column right, if the mouse is right of the panel</li>
+     * </ul>
+     * 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;
     }
   }