JAL-1807 explicit imports (jalview.ws.*)
[jalview.git] / src / jalview / gui / AlignmentPanel.java
index beafa8c..4c66214 100644 (file)
@@ -29,11 +29,14 @@ import jalview.datamodel.SearchResults;
 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.ImageMaker;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -42,6 +45,7 @@ import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
+import java.awt.Insets;
 import java.awt.event.AdjustmentEvent;
 import java.awt.event.AdjustmentListener;
 import java.awt.print.PageFormat;
@@ -52,6 +56,7 @@ import java.beans.PropertyChangeListener;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.PrintWriter;
+import java.util.List;
 
 import javax.swing.SwingUtilities;
 
@@ -91,6 +96,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   int vextent = 0;
 
+  /*
+   * Flag set while scrolling to follow complementary cDNA/protein scroll. When
+   * true, suppresses invoking the same method recursively.
+   */
+  private boolean followingComplementScroll;
+
   /**
    * Creates a new AlignmentPanel object.
    * 
@@ -290,33 +301,52 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
-   * scroll the view to show the position of the highlighted region in results
+   * Scroll the view to show the position of the highlighted region in results
    * (if any) and redraw the overview
    * 
    * @param results
    */
   public boolean scrollToPosition(SearchResults results)
   {
-    return scrollToPosition(results, true);
+    return scrollToPosition(results, 0, true, false);
   }
 
   /**
-   * scroll the view to show the position of the highlighted region in results
+   * Scroll the view to show the position of the highlighted region in results
+   * (if any)
+   * 
+   * @param searchResults
+   * @param redrawOverview
+   * @return
+   */
+  public boolean scrollToPosition(SearchResults searchResults, boolean redrawOverview)
+  {
+    return scrollToPosition(searchResults, 0, redrawOverview, false);
+  }
+
+  /**
+   * Scroll the view to show the position of the highlighted region in results
    * (if any)
    * 
    * @param results
+   * @param verticalOffset
+   *          if greater than zero, allows scrolling to a position below the
+   *          first displayed sequence
    * @param redrawOverview
    *          - 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
    */
   public boolean scrollToPosition(SearchResults results,
-          boolean redrawOverview)
+          int verticalOffset,
+          boolean redrawOverview, boolean centre)
   {
-    int startv, endv, starts, ends, width;
+    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 nullpointereexceptions raised here.
+    // TODO: tons of nullpointerexceptions raised here.
     if (results != null && results.getSize() > 0 && av != null
             && av.getAlignment() != null)
     {
@@ -336,6 +366,17 @@ public class AlignmentPanel extends GAlignmentPanel implements
       int end = r[1];
       // System.err.println("Seq : "+seqIndex+" Scroll to "+start+","+end); //
       // DEBUG
+
+      /*
+       * To centre results, scroll to positions half the visible width
+       * left/right of the start/end positions
+       */
+      if (centre)
+      {
+        int offset = (av.getEndRes() - av.getStartRes() + 1) / 2 - 1;
+        start = Math.max(start - offset, 0);
+        end = Math.min(end + offset, seq.getEnd() - 1);
+      }
       if (start < 0)
       {
         return false;
@@ -357,24 +398,47 @@ public class AlignmentPanel extends GAlignmentPanel implements
           }
         }
       }
