JAL-1858 highlights on wrapped panel - work in progress
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 27 Jun 2017 13:01:47 +0000 (14:01 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 27 Jun 2017 13:01:47 +0000 (14:01 +0100)
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java

index 8707e9a..ed3d298 100755 (executable)
@@ -52,6 +52,8 @@ import javax.swing.JComponent;
  */
 public class SeqCanvas extends JComponent implements ViewportListenerI
 {
+  private static String ZEROS = "0000000000";
+
   final FeatureRenderer fr;
 
   final SequenceRenderer sr;
@@ -68,9 +70,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   boolean fastPaint = false;
 
-  int LABEL_WEST;
+  int labelWidthWest;
 
-  int LABEL_EAST;
+  int labelWidthEast;
 
   int cursorX = 0;
 
@@ -206,7 +208,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
       if (value != -1)
       {
-        int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
+        int x = labelWidthWest - fm.stringWidth(String.valueOf(value))
                 - charWidth / 2;
         g.drawString(value + "", x, (ypos + (i * charHeight))
                 - (charHeight / 5));
@@ -422,46 +424,46 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   {
     FontMetrics fm = getFontMetrics(av.getFont());
 
-    LABEL_EAST = 0;
-    LABEL_WEST = 0;
+    labelWidthEast = 0;
+    labelWidthWest = 0;
 
     if (av.getScaleRightWrapped())
     {
-      LABEL_EAST = fm.stringWidth(getMask());
+      labelWidthEast = getLabelWidth(fm);
     }
 
     if (av.getScaleLeftWrapped())
     {
-      LABEL_WEST = fm.stringWidth(getMask());
+      labelWidthWest = getLabelWidth(fm);
     }
 
-    return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
+    return (cwidth - labelWidthEast - labelWidthWest) / charWidth;
   }
 
   /**
-   * Generates a string of zeroes.
+   * Returns a pixel width suitable for showing the largest sequence coordinate
+   * (end position) in the alignment. Returns 2 plus the number of decimal
+   * digits to be shown (3 for 1-10, 4 for 11-99 etc).
    * 
-   * @return String
+   * @param fm
+   * @return
    */
-  String getMask()
+  protected int getLabelWidth(FontMetrics fm)
   {
-    String mask = "00";
     int maxWidth = 0;
-    int tmp;
     for (int i = 0; i < av.getAlignment().getHeight(); i++)
     {
-      tmp = av.getAlignment().getSequenceAt(i).getEnd();
-      if (tmp > maxWidth)
-      {
-        maxWidth = tmp;
-      }
+      maxWidth = Math.max(maxWidth, av.getAlignment().getSequenceAt(i)
+              .getEnd());
     }
 
+    int length = 2;
     for (int i = maxWidth; i > 0; i /= 10)
     {
-      mask += "0";
+      length++;
     }
-    return mask;
+
+    return fm.stringWidth(ZEROS.substring(0, length));
   }
 
   /**
@@ -482,17 +484,15 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     updateViewport();
     AlignmentI al = av.getAlignment();
 
-    FontMetrics fm = getFontMetrics(av.getFont());
-
-    if (av.getScaleRightWrapped())
+    int labelWidth = 0;
+    if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
     {
-      LABEL_EAST = fm.stringWidth(getMask());
+      FontMetrics fm = getFontMetrics(av.getFont());
+      labelWidth = getLabelWidth(fm);
     }
 
-    if (av.getScaleLeftWrapped())
-    {
-      LABEL_WEST = fm.stringWidth(getMask());
-    }
+    labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
+    labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
 
     int hgap = charHeight;
     if (av.getScaleAboveWrapped())
@@ -500,7 +500,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       hgap += charHeight;
     }
 
-    int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
+    int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
     int cHeight = av.getAlignment().getHeight() * charHeight;
 
     av.setWrappedWidth(cWidth);
@@ -517,6 +517,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
               .findColumnPosition(maxwidth) - 1;
     }
 
+    int annotationHeight = getAnnotationHeight();
+
     while ((ypos <= canvasHeight) && (startRes < maxwidth))
     {
       endx = startRes + cWidth - 1;
@@ -536,12 +538,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
       if (av.getScaleRightWrapped())
       {
-        g.translate(canvasWidth - LABEL_EAST, 0);
+        g.translate(canvasWidth - labelWidthEast, 0);
         drawEastScale(g, startRes, endx, ypos);
-        g.translate(-(canvasWidth - LABEL_EAST), 0);
+        g.translate(-(canvasWidth - labelWidthEast), 0);
       }
 
-      g.translate(LABEL_WEST, 0);
+      g.translate(labelWidthWest, 0);
 
       if (av.getScaleAboveWrapped())
       {
@@ -601,9 +603,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         g.translate(0, -cHeight - ypos - 3);
       }
       g.setClip(clip);
-      g.translate(-LABEL_WEST, 0);
+      g.translate(-labelWidthWest, 0);
 
-      ypos += cHeight + getAnnotationHeight() + hgap;
+      ypos += cHeight + annotationHeight + hgap;
 
       startRes += cWidth;
     }
@@ -989,23 +991,28 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   /**
    * Highlights search results in the visible region by rendering as white text
-   * on a black background. Any previous highlighting is removed.
+   * on a black background. Any previous highlighting is removed. Answers true
+   * if any highlight was left on the visible alignment (so status bar should be
+   * set to match), else false.
    * 
    * @param results
+   * @return
    */
-  public void highlightSearchResults(SearchResultsI results)
+  public boolean highlightSearchResults(SearchResultsI results)
   {
     updateViewport();
 
     /*
      * for now, don't attempt fastpaint if wrapped format
      */
-    if (av.getWrapAlignment())
+    boolean wrapped = av.getWrapAlignment();
+    if (wrapped)
     {
-      img = null;
-      av.setSearchResults(results);
-      repaint();
-      return;
+      // drawWrappedMappedPositions(results);
+      // img = null;
+      // av.setSearchResults(results);
+      // repaint();
+      // return;
     }
     
     fastpainting = true;
@@ -1020,16 +1027,39 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
        */
       SearchResultsI previous = av.getSearchResults();
       av.setSearchResults(results);
-      boolean redrawn = drawMappedPositions(previous);
-      redrawn |= drawMappedPositions(results);
+      boolean redrawn = false;
+      boolean drawn = false;
+      if (wrapped)
+      {
+        redrawn = drawWrappedMappedPositions(previous);
+        drawn = drawWrappedMappedPositions(results);
+        redrawn |= drawn;
+      }
+      else
+      {
+        redrawn = drawMappedPositions(previous);
+        drawn = drawMappedPositions(results);
+        redrawn |= drawn;
+      }
+
+      /*
+       * if highlights were either removed or added, repaint
+       */
       if (redrawn)
       {
         repaint();
       }
+
+      /*
+       * return true only if highlights were added
+       */
+      return drawn;
+
     } finally
     {
       fastpainting = false;
     }
+
   }
 
   /**
@@ -1154,4 +1184,123 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       }
     }
   }
+
+  /**
+   * Redraws any positions in the search results in the visible region. Any
+   * highlights are drawn depending on the search results set on the Viewport,
+   * not the results parameter. This allows this method to be called to either
+   * clear highlighting (passing the previous search results), or set new
+   * highlighting.
+   * 
+   * @param results
+   * @return
+   */
+  protected boolean drawWrappedMappedPositions(SearchResultsI results)
+  {
+    if (results == null)
+    {
+      return false;
+    }
+  
+    boolean matchFound = false;
+  
+    /*
+     * Viewport ranges are set for the 'row' of the wrapped alignment
+     * the cursor is in, not the whole visible region; really we want
+     * the latter; +-6 a temporary fudge for codons wrapping across lines
+     */
+    ViewportRanges ranges = av.getRanges();
+    int firstVisibleColumn = ranges.getStartRes() - 6;
+    int lastVisibleColumn = ranges.getEndRes() + 6;
+    AlignmentI alignment = av.getAlignment();
+    if (av.hasHiddenColumns())
+    {
+      firstVisibleColumn = alignment.getHiddenColumns()
+              .adjustForHiddenColumns(firstVisibleColumn);
+      lastVisibleColumn = alignment.getHiddenColumns()
+              .adjustForHiddenColumns(lastVisibleColumn);
+    }
+  
+    /*
+     * find width of alignment in residues, and height of alignment, 
+     * so we can calculate where to render each matched position
+     */
+    int wrappedWidth = av.getWrappedWidth();
+    int wrappedHeight = av.getAlignment().getHeight() * av.getCharHeight();
+    int gapHeight = av.getCharHeight()
+            * (av.getScaleAboveWrapped() ? 2 : 1);
+    wrappedHeight += gapHeight;
+    wrappedHeight += getAnnotationHeight();
+
+    for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
+            .getEndSeq(); seqNo++)
+    {
+      SequenceI seq = alignment.getSequenceAt(seqNo);
+
+      int[] visibleResults = results.getResults(seq, firstVisibleColumn,
+              lastVisibleColumn);
+      if (visibleResults != null)
+      {
+        for (int i = 0; i < visibleResults.length - 1; i += 2)
+        {
+          int firstMatchedColumn = visibleResults[i];
+          int lastMatchedColumn = visibleResults[i + 1];
+          if (firstMatchedColumn <= lastVisibleColumn
+                  && lastMatchedColumn >= firstVisibleColumn)
+          {
+            /*
+             * found a search results match in the visible region
+             */
+            firstMatchedColumn = Math.max(firstMatchedColumn,
+                    firstVisibleColumn);
+            lastMatchedColumn = Math.min(lastMatchedColumn,
+                    lastVisibleColumn);
+
+            /*
+             * draw each mapped position separately (as contiguous positions may
+             * wrap across lines)
+             */
+            for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
+            {
+              int displayColumn = mappedPos;
+              if (av.hasHiddenColumns())
+              {
+                displayColumn = alignment.getHiddenColumns()
+                        .findColumnPosition(displayColumn);
+              }
+
+              /*
+               * transX: offset from left edge of canvas to residue position
+               */
+              int transX = labelWidthWest
+                      + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
+                      * av.getCharWidth();
+
+              /*
+               * transY: offset from top edge of canvas to residue position
+               */
+              int transY = gapHeight;
+              transY += (displayColumn / wrappedWidth) * wrappedHeight;
+              transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
+
+              /*
+               * yOffset is from graphics origin to start of visible region
+               */
+              int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
+              if (transY < getHeight())
+              {
+                matchFound = true;
+                gg.translate(transX, transY);
+                drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
+                        yOffset);
+                gg.translate(-transX, -transY);
+              }
+            }
+          }
+        }
+      }
+    }
+  
+    return matchFound;
+  }
 }
index 5634ead..55a508b 100644 (file)
@@ -215,7 +215,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
       int y = evt.getY();
       y -= hgap;
-      x -= seqCanvas.LABEL_WEST;
+      x -= seqCanvas.labelWidthWest;
 
       int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
       if (cwidth < 1)
@@ -694,8 +694,10 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
       ap.setToScrollComplementPanel(true);
     }
-    setStatusMessage(results);
-    seqCanvas.highlightSearchResults(results);
+    if (seqCanvas.highlightSearchResults(results))
+    {
+      setStatusMessage(results);
+    }
   }
 
   @Override