JAL-3949 - refactor logging from jalview.bin.Cache to jalview.bin.Console
[jalview.git] / src / jalview / gui / SeqPanel.java
index 75bf0cc..827c315 100644 (file)
  */
 package jalview.gui;
 
+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;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+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;
-import jalview.bin.Cache;
+import jalview.bin.Console;
 import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
@@ -53,24 +76,6 @@ import jalview.viewmodel.AlignmentViewport;
 import jalview.viewmodel.ViewportRanges;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Point;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.MouseMotionListener;
-import java.awt.event.MouseWheelEvent;
-import java.awt.event.MouseWheelListener;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.swing.JPanel;
-import javax.swing.SwingUtilities;
-import javax.swing.ToolTipManager;
-
 /**
  * DOCUMENT ME!
  * 
@@ -207,13 +212,21 @@ public class SeqPanel extends JPanel
 
   StringBuffer keyboardNo2;
 
-  java.net.URL linkImageURL;
-
   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;
 
@@ -229,11 +242,12 @@ public class SeqPanel extends JPanel
    */
   public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel)
   {
-    linkImageURL = getClass().getResource("/images/link.gif");
-    seqARep = new SequenceAnnotationReport(linkImageURL.toString());
+    seqARep = new SequenceAnnotationReport(true);
     ToolTipManager.sharedInstance().registerComponent(this);
     ToolTipManager.sharedInstance().setInitialDelay(0);
     ToolTipManager.sharedInstance().setDismissDelay(10000);
+    
+    
     this.av = viewport;
     setBackground(Color.white);
 
@@ -261,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
+   * <p>
+   * 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
@@ -328,6 +345,9 @@ public class SeqPanel extends JPanel
   /**
    * Returns the aligned sequence position (base 0) at the mouse position, or
    * the closest visible one
+   * <p>
+   * Returns -1 if in wrapped mode with the mouse over either left or right
+   * vertical scale.
    * 
    * @param evt
    * @return
@@ -463,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.
    * 
@@ -860,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);
     }
@@ -895,11 +948,12 @@ public class SeqPanel extends JPanel
     AlignFrame af = Desktop.getAlignFrameFor(complement);
     FeatureRendererModel fr2 = af.getFeatureRenderer();
 
-    int j = results.getSize();
+    List<SearchResultMatchI> matches = results.getResults();
+    int j = matches.size();
     List<String> 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())
       {
@@ -975,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())
@@ -992,6 +1048,7 @@ public class SeqPanel extends JPanel
       lastMousePosition = null;
       setToolTipText(null);
       lastTooltip = null;
+      lastFormattedTooltip = null;
       ap.alignFrame.setStatus("");
       return;
     }
@@ -1013,7 +1070,7 @@ public class SeqPanel extends JPanel
       mouseOverSequence(sequence, column, pos);
     }
 
-    tooltipText.setLength(6); // Cuts the buffer back to <html>
+    StringBuilder tooltipText = new StringBuilder(64);
 
     SequenceGroup[] groups = av.getAlignment().findAllGroups(sequence);
     if (groups != null)
@@ -1042,12 +1099,14 @@ public class SeqPanel extends JPanel
      * add features that straddle the gap (pos may be the residue before or
      * after the gap)
      */
+    int unshownFeatures = 0;
     if (av.isShowSequenceFeatures())
     {
       List<SequenceFeature> features = ap.getFeatureRenderer()
               .findFeaturesAtColumn(sequence, column + 1);
-      seqARep.appendFeatures(tooltipText, pos, features,
-              this.ap.getSeqPanel().seqCanvas.fr);
+      unshownFeatures = seqARep.appendFeatures(tooltipText, pos,
+              features, this.ap.getSeqPanel().seqCanvas.fr,
+              MAX_TOOLTIP_LENGTH);
 
       /*
        * add features in CDS/protein complement at the corresponding
@@ -1065,30 +1124,38 @@ public class SeqPanel extends JPanel
                   pos);
           if (mf != null)
           {
-            seqARep.appendFeatures(tooltipText, pos, mf, fr2);
+            unshownFeatures += seqARep.appendFeatures(tooltipText,
+                    pos, mf, fr2, MAX_TOOLTIP_LENGTH);
           }
         }
       }
     }
-    if (tooltipText.length() == 6) // <html>
+    if (tooltipText.length() == 0) // nothing added
     {
       setToolTipText(null);
       lastTooltip = null;
     }
     else
     {
-      if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
+      if (tooltipText.length() > MAX_TOOLTIP_LENGTH)
       {
         tooltipText.setLength(MAX_TOOLTIP_LENGTH);
         tooltipText.append("...");
       }
+      if (unshownFeatures > 0)
+      {
+        tooltipText.append("<br/>").append("... ").append("<i>")
+                .append(MessageManager.formatMessage(
+                        "label.features_not_shown", unshownFeatures))
+                .append("</i>");
+      }
       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);
       }
     }
   }
@@ -1114,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)
@@ -1132,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
@@ -1192,7 +1281,7 @@ public class SeqPanel extends JPanel
   {
     char sequenceChar = sequence.getCharAt(column);
     int pos = sequence.findPosition(column);
-    setStatusMessage(sequence, seqIndex, sequenceChar, pos);
+    setStatusMessage(sequence.getName(), seqIndex, sequenceChar, pos);
 
     return pos;
   }
@@ -1208,7 +1297,7 @@ public class SeqPanel extends JPanel
    * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
    * </pre>
    * 
-   * @param sequence
+   * @param seqName
    * @param seqIndex
    *          sequence position in the alignment (1..)
    * @param sequenceChar
@@ -1216,7 +1305,7 @@ public class SeqPanel extends JPanel
    * @param residuePos
    *          the sequence residue position (if not over a gap)
    */
-  protected void setStatusMessage(SequenceI sequence, int seqIndex,
+  protected void setStatusMessage(String seqName, int seqIndex,
           char sequenceChar, int residuePos)
   {
     StringBuilder text = new StringBuilder(32);
@@ -1226,7 +1315,7 @@ public class SeqPanel extends JPanel
      */
     String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
     text.append("Sequence").append(seqno).append(" ID: ")
-            .append(sequence.getName());
+            .append(seqName);
 
     String residue = null;
 
@@ -1271,7 +1360,8 @@ public class SeqPanel extends JPanel
     {
       return;
     }
-    SequenceI ds = al.getSequenceAt(sequenceIndex).getDatasetSequence();
+    SequenceI alignedSeq = al.getSequenceAt(sequenceIndex);
+    SequenceI ds = alignedSeq.getDatasetSequence();
     for (SearchResultMatchI m : results.getResults())
     {
       SequenceI seq = m.getSequence();
@@ -1283,8 +1373,8 @@ public class SeqPanel extends JPanel
       if (seq == ds)
       {
         int start = m.getStart();
-        setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
-                start);
+        setStatusMessage(alignedSeq.getName(), sequenceIndex,
+                seq.getCharAt(start - 1), start);
         return;
       }
     }
@@ -2030,7 +2120,7 @@ public class SeqPanel extends JPanel
 
     if (mouseDragging && scrollThread == null)
     {
-      scrollThread = new ScrollThread();
+      startScrolling(e.getPoint());
     }
   }
 
@@ -2049,7 +2139,7 @@ public class SeqPanel extends JPanel
       return;
     }
 
-    if (evt.getClickCount() > 1)
+    if (evt.getClickCount() > 1 && av.isShowSequenceFeatures())
     {
       sg = av.getSelectionGroup();
       if (sg != null && sg.getSize() == 1
@@ -2076,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<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();
       }
     }
   }
@@ -2168,22 +2254,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;
@@ -2476,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
@@ -2506,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
      */
@@ -2525,7 +2657,7 @@ public class SeqPanel extends JPanel
      */
     public void stopScrolling()
     {
-      threadRunning = false;
+      keepRunning = false;
     }
 
     /**
@@ -2536,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);
@@ -2585,6 +2681,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;
     }
   }
 
@@ -2652,7 +2802,7 @@ public class SeqPanel extends JPanel
     {
       if (av.getAlignment() == null)
       {
-        Cache.log.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
+        Console.warn("alignviewport av SeqSetId=" + av.getSequenceSetId()
                 + " ViewId=" + av.getViewId()
                 + " 's alignment is NULL! returning immediately.");
         return;
@@ -2752,7 +2902,7 @@ public class SeqPanel extends JPanel
      * Map sequence selection
      */
     SequenceGroup sg = MappingUtils.mapSequenceGroup(seqsel, sourceAv, av);
-    av.setSelectionGroup(sg);
+    av.setSelectionGroup(sg != null && sg.getSize() > 0 ? sg : null);
     av.isSelectionGroupChanged(true);
 
     /*