+
+      /*
+       * allow for offset of target sequence (actually scroll to one above it)
+       */
+      seqIndex = Math.max(0, seqIndex - verticalOffset);
+
       if (!av.getWrapAlignment())
       {
         if ((startv = av.getStartRes()) >= start)
         {
-          setScrollValues(start - 1, seqIndex);
+          /*
+           * Scroll left to make start of search results visible
+           */
+          // setScrollValues(start - 1, seqIndex); // plus one residue
+          setScrollValues(start, seqIndex);
         }
         else if ((endv = av.getEndRes()) <= end)
         {
-          setScrollValues(startv + 1 + end - endv, seqIndex);
+          /*
+           * Scroll right to make end of search results visible
+           */
+          // setScrollValues(startv + 1 + end - endv, seqIndex); // plus one
+          setScrollValues(startv + end - endv, seqIndex);
         }
         else if ((starts = av.getStartSeq()) > seqIndex)
         {
+          /*
+           * Scroll up to make start of search results visible
+           */
           setScrollValues(av.getStartRes(), seqIndex);
         }
         else if ((ends = av.getEndSeq()) <= seqIndex)
         {
+          /*
+           * Scroll down to make end of search results visible
+           */
           setScrollValues(av.getStartRes(), starts + seqIndex - ends + 1);
         }
+        /*
+         * Else results are already visible - no need to scroll
+         */
       }
       else
       {
@@ -461,47 +525,47 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   protected void validateAnnotationDimensions(boolean adjustPanelHeight)
   {
-    int height = getAnnotationPanel().adjustPanelHeight();
-
-    int theight = av.getCharHeight()
-            * (av.getAlignment().getHeight() + (!av.hasHiddenRows() ? 0
-                    : av.getAlignment().getHiddenSequences().getSize()));
-    float sscaling = (float) (theight / (1.0 * theight + height));
-    float ascaling = (float) (height * 1.0 / alignFrame.getHeight());
-    int rheight = alignFrame.getHeight() - height - av.getCharHeight();
+    int annotationHeight = getAnnotationPanel().adjustPanelHeight();
+
     if (adjustPanelHeight)
     {
-      // NOTE: this logic is different in the applet. Need a better algorithm to
-      // define behaviour
-      // try and set height according to alignment
-      if (ascaling > 0 && sscaling < 0.5)
-      {
-        // if the alignment is too big then
-        // default is 0.5 split
-        height = alignFrame.getHeight() / 2;
-      }
-      else
+      int rowHeight = av.getCharHeight();
+      int alignmentHeight = rowHeight * av.getAlignment().getHeight();
+
+      /*
+       * Estimate available height in the AlignFrame for alignment +
+       * annotations. Deduct an estimate for title bar, menu bar, scale panel,
+       * hscroll, status bar (as these are not laid out we can't inspect their
+       * actual heights). Insets gives frame borders.
+       */
+      int stuff = Platform.isAMac() ? 80 : 100;
+      Insets insets = alignFrame.getInsets();
+      int availableHeight = alignFrame.getHeight() - stuff - insets.top
+              - insets.bottom;
+
+      /*
+       * If not enough vertical space, maximize annotation height while keeping
+       * at least two rows of alignment visible
+       */
+      if (annotationHeight + alignmentHeight > availableHeight)
       {
-        // if space for more than one sequence row left when annotation is fully
-        // displayed then set height to annotation height
-        // otherwise, leave at least two lines of sequence shown.
-        height = (rheight > av.getCharHeight()) ? height
-                : (-av.getCharHeight() * 3 + (int) (alignFrame.getHeight() * (1 - sscaling)));
+        annotationHeight = Math.min(annotationHeight, availableHeight - 2
+                * rowHeight);
       }
     }
     else
     {
       // maintain same window layout whilst updating sliders
-      height = annotationScroller.getSize().height;
+      annotationHeight = annotationScroller.getSize().height;
     }
     hscroll.addNotify();
 
     annotationScroller.setPreferredSize(new Dimension(annotationScroller
-            .getWidth(), height));
+            .getWidth(), annotationHeight));
 
     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
-            annotationSpaceFillerHolder.getWidth(), height));
-    annotationScroller.validate();// repaint();
+            annotationSpaceFillerHolder.getWidth(), annotationHeight));
+    annotationScroller.validate();
     annotationScroller.addNotify();
   }
 
@@ -756,6 +820,18 @@ public class AlignmentPanel extends GAlignmentPanel implements
         }
       }
     }
