JAL-4375 Improved trackpad user experience for wrapped format, with vertical scrollin...
[jalview.git] / src / jalview / gui / SeqPanel.java
index 6918811..423658d 100644 (file)
@@ -44,6 +44,7 @@ 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;
@@ -137,7 +138,7 @@ 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);
+      // jalview.bin.Console.outPrintln(obj + (b ? "= " : "!= ") + this);
       return b;
     }
 
@@ -162,6 +163,41 @@ public class SeqPanel extends JPanel
     }
   }
 
+  /**
+   * Rotation threshold for up/down trackpad/wheelmouse movement to register for
+   * an alignment in wrapped format. Up/down scrolling here results in a large
+   * jump so a larger threshold is appropriate, and reduces unintended up/down
+   * jumps when panning left/right. Should be at least 0.1 and less than 1.0
+   * since some platforms only send a value of 1.0.
+   */
+  private static double wrappedVerticalScrollRotationThreshold;
+
+  /**
+   * Property name if user needs to change rotation threshold
+   */
+  private static String WRAPPEDVERTICALSCROLLROTATIONTHRESHOLDPC = "WRAPPEDVERTICALSCROLLROTATIONTHRESHOLDPC";
+
+  /**
+   * Time threshold since last left/right trackpad/wheelmouse scroll for up/down
+   * trackpad/wheelmouse movement to register for an alignment in wrapped
+   * format. This reduces unintended up/down jumps when panning left/right. In
+   * ms.
+   */
+  private static int wrappedVerticalScrollChangeTimeThreshold;
+
+  /**
+   * Property name if user needs to change rotation threshold
+   */
+  private static String WRAPPEDVERTICALSCROLLCHANGETIMETHRESHOLD = "WRAPPEDVERTICALSCROLLCHANGETIMETHRESHOLD ";
+
+  static
+  {
+    wrappedVerticalScrollRotationThreshold = Cache.getDefault(
+            WRAPPEDVERTICALSCROLLROTATIONTHRESHOLDPC, 50) / 100.0;
+    wrappedVerticalScrollChangeTimeThreshold = Cache
+            .getDefault(WRAPPEDVERTICALSCROLLCHANGETIMETHRESHOLD, 200);
+  }
+
   private static final int MAX_TOOLTIP_LENGTH = 300;
 
   public SeqCanvas seqCanvas;
@@ -343,6 +379,15 @@ public class SeqPanel extends JPanel
   }
 
   /**
+   * @param evt
+   * @return absolute column in alignment nearest to the mouse pointer
+   */
+  int findAlignmentColumn(MouseEvent evt)
+  {
+    return findNearestColumn(evt, true);
+  }
+
+  /**
    * Returns the aligned sequence position (base 0) at the mouse position, or
    * the closest visible one
    * <p>
@@ -354,6 +399,17 @@ public class SeqPanel extends JPanel
    */
   int findColumn(MouseEvent evt)
   {
+    return findNearestColumn(evt, false);
+  }
+
+  /**
+   * @param nearestColumn
+   *          when false returns negative values for out of bound positions - -1
+   *          for scale left/right, <-1 if far to right
+   * @return nearest absolute column to mouse pointer
+   */
+  private int findNearestColumn(MouseEvent evt, boolean nearestColumn)
+  {
     int res = 0;
     int x = evt.getX();
 
@@ -377,7 +433,14 @@ public class SeqPanel extends JPanel
       if (x < 0)
       {
         // mouse is over left scale
-        return -1;
+        if (!nearestColumn)
+        {
+          return -1;
+        }
+        else
+        {
+          x = 0;
+        }
       }
 
       int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
@@ -387,8 +450,15 @@ public class SeqPanel extends JPanel
       }
       if (x >= cwidth * charWidth)
       {
-        // mouse is over right scale
-        return -1;
+        if (!nearestColumn)
+        {
+          // mouse is over right scale
+          return -1;
+        }
+        else
+        {
+          x = cwidth * charWidth - 1;
+        }
       }
 
       wrappedBlock = y / cHeight;
@@ -405,8 +475,14 @@ public class SeqPanel extends JPanel
        * rather than right-hand gutter
        */
       x = Math.min(x, seqCanvas.getX() + seqCanvas.getWidth());
+      if (nearestColumn)
+      {
+        x = Math.max(x, 0);
+      }
+
       res = (x / charWidth) + startRes;
       res = Math.min(res, av.getRanges().getEndRes());
+
     }
 
     if (av.hasHiddenColumns())
@@ -881,7 +957,7 @@ public class SeqPanel extends JPanel
 
     if (lastMessage == null || !lastMessage.equals(tmp))
     {
-      // System.err.println("mouseOver Sequence: "+tmp);
+      // jalview.bin.Console.errPrintln("mouseOver Sequence: "+tmp);
       ssm.mouseOverSequence(sequence, index, pos, av);
     }
     lastMessage = tmp;
@@ -1006,7 +1082,7 @@ public class SeqPanel extends JPanel
   @Override
   public void updateColours(SequenceI seq, int index)
   {
-    System.out.println("update the seqPanel colours");
+    jalview.bin.Console.outPrintln("update the seqPanel colours");
     // repaint();
   }
 
