seq panel issue with shift-left-mouse drag fix
[jalview.git] / src / jalview / gui / SeqPanel.java
index 8b2e7bc..5eb97e0 100644 (file)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 import jalview.api.AlignViewportI;
 import jalview.bin.Cache;
+import jalview.bin.Jalview;
 import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
@@ -48,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;
@@ -62,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;
 
 /**
@@ -158,6 +165,8 @@ public class SeqPanel extends JPanel
     ToolTipManager.sharedInstance().registerComponent(this);
     ToolTipManager.sharedInstance().setInitialDelay(0);
     ToolTipManager.sharedInstance().setDismissDelay(10000);
+    
+    
     this.av = viewport;
     setBackground(Color.white);
 
@@ -628,13 +637,14 @@ public class SeqPanel extends JPanel
       return;
     }
 
-    if (!editingSeqs)
+    if (editingSeqs)
+    {
+      endEditing();
+    }
+    else
     {
       doMouseReleasedDefineMode(evt, didDrag);
-      return;
     }
-
-    endEditing();
   }
 
   /**
@@ -694,6 +704,8 @@ public class SeqPanel extends JPanel
 
   String lastMessage;
 
+  private String formattedTooltipText;
+
   @Override
   public void mouseOverSequence(SequenceI sequence, int index, int pos)
   {
@@ -739,8 +751,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);
     }
@@ -863,16 +875,20 @@ 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);
+        
         lastTooltip = textString;
       }
     }
   }
 
+  
   private Point lastp = null;
 
+  private JToolTip tempTip = new JLabel().createToolTip();
+
   /*
    * (non-Javadoc)
    * 
@@ -881,19 +897,31 @@ public class SeqPanel extends JPanel
   @Override
   public Point getToolTipLocation(MouseEvent event)
   {
-    int x = event.getX(), w = getWidth();
-    int wdth = (w - x < 200) ? -(w / 2) : 5; // switch sides when tooltip is too
-    // close to edge
+    // BH 2018
+
+    if (tooltipText == null || tooltipText.length() == 6)
+      return null;
+
+    if (lastp != null && event.isShiftDown())
+      return lastp;
+
     Point p = lastp;
-    if (!event.isShiftDown() || p == null)
-    {
-      p = (tooltipText != null && tooltipText.length() > 6)
-              ? new Point(event.getX() + wdth, event.getY() - 20)
-              : null;
-    }
+    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?
     /*
      * TODO: try to modify position region is not obcured by tooltip
+     * 
+     * Done? 
      */
+
     return lastp = p;
   }
 
@@ -997,7 +1025,7 @@ public class SeqPanel extends JPanel
 
       text.append(" (").append(Integer.toString(residuePos)).append(")");
     }
-    ap.alignFrame.statusBar.setText(text.toString());
+    ap.alignFrame.setStatus(text.toString());
   }
 
   /**
@@ -1161,9 +1189,9 @@ public class SeqPanel extends JPanel
     }
 
     mouseDragging = true;
-    if ((scrollThread != null) && (scrollThread.isRunning()))
+    if (scrollThread != null)
     {
-      scrollThread.setEvent(evt);
+      scrollThread.setMousePosition(evt.getPoint());
     }
   }
 
@@ -1224,7 +1252,7 @@ public class SeqPanel extends JPanel
     }
 
     message.append(Math.abs(startres - lastres) + " gaps.");
-    ap.alignFrame.statusBar.setText(message.toString());
+    ap.alignFrame.setStatus(message.toString());
 
     // Are we editing within a selection group?
     if (groupEditing || (sg != null
@@ -1575,10 +1603,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)
@@ -1587,31 +1615,21 @@ 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)
   {
-    if (av.getWrapAlignment())
+    if (mouseDragging)
     {
-      return;
-    }
-
-    if (mouseDragging && scrollThread == null)
-    {
-      scrollThread = new ScrollThread();
+      startScrolling(e.getPoint());
     }
   }
 
@@ -1651,17 +1669,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();
       }
     }
   }
@@ -1919,10 +1933,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)
     {
@@ -2008,92 +2019,153 @@ public class SeqPanel extends JPanel
 
     mouseDragging = true;
 
-    if ((scrollThread != null) && (scrollThread.isRunning()))
+    if (scrollThread != null)
     {
-      scrollThread.setEvent(evt);
+      scrollThread.setMousePosition(evt.getPoint());
     }
   }
 
-  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 (!Jalview.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
+         */
+        // java.util.Timer version:
+        // Timer t = new Timer("ScrollThreadTimer", true);
+        // TimerTask task = new TimerTask()
+        // {
+        // @Override
+        // public void run()
+        // {
+        // if (!scrollThread.scrollOnce())
+        // {
+        // cancel();
+        // }
+        // }
+        // };
+        // t.schedule(task, 20, 20);
+        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)
+            {
+              // finished and nulled itself
+              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);
@@ -2101,6 +2173,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;
     }
   }