+    /*
+     * If there is one, scroll the (Protein/cDNA) complementary alignment to
+     * match, unless we are ourselves doing that.
+     */
+    if (isFollowingComplementScroll())
+    {
+      setFollowingComplementScroll(false);
+    }
+    else
+    {
+      av.scrollComplementaryAlignment();
+    }
   }
 
   /**
@@ -1163,7 +1239,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     return idwidth.intValue() + 4;
   }
 
-  void makeAlignmentImage(jalview.util.ImageMaker.TYPE type, File file)
+  void makeAlignmentImage(ImageMaker.TYPE type, File file)
   {
     long progress = System.currentTimeMillis();
     headless = (System.getProperty("java.awt.headless") != null && System
@@ -1179,14 +1255,14 @@ public class AlignmentPanel extends GAlignmentPanel implements
       AlignmentDimension aDimension = getAlignmentDimension();
       try
       {
-        jalview.util.ImageMaker im;
+        ImageMaker im;
         final String imageAction, imageTitle;
-        if (type == jalview.util.ImageMaker.TYPE.PNG)
+        if (type == ImageMaker.TYPE.PNG)
         {
           imageAction = "Create PNG image from alignment";
           imageTitle = null;
         }
-        else if (type == jalview.util.ImageMaker.TYPE.EPS)
+        else if (type == ImageMaker.TYPE.EPS)
         {
           imageAction = "Create EPS file from alignment";
           imageTitle = alignFrame.getTitle();
@@ -1197,7 +1273,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
           imageTitle = alignFrame.getTitle();
         }
 
-        im = new jalview.util.ImageMaker(this, type, imageAction,
+        im = new ImageMaker(this, type, imageAction,
                 aDimension.getWidth(), aDimension.getHeight(), file,
                 imageTitle);
         if (av.getWrapAlignment())
@@ -1283,7 +1359,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public void makeEPS(File epsFile)
   {
-    makeAlignmentImage(jalview.util.ImageMaker.TYPE.EPS, epsFile);
+    makeAlignmentImage(ImageMaker.TYPE.EPS, epsFile);
   }
 
   /**
@@ -1291,12 +1367,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public void makePNG(File pngFile)
   {
-    makeAlignmentImage(jalview.util.ImageMaker.TYPE.PNG, pngFile);
+    makeAlignmentImage(ImageMaker.TYPE.PNG, pngFile);
   }
 
   public void makeSVG(File svgFile)
   {
-    makeAlignmentImage(jalview.util.ImageMaker.TYPE.SVG, svgFile);
+    makeAlignmentImage(ImageMaker.TYPE.SVG, svgFile);
   }
   public void makePNGImageMap(File imgMapFile, String imageName)
   {
@@ -1316,7 +1392,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
                 .getAlignment().getWidth(), g, gSize, f, fSize, sy;
         StringBuffer text = new StringBuffer();
         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\">");
@@ -1492,7 +1568,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     PaintRefresher.RemoveComponent(this);
     if (av != null)
     {
-      jalview.structure.StructureSelectionManager ssm = av
+      StructureSelectionManager ssm = av
               .getStructureSelectionManager();
       ssm.removeStructureViewerListener(getSeqPanel(), null);
       ssm.removeSelectionListener(getSeqPanel());
@@ -1554,11 +1630,17 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     try
     {
+      if (alignFrame.getSplitViewContainer() != null)
+      {
+        /*
+         * bring enclosing SplitFrame to front first if there is one
+         */
+        ((SplitFrame) alignFrame.getSplitViewContainer()).setSelected(b);
+      }
       alignFrame.setSelected(b);
     } catch (Exception ex)
     {
     }
-    ;
 
     if (b)
     {
@@ -1651,4 +1733,63 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     this.idPanel = idPanel;
   }
+
+  /**
+   * Follow a scrolling change in the (cDNA/Protein) complementary alignment.
+   * The aim is to keep the two alignments 'lined up' on their centre columns.
+   * 
+   * @param sr
+   *          holds mapped region(s) of this alignment that we are scrolling
+   *          'to'; may be modified for sequence offset by this method
+   * @param verticalOffset
+   *          the number of visible sequences to show above the mapped region
+   */
+  public void scrollToCentre(SearchResults sr, int verticalOffset)
+  {
+    /*
+     * To avoid jumpy vertical scrolling (if some sequences are gapped or not
+     * mapped), we can make the scroll-to location a sequence above the one
+     * actually mapped.
+     */
+    SequenceI mappedTo = sr.getResultSequence(0);
+    List<SequenceI> seqs = av.getAlignment().getSequences();
+
+    /*
+     * This is like AlignmentI.findIndex(seq) but here we are matching the
+     * dataset sequence not the aligned sequence
+     */
+    boolean matched = false;
+    for (SequenceI seq : seqs)
+    {
+      if (mappedTo == seq.getDatasetSequence())
+      {
+        matched = true;
+        break;
+      }
+    }
+    if (!matched)
+    {
+      return; // failsafe, shouldn't happen
+    }
+
+    /*
+     * Scroll to position but centring the target residue.
+     */
+    scrollToPosition(sr, verticalOffset, true, true);
+  }
+
+  /**
+   * Set a flag to say we are scrolling to follow a (cDNA/protein) complement.
+   * 
+   * @param b
+   */
+  protected void setFollowingComplementScroll(boolean b)
+  {
+    this.followingComplementScroll = b;
+  }
+
+  protected boolean isFollowingComplementScroll()
+  {
+    return this.followingComplementScroll;
+  }
 }