@@ -1184,7 +1260,7 @@ public class SeqPanel extends JPanel
     AlignmentAnnotation[] anns = av.getAlignment().getAlignmentAnnotation();
 
     String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column,
-            anns, 0, av);
+            anns, 0, av, ap);
     if (tooltip == null ? tooltip != lastTooltip
             : !tooltip.equals(lastTooltip))
     {
@@ -2181,32 +2257,112 @@ public class SeqPanel extends JPanel
     }
   }
 
+  /**
+   * recorded time of last left/right mousewheel/trackpad scroll in wrapped mode
+   */
+  private long lastLeftRightWrappedScrollTime = 0;
+
+  /**
+   * Responds to a mouse wheel movement by scrolling the alignment
+   * <ul>
+   * <li>left or right, if the shift key is down, else up or down</li>
+   * <li>right (or down) if the reported mouse movement is positive</li>
+   * <li>left (or up) if the reported mouse movement is negative</li>
+   * </ul>
+   * Note that this method may also be fired by scrolling with a gesture on a
+   * trackpad.
+   */
   @Override
   public void mouseWheelMoved(MouseWheelEvent e)
   {
     e.consume();
-    double wheelRotation = e.getPreciseWheelRotation();
+    double preciseWheelRotation = e.getPreciseWheelRotation();
+    int wheelRotation = e.getWheelRotation();
+    if (wheelRotation == 0 && Math.abs(preciseWheelRotation) > 0.1)
+    {
+      // this is one of -1, 0 ,+1 for <0, ==0, >0
+      wheelRotation = (int) Math.signum(preciseWheelRotation);
+    }
+
+    /*
+     * scroll more for large (fast) mouse movements
+     */
+    int size = Math.abs(wheelRotation);
+
     if (wheelRotation > 0)
     {
       if (e.isShiftDown())
       {
-        av.getRanges().scrollRight(true);
-
+        /*
+         * scroll right
+         * stop trying to scroll right when limit is reached (saves
+         * expensive calls to Alignment.getWidth())
+         */
+        if (!ap.isScrolledFullyRight())
+        {
+          av.getRanges().scrollRight(true, size);
+        }
+        this.lastLeftRightWrappedScrollTime = System.currentTimeMillis();
       }
       else
       {
-        av.getRanges().scrollUp(false);
+        /*
+         * scroll down
+         */
+        // apply a more definite threshold for up and down scrolling in wrap
+        // format (either not a wrapped alignment, or BOTH time since last
+        // left/right scroll is above threshold AND trackpad/mousewheel movement
+        // is above threshold)
+        if (!ap.getAlignViewport().getWrapAlignment() || (Math.abs(
+                preciseWheelRotation) > wrappedVerticalScrollRotationThreshold
+                && System.currentTimeMillis()
+                        - lastLeftRightWrappedScrollTime > wrappedVerticalScrollChangeTimeThreshold))
+        {
+          while (size-- > 0)
+          {
+            if (!av.getRanges().scrollUp(false))
+            {
+              break;
+            }
+          }
+        }
       }
     }
     else if (wheelRotation < 0)
     {
       if (e.isShiftDown())
       {
-        av.getRanges().scrollRight(false);
+        /*
+         * scroll left if not already at start
+         */
+        if (av.getRanges().getStartRes() > 0)
+        {
+          av.getRanges().scrollRight(false, size);
+        }
+        this.lastLeftRightWrappedScrollTime = System.currentTimeMillis();
       }
       else
       {
-        av.getRanges().scrollUp(true);
+        /*
+         * scroll up
+         */
+        // apply a more definite threshold for up and down scrolling in wrap
+        // format (either not a wrapped alignment, or BOTH time since last
+        // left/right scroll is above threshold AND trackpad/mousewheel movement
+        // is above threshold)
+        if (!ap.getAlignViewport().getWrapAlignment() || (Math.abs(
+                preciseWheelRotation) > wrappedVerticalScrollRotationThreshold
+                && System.currentTimeMillis()
+                        - lastLeftRightWrappedScrollTime > wrappedVerticalScrollChangeTimeThreshold))
+        {
+          while (size-- > 0)
+          {
+            if (!av.getRanges().scrollUp(true))
+            {
+              break;
+            }
+          }
+        }
       }
     }
 
@@ -2861,7 +3017,7 @@ public class SeqPanel extends JPanel
     if (copycolsel && av.hasHiddenColumns()
             && (av.getAlignment().getHiddenColumns() == null))
     {
-      System.err.println("Bad things");
+      jalview.bin.Console.errPrintln("Bad things");
     }
     if (repaint) // always true!
     {
@@ -2931,6 +3087,8 @@ public class SeqPanel extends JPanel
      * if hidden column selection has changed
      */
     ap.paintAlignment(hiddenChanged, hiddenChanged);
+    // propagate any selection changes
+    PaintRefresher.Refresh(ap, av.getSequenceSetId());
 
     return true;
   }