spike branch updated from latest features/JAL-2446
[jalview.git] / src / jalview / gui / AlignmentPanel.java
index ce9e989..fc687b4 100644 (file)
@@ -30,10 +30,12 @@ import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.io.HTMLOutput;
 import jalview.jbgui.GAlignmentPanel;
 import jalview.math.AlignmentDimension;
 import jalview.schemes.ResidueProperties;
 import jalview.structure.StructureSelectionManager;
+import jalview.util.Comparison;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.viewmodel.ViewportListenerI;
@@ -341,19 +343,11 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public void highlightSearchResults(SearchResultsI results)
   {
-    scrollToPosition(results);
-    getSeqPanel().seqCanvas.highlightSearchResults(results);
-  }
+    boolean scrolled = scrollToPosition(results, 0, true, false);
 
-  /**
-   * Scroll the view to show the position of the highlighted region in results
-   * (if any) and redraw the overview
-   * 
-   * @param results
-   */
-  public boolean scrollToPosition(SearchResultsI results)
-  {
-    return scrollToPosition(results, 0, true, false);
+    boolean noFastPaint = scrolled && av.getWrapAlignment();
+
+    getSeqPanel().seqCanvas.highlightSearchResults(results, noFastPaint);
   }
 
   /**
@@ -371,8 +365,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
-   * Scroll the view to show the position of the highlighted region in results
-   * (if any)
+   * Scrolls the view (if necessary) to show the position of the first
+   * highlighted region in results (if any). Answers true if the view was
+   * scrolled, or false if no matched region was found, or it is already
+   * visible.
    * 
    * @param results
    * @param verticalOffset
@@ -382,115 +378,117 @@ public class AlignmentPanel extends GAlignmentPanel implements
    *          - when set, the overview will be recalculated (takes longer)
    * @param centre
    *          if true, try to centre the search results horizontally in the view
-   * @return false if results were not found
+   * @return
    */
