JAL-3345 JAL3253 Smoother ScalePanel-based scrolling
[jalview.git] / src / jalview / gui / SeqPanel.java
index 6bc1ad8..26ec081 100644 (file)
@@ -56,6 +56,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;
@@ -64,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;
 
 /**
@@ -129,7 +134,6 @@ public class SeqPanel extends JPanel
       MousePos o = (MousePos) obj;
       boolean b = (column == o.column && seqIndex == o.seqIndex
               && annotationIndex == o.annotationIndex);
-      // System.out.println(obj + (b ? "= " : "!= ") + this);
       return b;
     }
 
@@ -231,6 +235,8 @@ public class SeqPanel extends JPanel
     ToolTipManager.sharedInstance().registerComponent(this);
     ToolTipManager.sharedInstance().setInitialDelay(0);
     ToolTipManager.sharedInstance().setDismissDelay(10000);
+    
+    
     this.av = viewport;
     setBackground(Color.white);
 
@@ -255,11 +261,6 @@ public class SeqPanel extends JPanel
 
   int wrappedBlock = -1;
 
-  MousePos findMousePosition(MouseEvent evt)
-  {
-    return findMousePosition(evt, null);
-  }
-
   /**
    * Computes the column and sequence row (and possibly annotation row when in
    * wrapped mode) for the given mouse position
@@ -267,22 +268,15 @@ public class SeqPanel extends JPanel
    * @param evt
    * @return
    */
-  MousePos findMousePosition(MouseEvent evt, String debug)
+  MousePos findMousePosition(MouseEvent evt)
   {
-    int col = findColumn(evt, debug);
+    int col = findColumn(evt);
     int seqIndex = -1;
     int annIndex = -1;
     int y = evt.getY();
 
     int charHeight = av.getCharHeight();
     int alignmentHeight = av.getAlignment().getHeight();
-    if (debug != null)
-    {
-      System.out.println(String.format(
-              "%s: charHeight %d alHeight %d canvasWidth %d canvasHeight %d",
-              debug, charHeight, alignmentHeight, seqCanvas.getWidth(),
-              seqCanvas.getHeight()));
-    }
     if (av.getWrapAlignment())
     {
       seqCanvas.calculateWrappedGeometry(seqCanvas.getWidth(),
@@ -343,11 +337,6 @@ public class SeqPanel extends JPanel
    */
   int findColumn(MouseEvent evt)
   {
-    return findColumn(evt, null);
-  }
-
-  int findColumn(MouseEvent evt, String debug)
-  {
     int res = 0;
     int x = evt.getX();
 
@@ -368,14 +357,6 @@ public class SeqPanel extends JPanel
       int y = evt.getY();
       y = Math.max(0, y - hgap);
       x -= seqCanvas.getLabelWidthWest();
-      if (debug != null)
-      {
-        System.out.println(
-                String.format("%s: %s %d x %d labelWest %d charWidth %d ",
-                        debug, Thread.currentThread().getName(),
-                        System.currentTimeMillis(), x,
-                        seqCanvas.getLabelWidthWest(), charWidth));
-      }
       if (x < 0)
       {
         // mouse is over left scale
@@ -389,10 +370,6 @@ public class SeqPanel extends JPanel
       }
       if (x >= cwidth * charWidth)
       {
-        if (debug != null)
-        {
-          System.out.println(debug + ": cwidth = " + cwidth);
-        }
         // mouse is over right scale
         return -1;
       }
@@ -841,6 +818,8 @@ public class SeqPanel extends JPanel
 
   String lastMessage;
 
+  private String formattedTooltipText;
+
   @Override
   public void mouseOverSequence(SequenceI sequence, int index, int pos)
   {
@@ -886,8 +865,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);
     }
@@ -968,7 +947,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)
@@ -1019,9 +998,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;
       }
     }
@@ -1058,6 +1037,8 @@ public class SeqPanel extends JPanel
 
   private Point lastp = null;
 
+  private JToolTip tempTip = new JLabel().createToolTip();
+
   /*
    * (non-Javadoc)
    * 
@@ -1066,26 +1047,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;
@@ -1517,7 +1503,6 @@ public class SeqPanel extends JPanel
         return;
       }
 
-      // System.out.print(y1+" "+y2+" "+fixedLeft+" "+fixedRight+"~~");
       // Selection spans a hidden region
       if (fixedLeft < y1 && (fixedRight > y2 || fixedRight == -1))
       {
@@ -1955,16 +1940,20 @@ public class SeqPanel extends JPanel
   @Override
   public void mouseExited(MouseEvent e)
   {
+    if (ap.getScalePanel().isMouseDragging())
+    {
+      ap.getScalePanel().mouseExited(e);
+      return;
+    }
     lastMousePosition = null;
     ap.alignFrame.setStatus(" ");
     if (av.getWrapAlignment())
     {
       return;
     }
-
     if (mouseDragging && scrollThread == null)
     {
-      scrollThread = new ScrollThread();
+      startScrolling(e.getPoint());
     }
   }
 
@@ -2010,17 +1999,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();
       }
     }
   }
@@ -2102,19 +2087,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;
     }
 
@@ -2410,29 +2395,66 @@ 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();
+            }
+            if (scrollThread == null)
+            {
+              // SeqPanel.stopScrolling called
+              ((Timer) e.getSource()).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
@@ -2440,12 +2462,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
      */
@@ -2459,7 +2483,7 @@ public class SeqPanel extends JPanel
      */
     public void stopScrolling()
     {
-      threadRunning = false;
+      keepRunning = false;
     }
 
     /**
@@ -2470,48 +2494,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);
@@ -2519,6 +2507,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;
     }
   }