JAL-3638 shift+arrow keys in cursor mode jumps cursor in a gapped region to next...
[jalview.git] / src / jalview / gui / SeqPanel.java
index e7bd200..407acd6 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.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;
+
 import jalview.api.AlignViewportI;
 import jalview.bin.Cache;
 import jalview.commands.EditCommand;
@@ -53,24 +71,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,8 +207,6 @@ public class SeqPanel extends JPanel
 
   StringBuffer keyboardNo2;
 
-  java.net.URL linkImageURL;
-
   private final SequenceAnnotationReport seqARep;
 
   StringBuilder tooltipText = new StringBuilder();
@@ -229,8 +227,7 @@ 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);
@@ -463,47 +460,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);
+  }
+
+  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 (seqCanvas.cursorX >= maxWidth
-              || !hidden.isVisible(seqCanvas.cursorX))
+      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.
    * 
@@ -865,7 +895,8 @@ public class SeqPanel extends JPanel
     {
       setStatusMessage(results);
     }
-    return results.isEmpty() ? null : getHighlightInfo(results);
+    // JAL-3303 feature suppressed for now pending review
+    return null; // results.isEmpty() ? null : getHighlightInfo(results);
   }
 
   /**
@@ -910,12 +941,12 @@ public class SeqPanel extends JPanel
                 .findComplementFeaturesAtResidue(ds, pos);
         if (mf != null)
         {
-          List<String> pv = mf.findProteinVariants();
-          for (String s : pv)
+          for (SequenceFeature sf : mf.features)
           {
-            if (!infos.contains(s))
+            String pv = mf.findProteinVariants(sf);
+            if (pv.length() > 0 && !infos.contains(pv))
             {
-              infos.addAll(pv);
+              infos.add(pv);
             }
           }
         }
@@ -1042,12 +1073,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,23 +1098,31 @@ public class SeqPanel extends JPanel
                   pos);
           if (mf != null)
           {
-            seqARep.appendFeatures(tooltipText, pos, mf.features, fr2);
+            unshownFeatures = seqARep.appendFeatures(tooltipText,
+                    pos, mf, fr2, MAX_TOOLTIP_LENGTH);
           }
         }
       }
     }
-    if (tooltipText.length() == 6) // <html>
+    if (tooltipText.length() == 6) // "<html>"
     {
       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))
       {
@@ -1192,7 +1233,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 +1249,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 +1257,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 +1267,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 +1312,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 +1325,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;
       }
     }
@@ -2049,7 +2091,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
@@ -2254,11 +2296,11 @@ public class SeqPanel extends JPanel
     final int column = pos.column;
     final int seq = pos.seqIndex;
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
-    List<SequenceFeature> features = ap.getFeatureRenderer()
-            .findFeaturesAtColumn(sequence, column + 1);
-
-    PopupMenu pop = new PopupMenu(ap, sequence, features);
-    pop.show(this, evt.getX(), evt.getY());
+    if (sequence != null)
+    {
+      PopupMenu pop = new PopupMenu(ap, sequence, column);
+      pop.show(this, evt.getX(), evt.getY());
+    }
   }
 
   /**
@@ -2752,7 +2794,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);
 
     /*