-  public boolean scrollToPosition(SearchResultsI results,
+  protected boolean scrollToPosition(SearchResultsI results,
           int verticalOffset, boolean redrawOverview, boolean centre)
   {
     int startv, endv, starts, ends;
-    // TODO: properly locate search results in view when large numbers of hidden
-    // columns exist before highlighted region
-    // do we need to scroll the panel?
-    // TODO: tons of nullpointerexceptions raised here.
-    if (results != null && results.getSize() > 0 && av != null
-            && av.getAlignment() != null)
-    {
-      int seqIndex = av.getAlignment().findIndex(results);
-      if (seqIndex == -1)
-      {
-        return false;
-      }
-      SequenceI seq = av.getAlignment().getSequenceAt(seqIndex);
 
-      int[] r = results.getResults(seq, 0, av.getAlignment().getWidth());
-      if (r == null)
-      {
-        return false;
-      }
-      int start = r[0];
-      int end = r[1];
+    if (results == null || results.isEmpty() || av == null
+            || av.getAlignment() == null)
+    {
+      return false;
+    }
+    int seqIndex = av.getAlignment().findIndex(results);
+    if (seqIndex == -1)
+    {
+      return false;
+    }
+    SequenceI seq = av.getAlignment().getSequenceAt(seqIndex);
 
-      /*
-       * To centre results, scroll to positions half the visible width
-       * left/right of the start/end positions
-       */
-      if (centre)
-      {
-        int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
-        start = Math.max(start - offset, 0);
-        end = end + offset - 1;
-      }
-      if (start < 0)
-      {
-        return false;
-      }
-      if (end == seq.getEnd())
-      {
-        return false;
-      }
-      if (av.hasHiddenColumns())
+    int[] r = results.getResults(seq, 0, av.getAlignment().getWidth());
+    if (r == null)
+    {
+      return false;
+    }
+    int start = r[0];
+    int end = r[1];
+
+    /*
+     * To centre results, scroll to positions half the visible width
+     * left/right of the start/end positions
+     */
+    if (centre)
+    {
+      int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
+      start = Math.max(start - offset, 0);
+      end = end + offset - 1;
+    }
+    if (start < 0)
+    {
+      return false;
+    }
+    if (end == seq.getEnd())
+    {
+      return false;
+    }
+
+    if (av.hasHiddenColumns())
+    {
+      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+      start = hidden.findColumnPosition(start);
+      end = hidden.findColumnPosition(end);
+      if (start == end)
       {
-        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-        start = hidden.findColumnPosition(start);
-        end = hidden.findColumnPosition(end);
-        if (start == end)
+        if (!hidden.isVisible(r[0]))
         {
-          if (!hidden.isVisible(r[0]))
-          {
-            // don't scroll - position isn't visible
-            return false;
-          }
+          // don't scroll - position isn't visible
+          return false;
         }
       }
+    }
 
-      /*
-       * allow for offset of target sequence (actually scroll to one above it)
-       */
-      seqIndex = Math.max(0, seqIndex - verticalOffset);
+    /*
+     * allow for offset of target sequence (actually scroll to one above it)
+     */
+    seqIndex = Math.max(0, seqIndex - verticalOffset);
+    boolean scrollNeeded = true;
 
-      if (!av.getWrapAlignment())
+    if (!av.getWrapAlignment())
+    {
+      if ((startv = vpRanges.getStartRes()) >= start)
       {
-        if ((startv = vpRanges.getStartRes()) >= start)
-        {
-          /*
-           * Scroll left to make start of search results visible
-           */
-          setScrollValues(start, seqIndex);
-        }
-        else if ((endv = vpRanges.getEndRes()) <= end)
-        {
-          /*
-           * Scroll right to make end of search results visible
-           */
-          setScrollValues(startv + end - endv, seqIndex);
-        }
-        else if ((starts = vpRanges.getStartSeq()) > seqIndex)
-        {
-          /*
-           * Scroll up to make start of search results visible
-           */
-          setScrollValues(vpRanges.getStartRes(), seqIndex);
-        }
-        else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
-        {
-          /*
-           * Scroll down to make end of search results visible
-           */
-          setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
-                  + 1);
-        }
         /*
-         * Else results are already visible - no need to scroll
+         * Scroll left to make start of search results visible
          */
+        setScrollValues(start, seqIndex);
       }
-      else
+      else if ((endv = vpRanges.getEndRes()) <= end)
+      {
+        /*
+         * Scroll right to make end of search results visible
+         */
+        setScrollValues(startv + end - endv, seqIndex);
+      }
+      else if ((starts = vpRanges.getStartSeq()) > seqIndex)
       {
-        vpRanges.scrollToWrappedVisible(start);
+        /*
+         * Scroll up to make start of search results visible
+         */
+        setScrollValues(vpRanges.getStartRes(), seqIndex);
+      }
+      else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
+      {
+        /*
+         * Scroll down to make end of search results visible
+         */
+        setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
+                + 1);
       }
+      /*
+       * Else results are already visible - no need to scroll
+       */
+      scrollNeeded = false;
+    }
+    else
+    {
+      scrollNeeded = vpRanges.scrollToWrappedVisible(start);
     }
 
     paintAlignment(redrawOverview);
-    return true;
+
+    return scrollNeeded;
   }
 
   /**
@@ -628,21 +626,24 @@ public class AlignmentPanel extends GAlignmentPanel implements
       annotationSpaceFillerHolder.setVisible(true);
     }
 
-    if (wrap)
-    {
-      int widthInRes = getSeqPanel().seqCanvas
-              .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-      vpRanges.setViewportWidth(widthInRes);
-    }
-    else
-    {
-      int widthInRes = (getSeqPanel().seqCanvas.getWidth() / av
-              .getCharWidth()) - 1;
-      int heightInSeq = (getSeqPanel().seqCanvas.getHeight() / av
-              .getCharHeight()) - 1;
+    int canvasWidth = getSeqPanel().seqCanvas.getWidth();
+    if (canvasWidth > 0)
+    { // may not yet be laid out
+      if (wrap)
+      {
+        int widthInRes = getSeqPanel().seqCanvas
+                .getWrappedCanvasWidth(canvasWidth);
+        vpRanges.setViewportWidth(widthInRes);
+      }
+      else
+      {
+        int widthInRes = (canvasWidth / av.getCharWidth()) - 1;
+        int heightInSeq = (getSeqPanel().seqCanvas.getHeight() / av
+                .getCharHeight()) - 1;
 
-      vpRanges.setViewportWidth(widthInRes);
-      vpRanges.setViewportHeight(heightInSeq);
+        vpRanges.setViewportWidth(widthInRes);
+        vpRanges.setViewportHeight(heightInSeq);
+      }
     }
 
     idSpaceFillerPanel1.setVisible(!wrap);
@@ -734,97 +735,111 @@ public class AlignmentPanel extends GAlignmentPanel implements
   @Override
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
-    int oldX = vpRanges.getStartRes();
-    int oldwidth = vpRanges.getViewportWidth();
-    int oldY = vpRanges.getStartSeq();
-    int oldheight = vpRanges.getViewportHeight();
-
     if (av.getWrapAlignment())
     {
-      if (evt.getSource() == hscroll)
+      adjustScrollingWrapped(evt);
+      return;
+    }
+
+    if (evt.getSource() == hscroll)
+    {
+      int oldX = vpRanges.getStartRes();
+      int oldwidth = vpRanges.getViewportWidth();
+      int x = hscroll.getValue();
+      int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
+
+      // if we're scrolling to the position we're already at, stop
+      // this prevents infinite recursion of events when the scroll/viewport
+      // ranges values are the same
+      if ((x == oldX) && (width == oldwidth))
       {
-        return; // no horizontal scroll when wrapped
+        return;
       }
-      else if (evt.getSource() == vscroll)
-      {
-        int offy = vscroll.getValue();
-        int rowSize = getSeqPanel().seqCanvas
-                .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-
-        // if we're scrolling to the position we're already at, stop
-        // this prevents infinite recursion of events when the scroll/viewport
-        // ranges values are the same
-        if ((offy * rowSize == oldX) && (oldwidth == rowSize))
-        {
-          return;
-        }
-        else if (offy > -1)
-        {
-          vpRanges.setViewportStartAndWidth(offy * rowSize, rowSize);
-        }
-      }
-      else
+      vpRanges.setViewportStartAndWidth(x, width);
+    }
+    else if (evt.getSource() == vscroll)
+    {
+      int oldY = vpRanges.getStartSeq();
+      int oldheight = vpRanges.getViewportHeight();
+      int y = vscroll.getValue();
+      int height = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
+
+      // if we're scrolling to the position we're already at, stop
+      // this prevents infinite recursion of events when the scroll/viewport
+      // ranges values are the same
+      if ((y == oldY) && (height == oldheight))
       {
-        // This is only called if file loaded is a jar file that
-        // was wrapped when saved and user has wrap alignment true
-        // as preference setting
-        SwingUtilities.invokeLater(new Runnable()
-        {
-          @Override
-          public void run()
-        {
-            // When updating scrolling to use ViewportChange events, this code
-            // could not be validated and it is not clear if it is now being
-            // called. Log warning here in case it is called and unforeseen
-            // problems occur
-            Cache.log
-                    .warn("Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
-
-            // scroll to start of panel
-            vpRanges.setStartRes(0);
-            vpRanges.setStartSeq(0);
-          }
-        });
+        return;
       }
+      vpRanges.setViewportStartAndHeight(y, height);
+    }
+    if (!fastPaint)
+    {
       repaint();
     }
-    else
+  }
+
+  /**
+   * Responds to a scroll change by setting the start position of the viewport.
+   * Does
+   * 
+   * @param evt
+   */
+  protected void adjustScrollingWrapped(AdjustmentEvent evt)
+  {
+    if (evt.getSource() == hscroll)
     {
-      // horizontal scroll
-      if (evt.getSource() == hscroll)
-      {
-        int x = hscroll.getValue();
-        int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
+      return; // no horizontal scroll when wrapped
+    }
+    if (evt.getSource() == vscroll)
+    {
+      int newY = vscroll.getValue();
 
-        // if we're scrolling to the position we're already at, stop
-        // this prevents infinite recursion of events when the scroll/viewport
-        // ranges values are the same
-        if ((x == oldX) && (width == oldwidth))
-        {
-          return;
-        }
-        vpRanges.setViewportStartAndWidth(x, width);
-      }
-      else if (evt.getSource() == vscroll)
+      /*
+       * if we're scrolling to the position we're already at, stop
+       * this prevents infinite recursion of events when the scroll/viewport
+       * ranges values are the same
+       */
+      int oldX = vpRanges.getStartRes();
+      int oldY = vpRanges.getWrappedScrollPosition(oldX);
+      if (oldY == newY)
       {
-        int y = vscroll.getValue();
-        int height = getSeqPanel().seqCanvas.getHeight()
-                / av.getCharHeight();
-
-        // if we're scrolling to the position we're already at, stop
-        // this prevents infinite recursion of events when the scroll/viewport
-        // ranges values are the same
-        if ((y == oldY) && (height == oldheight))
-        {
-          return;
-        }
-        vpRanges.setViewportStartAndHeight(y, height);
+        return;
       }
-      if (!fastPaint)
+      if (newY > -1)
       {
-        repaint();
+        /*
+         * limit page up/down to one width's worth of positions
+         */
+        int rowSize = vpRanges.getViewportWidth();
+        int newX = newY > oldY ? oldX + rowSize : oldX - rowSize;
+        vpRanges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
       }
     }
+    else
+    {
+      // This is only called if file loaded is a jar file that
+      // was wrapped when saved and user has wrap alignment true
+      // as preference setting
+      SwingUtilities.invokeLater(new Runnable()
+      {
+        @Override
+        public void run()
+      {
+          // When updating scrolling to use ViewportChange events, this code
+          // could not be validated and it is not clear if it is now being
+          // called. Log warning here in case it is called and unforeseen
+          // problems occur
+          Cache.log
+                  .warn("Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
+
+          // scroll to start of panel
+          vpRanges.setStartRes(0);
+          vpRanges.setStartSeq(0);
+        }
+      });
+    }
+    repaint();
   }
 
   /**
@@ -874,35 +889,24 @@ public class AlignmentPanel extends GAlignmentPanel implements
     setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
   }
 
-  /*
-   * Set vertical scroll bar parameters for wrapped panel
-   * @param res 
-   *    the residue to scroll to
+  /**
+   * Set vertical scroll bar position, and number of increments, for wrapped
+   * panel
+   * 
+   * @param topLeftColumn
+   *          the column position at top left (0..)
    */
-  private void setScrollingForWrappedPanel(int res)
+  private void setScrollingForWrappedPanel(int topLeftColumn)
   {
-    // get the width of the alignment in residues
-    int maxwidth = av.getAlignment().getWidth();
-    if (av.hasHiddenColumns())
-    {
-        maxwidth = av.getAlignment().getHiddenColumns()
-                .findColumnPosition(maxwidth) - 1;
-    }
-
-    // get the width of the canvas in residues
-    int canvasWidth = getSeqPanel().seqCanvas
-            .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
-    if (canvasWidth > 0)
-    {
-      // position we want to scroll to is number of canvasWidth's to get there
-      int current = res / canvasWidth;
+    int scrollPosition = vpRanges.getWrappedScrollPosition(topLeftColumn);
+    int maxScroll = vpRanges.getWrappedMaxScroll(topLeftColumn);
 
-      // max scroll position: add one because extent is 1 and scrollbar value
-      // can only be set to at most max - extent
-      int max = maxwidth / canvasWidth + 1;
-      vscroll.setUnitIncrement(1);
-      vscroll.setValues(current, 1, 0, max);
-    }
+    /*
+     * a scrollbar's value can be set to at most (maximum-extent)
+     * so we add extent (1) to the maxScroll value
+     */
+    vscroll.setUnitIncrement(1);
+    vscroll.setValues(scrollPosition, 1, 0, maxScroll + 1);
   }
 
   /**
@@ -1437,33 +1441,32 @@ public class AlignmentPanel extends GAlignmentPanel implements
     {
       try
       {
-        int s, sSize = av.getAlignment().getHeight(), res, alwidth = av
-                .getAlignment().getWidth(), g, gSize, f, fSize, sy;
+        int sSize = av.getAlignment().getHeight();
+        int alwidth = av.getAlignment().getWidth();
         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
-        out.println(jalview.io.HTMLOutput.getImageMapHTML());
+        out.println(HTMLOutput.getImageMapHTML());
         out.println("<img src=\"" + imageName
                 + "\" border=\"0\" usemap=\"#Map\" >"
                 + "<map name=\"Map\">");
 
-        for (s = 0; s < sSize; s++)
+        for (int s = 0; s < sSize; s++)
         {
-          sy = s * av.getCharHeight() + scaleHeight;
+          int sy = s * av.getCharHeight() + scaleHeight;
 
           SequenceI seq = av.getAlignment().getSequenceAt(s);
-          SequenceFeature[] features = seq.getSequenceFeatures();
           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
-          for (res = 0; res < alwidth; res++)
+          for (int column = 0; column < alwidth; column++)
           {
-            StringBuilder text = new StringBuilder();
+            StringBuilder text = new StringBuilder(512);
             String triplet = null;
             if (av.getAlignment().isNucleotide())
             {
               triplet = ResidueProperties.nucleotideName.get(seq
-                      .getCharAt(res) + "");
+                      .getCharAt(column) + "");
             }
             else
             {
-              triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
+              triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(column)
                       + "");
             }
 
@@ -1472,84 +1475,73 @@ public class AlignmentPanel extends GAlignmentPanel implements
               continue;
             }
 
-            int alIndex = seq.findPosition(res);
-            gSize = groups.length;
-            for (g = 0; g < gSize; g++)
+            int seqPos = seq.findPosition(column);
+            int gSize = groups.length;
+            for (int g = 0; g < gSize; g++)
             {
               if (text.length() < 1)
               {
                 text.append("<area shape=\"rect\" coords=\"")
-                        .append((idWidth + res * av.getCharWidth()))
+                        .append((idWidth + column * av.getCharWidth()))
                         .append(",").append(sy).append(",")
-                        .append((idWidth + (res + 1) * av.getCharWidth()))
+                        .append((idWidth + (column + 1) * av.getCharWidth()))
                         .append(",").append((av.getCharHeight() + sy))
                         .append("\"").append(" onMouseOver=\"toolTip('")
-                        .append(alIndex).append(" ").append(triplet);
+                        .append(seqPos).append(" ").append(triplet);
               }
 
-              if (groups[g].getStartRes() < res
-                      && groups[g].getEndRes() > res)
+              if (groups[g].getStartRes() < column
+                      && groups[g].getEndRes() > column)
               {
                 text.append("<br><em>").append(groups[g].getName())
                         .append("</em>");
               }
             }
 
-            if (features != null)
+            if (text.length() < 1)
             {
-              if (text.length() < 1)
-              {
-                text.append("<area shape=\"rect\" coords=\"")
-                        .append((idWidth + res * av.getCharWidth()))
-                        .append(",").append(sy).append(",")
-                        .append((idWidth + (res + 1) * av.getCharWidth()))
-                        .append(",").append((av.getCharHeight() + sy))
-                        .append("\"").append(" onMouseOver=\"toolTip('")
-                        .append(alIndex).append(" ").append(triplet);
-              }
-              fSize = features.length;
-              for (f = 0; f < fSize; f++)
+              text.append("<area shape=\"rect\" coords=\"")
+                      .append((idWidth + column * av.getCharWidth()))
+                      .append(",").append(sy).append(",")
+                      .append((idWidth + (column + 1) * av.getCharWidth()))
+                      .append(",").append((av.getCharHeight() + sy))
+                      .append("\"").append(" onMouseOver=\"toolTip('")
+                      .append(seqPos).append(" ").append(triplet);
+            }
+            if (!Comparison.isGap(seq.getCharAt(column)))
+            {
+              List<SequenceFeature> features = seq.findFeatures(column, column);
+              for (SequenceFeature sf : features)
               {
-
-                if ((features[f].getBegin() <= seq.findPosition(res))
-                        && (features[f].getEnd() >= seq.findPosition(res)))
+                if (sf.isContactFeature())
                 {
-                  if (features[f].isContactFeature())
-                  {
-                    if (features[f].getBegin() == seq.findPosition(res)
-                            || features[f].getEnd() == seq
-                                    .findPosition(res))
-                    {
-                      text.append("<br>").append(features[f].getType())
-                              .append(" ").append(features[f].getBegin())
-                              .append(":").append(features[f].getEnd());
-                    }
-                  }
-                  else
+                  text.append("<br>").append(sf.getType()).append(" ")
+                          .append(sf.getBegin()).append(":")
+                          .append(sf.getEnd());
+                }
+                else
+                {
+                  text.append("<br>");
+                  text.append(sf.getType());
+                  String description = sf.getDescription();
+                  if (description != null
+                          && !sf.getType().equals(description))
                   {
-                    text.append("<br>");
-                    text.append(features[f].getType());
-                    if (features[f].getDescription() != null
-                            && !features[f].getType().equals(
-                                    features[f].getDescription()))
-                    {
-                      text.append(" ").append(features[f].getDescription());
-                    }
-
-                    if (features[f].getValue("status") != null)
-                    {
-                      text.append(" (").append(features[f].getValue("status"))
-                              .append(")");
-                    }
+                    description = description.replace("\"", "&quot;");
+                    text.append(" ").append(description);
                   }
                 }
-
+                String status = sf.getStatus();
+                if (status != null && !"".equals(status))
+                {
+                  text.append(" (").append(status).append(")");
+                }
+              }
+              if (text.length() > 1)
+              {
+                text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
+                out.println(text.toString());
               }
-            }
-            if (text.length() > 1)
-            {
-              text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
-              out.println(text.toString());
             }
           }
         }
@@ -1818,7 +1810,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * @param verticalOffset
    *          the number of visible sequences to show above the mapped region
    */
-  public void scrollToCentre(SearchResultsI sr, int verticalOffset)
+  protected void scrollToCentre(SearchResultsI sr, int verticalOffset)
   {
     /*
      * To avoid jumpy vertical scrolling (if some sequences are gapped or not