JAL-1793 spike branch updated to latest
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 27 Oct 2017 09:54:03 +0000 (10:54 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 27 Oct 2017 09:54:03 +0000 (10:54 +0100)
85 files changed:
RELEASE
build.xml
help/html/calculations/pairwise.html
help/html/releases.html
src/MCview/AppletPDBCanvas.java
src/MCview/PDBCanvas.java
src/jalview/analysis/AlignSeq.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AppletJmol.java
src/jalview/appletgui/AppletJmolBinding.java
src/jalview/appletgui/ExtJmol.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/PaintRefresher.java
src/jalview/controller/AlignViewController.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/Mapping.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/datamodel/features/FeatureAttributeType.java [new file with mode: 0644]
src/jalview/datamodel/features/FeatureSource.java [new file with mode: 0644]
src/jalview/datamodel/features/FeatureSourceI.java [new file with mode: 0644]
src/jalview/datamodel/features/FeatureSources.java [new file with mode: 0644]
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/ext/ensembl/EnsemblLookup.java
src/jalview/ext/ensembl/EnsemblSymbol.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AppJmol.java
src/jalview/gui/AppJmolBinding.java
src/jalview/gui/AquaInternalFrameManager.java [new file with mode: 0644]
src/jalview/gui/CalculationChooser.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/CrossRefAction.java
src/jalview/gui/CutAndPasteHtmlTransfer.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/FontChooser.java
src/jalview/gui/IProgressIndicator.java
src/jalview/gui/IdPanel.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PaintRefresher.java
src/jalview/gui/PairwiseAlignPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/ProgressBar.java
src/jalview/gui/PromptUserConfig.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/TreePanel.java
src/jalview/gui/ViewSelectionMenu.java
src/jalview/io/FileLoader.java
src/jalview/io/InputStreamParser.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/vcf/VCFLoader.java
src/jalview/javascript/JSFunctionExec.java
src/jalview/javascript/MouseOverStructureListener.java
src/jalview/jbgui/GCutAndPasteHtmlTransfer.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/util/MappingUtils.java
src/jalview/util/StringUtils.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/ViewportRanges.java
src/jalview/ws/jws2/AbstractJabaCalcWorker.java
src/jalview/ws/jws2/jabaws2/Jws2Instance.java
test/jalview/analysis/AlignmentGenerator.java
test/jalview/analysis/AlignmentUtilsTests.java
test/jalview/analysis/TestAlignSeq.java
test/jalview/datamodel/SequenceFeatureTest.java
test/jalview/gui/FreeUpMemoryTest.java [new file with mode: 0644]
test/jalview/gui/PairwiseAlignmentPanelTest.java [new file with mode: 0644]
test/jalview/gui/ProgressBarTest.java
test/jalview/io/vcf/VCFLoaderTest.java
test/jalview/io/vcf/testVcf.dat
test/jalview/structures/models/AAStructureBindingModelTest.java
test/jalview/util/MappingUtilsTest.java
test/jalview/util/StringUtilsTest.java
test/jalview/viewmodel/ViewportRangesTest.java

diff --git a/RELEASE b/RELEASE
index cecefec..f1faf34 100644 (file)
--- a/RELEASE
+++ b/RELEASE
@@ -1,2 +1,2 @@
-jalview.release=releases/Release_2_10_2b1_Branch
-jalview.version=2.10.2b2
+jalview.release=releases/Release_2_10_3_Branch
+jalview.version=2.10.3
index f39fdf3..bb146dd 100755 (executable)
--- a/build.xml
+++ b/build.xml
     <!-- Anne's version needs 1.7 - should rebuild VARNA to java 1.6 for release -->
     <property name="j2sev" value="1.7+" />
     <!-- Java Compilation settings - source and target javac version -->
-    <property name="javac.source" value="1.7" />
-    <property name="javac.target" value="1.7" />
+    <property name="javac.source" value="1.8" />
+    <property name="javac.target" value="1.8" />
 
     <!-- Permissions for running Java applets and applications. -->
     <!-- Defaults are those suitable for deploying jalview webstart www.jalview.org -->
           <offline_allowed />
         </information>
         <resources>
-          <j2se version="1.7+" />
+          <j2se version="1.8+" />
           <jar main="true" href="jalview.jar"/>
           <fileset dir="${packageDir}">
             <exclude name="jalview.jar" />
 
     <jnlpf toFile="${jnlpFile}" />
     <!-- add the add-modules j2se attribute for java 9 -->
-    <replace file="${jnlpFile}" value="j2se version=&quot;1.7+&quot; initial-heap-size=&quot;${inih}&quot; max-heap-size=&quot;${maxh}&quot; java-vm-args=&quot;--add-modules=java.se.ee&quot;">
-          <replacetoken>j2se version="1.7+"</replacetoken>
+    <replace file="${jnlpFile}" value="j2se version=&quot;1.8+&quot; initial-heap-size=&quot;${inih}&quot; max-heap-size=&quot;${maxh}&quot; java-vm-args=&quot;--add-modules=java.se.ee&quot;">
+          <replacetoken>j2se version="1.8+"</replacetoken>
            
         </replace>
   </target>
index bb80b84..1090253 100755 (executable)
   <p>
     Gap open : 12 <br> Gap extend : 2
   </p>
-  <p>When you select the pairwise alignment option a new window will
-    come up which will display the alignments in a text format as they
-    are calculated. Also displayed is information about the alignment
-    such as alignment score, length and percentage identity between the
+  <p>When you select the pairwise alignment option, a new window
+    will come up which displays the alignments in a text format, for
+    example:</p>
+  <p>
+  <pre>
+    FER1_SPIOL/5-13 TTMMGMAT<br />
+                    |. .. ||<br />
+    FER1_MESCR/5-15 TAALSGAT
+    </pre>
+  shows the aligned sequences, where '|' links identical residues, and
+  (for peptide) '.' links residues that have a positive PAM250 score.
+  <p>The window also shows information about the alignment such as
+    alignment score, length and percentage identity between the
     sequences.</p>
-  <p>&nbsp;</p>
+  <p>A button is also provided to allow you to view the sequences as
+    an alignment.</p>
 </body>
 </html>
index cb2b278..92af377 100755 (executable)
@@ -89,11 +89,27 @@ li:before {
               <!-- JAL-2773 -->Structure views don't get updated unless
               their colours have changed
             </li>
+            <li><!-- JAL-2495 -->All linked sequences are highlighted for a structure mousover (Jmol) or selection (Chimera)</li>
+            <li><!-- JAL-2790 -->'Cancel' button in progress bar for JABAWS AACon, RNAAliFold and Disorder prediction jobs
+            </li>
+            
+            <li><!-- JAL-2617 -->Stop codons are excluded in CDS/Protein view from Ensembl locus cross-references</li>
+            <li><!-- JAL-2685 -->Start/End limits are shown in Pairwise Alignment report</li>
           </ul>
           <ul><li>Example groovy script for generating a matrix of percent identity scores for current alignment.</li></ul>
+          <em>Testing and Deployment</em>
+          <ul><li><!-- JAL-2727 -->Test to catch memory leaks in Jalview UI</li></ul>
+          </div>
       </td>
       <td><div align="left">
-          <em></em>
+          <em>General</em>
+          <ul>
+            <li><!-- JAL-2643 -->Pressing tab after updating the colour threshold text field doesn't trigger an update to the alignment view</li>
+            <li><!-- JAL-2682 -->Race condition when parsing sequence ID strings in parallel</li>
+            <li><!-- JAL-2608 -->Overview windows are also closed when alignment window is closed</li>
+            <li><!-- JAL-2548 -->Export of features doesn't always respect group visibility</li> 
+          </ul>
+          <em>Desktop</em>
           <ul>
             <li><!-- JAL-2777 -->Structures with whitespace chainCode cannot be viewed in Chimera</li>
             <li><!-- JAL-2728 -->Protein annotation panel too high in CDS/Protein view
@@ -101,10 +117,33 @@ li:before {
             <li><!-- JAL-2757 -->Can't edit the query after the server error warning icon is shown in Uniprot and PDB Free Text Search Dialogs
             </li> 
             <li><!-- JAL-2253 -->Slow EnsemblGenome ID lookup</li>
-            <li><!-- JAL-2682 -->Race condition when parsing sequence ID strings in parallel</li>
+            <li><!-- JAL-2529 -->Revised Ensembl REST API CDNA query</li>
             <li><!-- JAL-2739 -->Hidden column marker in last column not rendered when switching back from Wrapped to normal view</li> 
             <li><!-- JAL-2768 -->Annotation display corrupted when scrolling right in unwapped alignment view</li> 
+            <li><!-- JAL-2542 -->Existing features on subsequence incorrectly relocated when full sequence retrieved from database</li> 
+            <li><!-- JAL-2733 -->Last reported memory still shown when Desktop->Show Memory is unticked (OSX only)</li>
+            <li><!-- JAL-2658 -->Amend Features dialog doesn't allow features of same type and group to be selected for amending</li>
+            <li><!-- JAL-2524 -->Jalview becomes sluggish in wide alignments when hidden columns are present</li>
+            <li><!-- JAL-2392 -->Jalview freezes when loading and displaying several structures</li>
+            <li><!-- JAL-2732 -->Black outlines left after resizing or moving a window</li>
+            <li><!-- JAL-1900,JAL-1625 -->Unable to minimise windows within the Jalview desktop on OSX</li>
+            <li><!-- JAL-2667 -->Mouse wheel doesn't scroll vertically when in wrapped alignment mode</li>
+            <li><!-- JAL-2636 -->Scale mark not shown when close to right hand end of alignment</li>
+            <li><!-- JAL-2684 -->Pairwise alignment only aligns selected regions of each selected sequence</li>
+            <li><!-- JAL-2973 -->Alignment ruler height set incorrectly after canceling the Alignment Window's Font dialog</li>
+            <li><!-- JAL-2036 -->Show cross-references not enabled after restoring project until a new view is created</li>           
+           </ul>
+          <strong><em>Applet</em></strong><br/>
+           <ul>
+            <li><!-- JAL-2687 -->Concurrent modification exception when closing alignment panel</li> 
           </ul>
+          <strong><em>BioJSON</em></strong><br/>
+          <ul>
+          <li>
+            <!-- JAL-2546 -->BioJSON export does not preserve non-positional features
+          </li>
+          </ul>
+          </div>
       </td>
     </tr>
     <tr>
@@ -1462,6 +1501,10 @@ li:before {
               after clicking on it to create new annotation for a
               column.
             </li>
+            <li>
+              <!-- JAL-1980 -->Null Pointer Exception raised when 
+              pressing Add on an orphaned cut'n'paste window.
+            </li>
             <!--  may exclude, this is an external service stability issue  JAL-1941 
             -- > RNA 3D structure not added via DSSR service</li> -->
           </ul>
index f94faba..b15c3cc 100644 (file)
@@ -159,7 +159,7 @@ public class AppletPDBCanvas extends Panel
 
     try
     {
-      pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol);
+      pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol, null);
 
       if (protocol == DataSourceType.PASTE)
       {
index b2f2503..ab172f2 100644 (file)
@@ -153,7 +153,8 @@ public class PDBCanvas extends JPanel
 
     try
     {
-      pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol);
+      pdb = ssm.setMapping(seq, chains, pdbentry.getFile(), protocol,
+              ap.alignFrame);
 
       if (protocol.equals(jalview.io.DataSourceType.PASTE))
       {
index 34a21e6..1b2578e 100755 (executable)
@@ -36,6 +36,7 @@ import jalview.util.MessageManager;
 
 import java.awt.Color;
 import java.awt.Graphics;
+import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -49,6 +50,14 @@ import java.util.StringTokenizer;
  */
 public class AlignSeq
 {
+  private static final int MAX_NAME_LENGTH = 30;
+
+  private static final int GAP_OPEN_COST = 120;
+
+  private static final int GAP_EXTEND_COST = 20;
+
+  private static final int GAP_INDEX = -1;
+
   public static final String PEP = "pep";
 
   public static final String DNA = "dna";
@@ -61,7 +70,7 @@ public class AlignSeq
 
   float[][] F;
 
-  int[][] traceback;
+  int[][] traceback; // todo is this actually used?
 
   int[] seq1;
 
@@ -96,30 +105,20 @@ public class AlignSeq
   /** DOCUMENT ME!! */
   public int seq2start;
 
-  /** DOCUMENT ME!! */
   public int seq2end;
 
   int count;
 
-  /** DOCUMENT ME!! */
   public float maxscore;
 
-  float pid;
-
   int prev = 0;
 
-  int gapOpen = 120;
-
-  int gapExtend = 20;
-
   StringBuffer output = new StringBuffer();
 
   String type; // AlignSeq.PEP or AlignSeq.DNA
 
   private ScoreMatrix scoreMatrix;
 
-  private static final int GAP_INDEX = -1;
-
   /**
    * Creates a new AlignSeq object.
    * 
@@ -378,11 +377,10 @@ public class AlignSeq
       }
     }
 
-    // System.out.println(maxi + " " + maxj + " " + score[maxi][maxj]);
     int i = maxi;
     int j = maxj;
     int trace;
-    maxscore = score[i][j] / 10;
+    maxscore = score[i][j] / 10f;
 
     seq1end = maxi + 1;
     seq2end = maxj + 1;
@@ -451,49 +449,48 @@ public class AlignSeq
   /**
    * DOCUMENT ME!
    */
-  public void printAlignment(java.io.PrintStream os)
+  public void printAlignment(PrintStream os)
   {
     // TODO: Use original sequence characters rather than re-translated
     // characters in output
     // Find the biggest id length for formatting purposes
-    String s1id = s1.getName(), s2id = s2.getName();
-    int maxid = s1.getName().length();
-    if (s2.getName().length() > maxid)
-    {
-      maxid = s2.getName().length();
-    }
-    if (maxid > 30)
+    String s1id = getAlignedSeq1().getDisplayId(true);
+    String s2id = getAlignedSeq2().getDisplayId(true);
+    int nameLength = Math.max(s1id.length(), s2id.length());
+    if (nameLength > MAX_NAME_LENGTH)
     {
-      maxid = 30;
+      int truncateBy = nameLength - MAX_NAME_LENGTH;
+      nameLength = MAX_NAME_LENGTH;
       // JAL-527 - truncate the sequence ids
-      if (s1.getName().length() > maxid)
+      if (s1id.length() > nameLength)
       {
-        s1id = s1.getName().substring(0, 30);
+        int slashPos = s1id.lastIndexOf('/');
+        s1id = s1id.substring(0, slashPos - truncateBy)
+                + s1id.substring(slashPos);
       }
-      if (s2.getName().length() > maxid)
+      if (s2id.length() > nameLength)
       {
-        s2id = s2.getName().substring(0, 30);
+        int slashPos = s2id.lastIndexOf('/');
+        s2id = s2id.substring(0, slashPos - truncateBy)
+                + s2id.substring(slashPos);
       }
     }
-    int len = 72 - maxid - 1;
+    int len = 72 - nameLength - 1;
     int nochunks = ((aseq1.length - count) / len)
             + ((aseq1.length - count) % len > 0 ? 1 : 0);
-    pid = 0;
+    float pid = 0f;
 
     output.append("Score = ").append(score[maxi][maxj]).append(NEWLINE);
     output.append("Length of alignment = ")
             .append(String.valueOf(aseq1.length - count)).append(NEWLINE);
     output.append("Sequence ");
-    output.append(new Format("%" + maxid + "s").form(s1.getName()));
-    output.append(" :  ").append(String.valueOf(s1.getStart()))
-            .append(" - ").append(String.valueOf(s1.getEnd()));
+    Format nameFormat = new Format("%" + nameLength + "s");
+    output.append(nameFormat.form(s1id));
     output.append(" (Sequence length = ")
             .append(String.valueOf(s1str.length())).append(")")
             .append(NEWLINE);
     output.append("Sequence ");
-    output.append(new Format("%" + maxid + "s").form(s2.getName()));
-    output.append(" :  ").append(String.valueOf(s2.getStart()))
-            .append(" - ").append(String.valueOf(s2.getEnd()));
+    output.append(nameFormat.form(s2id));
     output.append(" (Sequence length = ")
             .append(String.valueOf(s2str.length())).append(")")
             .append(NEWLINE).append(NEWLINE);
@@ -503,7 +500,7 @@ public class AlignSeq
     for (int j = 0; j < nochunks; j++)
     {
       // Print the first aligned sequence
-      output.append(new Format("%" + (maxid) + "s").form(s1id)).append(" ");
+      output.append(nameFormat.form(s1id)).append(" ");
 
       for (int i = 0; i < len; i++)
       {
@@ -514,7 +511,7 @@ public class AlignSeq
       }
 
       output.append(NEWLINE);
-      output.append(new Format("%" + (maxid) + "s").form(" ")).append(" ");
+      output.append(nameFormat.form(" ")).append(" ");
 
       /*
        * Print out the match symbols:
@@ -534,7 +531,7 @@ public class AlignSeq
             pid++;
             output.append("|");
           }
-          else if (type.equals("pep"))
+          else if (PEP.equals(type))
           {
             if (pam250.getPairwiseScore(c1, c2) > 0)
             {
@@ -554,8 +551,7 @@ public class AlignSeq
 
       // Now print the second aligned sequence
       output = output.append(NEWLINE);
-      output = output.append(new Format("%" + (maxid) + "s").form(s2id))
-              .append(" ");
+      output = output.append(nameFormat.form(s2id)).append(" ");
 
       for (int i = 0; i < len; i++)
       {
@@ -569,7 +565,8 @@ public class AlignSeq
     }
 
     pid = pid / (aseq1.length - count) * 100;
-    output = output.append(new Format("Percentage ID = %2.2f\n").form(pid));
+    output.append(new Format("Percentage ID = %3.2f\n").form(pid));
+    output.append(NEWLINE);
     try
     {
       os.print(output.toString());
@@ -591,7 +588,6 @@ public class AlignSeq
   public int findTrace(int i, int j)
   {
     int t = 0;
-    // float pairwiseScore = lookup[seq1[i]][seq2[j]];
     float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
             s2str.charAt(j));
     float max = score[i - 1][j - 1] + (pairwiseScore * 10);
@@ -640,19 +636,19 @@ public class AlignSeq
     // top left hand element
     score[0][0] = scoreMatrix.getPairwiseScore(s1str.charAt(0),
             s2str.charAt(0)) * 10;
-    E[0][0] = -gapExtend;
+    E[0][0] = -GAP_EXTEND_COST;
     F[0][0] = 0;
 
     // Calculate the top row first
     for (int j = 1; j < m; j++)
     {
       // What should these values be? 0 maybe
-      E[0][j] = max(score[0][j - 1] - gapOpen, E[0][j - 1] - gapExtend);
-      F[0][j] = -gapExtend;
+      E[0][j] = max(score[0][j - 1] - GAP_OPEN_COST, E[0][j - 1] - GAP_EXTEND_COST);
+      F[0][j] = -GAP_EXTEND_COST;
 
       float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(0),
               s2str.charAt(j));
-      score[0][j] = max(pairwiseScore * 10, -gapOpen, -gapExtend);
+      score[0][j] = max(pairwiseScore * 10, -GAP_OPEN_COST, -GAP_EXTEND_COST);
 
       traceback[0][j] = 1;
     }
@@ -660,8 +656,8 @@ public class AlignSeq
     // Now do the left hand column
     for (int i = 1; i < n; i++)
     {
-      E[i][0] = -gapOpen;
-      F[i][0] = max(score[i - 1][0] - gapOpen, F[i - 1][0] - gapExtend);
+      E[i][0] = -GAP_OPEN_COST;
+      F[i][0] = max(score[i - 1][0] - GAP_OPEN_COST, F[i - 1][0] - GAP_EXTEND_COST);
 
       float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
               s2str.charAt(0));
@@ -674,8 +670,8 @@ public class AlignSeq
     {
       for (int j = 1; j < m; j++)
       {
-        E[i][j] = max(score[i][j - 1] - gapOpen, E[i][j - 1] - gapExtend);
-        F[i][j] = max(score[i - 1][j] - gapOpen, F[i - 1][j] - gapExtend);
+        E[i][j] = max(score[i][j - 1] - GAP_OPEN_COST, E[i][j - 1] - GAP_EXTEND_COST);
+        F[i][j] = max(score[i - 1][j] - GAP_OPEN_COST, F[i - 1][j] - GAP_EXTEND_COST);
 
         float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
                 s2str.charAt(j));
index 228446b..bef667d 100644 (file)
@@ -2217,7 +2217,10 @@ public class AlignmentUtils
 
   /**
    * Returns a mapping from dna to protein by inspecting sequence features of
-   * type "CDS" on the dna.
+   * type "CDS" on the dna. A mapping is constructed if the total CDS feature
+   * length is 3 times the peptide length (optionally after dropping a trailing
+   * stop codon). This method does not check whether the CDS nucleotide sequence
+   * translates to the peptide sequence.
    * 
    * @param dnaSeq
    * @param proteinSeq
@@ -2229,6 +2232,15 @@ public class AlignmentUtils
     List<int[]> ranges = findCdsPositions(dnaSeq);
     int mappedDnaLength = MappingUtils.getLength(ranges);
 
+    /*
+     * if not a whole number of codons, something is wrong,
+     * abort mapping
+     */
+    if (mappedDnaLength % CODON_LENGTH > 0)
+    {
+      return null;
+    }
+
     int proteinLength = proteinSeq.getLength();
     int proteinStart = proteinSeq.getStart();
     int proteinEnd = proteinSeq.getEnd();
@@ -2252,8 +2264,12 @@ public class AlignmentUtils
     if (codesForResidues == (proteinLength + 1))
     {
       // assuming extra codon is for STOP and not in peptide
+      // todo: check trailing codon is indeed a STOP codon
       codesForResidues--;
+      mappedDnaLength -= CODON_LENGTH;
+      MappingUtils.removeEndPositions(CODON_LENGTH, ranges);
     }
+
     if (codesForResidues == proteinLength)
     {
       proteinRange.add(new int[] { proteinStart, proteinEnd });
@@ -2264,7 +2280,7 @@ public class AlignmentUtils
 
   /**
    * Returns a list of CDS ranges found (as sequence positions base 1), i.e. of
-   * start/end positions of sequence features of type "CDS" (or a sub-type of
+   * [start, end] positions of sequence features of type "CDS" (or a sub-type of
    * CDS in the Sequence Ontology). The ranges are sorted into ascending start
    * position order, so this method is only valid for linear CDS in the same
    * sense as the protein product.
@@ -2283,7 +2299,6 @@ public class AlignmentUtils
       return result;
     }
     SequenceFeatures.sortFeatures(sfs, true);
-    int startPhase = 0;
 
     for (SequenceFeature sf : sfs)
     {
@@ -2301,7 +2316,7 @@ public class AlignmentUtils
        */
       int begin = sf.getBegin();
       int end = sf.getEnd();
-      if (result.isEmpty())
+      if (result.isEmpty() && phase > 0)
       {
         begin += phase;
         if (begin > end)
@@ -2316,16 +2331,6 @@ public class AlignmentUtils
     }
 
     /*
-     * remove 'startPhase' positions (usually 0) from the first range 
-     * so we begin at the start of a complete codon
-     */
-    if (!result.isEmpty())
-    {
-      // TODO JAL-2022 correctly model start phase > 0
-      result.get(0)[0] += startPhase;
-    }
-
-    /*
      * Finally sort ranges by start position. This avoids a dependency on 
      * keeping features in order on the sequence (if they are in order anyway,
      * the sort will have almost no work to do). The implicit assumption is CDS
index 3cb06c1..931eba6 100644 (file)
@@ -21,6 +21,7 @@
 package jalview.api;
 
 import jalview.analysis.Conservation;
+import jalview.analysis.TreeModel;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
@@ -485,4 +486,8 @@ public interface AlignViewportI extends ViewStyleI
    */
   @Override
   void setProteinFontAsCdna(boolean b);
+
+  public abstract TreeModel getCurrentTree();
+
+  public abstract void setCurrentTree(TreeModel tree);
 }
index 676d3cf..ef87671 100644 (file)
@@ -1603,10 +1603,12 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       System.exit(0);
     }
-    else
+
+    viewport = null;
+    if (alignPanel != null && alignPanel.overviewPanel != null)
     {
+      alignPanel.overviewPanel.dispose();
     }
-    viewport = null;
     alignPanel = null;
     this.dispose();
   }
@@ -4147,7 +4149,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       // register the association(s) and quit, don't create any windows.
       if (StructureSelectionManager.getStructureSelectionManager(applet)
-              .setMapping(seqs, chains, pdb.getFile(), protocol) == null)
+              .setMapping(seqs, chains, pdb.getFile(), protocol, null) == null)
       {
         System.err.println("Failed to map " + pdb.getFile() + " ("
                 + protocol + ") to any sequences");
index b07666e..262948d 100644 (file)
@@ -20,7 +20,6 @@
  */
 package jalview.appletgui;
 
-import jalview.analysis.TreeModel;
 import jalview.api.AlignViewportI;
 import jalview.api.FeatureSettingsModelI;
 import jalview.bin.JalviewLite;
@@ -54,23 +53,12 @@ public class AlignViewport extends AlignmentViewport
 
   boolean validCharWidth = true;
 
-  TreeModel currentTree = null;
-
   public jalview.bin.JalviewLite applet;
 
   boolean MAC = false;
 
   private AnnotationColumnChooser annotationColumnSelectionState;
 
-  @Override
-  public void finalize()
-  {
-    applet = null;
-    quality = null;
-    alignment = null;
-    colSel = null;
-  }
-
   public AlignViewport(AlignmentI al, JalviewLite applet)
   {
     super(al);
@@ -274,16 +262,6 @@ public class AlignViewport extends AlignmentViewport
     ranges.setEndSeq(height / getCharHeight());
   }
 
-  public void setCurrentTree(TreeModel tree)
-  {
-    currentTree = tree;
-  }
-
-  public TreeModel getCurrentTree()
-  {
-    return currentTree;
-  }
-
   boolean centreColumnLabels;
 
   public boolean getCentreColumnLabels()
index 906acc1..270b2f7 100644 (file)
@@ -73,23 +73,6 @@ public class AlignmentPanel extends Panel
   // this value is set false when selection area being dragged
   boolean fastPaint = true;
 
-  @Override
-  public void finalize() throws Throwable
-  {
-    alignFrame = null;
-    av = null;
-    vpRanges = null;
-    seqPanel = null;
-    seqPanelHolder = null;
-    sequenceHolderPanel = null;
-    scalePanel = null;
-    scalePanelHolder = null;
-    annotationPanel = null;
-    annotationPanelHolder = null;
-    annotationSpaceFillerHolder = null;
-    super.finalize();
-  }
-
   public AlignmentPanel(AlignFrame af, final AlignViewport av)
   {
     try
index 49219b9..3d1442d 100644 (file)
@@ -134,7 +134,7 @@ public class AppletJmol extends EmbmenuFrame implements
 
   AlignmentPanel ap;
 
-  List<AlignmentPanel> _aps = new ArrayList<AlignmentPanel>(); // remove? never
+  List<AlignmentPanel> _aps = new ArrayList<>(); // remove? never
                                                                // added to
 
   String fileLoadingError;
@@ -213,7 +213,7 @@ public class AppletJmol extends EmbmenuFrame implements
     {
       reader = StructureSelectionManager
               .getStructureSelectionManager(ap.av.applet)
-              .setMapping(seq, chains, pdbentry.getFile(), protocol);
+              .setMapping(seq, chains, pdbentry.getFile(), protocol, null);
       // PROMPT USER HERE TO ADD TO NEW OR EXISTING VIEW?
       // FOR NOW, LETS JUST OPEN A NEW WINDOW
     }
@@ -394,7 +394,7 @@ public class AppletJmol extends EmbmenuFrame implements
 
   void centerViewer()
   {
-    Vector<String> toshow = new Vector<String>();
+    Vector<String> toshow = new Vector<>();
     for (int i = 0; i < chainMenu.getItemCount(); i++)
     {
       if (chainMenu.getItem(i) instanceof CheckboxMenuItem)
index d5d53fb..2f61b24 100644 (file)
@@ -24,6 +24,7 @@ import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.ext.jmol.JalviewJmolBinding;
+import jalview.gui.IProgressIndicator;
 import jalview.io.DataSourceType;
 import jalview.structure.StructureSelectionManager;
 
@@ -183,4 +184,11 @@ class AppletJmolBinding extends JalviewJmolBinding
     // TODO Auto-generated method stub
     return null;
   }
+
+  @Override
+  protected IProgressIndicator getIProgressIndicator()
+  {
+    // no progress indicators on the applet
+    return null;
+  }
 }
index 3966536..89228d5 100644 (file)
@@ -26,6 +26,7 @@ import jalview.api.SequenceRenderer;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.ext.jmol.JalviewJmolBinding;
+import jalview.gui.IProgressIndicator;
 import jalview.io.DataSourceType;
 
 import java.awt.Container;
@@ -65,6 +66,13 @@ public class ExtJmol extends JalviewJmolBinding
   }
 
   @Override
+  protected IProgressIndicator getIProgressIndicator()
+  {
+    // no progress indicators on applet (could access javascript for this)
+    return null;
+  }
+
+  @Override
   public void updateColours(Object source)
   {
 
@@ -92,6 +100,7 @@ public class ExtJmol extends JalviewJmolBinding
     }
   }
 
+
   @Override
   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
   {
@@ -137,8 +146,8 @@ public class ExtJmol extends JalviewJmolBinding
   @Override
   public void refreshPdbEntries()
   {
-    List<PDBEntry> pdbe = new ArrayList<PDBEntry>();
-    List<String> fileids = new ArrayList<String>();
+    List<PDBEntry> pdbe = new ArrayList<>();
+    List<String> fileids = new ArrayList<>();
     SequenceI[] sq = ap.av.getAlignment().getSequencesArray();
     for (int s = 0; s < sq.length; s++)
     {
index 0256055..8ce597d 100755 (executable)
@@ -31,6 +31,7 @@ import java.awt.BorderLayout;
 import java.awt.CheckboxMenuItem;
 import java.awt.Cursor;
 import java.awt.Dimension;
+import java.awt.Frame;
 import java.awt.Panel;
 import java.awt.PopupMenu;
 import java.awt.event.ComponentAdapter;
@@ -322,6 +323,9 @@ public class OverviewPanel extends Panel implements Runnable,
     try
     {
       av.getRanges().removePropertyChangeListener(this);
+      Frame parent = (Frame) getParent();
+      parent.dispose();
+      parent.setVisible(false);
     } finally
     {
       av = null;
index 32507fe..fe99187 100755 (executable)
@@ -24,8 +24,8 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
 
 import java.awt.Component;
-import java.util.Enumeration;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Vector;
@@ -78,13 +78,14 @@ public class PaintRefresher
       return;
     }
 
-    for (String id : components.keySet())
+    Iterator<String> it = components.keySet().iterator();
+    while (it.hasNext())
     {
-      Vector<Component> comps = components.get(id);
+      Vector<Component> comps = components.get(it.next());
       comps.removeElement(comp);
-      if (comps.size() == 0)
+      if (comps.isEmpty())
       {
-        components.remove(id);
+        it.remove();
       }
     }
   }
@@ -110,10 +111,10 @@ public class PaintRefresher
       return;
     }
 
-    Enumeration<Component> e = comps.elements();
-    while (e.hasMoreElements())
+    Iterator<Component> it = comps.iterator();
+    while (it.hasNext())
     {
-      comp = e.nextElement();
+      comp = it.next();
 
       if (comp == source)
       {
@@ -122,7 +123,7 @@ public class PaintRefresher
 
       if (!comp.isValid())
       {
-        comps.removeElement(comp);
+        it.remove();
       }
       else if (validateSequences && comp instanceof AlignmentPanel
               && source instanceof AlignmentPanel)
index 69e31cf..460c2b3 100644 (file)
@@ -52,14 +52,6 @@ public class AlignViewController implements AlignViewControllerI
    */
   private AlignViewControllerGuiI avcg;
 
-  @Override
-  protected void finalize() throws Throwable
-  {
-    viewport = null;
-    alignPanel = null;
-    avcg = null;
-  };
-
   public AlignViewController(AlignViewControllerGuiI alignFrame,
           AlignViewportI viewport, AlignmentViewPanel alignPanel)
   {
index c464af2..09facbf 100755 (executable)
@@ -241,19 +241,6 @@ public class AlignmentAnnotation
 
   private boolean isrna;
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see java.lang.Object#finalize()
-   */
-  @Override
-  protected void finalize() throws Throwable
-  {
-    sequenceRef = null;
-    groupRef = null;
-    super.finalize();
-  }
-
   public static int getGraphValueFromString(String string)
   {
     if (string.equalsIgnoreCase("BAR_GRAPH"))
index 328b96a..b5184fb 100644 (file)
@@ -693,19 +693,6 @@ public class Mapping
     to = tto;
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see java.lang.Object#finalize()
-   */
-  @Override
-  protected void finalize() throws Throwable
-  {
-    map = null;
-    to = null;
-    super.finalize();
-  }
-
   /**
    * Returns an iterator which can serve up the aligned codon column positions
    * and their corresponding peptide products
index 9680766..1905f42 100755 (executable)
@@ -713,7 +713,7 @@ public class Sequence extends ASequence implements SequenceI
             @Override
             public String getChromosomeId()
             {
-              // strip of "chromosome:" prefix to chrId
+              // strip off "chromosome:" prefix to chrId
               return ref.getAccessionId().substring(
                       DBRefEntry.CHROMOSOME.length() + 1);
             }
index 4208ce1..8f82a1a 100755 (executable)
  */
 package jalview.datamodel;
 
+import jalview.datamodel.features.FeatureAttributeType;
 import jalview.datamodel.features.FeatureLocationI;
+import jalview.datamodel.features.FeatureSourceI;
+import jalview.datamodel.features.FeatureSources;
+import jalview.util.StringUtils;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -29,10 +33,9 @@ import java.util.TreeMap;
 import java.util.Vector;
 
 /**
- * DOCUMENT ME!
- * 
- * @author $author$
- * @version $Revision$
+ * A class that models a single contiguous feature on a sequence. If flag
+ * 'contactFeature' is true, the start and end positions are interpreted instead
+ * as two contact points.
  */
 public class SequenceFeature implements FeatureLocationI
 {
@@ -52,6 +55,8 @@ public class SequenceFeature implements FeatureLocationI
   // private key for ENA location designed not to conflict with real GFF data
   private static final String LOCATION = "!Location";
 
+  private static final String ROW_DATA = "<tr><td>%s</td><td>%s</td><td>%s</td></tr>";
+
   /*
    * map of otherDetails special keys, and their value fields' delimiter
    */
@@ -60,6 +65,8 @@ public class SequenceFeature implements FeatureLocationI
   static
   {
     INFO_KEYS.put("CSQ", ",");
+    // todo capture second level metadata (CSQ FORMAT)
+    // and delimiter "|" so as to report in a table within a table?
   }
 
   /*
@@ -95,6 +102,12 @@ public class SequenceFeature implements FeatureLocationI
 
   public Vector<String> links;
 
+  /*
+   * the identifier (if known) for the FeatureSource held in FeatureSources,
+   * as a provider of metadata about feature attributes 
+   */
+  private String source;
+
   /**
    * Constructs a duplicate feature. Note: Uses makes a shallow copy of the
    * otherDetails map, so the new and original SequenceFeature may reference the
@@ -166,6 +179,8 @@ public class SequenceFeature implements FeatureLocationI
     this(newType, sf.getDescription(), newBegin, newEnd, newScore,
             newGroup);
 
+    this.source = sf.source;
+
     if (sf.otherDetails != null)
     {
       otherDetails = new HashMap<String, Object>();
@@ -548,30 +563,31 @@ public class SequenceFeature implements FeatureLocationI
   }
 
   /**
-   * Answers a formatted text report of feature details
+   * Answers an html-formatted report of feature details
    * 
    * @return
    */
   public String getDetailsReport()
   {
+    FeatureSourceI metadata = FeatureSources.getInstance()
+            .getSource(source);
+
     StringBuilder sb = new StringBuilder(128);
-    if (begin == end)
-    {
-      sb.append(String.format("%s %d %s", type, begin, description));
-    }
-    else
-    {
-      sb.append(String.format("%s %d-%d %s", type, begin, end, description));
-    }
+    sb.append("<br>");
+    sb.append("<table>");
+    sb.append(String.format(ROW_DATA, "Type", type, ""));
+    sb.append(String.format(ROW_DATA, "Start/end", begin == end ? begin
+            : begin + (isContactFeature() ? ":" : "-") + end, ""));
+    String desc = StringUtils.stripHtmlTags(description);
+    sb.append(String.format(ROW_DATA, "Description", desc, ""));
     if (!Float.isNaN(score) && score != 0f)
     {
-      sb.append(" score=").append(score);
+      sb.append(String.format(ROW_DATA, "Score", score, ""));
     }
     if (featureGroup != null)
     {
-      sb.append(" (").append(featureGroup).append(")");
+      sb.append(String.format(ROW_DATA, "Group", featureGroup, ""));
     }
-    sb.append("\n\n");
 
     if (otherDetails != null)
     {
@@ -591,18 +607,89 @@ public class SequenceFeature implements FeatureLocationI
           /*
            * split selected INFO data by delimiter over multiple lines
            */
-          sb.append(key).append("=\n  ");
           String delimiter = INFO_KEYS.get(key);
-          String value = entry.getValue().toString();
-          sb.append(value.replace(delimiter, "\n  "));
+          String[] values = entry.getValue().toString().split(delimiter);
+          for (String value : values)
+          {
+            sb.append(String.format(ROW_DATA, key, "", value));
+          }
         }
         else
-        {
-          sb.append(key + "=" + entry.getValue().toString() + "\n");
+        { // tried <td title="key"> but it failed to provide a tooltip :-(
+          String attDesc = null;
+          if (metadata != null)
+          {
+            attDesc = metadata.getAttributeName(key);
+          }
+          String value = entry.getValue().toString();
+          if (isValueInteresting(key, value, metadata))
+          {
+            sb.append(String.format(ROW_DATA, key, attDesc == null ? ""
+                    : attDesc, value));
+          }
         }
       }
     }
+    sb.append("</table>");
+
     String text = sb.toString();
     return text;
   }
+
+  /**
+   * Answers true if we judge the value is worth displaying, by some heuristic
+   * rules, else false
+   * 
+   * @param key
+   * @param value
+   * @param metadata
+   * @return
+   */
+  boolean isValueInteresting(String key, String value,
+          FeatureSourceI metadata)
+  {
+    /*
+     * currently suppressing zero values as well as null or empty
+     */
+    if (value == null || "".equals(value) || ".".equals(value)
+            || "0".equals(value))
+    {
+      return false;
+    }
+
+    if (metadata == null)
+    {
+      return true;
+    }
+
+    FeatureAttributeType attType = metadata.getAttributeType(key);
+    if (attType != null
+            && (attType == FeatureAttributeType.Float || attType
+                    .equals(FeatureAttributeType.Integer)))
+    {
+      try
+      {
+        float fval = Float.valueOf(value);
+        if (fval == 0f)
+        {
+          return false;
+        }
+      } catch (NumberFormatException e)
+      {
+        // ignore
+      }
+    }
+
+    return true; // default to interesting
+  }
+
+  /**
+   * Sets the feature source identifier
+   * 
+   * @param theSource
+   */
+  public void setSource(String theSource)
+  {
+    source = theSource;
+  }
 }
diff --git a/src/jalview/datamodel/features/FeatureAttributeType.java b/src/jalview/datamodel/features/FeatureAttributeType.java
new file mode 100644 (file)
index 0000000..fd3069d
--- /dev/null
@@ -0,0 +1,12 @@
+package jalview.datamodel.features;
+
+/**
+ * A class to model the datatype of feature attributes.
+ * 
+ * @author gmcarstairs
+ *
+ */
+public enum FeatureAttributeType
+{
+  String, Integer, Float, Character, Flag;
+}
diff --git a/src/jalview/datamodel/features/FeatureSource.java b/src/jalview/datamodel/features/FeatureSource.java
new file mode 100644 (file)
index 0000000..a1be1dc
--- /dev/null
@@ -0,0 +1,78 @@
+package jalview.datamodel.features;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class to model one source of feature data, including metadata about
+ * attributes of features
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class FeatureSource implements FeatureSourceI
+{
+  private String name;
+
+  private Map<String, String> attributeNames;
+  
+  private Map<String, FeatureAttributeType> attributeTypes;
+  
+  /**
+   * Constructor
+   * 
+   * @param theName
+   */
+  public FeatureSource(String theName)
+  {
+    this.name = theName;
+    attributeNames = new HashMap<>();
+    attributeTypes = new HashMap<>();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getName()
+  {
+    return name;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getAttributeName(String attributeId)
+  {
+    return attributeNames.get(attributeId);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public FeatureAttributeType getAttributeType(String attributeId)
+  {
+    return attributeTypes.get(attributeId);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void setAttributeName(String id, String attName)
+  {
+    attributeNames.put(id, attName);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void setAttributeType(String id, FeatureAttributeType type)
+  {
+    attributeTypes.put(id, type);
+  }
+
+}
diff --git a/src/jalview/datamodel/features/FeatureSourceI.java b/src/jalview/datamodel/features/FeatureSourceI.java
new file mode 100644 (file)
index 0000000..c873593
--- /dev/null
@@ -0,0 +1,45 @@
+package jalview.datamodel.features;
+
+public interface FeatureSourceI
+{
+  /**
+   * Answers a name for the feature source (not necessarily unique)
+   * 
+   * @return
+   */
+  String getName();
+
+  /**
+   * Answers the 'long name' of an attribute given its id (short name or
+   * abbreviation), or null if not known
+   * 
+   * @param attributeId
+   * @return
+   */
+  String getAttributeName(String attributeId);
+
+  /**
+   * Sets the 'long name' of an attribute given its id (short name or
+   * abbreviation).
+   * 
+   * @param id
+   * @param name
+   */
+  void setAttributeName(String id, String name);
+
+  /**
+   * Answers the datatype of the attribute with given id, or null if not known
+   * 
+   * @param attributeId
+   * @return
+   */
+  FeatureAttributeType getAttributeType(String attributeId);
+
+  /**
+   * Sets the datatype of the attribute with given id
+   * 
+   * @param id
+   * @param type
+   */
+  void setAttributeType(String id, FeatureAttributeType type);
+}
diff --git a/src/jalview/datamodel/features/FeatureSources.java b/src/jalview/datamodel/features/FeatureSources.java
new file mode 100644 (file)
index 0000000..96efb41
--- /dev/null
@@ -0,0 +1,51 @@
+package jalview.datamodel.features;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class FeatureSources
+{
+  private static FeatureSources instance = new FeatureSources();
+
+  private Map<String, FeatureSourceI> sources;
+
+  /**
+   * Answers the singelton instance of this class
+   * 
+   * @return
+   */
+  public static FeatureSources getInstance()
+  {
+    return instance;
+  }
+
+  private FeatureSources()
+  {
+    sources = new HashMap<>();
+  }
+
+  /**
+   * Answers the FeatureSource with the given unique identifier, or null if not
+   * known
+   * 
+   * @param sourceId
+   * @return
+   */
+  public FeatureSourceI getSource(String sourceId)
+  {
+    return sources.get(sourceId);
+  }
+
+  /**
+   * Adds the given source under the given key. This will replace any existing
+   * source with the same id, it is the caller's responsibility to ensure keys
+   * are unique if necessary.
+   * 
+   * @param sourceId
+   * @param source
+   */
+  public void addSource(String sourceId, FeatureSource source)
+  {
+    sources.put(sourceId, source);
+  }
+}
index afff4c2..cdcfa96 100644 (file)
@@ -147,10 +147,9 @@ public class EnsemblGene extends EnsemblSeqProxy
         continue;
       }
       
-      parseChromosomeLocations(geneAlignment);
-      
       if (geneAlignment.getHeight() == 1)
       {
+        findGeneLoci(geneAlignment.getSequenceAt(0), geneId);
         getTranscripts(geneAlignment, geneId);
       }
       if (al == null)
@@ -166,42 +165,64 @@ public class EnsemblGene extends EnsemblSeqProxy
   }
 
   /**
+   * Calls the /lookup/id REST service, parses the response for gene
+   * coordinates, and if successful, adds these to the sequence. If this fails,
+   * fall back on trying to parse the sequence description in case it is in
+   * Ensembl-gene format e.g. chromosome:GRCh38:17:45051610:45109016:1.
+   * 
+   * @param seq
+   * @param geneId
+   */
+  void findGeneLoci(SequenceI seq, String geneId)
+  {
+    GeneLociI geneLoci = new EnsemblLookup(getDomain()).getGeneLoci(geneId);
+    if (geneLoci != null)
+    {
+      seq.setGeneLoci(geneLoci.getSpeciesId(), geneLoci.getAssemblyId(),
+              geneLoci.getChromosomeId(), geneLoci.getMap());
+    }
+    else
+    {
+      parseChromosomeLocations(seq);
+    }
+  }
+
+  /**
    * Parses and saves fields of an Ensembl-style description e.g.
    * chromosome:GRCh38:17:45051610:45109016:1
    * 
-   * @param alignment
+   * @param seq
    */
-  private void parseChromosomeLocations(AlignmentI alignment)
+  boolean parseChromosomeLocations(SequenceI seq)
   {
-    for (SequenceI seq : alignment.getSequences())
+    String description = seq.getDescription();
+    if (description == null)
     {
-      String description = seq.getDescription();
-      if (description == null)
+      return false;
+    }
+    String[] tokens = description.split(":");
+    if (tokens.length == 6 && tokens[0].startsWith(DBRefEntry.CHROMOSOME))
+    {
+      String ref = tokens[1];
+      String chrom = tokens[2];
+      try
       {
-        continue;
-      }
-      String[] tokens = description.split(":");
-      if (tokens.length == 6 && tokens[0].startsWith(DBRefEntry.CHROMOSOME))
+        int chStart = Integer.parseInt(tokens[3]);
+        int chEnd = Integer.parseInt(tokens[4]);
+        boolean forwardStrand = "1".equals(tokens[5]);
+        String species = ""; // not known here
+        int[] from = new int[] { seq.getStart(), seq.getEnd() };
+        int[] to = new int[] { forwardStrand ? chStart : chEnd,
+            forwardStrand ? chEnd : chStart };
+        MapList map = new MapList(from, to, 1, 1);
+        seq.setGeneLoci(species, ref, chrom, map);
+        return true;
+      } catch (NumberFormatException e)
       {
-        String ref = tokens[1];
-        String chrom = tokens[2];
-        try
-        {
-          int chStart = Integer.parseInt(tokens[3]);
-          int chEnd = Integer.parseInt(tokens[4]);
-          boolean forwardStrand = "1".equals(tokens[5]);
-          String species = ""; // dunno yet!
-          int[] from = new int[] { seq.getStart(), seq.getEnd() };
-          int[] to = new int[] { forwardStrand ? chStart : chEnd,
-              forwardStrand ? chEnd : chStart };
-          MapList map = new MapList(from, to, 1, 1);
-          seq.setGeneLoci(species, ref, chrom, map);
-        } catch (NumberFormatException e)
-        {
-          System.err.println("Bad integers in description " + description);
-        }
+        System.err.println("Bad integers in description " + description);
       }
     }
+    return false;
   }
 
   /**
index f314b0a..0d1b554 100644 (file)
  */
 package jalview.ext.ensembl;
 
+import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.GeneLociI;
+import jalview.util.MapList;
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Function;
 
@@ -124,15 +128,20 @@ public class EnsemblLookup extends EnsemblRestClient
   }
 
   /**
-   * Calls the Ensembl lookup REST endpoint and retrieves the 'Parent' for the
-   * given identifier, or null if not found
+   * Calls the Ensembl lookup REST endpoint and returns
+   * <ul>
+   * <li>the 'id' for the identifier if its type is "Gene"</li>
+   * <li>the 'Parent' if its type is 'Transcript'</li>
+   * <ul>
+   * If the type is 'Translation', does a recursive call to this method, passing
+   * in the 'Parent' (transcript id).
    * 
    * @param identifier
    * @return
    */
   public String getGeneId(String identifier)
   {
-    return getResult(identifier, br -> parseGeneId(br));
+    return (String) getResult(identifier, br -> parseGeneId(br));
   }
 
   /**
@@ -144,16 +153,19 @@ public class EnsemblLookup extends EnsemblRestClient
    */
   public String getSpecies(String identifier)
   {
-    return getResult(identifier, br -> getAttribute(br, SPECIES));
+    return (String) getResult(identifier, br -> getAttribute(br, SPECIES));
   }
 
   /**
+   * Calls the /lookup/id rest service and delegates parsing of the JSON
+   * response to the supplied parser
+   * 
    * @param identifier
-   * @param attribute
+   * @param parser
    * @return
    */
-  protected String getResult(String identifier,
-          Function<BufferedReader, String> parser)
+  protected Object getResult(String identifier,
+          Function<BufferedReader, Object> parser)
   {
     List<String> ids = Arrays.asList(new String[] { identifier });
 
@@ -257,4 +269,80 @@ public class EnsemblLookup extends EnsemblRestClient
     return geneId;
   }
 
+  /**
+   * Calls the /lookup/id rest service for the given id, and if successful,
+   * parses and returns the gene's chromosomal coordinates
+   * 
+   * @param geneId
+   * @return
+   */
+  public GeneLociI getGeneLoci(String geneId)
+  {
+    return (GeneLociI) getResult(geneId, br -> parseGeneLoci(br));
+  }
+
+  /**
+   * Parses the /lookup/id response for species, asssembly_name,
+   * seq_region_name, start, end and returns an object that wraps them, or null
+   * if unsuccessful
+   * 
+   * @param br
+   * @return
+   */
+  GeneLociI parseGeneLoci(BufferedReader br)
+  {
+    JSONParser jp = new JSONParser();
+    try
+    {
+      JSONObject val = (JSONObject) jp.parse(br);
+      final String species = val.get("species").toString();
+      final String assembly = val.get("assembly_name").toString();
+      final String chromosome = val.get("seq_region_name").toString();
+      String strand = val.get("strand").toString();
+      int start = Integer.parseInt(val.get("start").toString());
+      int end = Integer.parseInt(val.get("end").toString());
+      int fromEnd = end - start + 1;
+      boolean reverseStrand = "-1".equals(strand);
+      int toStart = reverseStrand ? end : start;
+      int toEnd = reverseStrand ? start : end;
+      List<int[]> fromRange = Collections.singletonList(new int[] { 1,
+          fromEnd });
+      List<int[]> toRange = Collections.singletonList(new int[] { toStart,
+          toEnd });
+      final MapList map = new MapList(fromRange, toRange, 1, 1);
+      return new GeneLociI()
+      {
+
+        @Override
+        public String getSpeciesId()
+        {
+          return species == null ? "" : species;
+        }
+
+        @Override
+        public String getAssemblyId()
+        {
+          return assembly;
+        }
+
+        @Override
+        public String getChromosomeId()
+        {
+          return chromosome;
+        }
+
+        @Override
+        public MapList getMap()
+        {
+          return map;
+        }
+      };
+    } catch (ParseException | NullPointerException | IOException
+            | NumberFormatException | ClassCastException e)
+    {
+      Cache.log.error("Error looking up gene loci: " + e.getMessage());
+    }
+    return null;
+  }
+
 }
index 75598a0..65be906 100644 (file)
@@ -152,7 +152,6 @@ public class EnsemblSymbol extends EnsemblXref
             if (br != null)
             {
               String geneId = parseSymbolResponse(br);
-              System.out.println(url + " returned " + geneId);
               if (geneId != null && !result.contains(geneId))
               {
                 result.add(geneId);
index 50aba62..41bc116 100644 (file)
@@ -27,6 +27,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
+import jalview.gui.IProgressIndicator;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
 import jalview.schemes.ColourSchemeI;
@@ -72,7 +73,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
    */
   private boolean associateNewStructs = false;
 
-  Vector<String> atomsPicked = new Vector<String>();
+  Vector<String> atomsPicked = new Vector<>();
 
   private List<String> chainNames;
 
@@ -610,7 +611,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     }
     if (modelFileNames == null)
     {
-      List<String> mset = new ArrayList<String>();
+      List<String> mset = new ArrayList<>();
       _modelFileNameMap = new int[viewer.ms.mc];
       String m = viewer.ms.getModelFileName(0);
       if (m != null)
@@ -670,7 +671,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   @Override
   public synchronized String[] getStructureFiles()
   {
-    List<String> mset = new ArrayList<String>();
+    List<String> mset = new ArrayList<>();
     if (viewer == null)
     {
       return new String[0];
@@ -1060,8 +1061,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     fileLoadingError = null;
     String[] oldmodels = modelFileNames;
     modelFileNames = null;
-    chainNames = new ArrayList<String>();
-    chainFile = new Hashtable<String, String>();
+    chainNames = new ArrayList<>();
+    chainFile = new Hashtable<>();
     boolean notifyLoaded = false;
     String[] modelfilenames = getStructureFiles();
     // first check if we've lost any structures
@@ -1127,7 +1128,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
           // see JAL-623 - need method of matching pasted data up
           {
             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
-                    pdbfile, DataSourceType.PASTE);
+                    pdbfile, DataSourceType.PASTE,
+                    getIProgressIndicator());
             getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
             matches = true;
             foundEntry = true;
@@ -1159,7 +1161,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
             }
             // Explicitly map to the filename used by Jmol ;
             pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
-                    fileName, protocol);
+                    fileName, protocol, getIProgressIndicator());
             // pdbentry[pe].getFile(), protocol);
 
           }
@@ -1227,6 +1229,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     return chainNames;
   }
 
+  protected abstract IProgressIndicator getIProgressIndicator();
+
   public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
   {
     notifyAtomPicked(iatom, strMeasure, null);
index bf94ec0..5b812c2 100644 (file)
@@ -164,8 +164,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   AlignViewport viewport;
 
-  ViewportRanges vpRanges;
-
   public AlignViewControllerI avc;
 
   List<AlignmentPanel> alignPanels = new ArrayList<>();
@@ -337,7 +335,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       progressBar = new ProgressBar(this.statusPanel, this.statusBar);
     }
 
-    vpRanges = viewport.getRanges();
     avc = new jalview.controller.AlignViewController(this, viewport,
             alignPanel);
     if (viewport.getAlignmentConservationAnnotation() == null)
@@ -655,9 +652,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                   { (viewport.cursorMode ? "on" : "off") }));
           if (viewport.cursorMode)
           {
-            alignPanel.getSeqPanel().seqCanvas.cursorX = vpRanges
+            ViewportRanges ranges = viewport.getRanges();
+            alignPanel.getSeqPanel().seqCanvas.cursorX = ranges
                     .getStartRes();
-            alignPanel.getSeqPanel().seqCanvas.cursorY = vpRanges
+            alignPanel.getSeqPanel().seqCanvas.cursorY = ranges
                     .getStartSeq();
           }
           alignPanel.getSeqPanel().seqCanvas.repaint();
@@ -690,10 +688,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           break;
         }
         case KeyEvent.VK_PAGE_UP:
-          vpRanges.pageUp();
+          viewport.getRanges().pageUp();
           break;
         case KeyEvent.VK_PAGE_DOWN:
-          vpRanges.pageDown();
+          viewport.getRanges().pageDown();
           break;
         }
       }
@@ -2149,7 +2147,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
 
         // propagate alignment changed.
-        vpRanges.setEndSeq(alignment.getHeight());
+        viewport.getRanges().setEndSeq(alignment.getHeight());
         if (annotationAdded)
         {
           // Duplicate sequence annotation in all views.
@@ -2550,7 +2548,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
         trimRegion = new TrimRegionCommand("Remove Left", true, seqs,
                 column, viewport.getAlignment());
-        vpRanges.setStartRes(0);
+        viewport.getRanges().setStartRes(0);
       }
       else
       {
@@ -2615,13 +2613,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // This is to maintain viewport position on first residue
     // of first sequence
     SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(vpRanges.getStartRes());
+    ViewportRanges ranges = viewport.getRanges();
+    int startRes = seq.findPosition(ranges.getStartRes());
     // ShiftList shifts;
     // viewport.getAlignment().removeGaps(shifts=new ShiftList());
     // edit.alColumnChanges=shifts.getInverse();
     // if (viewport.hasHiddenColumns)
     // viewport.getColumnSelection().compensateForEdits(shifts);
-    vpRanges.setStartRes(seq.findIndex(startRes) - 1);
+    ranges.setStartRes(seq.findIndex(startRes) - 1);
     viewport.firePropertyChange("alignment", null,
             viewport.getAlignment().getSequences());
 
@@ -2654,12 +2653,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     // This is to maintain viewport position on first residue
     // of first sequence
     SequenceI seq = viewport.getAlignment().getSequenceAt(0);
-    int startRes = seq.findPosition(vpRanges.getStartRes());
+    int startRes = seq.findPosition(viewport.getRanges().getStartRes());
 
     addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end,
             viewport.getAlignment()));
 
-    vpRanges.setStartRes(seq.findIndex(startRes) - 1);
+    viewport.getRanges().setStartRes(seq.findIndex(startRes) - 1);
 
     viewport.firePropertyChange("alignment", null,
             viewport.getAlignment().getSequences());
@@ -2715,8 +2714,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     /*
      * Create a new AlignmentPanel (with its own, new Viewport)
      */
-    AlignmentPanel newap = new Jalview2XML().copyAlignPanel(alignPanel,
-            true);
+    AlignmentPanel newap = new Jalview2XML().copyAlignPanel(alignPanel);
     if (!copyAnnotation)
     {
       /*
index c22a37d..90271c8 100644 (file)
@@ -76,8 +76,6 @@ public class AlignViewport extends AlignmentViewport
 {
   Font font;
 
-  TreeModel currentTree = null;
-
   boolean cursorMode = false;
 
   boolean antiAlias = false;
@@ -448,27 +446,6 @@ public class AlignViewport extends AlignmentViewport
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param tree
-   *          DOCUMENT ME!
-   */
-  public void setCurrentTree(TreeModel tree)
-  {
-    currentTree = tree;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public TreeModel getCurrentTree()
-  {
-    return currentTree;
-  }
-
-  /**
    * returns the visible column regions of the alignment
    * 
    * @param selectedRegionOnly
@@ -1110,5 +1087,4 @@ public class AlignViewport extends AlignmentViewport
     }
     fr.setTransparency(featureSettings.getTransparency());
   }
-
 }
index b2c4809..3a1dbe8 100644 (file)
@@ -76,8 +76,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
 {
   public AlignViewport av;
 
-  ViewportRanges vpRanges;
-
   OverviewPanel overviewPanel;
 
   private SeqPanel seqPanel;
@@ -121,7 +119,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     alignFrame = af;
     this.av = av;
-    vpRanges = av.getRanges();
     setSeqPanel(new SeqPanel(av, this));
     setIdPanel(new IdPanel(av, this));
 
@@ -153,11 +150,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
         // reset the viewport ranges when the alignment panel is resized
         // in particular, this initialises the end residue value when Jalview
         // is initialised
+        ViewportRanges ranges = av.getRanges();
         if (av.getWrapAlignment())
         {
           int widthInRes = getSeqPanel().seqCanvas.getWrappedCanvasWidth(
                   getSeqPanel().seqCanvas.getWidth());
-          vpRanges.setViewportWidth(widthInRes);
+          ranges.setViewportWidth(widthInRes);
         }
         else
         {
@@ -166,8 +164,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
           int heightInSeq = getSeqPanel().seqCanvas.getHeight()
                   / av.getCharHeight();
 
-          vpRanges.setViewportWidth(widthInRes);
-          vpRanges.setViewportHeight(heightInSeq);
+          ranges.setViewportWidth(widthInRes);
+          ranges.setViewportHeight(heightInSeq);
         }
       }
 
@@ -377,6 +375,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
           int verticalOffset, boolean redrawOverview, boolean centre)
   {
     int startv, endv, starts, ends;
+    ViewportRanges ranges = av.getRanges();
 
     if (results == null || results.isEmpty() || av == null
             || av.getAlignment() == null)
@@ -404,7 +403,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
      */
     if (centre)
     {
-      int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
+      int offset = (ranges.getEndRes() - ranges.getStartRes() + 1) / 2 - 1;
       start = Math.max(start - offset, 0);
       end = end + offset - 1;
     }
@@ -440,33 +439,33 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     if (!av.getWrapAlignment())
     {
-      if ((startv = vpRanges.getStartRes()) >= start)
+      if ((startv = ranges.getStartRes()) >= start)
       {
         /*
          * Scroll left to make start of search results visible
          */
         setScrollValues(start, seqIndex);
       }
-      else if ((endv = vpRanges.getEndRes()) <= end)
+      else if ((endv = ranges.getEndRes()) <= end)
       {
         /*
          * Scroll right to make end of search results visible
          */
         setScrollValues(startv + end - endv, seqIndex);
       }
-      else if ((starts = vpRanges.getStartSeq()) > seqIndex)
+      else if ((starts = ranges.getStartSeq()) > seqIndex)
       {
         /*
          * Scroll up to make start of search results visible
          */
-        setScrollValues(vpRanges.getStartRes(), seqIndex);
+        setScrollValues(ranges.getStartRes(), seqIndex);
       }
-      else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
+      else if ((ends = ranges.getEndSeq()) <= seqIndex)
       {
         /*
          * Scroll down to make end of search results visible
          */
-        setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
+        setScrollValues(ranges.getStartRes(), starts + seqIndex - ends
                 + 1);
       }
       /*
@@ -476,7 +475,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     else
     {
-      scrollNeeded = vpRanges.scrollToWrappedVisible(start);
+      scrollNeeded = ranges.scrollToWrappedVisible(start);
     }
 
     paintAlignment(redrawOverview, false);
@@ -605,7 +604,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     fontChanged();
     setAnnotationVisible(av.isShowAnnotation());
     boolean wrap = av.getWrapAlignment();
-    vpRanges.setStartSeq(0);
+    ViewportRanges ranges = av.getRanges();
+    ranges.setStartSeq(0);
     scalePanelHolder.setVisible(!wrap);
     hscroll.setVisible(!wrap);
     idwidthAdjuster.setVisible(!wrap);
@@ -628,7 +628,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       {
         int widthInRes = getSeqPanel().seqCanvas
                 .getWrappedCanvasWidth(canvasWidth);
-        vpRanges.setViewportWidth(widthInRes);
+        ranges.setViewportWidth(widthInRes);
       }
       else
       {
@@ -636,8 +636,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
         int heightInSeq = (getSeqPanel().seqCanvas.getHeight()
                 / av.getCharHeight());
 
-        vpRanges.setViewportWidth(widthInRes);
-        vpRanges.setViewportHeight(heightInSeq);
+        ranges.setViewportWidth(widthInRes);
+        ranges.setViewportHeight(heightInSeq);
       }
     }
 
@@ -736,10 +736,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
       return;
     }
 
+    ViewportRanges ranges = av.getRanges();
+
     if (evt.getSource() == hscroll)
     {
-      int oldX = vpRanges.getStartRes();
-      int oldwidth = vpRanges.getViewportWidth();
+      int oldX = ranges.getStartRes();
+      int oldwidth = ranges.getViewportWidth();
       int x = hscroll.getValue();
       int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
 
@@ -750,12 +752,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
       {
         return;
       }
-      vpRanges.setViewportStartAndWidth(x, width);
+      ranges.setViewportStartAndWidth(x, width);
     }
     else if (evt.getSource() == vscroll)
     {
-      int oldY = vpRanges.getStartSeq();
-      int oldheight = vpRanges.getViewportHeight();
+      int oldY = ranges.getStartSeq();
+      int oldheight = ranges.getViewportHeight();
       int y = vscroll.getValue();
       int height = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
 
@@ -766,7 +768,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       {
         return;
       }
-      vpRanges.setViewportStartAndHeight(y, height);
+      ranges.setViewportStartAndHeight(y, height);
     }
     repaint();
   }
@@ -783,6 +785,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     {
       return; // no horizontal scroll when wrapped
     }
+    final ViewportRanges ranges = av.getRanges();
+
     if (evt.getSource() == vscroll)
     {
       int newY = vscroll.getValue();
@@ -792,8 +796,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
        * this prevents infinite recursion of events when the scroll/viewport
        * ranges values are the same
        */
-      int oldX = vpRanges.getStartRes();
-      int oldY = vpRanges.getWrappedScrollPosition(oldX);
+      int oldX = ranges.getStartRes();
+      int oldY = ranges.getWrappedScrollPosition(oldX);
       if (oldY == newY)
       {
         return;
@@ -803,9 +807,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
         /*
          * limit page up/down to one width's worth of positions
          */
-        int rowSize = vpRanges.getViewportWidth();
+        int rowSize = ranges.getViewportWidth();
         int newX = newY > oldY ? oldX + rowSize : oldX - rowSize;
-        vpRanges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
+        ranges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
       }
     }
     else
@@ -826,8 +830,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
                   "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);
+          ranges.setStartRes(0);
+          ranges.setStartSeq(0);
         }
       });
     }
@@ -880,7 +884,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     /*
      * set scroll bar positions
      */
-    setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
+    ViewportRanges ranges = av.getRanges();
+    setScrollValues(ranges.getStartRes(), ranges.getStartSeq());
   }
 
   /**
@@ -892,8 +897,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   private void setScrollingForWrappedPanel(int topLeftColumn)
   {
-    int scrollPosition = vpRanges.getWrappedScrollPosition(topLeftColumn);
-    int maxScroll = vpRanges.getWrappedMaxScroll(topLeftColumn);
+    ViewportRanges ranges = av.getRanges();
+    int scrollPosition = ranges.getWrappedScrollPosition(topLeftColumn);
+    int maxScroll = ranges.getWrappedMaxScroll(topLeftColumn);
 
     /*
      * a scrollbar's value can be set to at most (maximum-extent)
@@ -1604,13 +1610,14 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (annotationPanel != null)
     {
       annotationPanel.dispose();
+      annotationPanel = null;
     }
 
     if (av != null)
     {
       av.removePropertyChangeListener(propertyChangeListener);
-      jalview.structure.StructureSelectionManager ssm = av
-              .getStructureSelectionManager();
+      propertyChangeListener = null;
+      StructureSelectionManager ssm = av.getStructureSelectionManager();
       ssm.removeStructureViewerListener(getSeqPanel(), null);
       ssm.removeSelectionListener(getSeqPanel());
       ssm.removeCommandListener(av);
@@ -1633,9 +1640,15 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   protected void closeChildFrames()
   {
+    if (overviewPanel != null)
+    {
+      overviewPanel.dispose();
+      overviewPanel = null;
+    }
     if (calculationDialog != null)
     {
       calculationDialog.closeFrame();
+      calculationDialog = null;
     }
   }
 
@@ -1882,8 +1895,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
   public void propertyChange(PropertyChangeEvent evt)
   {
     // update this panel's scroll values based on the new viewport ranges values
-    int x = vpRanges.getStartRes();
-    int y = vpRanges.getStartSeq();
+    ViewportRanges ranges = av.getRanges();
+    int x = ranges.getStartRes();
+    int y = ranges.getStartSeq();
     setScrollValues(x, y);
 
     // now update any complementary alignment (its viewport ranges object
index a4597d3..fef7451 100644 (file)
@@ -157,6 +157,11 @@ public class AppJmol extends StructureViewerBase
 
   IProgressIndicator progressBar = null;
 
+  @Override
+  protected IProgressIndicator getIProgressIndicator()
+  {
+    return progressBar;
+  }
   /**
    * add a single PDB structure to a new or existing Jmol view
    * 
@@ -248,7 +253,7 @@ public class AppJmol extends StructureViewerBase
   @Override
   protected List<StructureViewerBase> getViewersFor(AlignmentPanel apanel)
   {
-    List<StructureViewerBase> result = new ArrayList<StructureViewerBase>();
+    List<StructureViewerBase> result = new ArrayList<>();
     JInternalFrame[] frames = Desktop.instance.getAllFrames();
 
     for (JInternalFrame frame : frames)
@@ -300,7 +305,7 @@ public class AppJmol extends StructureViewerBase
   @Override
   void showSelectedChains()
   {
-    Vector<String> toshow = new Vector<String>();
+    Vector<String> toshow = new Vector<>();
     for (int i = 0; i < chainMenu.getItemCount(); i++)
     {
       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
@@ -489,7 +494,7 @@ public class AppJmol extends StructureViewerBase
     // todo - record which pdbids were successfully imported.
     StringBuilder errormsgs = new StringBuilder();
 
-    List<String> files = new ArrayList<String>();
+    List<String> files = new ArrayList<>();
     String pdbid = "";
     try
     {
index 9325172..724cec1 100644 (file)
@@ -49,6 +49,12 @@ public class AppJmolBinding extends JalviewJmolBinding
   }
 
   @Override
+  protected IProgressIndicator getIProgressIndicator()
+  {
+    return appJmolWindow.progressBar;
+  }
+
+  @Override
   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
   {
     return new SequenceRenderer(((AlignmentPanel) alignment).av);
diff --git a/src/jalview/gui/AquaInternalFrameManager.java b/src/jalview/gui/AquaInternalFrameManager.java
new file mode 100644 (file)
index 0000000..829135b
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jalview.gui;
+
+import java.awt.Container;
+import java.beans.PropertyVetoException;
+import java.util.Vector;
+
+import javax.swing.DefaultDesktopManager;
+import javax.swing.DesktopManager;
+import javax.swing.JInternalFrame;
+
+/**
+ * Based on AquaInternalFrameManager
+ *
+ * DesktopManager implementation for Aqua
+ *
+ * Mac is more like Windows than it's like Motif/Basic
+ *
+ * From WindowsDesktopManager:
+ *
+ * This class implements a DesktopManager which more closely follows the MDI
+ * model than the DefaultDesktopManager. Unlike the DefaultDesktopManager
+ * policy, MDI requires that the selected and activated child frames are the
+ * same, and that that frame always be the top-most window.
+ * <p>
+ * The maximized state is managed by the DesktopManager with MDI, instead of
+ * just being a property of the individual child frame. This means that if the
+ * currently selected window is maximized and another window is selected, that
+ * new window will be maximized.
+ *
+ * Downloaded from
+ * https://raw.githubusercontent.com/frohoff/jdk8u-jdk/master/src/macosx/classes/com/apple/laf/AquaInternalFrameManager.java
+ * 
+ * Patch from Jim Procter - when the most recently opened frame is closed,
+ * correct behaviour is to go to the next most recent frame, rather than wrap
+ * around to the bottom of the window stack (as the original implementation
+ * does)
+ * 
+ */
+public class AquaInternalFrameManager extends DefaultDesktopManager
+{
+  // Variables
+
+  /* The frame which is currently selected/activated.
+   * We store this value to enforce Mac's single-selection model.
+   */
+  JInternalFrame fCurrentFrame;
+
+  JInternalFrame fInitialFrame;
+
+  /* The list of frames, sorted by order of creation.
+   * This list is necessary because by default the order of
+   * child frames in the JDesktopPane changes during frame
+   * activation (the activated frame is moved to index 0).
+   * We preserve the creation order so that "next" and "previous"
+   * frame actions make sense.
+   */
+  Vector<JInternalFrame> fChildFrames = new Vector<>(1);
+
+  /**
+   * keep a reference to the original LAF manager so we can iconise/de-iconise
+   * correctly
+   */
+  private DesktopManager ourManager;
+
+  public AquaInternalFrameManager(DesktopManager desktopManager)
+  {
+    ourManager = desktopManager;
+  }
+
+  @Override
+  public void closeFrame(final JInternalFrame f)
+  {
+    if (f == fCurrentFrame)
+    {
+      boolean mostRecentFrame = fChildFrames
+              .indexOf(f) == fChildFrames.size() - 1;
+      if (!mostRecentFrame)
+      {
+        activateNextFrame();
+      }
+      else
+      {
+        activatePreviousFrame();
+      }
+    }
+    fChildFrames.removeElement(f);
+    super.closeFrame(f);
+  }
+
+  @Override
+  public void deiconifyFrame(final JInternalFrame f)
+  {
+    JInternalFrame.JDesktopIcon desktopIcon;
+
+    desktopIcon = f.getDesktopIcon();
+    // If the icon moved, move the frame to that spot before expanding it
+    // reshape does delta checks for us
+    f.reshape(desktopIcon.getX(), desktopIcon.getY(), f.getWidth(),
+            f.getHeight());
+    ourManager.deiconifyFrame(f);
+  }
+
+  void addIcon(final Container c,
+          final JInternalFrame.JDesktopIcon desktopIcon)
+  {
+    c.add(desktopIcon);
+  }
+
+  /**
+   * Removes the frame from its parent and adds its desktopIcon to the parent.
+   */
+  @Override
+  public void iconifyFrame(final JInternalFrame f)
+  {
+    ourManager.iconifyFrame(f);
+  }
+
+  // WindowsDesktopManager code
+  @Override
+  public void activateFrame(final JInternalFrame f)
+  {
+    try
+    {
+      if (f != null)
+      {
+        super.activateFrame(f);
+      }
+
+      // If this is the first activation, add to child list.
+      if (fChildFrames.indexOf(f) == -1)
+      {
+        fChildFrames.addElement(f);
+      }
+
+      if (fCurrentFrame != null && f != fCurrentFrame)
+      {
+        if (fCurrentFrame.isSelected())
+        {
+          fCurrentFrame.setSelected(false);
+        }
+      }
+
+      if (f != null && !f.isSelected())
+      {
+        f.setSelected(true);
+      }
+
+      fCurrentFrame = f;
+    } catch (final PropertyVetoException e)
+    {
+    }
+  }
+
+  private void switchFrame(final boolean next)
+  {
+    if (fCurrentFrame == null)
+    {
+      // initialize first frame we find
+      if (fInitialFrame != null)
+      {
+        activateFrame(fInitialFrame);
+      }
+      return;
+    }
+
+    final int count = fChildFrames.size();
+    if (count <= 1)
+    {
+      // No other child frames.
+      return;
+    }
+
+    final int currentIndex = fChildFrames.indexOf(fCurrentFrame);
+    if (currentIndex == -1)
+    {
+      // the "current frame" is no longer in the list
+      fCurrentFrame = null;
+      return;
+    }
+
+    int nextIndex;
+    if (next)
+    {
+      nextIndex = currentIndex + 1;
+      if (nextIndex == count)
+      {
+        nextIndex = 0;
+      }
+    }
+    else
+    {
+      nextIndex = currentIndex - 1;
+      if (nextIndex == -1)
+      {
+        nextIndex = count - 1;
+      }
+    }
+    final JInternalFrame f = fChildFrames.elementAt(nextIndex);
+    activateFrame(f);
+    fCurrentFrame = f;
+  }
+
+  /**
+   * Activate the next child JInternalFrame, as determined by the frames'
+   * Z-order. If there is only one child frame, it remains activated. If there
+   * are no child frames, nothing happens.
+   */
+  public void activateNextFrame()
+  {
+    switchFrame(true);
+  }
+
+  /**
+   * same as above but will activate a frame if none have been selected
+   */
+  public void activateNextFrame(final JInternalFrame f)
+  {
+    fInitialFrame = f;
+    switchFrame(true);
+  }
+
+  /**
+   * Activate the previous child JInternalFrame, as determined by the frames'
+   * Z-order. If there is only one child frame, it remains activated. If there
+   * are no child frames, nothing happens.
+   */
+  public void activatePreviousFrame()
+  {
+    switchFrame(false);
+  }
+}
\ No newline at end of file
index a9f3966..e403dba 100644 (file)
@@ -105,6 +105,11 @@ public class CalculationChooser extends JPanel
 
   List<String> tips = new ArrayList<String>();
 
+  /*
+   * the most recently opened PCA results panel
+   */
+  private PCAPanel pcaPanel;
+
   /**
    * Constructor
    * 
@@ -534,7 +539,7 @@ public class CalculationChooser extends JPanel
               JvOptionPane.WARNING_MESSAGE);
       return;
     }
-    new PCAPanel(af.alignPanel, modelName, params);
+    pcaPanel = new PCAPanel(af.alignPanel, modelName, params);
   }
 
   /**
@@ -592,4 +597,9 @@ public class CalculationChooser extends JPanel
     {
     }
   }
+
+  public PCAPanel getPcaPanel()
+  {
+    return pcaPanel;
+  }
 }
index ba360af..89de2e8 100644 (file)
@@ -358,7 +358,7 @@ public class ChimeraViewFrame extends StructureViewerBase
   @Override
   protected List<StructureViewerBase> getViewersFor(AlignmentPanel ap)
   {
-    List<StructureViewerBase> result = new ArrayList<StructureViewerBase>();
+    List<StructureViewerBase> result = new ArrayList<>();
     JInternalFrame[] frames = Desktop.instance.getAllFrames();
 
     for (JInternalFrame frame : frames)
@@ -414,7 +414,7 @@ public class ChimeraViewFrame extends StructureViewerBase
   @Override
   void showSelectedChains()
   {
-    List<String> toshow = new ArrayList<String>();
+    List<String> toshow = new ArrayList<>();
     for (int i = 0; i < chainMenu.getItemCount(); i++)
     {
       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
@@ -484,8 +484,8 @@ public class ChimeraViewFrame extends StructureViewerBase
     // todo - record which pdbids were successfully imported.
     StringBuilder errormsgs = new StringBuilder(128);
     StringBuilder files = new StringBuilder(128);
-    List<PDBEntry> filePDB = new ArrayList<PDBEntry>();
-    List<Integer> filePDBpos = new ArrayList<Integer>();
+    List<PDBEntry> filePDB = new ArrayList<>();
+    List<Integer> filePDBpos = new ArrayList<>();
     PDBEntry thePdbEntry = null;
     StructureFile pdb = null;
     try
@@ -598,9 +598,12 @@ public class ChimeraViewFrame extends StructureViewerBase
               stopProgressBar("", startTime);
             }
             // Explicitly map to the filename used by Chimera ;
+
             pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
-                    jmb.getChains()[pos], pe.getFile(), protocol);
+                    jmb.getChains()[pos], pe.getFile(), protocol,
+                    progressBar);
             stashFoundChains(pdb, pe.getFile());
+
           } catch (OutOfMemoryError oomerror)
           {
             new OOMWarning(
@@ -658,7 +661,7 @@ public class ChimeraViewFrame extends StructureViewerBase
 
   /**
    * Fetch PDB data and save to a local file. Returns the full path to the file,
-   * or null if fetch fails.
+   * or null if fetch fails. TODO: refactor to common with Jmol ? duplication
    * 
    * @param processingEntry
    * @return
@@ -891,4 +894,10 @@ public class ChimeraViewFrame extends StructureViewerBase
     }
     return reply;
   }
+
+  @Override
+  protected IProgressIndicator getIProgressIndicator()
+  {
+    return progressBar;
+  }
 }
index 21a0a84..285e574 100644 (file)
@@ -336,7 +336,7 @@ public class CrossRefAction implements Runnable
 
     /*
      * hack: ignore cross-references to Ensembl protein ids
-     * (can't fetch chromosomal mapping for these)
+     * (or use map/translation perhaps?)
      * todo: is there an equivalent in EnsemblGenomes?
      */
     if (accession.startsWith("ENSP"))
index 71a1520..2e51bce 100644 (file)
@@ -141,6 +141,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
    */
   public void setText(String text)
   {
+    textarea.setDocument(textarea.getEditorKit().createDefaultDocument());
     textarea.setText(text);
   }
 
index 1f8983f..2d1ba12 100644 (file)
@@ -68,8 +68,6 @@ import java.awt.dnd.DropTargetEvent;
 import java.awt.dnd.DropTargetListener;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.awt.event.FocusEvent;
-import java.awt.event.FocusListener;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
@@ -361,7 +359,10 @@ public class Desktop extends jalview.jbgui.GDesktop
     desktop.setDesktopManager(
             new MyDesktopManager(
                     (Platform.isWindows() ? new DefaultDesktopManager()
-                            : desktop.getDesktopManager())));
+                            : Platform.isAMac()
+                                    ? new AquaInternalFrameManager(
+                                            desktop.getDesktopManager())
+                                    : desktop.getDesktopManager())));
 
     Rectangle dims = getLastKnownDimensions("");
     if (dims != null)
@@ -431,24 +432,6 @@ public class Desktop extends jalview.jbgui.GDesktop
     });
     desktop.addMouseListener(ma);
 
-    this.addFocusListener(new FocusListener()
-    {
-
-      @Override
-      public void focusLost(FocusEvent e)
-      {
-        // TODO Auto-generated method stub
-
-      }
-
-      @Override
-      public void focusGained(FocusEvent e)
-      {
-        Cache.log.debug("Relaying windows after focus gain");
-        // make sure that we sort windows properly after we gain focus
-        instance.relayerWindows();
-      }
-    });
     this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
     // Spawn a thread that shows the splashscreen
     SwingUtilities.invokeLater(new Runnable()
@@ -886,6 +869,10 @@ public class Desktop extends jalview.jbgui.GDesktop
         JInternalFrame itf = desktop.getSelectedFrame();
         if (itf != null)
         {
+          if (itf instanceof AlignFrame)
+          {
+            Jalview.setCurrentAlignFrame((AlignFrame) itf);
+          }
           itf.requestFocus();
         }
       }
@@ -912,15 +899,7 @@ public class Desktop extends jalview.jbgui.GDesktop
           menuItem.removeActionListener(menuItem.getActionListeners()[0]);
         }
         windowMenu.remove(menuItem);
-        JInternalFrame itf = desktop.getSelectedFrame();
-        if (itf != null)
-        {
-          itf.requestFocus();
-          if (itf instanceof AlignFrame)
-          {
-            Jalview.setCurrentAlignFrame((AlignFrame) itf);
-          }
-        }
+
         System.gc();
       };
     });
@@ -2512,14 +2491,6 @@ public class Desktop extends jalview.jbgui.GDesktop
     }
   }
 
-  /**
-   * fixes stacking order after a modal dialog to ensure windows that should be
-   * on top actually are
-   */
-  public void relayerWindows()
-  {
-
-  }
 
   /**
    * Accessor method to quickly get all the AlignmentFrames loaded.
index b768339..3f1d9c7 100644 (file)
@@ -541,13 +541,8 @@ public class FeatureSettings extends JPanel
       public void itemStateChanged(ItemEvent evt)
       {
         fr.setGroupVisibility(check.getText(), check.isSelected());
-        af.alignPanel.getSeqPanel().seqCanvas.repaint();
-        if (af.alignPanel.overviewPanel != null)
-        {
-          af.alignPanel.overviewPanel.updateOverviewImage();
-        }
-
         resetTable(new String[] { grp });
+        af.alignPanel.paintAlignment(true, true);
       }
     });
     groupPanel.add(check);
index 80ac189..f3c8e8f 100755 (executable)
@@ -235,7 +235,7 @@ public class FontChooser extends GFontChooser
       ap.av.setScaleProteinAsCdna(oldProteinScale);
       ap.av.setProteinFontAsCdna(oldMirrorFont);
       ap.av.antiAlias = oldSmoothFont;
-      ap.paintAlignment(true, false);
+      ap.fontChanged();
 
       if (scaleAsCdna.isVisible() && scaleAsCdna.isEnabled())
       {
index 981e94c..35bd871 100644 (file)
@@ -34,7 +34,8 @@ public interface IProgressIndicator
    * is removed with a second call with same ID.
    * 
    * @param message
-   *          - displayed message for operation
+   *          - displayed message for operation. Please ensure message is
+   *          internationalised.
    * @param id
    *          - unique handle for this indicator
    */
index 35fd1b4..a4f79c2 100755 (executable)
@@ -154,7 +154,7 @@ public class IdPanel extends JPanel
       {
         av.getRanges().scrollRight(true);
       }
-      else if (!av.getWrapAlignment())
+      else
       {
         av.getRanges().scrollUp(false);
       }
@@ -165,7 +165,7 @@ public class IdPanel extends JPanel
       {
         av.getRanges().scrollRight(false);
       }
-      else if (!av.getWrapAlignment())
+      else
       {
         av.getRanges().scrollUp(true);
       }
index b357234..4a15024 100644 (file)
@@ -216,34 +216,6 @@ public class Jalview2XML
     }
   }
 
-  void clearSeqRefs()
-  {
-    if (_cleartables)
-    {
-      if (seqRefIds != null)
-      {
-        seqRefIds.clear();
-      }
-      if (seqsToIds != null)
-      {
-        seqsToIds.clear();
-      }
-      if (incompleteSeqs != null)
-      {
-        incompleteSeqs.clear();
-      }
-      // seqRefIds = null;
-      // seqsToIds = null;
-    }
-    else
-    {
-      // do nothing
-      warn("clearSeqRefs called when _cleartables was not set. Doing nothing.");
-      // seqRefIds = new Hashtable();
-      // seqsToIds = new IdentityHashMap();
-    }
-  }
-
   void initSeqRefs()
   {
     if (seqsToIds == null)
@@ -1084,7 +1056,7 @@ public class Jalview2XML
 
     // SAVE TREES
     // /////////////////////////////////
-    if (!storeDS && av.currentTree != null)
+    if (!storeDS && av.getCurrentTree() != null)
     {
       // FIND ANY ASSOCIATED TREES
       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
@@ -1102,7 +1074,7 @@ public class Jalview2XML
             {
               Tree tree = new Tree();
               tree.setTitle(tp.getTitle());
-              tree.setCurrentTree((av.currentTree == tp.getTree()));
+              tree.setCurrentTree((av.getCurrentTree() == tp.getTree()));
               tree.setNewick(tp.getTree().print());
               tree.setThreshold(tp.treeCanvas.threshold);
 
@@ -4250,7 +4222,8 @@ public class Jalview2XML
       StructureData filedat = oldFiles.get(id);
       String pdbFile = filedat.getFilePath();
       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
-      binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE);
+      binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
+              null);
       binding.addSequenceForStructFile(pdbFile, seq);
     }
     // and add the AlignmentPanel's reference to the view panel
@@ -5342,28 +5315,25 @@ public class Jalview2XML
 
   }
 
-  public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
-          boolean keepSeqRefs)
+  /**
+   * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
+   * view as XML (but not to file), and then reloading it
+   * 
+   * @param ap
+   * @return
+   */
+  public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
   {
     initSeqRefs();
     JalviewModel jm = saveState(ap, null, null, null);
 
-    if (!keepSeqRefs)
-    {
-      clearSeqRefs();
-      jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
-    }
-    else
-    {
-      uniqueSetSuffix = "";
-      jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
-      // overwrite the
-      // view we just
-      // copied
-    }
+    uniqueSetSuffix = "";
+    jm.getJalviewModelSequence().getViewport(0).setId(null);
+    // we don't overwrite the view we just copied
+
     if (this.frefedSequence == null)
     {
-      frefedSequence = new Vector();
+      frefedSequence = new Vector<SeqFref>();
     }
 
     viewportsAdded.clear();
@@ -5383,32 +5353,8 @@ public class Jalview2XML
     return af.alignPanel;
   }
 
-  /**
-   * flag indicating if hashtables should be cleared on finalization TODO this
-   * flag may not be necessary
-   */
-  private final boolean _cleartables = true;
-
   private Hashtable jvids2vobj;
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see java.lang.Object#finalize()
-   */
-  @Override
-  protected void finalize() throws Throwable
-  {
-    // really make sure we have no buried refs left.
-    if (_cleartables)
-    {
-      clearSeqRefs();
-    }
-    this.seqRefIds = null;
-    this.seqsToIds = null;
-    super.finalize();
-  }
-
   private void warn(String msg)
   {
     warn(msg, null);
index 51d7a84..9ddb751 100755 (executable)
@@ -40,8 +40,10 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseMotionAdapter;
 import java.beans.PropertyChangeEvent;
+import java.beans.PropertyVetoException;
 
 import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JInternalFrame;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.SwingUtilities;
@@ -350,8 +352,22 @@ public class OverviewPanel extends JPanel
   {
     try
     {
-      av.getRanges().removePropertyChangeListener(this);
+      if (av != null)
+      {
+        av.getRanges().removePropertyChangeListener(this);
+      }
+
       oviewCanvas.dispose();
+
+      /*
+       * close the parent frame (which also removes it from the
+       * Desktop Windows menu)
+       */
+      ((JInternalFrame) SwingUtilities.getAncestorOfClass(
+              JInternalFrame.class, (this))).setClosed(true);
+    } catch (PropertyVetoException e)
+    {
+      // ignore
     } finally
     {
       progressPanel = null;
index f861a7c..9f52d26 100644 (file)
@@ -79,6 +79,8 @@ public class PCAPanel extends GPCAPanel
 
   int top = 0;
 
+  private boolean working;
+
   /**
    * Creates a new PCAPanel object using default score model and parameters
    * 
@@ -234,6 +236,7 @@ public class PCAPanel extends GPCAPanel
       message = MessageManager.getString("label.pca_calculating");
     }
     progress.setProgressBar(message, progId);
+    working = true;
     try
     {
       calcSettings.setEnabled(false);
@@ -252,6 +255,7 @@ public class PCAPanel extends GPCAPanel
     } catch (OutOfMemoryError er)
     {
       new OOMWarning("calculating PCA", er);
+      working = false;
       return;
     } finally
     {
@@ -266,6 +270,7 @@ public class PCAPanel extends GPCAPanel
               .getString("label.principal_component_analysis"), 475, 450);
       this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
     }
+    working = false;
   }
 
   @Override
@@ -788,4 +793,14 @@ public class PCAPanel extends GPCAPanel
     top = t;
     zCombobox.setSelectedIndex(2);
   }
+
+  /**
+   * Answers true if PCA calculation is in progress, else false
+   * 
+   * @return
+   */
+  public boolean isWorking()
+  {
+    return working;
+  }
 }
index d731e70..ced5544 100755 (executable)
@@ -26,9 +26,9 @@ import jalview.datamodel.SequenceI;
 import java.awt.Component;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 /**
  * Route datamodel/view update events for a sequence set to any display
@@ -74,26 +74,21 @@ public class PaintRefresher
    */
   public static void RemoveComponent(Component comp)
   {
-    List<String> emptied = new ArrayList<String>();
-    for (Entry<String, List<Component>> registered : components.entrySet())
+    if (components == null)
     {
-      String id = registered.getKey();
-      List<Component> comps = components.get(id);
+      return;
+    }
+
+    Iterator<String> it = components.keySet().iterator();
+    while (it.hasNext())
+    {
+      List<Component> comps = components.get(it.next());
       comps.remove(comp);
       if (comps.isEmpty())
       {
-        emptied.add(id);
+        it.remove();
       }
     }
-
-    /*
-     * Remove now empty ids after the above (to avoid
-     * ConcurrentModificationException).
-     */
-    for (String id : emptied)
-    {
-      components.remove(id);
-    }
   }
 
   public static void Refresh(Component source, String id)
index f75407c..d081794 100755 (executable)
@@ -22,7 +22,8 @@ package jalview.gui;
 
 import jalview.analysis.AlignSeq;
 import jalview.datamodel.Alignment;
-import jalview.datamodel.Sequence;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.jbgui.GPairwiseAlignPanel;
 import jalview.util.MessageManager;
@@ -40,49 +41,56 @@ import java.util.Vector;
 public class PairwiseAlignPanel extends GPairwiseAlignPanel
 {
 
+  private static final String DASHES = "---------------------\n";
+
   AlignmentViewport av;
 
-  Vector sequences;
+  Vector<SequenceI> sequences;
 
   /**
    * Creates a new PairwiseAlignPanel object.
    * 
-   * @param av
+   * @param viewport
    *          DOCUMENT ME!
    */
-  public PairwiseAlignPanel(AlignmentViewport av)
+  public PairwiseAlignPanel(AlignmentViewport viewport)
   {
     super();
-    this.av = av;
+    this.av = viewport;
 
-    sequences = new Vector();
+    sequences = new Vector<SequenceI>();
 
-    SequenceI[] seqs;
-    String[] seqStrings = av.getViewAsString(true);
+    SequenceGroup selectionGroup = viewport.getSelectionGroup();
+    boolean isSelection = selectionGroup != null
+            && selectionGroup.getSize() > 0;
+    AlignmentView view = viewport.getAlignmentView(isSelection);
+    // String[] seqStrings = viewport.getViewAsString(true);
+    String[] seqStrings = view.getSequenceStrings(viewport
+            .getGapCharacter());
 
-    if (av.getSelectionGroup() == null)
+    SequenceI[] seqs;
+    if (isSelection)
     {
-      seqs = av.getAlignment().getSequencesArray();
+      seqs = (SequenceI[]) view.getAlignmentAndHiddenColumns(viewport
+              .getGapCharacter())[0];
     }
     else
     {
-      seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
+      seqs = av.getAlignment().getSequencesArray();
     }
 
-    String type = (av.getAlignment().isNucleotide()) ? AlignSeq.DNA
+    String type = (viewport.getAlignment().isNucleotide()) ? AlignSeq.DNA
             : AlignSeq.PEP;
 
     float[][] scores = new float[seqs.length][seqs.length];
-    double totscore = 0;
+    double totscore = 0D;
     int count = seqs.length;
-
-    Sequence seq;
+    boolean first = true;
 
     for (int i = 1; i < count; i++)
     {
       for (int j = 0; j < i; j++)
       {
-
         AlignSeq as = new AlignSeq(seqs[i], seqStrings[i], seqs[j],
                 seqStrings[j], type);
 
@@ -94,9 +102,15 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel
         as.calcScoreMatrix();
         as.traceAlignment();
 
+        if (!first)
+        {
+          System.out.println(DASHES);
+          textarea.append(DASHES);
+        }
+        first = false;
         as.printAlignment(System.out);
-        scores[i][j] = (float) as.getMaxScore()
-                / (float) as.getASeq1().length;
+        scores[i][j] = as.getMaxScore()
+                / as.getASeq1().length;
         totscore = totscore + scores[i][j];
 
         textarea.append(as.getOutput());
@@ -107,28 +121,53 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel
 
     if (count > 2)
     {
-      System.out.println(
-              "Pairwise alignment scaled similarity score matrix\n");
+      printScoreMatrix(seqs, scores, totscore);
+    }
+  }
 
-      for (int i = 0; i < count; i++)
-      {
-        jalview.util.Format.print(System.out, "%s \n",
-                ("" + i) + " " + seqs[i].getName());
-      }
+  /**
+   * Prints a matrix of seqi-seqj pairwise alignment scores to sysout
+   * 
+   * @param seqs
+   * @param scores
+   * @param totscore
+   */
+  protected void printScoreMatrix(SequenceI[] seqs, float[][] scores,
+          double totscore)
+  {
+    System.out
+            .println("Pairwise alignment scaled similarity score matrix\n");
 
-      System.out.println("\n");
+    for (int i = 0; i < seqs.length; i++)
+    {
+      System.out.println(String.format("%3d %s", i + 1,
+              seqs[i].getDisplayId(true)));
+    }
+
+    /*
+     * table heading columns for sequences 1, 2, 3...
+     */
+    System.out.print("\n ");
+    for (int i = 0; i < seqs.length; i++)
+    {
+      System.out.print(String.format("%7d", i + 1));
+    }
+    System.out.println();
 
-      for (int i = 0; i < count; i++)
+    for (int i = 0; i < seqs.length; i++)
+    {
+      System.out.print(String.format("%3d", i + 1));
+      for (int j = 0; j < i; j++)
       {
-        for (int j = 0; j < i; j++)
-        {
-          jalview.util.Format.print(System.out, "%7.3f",
-                  scores[i][j] / totscore);
-        }
+        /*
+         * as a fraction of tot score, outputs are 0 <= score <= 1
+         */
+        System.out.print(String.format("%7.3f", scores[i][j] / totscore));
       }
-
-      System.out.println("\n");
+      System.out.println();
     }
+
+    System.out.println("\n");
   }
 
   /**
@@ -137,13 +176,14 @@ public class PairwiseAlignPanel extends GPairwiseAlignPanel
    * @param e
    *          DOCUMENT ME!
    */
+  @Override
   protected void viewInEditorButton_actionPerformed(ActionEvent e)
   {
-    Sequence[] seq = new Sequence[sequences.size()];
+    SequenceI[] seq = new SequenceI[sequences.size()];
 
     for (int i = 0; i < sequences.size(); i++)
     {
-      seq[i] = (Sequence) sequences.elementAt(i);
+      seq[i] = sequences.elementAt(i);
     }
 
     AlignFrame af = new AlignFrame(new Alignment(seq),
index 40f5764..6da7d4f 100644 (file)
@@ -49,6 +49,7 @@ import jalview.schemes.PIDColourScheme;
 import jalview.util.GroupUrlLink;
 import jalview.util.GroupUrlLink.UrlStringTooLongException;
 import jalview.util.MessageManager;
+import jalview.util.StringUtils;
 import jalview.util.UrlLink;
 
 import java.awt.Color;
@@ -552,6 +553,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       String description = sf.getDescription();
       if (description != null)
       {
+        description = StringUtils.stripHtmlTags(description);
         if (description.length() <= 6)
         {
           desc = desc + " " + description;
@@ -585,8 +587,12 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
    */
   protected void showFeatureDetails(SequenceFeature sf)
   {
-    CutAndPasteTransfer cap = new CutAndPasteTransfer();
+    CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
+    // it appears Java's CSS does not support border-collaps :-(
+    cap.addStylesheetRule("table { border-collapse: collapse;}");
+    cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
     cap.setText(sf.getDetailsReport());
+
     Desktop.addInternalFrame(cap,
             MessageManager.getString("label.feature_details"), 500, 500);
   }
index ea341e3..011d810 100644 (file)
@@ -89,8 +89,8 @@ public class ProgressBar implements IProgressIndicator
     }
     this.statusPanel = container;
     this.statusBar = statusBar;
-    this.progressBars = new Hashtable<Long, JPanel>();
-    this.progressBarHandlers = new Hashtable<Long, IProgressIndicatorHandler>();
+    this.progressBars = new Hashtable<>();
+    this.progressBarHandlers = new Hashtable<>();
 
   }
 
@@ -119,46 +119,52 @@ public class ProgressBar implements IProgressIndicator
    * execution.
    */
   @Override
-  public void setProgressBar(String message, long id)
+  public void setProgressBar(final String message, final long id)
   {
-    Long longId = Long.valueOf(id);
-
-    JPanel progressPanel = progressBars.get(longId);
-    if (progressPanel != null)
+    SwingUtilities.invokeLater(new Runnable()
     {
-      /*
-       * Progress bar is displayed for this id - remove it now, and any handler
-       */
-      progressBars.remove(id);
-      if (message != null && statusBar != null)
-      {
-        statusBar.setText(message);
-      }
-      if (progressBarHandlers.containsKey(longId))
+      @Override
+      public void run()
       {
-        progressBarHandlers.remove(longId);
-      }
-      removeRow(progressPanel);
-    }
-    else
-    {
-      /*
-       * No progress bar for this id - add one now
-       */
-      progressPanel = new JPanel(new BorderLayout(10, 5));
+        JPanel progressPanel = progressBars.get(id);
+        if (progressPanel != null)
+        {
+          /*
+           * Progress bar is displayed for this id - remove it now, and any handler
+           */
+          progressBars.remove(id);
+          if (message != null && statusBar != null)
+          {
+            statusBar.setText(message);
+          }
+          if (progressBarHandlers.containsKey(id))
+          {
+            progressBarHandlers.remove(id);
+          }
+          removeRow(progressPanel);
+        }
+        else
+        {
+          /*
+           * No progress bar for this id - add one now
+           */
+          progressPanel = new JPanel(new BorderLayout(10, 5));
 
-      JProgressBar progressBar = new JProgressBar();
-      progressBar.setIndeterminate(true);
+          JProgressBar progressBar = new JProgressBar();
+          progressBar.setIndeterminate(true);
 
-      progressPanel.add(new JLabel(message), BorderLayout.WEST);
-      progressPanel.add(progressBar, BorderLayout.CENTER);
+          progressPanel.add(new JLabel(message), BorderLayout.WEST);
+          progressPanel.add(progressBar, BorderLayout.CENTER);
 
-      addRow(progressPanel);
+          addRow(progressPanel);
 
-      progressBars.put(longId, progressPanel);
-    }
+          progressBars.put(id, progressPanel);
+        }
+
+        refreshLayout();
+      }
+    });
 
-    refreshLayout();
   }
 
   /**
@@ -215,41 +221,50 @@ public class ProgressBar implements IProgressIndicator
   public void registerHandler(final long id,
           final IProgressIndicatorHandler handler)
   {
-    Long longId = Long.valueOf(id);
-    final JPanel progressPanel = progressBars.get(longId);
-    if (progressPanel == null)
-    {
-      System.err.println(
-              "call setProgressBar before registering the progress bar's handler.");
-      return;
-    }
-
-    /*
-     * Nothing useful to do if not a Cancel handler
-     */
-    if (!handler.canCancel())
-    {
-      return;
-    }
-
-    progressBarHandlers.put(longId, handler);
-    JButton cancel = new JButton(MessageManager.getString("action.cancel"));
     final IProgressIndicator us = this;
-    cancel.addActionListener(new ActionListener()
-    {
 
+    SwingUtilities.invokeLater(new Runnable()
+    {
       @Override
-      public void actionPerformed(ActionEvent e)
+      public void run()
       {
-        handler.cancelActivity(id);
-        us.setProgressBar(MessageManager
-                .formatMessage("label.cancelled_params", new Object[]
-                { ((JLabel) progressPanel.getComponent(0)).getText() }),
-                id);
+        final JPanel progressPanel = progressBars.get(id);
+        if (progressPanel == null)
+        {
+          System.err.println(
+                  "call setProgressBar before registering the progress bar's handler.");
+          return;
+        }
+
+        /*
+         * Nothing useful to do if not a Cancel handler
+         */
+        if (!handler.canCancel())
+        {
+          return;
+        }
+
+        progressBarHandlers.put(id, handler);
+        JButton cancel = new JButton(
+                MessageManager.getString("action.cancel"));
+        cancel.addActionListener(new ActionListener()
+        {
+
+          @Override
+          public void actionPerformed(ActionEvent e)
+          {
+            handler.cancelActivity(id);
+            us.setProgressBar(MessageManager
+                    .formatMessage("label.cancelled_params", new Object[]
+                    { ((JLabel) progressPanel.getComponent(0)).getText() }),
+                    id);
+          }
+        });
+        progressPanel.add(cancel, BorderLayout.EAST);
+        refreshLayout();
+
       }
     });
-    progressPanel.add(cancel, BorderLayout.EAST);
-    refreshLayout();
   }
 
 }
index 6261015..cb59452 100644 (file)
@@ -24,8 +24,6 @@ import jalview.bin.Cache;
 
 import java.awt.Component;
 
-import javax.swing.JOptionPane;
-
 public class PromptUserConfig implements Runnable
 {
   /**
@@ -120,6 +118,7 @@ public class PromptUserConfig implements Runnable
     this.allowCancel = allowCancel;
   }
 
+  @Override
   public void run()
   {
     if (property == null)
@@ -206,12 +205,7 @@ public class PromptUserConfig implements Runnable
               (allowCancel) ? JvOptionPane.YES_NO_CANCEL_OPTION
                       : JvOptionPane.YES_NO_OPTION,
               JvOptionPane.QUESTION_MESSAGE);
-      // now, ask the desktop to relayer any external windows that might have
-      // been obsured
-      if (Desktop.instance != null)
-      {
-        Desktop.instance.relayerWindows();
-      }
+
       // and finish parsing the result
       jalview.bin.Cache.log.debug("Got response : " + reply);
       if (reply == JvOptionPane.YES_OPTION)
index 282c810..6148a2e 100644 (file)
@@ -718,10 +718,12 @@ public class SeqPanel extends JPanel
   }
 
   /**
-   * DOCUMENT ME!
+   * Action on mouse movement is to update the status bar to show the current
+   * sequence position, and (if features are shown) to show any features at the
+   * position in a tooltip. Does nothing if the mouse move does not change
+   * residue position.
    * 
    * @param evt
-   *          DOCUMENT ME!
    */
   @Override
   public void mouseMoved(MouseEvent evt)
@@ -734,7 +736,8 @@ public class SeqPanel extends JPanel
     }
 
     final int column = findColumn(evt);
-    int seq = findSeq(evt);
+    final int seq = findSeq(evt);
+
     if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
     {
       lastMouseSeq = -1;
@@ -1626,7 +1629,7 @@ public class SeqPanel extends JPanel
         av.getRanges().scrollRight(true);
 
       }
-      else if (!av.getWrapAlignment())
+      else
       {
         av.getRanges().scrollUp(false);
       }
@@ -1637,12 +1640,18 @@ public class SeqPanel extends JPanel
       {
         av.getRanges().scrollRight(false);
       }
-      else if (!av.getWrapAlignment())
+      else
       {
         av.getRanges().scrollUp(true);
       }
     }
-    // TODO Update tooltip for new position.
+
+    /*
+     * update status bar and tooltip for new position
+     * (need to synthesize a mouse movement to refresh tooltip)
+     */
+    mouseMoved(e);
+    ToolTipManager.sharedInstance().mouseMoved(e);
   }
 
   /**
index da10e3f..20f4a49 100644 (file)
@@ -157,8 +157,8 @@ public class StructureChooser extends GStructureChooser
     Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
             .getStructureSummaryFields();
 
-    discoveredStructuresSet = new LinkedHashSet<FTSData>();
-    HashSet<String> errors = new HashSet<String>();
+    discoveredStructuresSet = new LinkedHashSet<>();
+    HashSet<String> errors = new HashSet<>();
     for (SequenceI seq : selectedSequences)
     {
       FTSRestRequest pdbRequest = new FTSRestRequest();
@@ -223,7 +223,7 @@ public class StructureChooser extends GStructureChooser
 
   public void loadLocalCachedPDBEntries()
   {
-    ArrayList<CachedPDB> entries = new ArrayList<CachedPDB>();
+    ArrayList<CachedPDB> entries = new ArrayList<>();
     for (SequenceI seq : selectedSequences)
     {
       if (seq.getDatasetSequence() != null
@@ -257,7 +257,7 @@ public class StructureChooser extends GStructureChooser
     boolean isPDBRefsFound = false;
     boolean isUniProtRefsFound = false;
     StringBuilder queryBuilder = new StringBuilder();
-    Set<String> seqRefs = new LinkedHashSet<String>();
+    Set<String> seqRefs = new LinkedHashSet<>();
 
     if (seq.getAllPDBEntries() != null
             && queryBuilder.length() < MAX_QLENGTH)
@@ -401,8 +401,8 @@ public class StructureChooser extends GStructureChooser
         lbl_loading.setVisible(true);
         Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
                 .getStructureSummaryFields();
-        Collection<FTSData> filteredResponse = new HashSet<FTSData>();
-        HashSet<String> errors = new HashSet<String>();
+        Collection<FTSData> filteredResponse = new HashSet<>();
+        HashSet<String> errors = new HashSet<>();
 
         for (SequenceI seq : selectedSequences)
         {
@@ -453,7 +453,7 @@ public class StructureChooser extends GStructureChooser
         if (!filteredResponse.isEmpty())
         {
           final int filterResponseCount = filteredResponse.size();
-          Collection<FTSData> reorderedStructuresSet = new LinkedHashSet<FTSData>();
+          Collection<FTSData> reorderedStructuresSet = new LinkedHashSet<>();
           reorderedStructuresSet.addAll(filteredResponse);
           reorderedStructuresSet.addAll(discoveredStructuresSet);
           getResultTable().setModel(FTSRestResponse
@@ -725,11 +725,10 @@ public class StructureChooser extends GStructureChooser
   @Override
   public void ok_ActionPerformed()
   {
-    final long progressSessionId = System.currentTimeMillis();
     final StructureSelectionManager ssm = ap.getStructureSelectionManager();
+
     final int preferredHeight = pnl_filter.getHeight();
-    ssm.setProgressIndicator(this);
-    ssm.setProgressSessionId(progressSessionId);
+
     new Thread(new Runnable()
     {
       @Override
@@ -747,7 +746,7 @@ public class StructureChooser extends GStructureChooser
           int[] selectedRows = getResultTable().getSelectedRows();
           PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
           int count = 0;
-          List<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
+          List<SequenceI> selectedSeqsToView = new ArrayList<>();
           for (int row : selectedRows)
           {
             String pdbIdStr = getResultTable()
@@ -761,6 +760,7 @@ public class StructureChooser extends GStructureChooser
               pdbEntry = getFindEntry(pdbIdStr,
                       selectedSeq.getAllPDBEntries());
             }
+
             if (pdbEntry == null)
             {
               pdbEntry = new PDBEntry();
@@ -783,7 +783,7 @@ public class StructureChooser extends GStructureChooser
                   .getModelIndex();
           int refSeqColIndex = tbl_local_pdb.getColumn("Ref Sequence")
                   .getModelIndex();
-          List<SequenceI> selectedSeqsToView = new ArrayList<SequenceI>();
+          List<SequenceI> selectedSeqsToView = new ArrayList<>();
           for (int row : selectedRows)
           {
             PDBEntry pdbEntry = (PDBEntry) tbl_local_pdb.getValueAt(row,
@@ -805,7 +805,6 @@ public class StructureChooser extends GStructureChooser
           {
             selectedSequence = userSelectedSeq;
           }
-
           String pdbIdStr = txt_search.getText();
           PDBEntry pdbEntry = selectedSequence.getPDBEntry(pdbIdStr);
           if (pdbEntry == null)
@@ -847,6 +846,7 @@ public class StructureChooser extends GStructureChooser
                   { selectedSequence });
         }
         closeAction(preferredHeight);
+        mainFrame.dispose();
       }
     }).start();
   }
@@ -870,13 +870,15 @@ public class StructureChooser extends GStructureChooser
           final PDBEntry[] pdbEntriesToView,
           final AlignmentPanel alignPanel, SequenceI[] sequences)
   {
-    ssm.setProgressBar(MessageManager
-            .getString("status.launching_3d_structure_viewer"));
+    long progressId = sequences.hashCode();
+    setProgressBar(MessageManager
+            .getString("status.launching_3d_structure_viewer"), progressId);
     final StructureViewer sViewer = new StructureViewer(ssm);
+    setProgressBar(null, progressId);
 
     if (SiftsSettings.isMapWithSifts())
     {
-      List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<SequenceI>();
+      List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
       int p = 0;
       // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
       // real PDB ID. For moment, we can also safely do this if there is already
@@ -907,41 +909,43 @@ public class StructureChooser extends GStructureChooser
       if (!seqsWithoutSourceDBRef.isEmpty())
       {
         int y = seqsWithoutSourceDBRef.size();
-        ssm.setProgressBar(null);
-        ssm.setProgressBar(MessageManager.formatMessage(
+        setProgressBar(MessageManager.formatMessage(
                 "status.fetching_dbrefs_for_sequences_without_valid_refs",
-                y));
+                y), progressId);
         SequenceI[] seqWithoutSrcDBRef = new SequenceI[y];
         int x = 0;
         for (SequenceI fSeq : seqsWithoutSourceDBRef)
         {
           seqWithoutSrcDBRef[x++] = fSeq;
         }
+
         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
         dbRefFetcher.fetchDBRefs(true);
+
+        setProgressBar("Fetch complete.", progressId); // todo i18n
       }
     }
     if (pdbEntriesToView.length > 1)
     {
-      ArrayList<SequenceI[]> seqsMap = new ArrayList<SequenceI[]>();
+      ArrayList<SequenceI[]> seqsMap = new ArrayList<>();
       for (SequenceI seq : sequences)
       {
         seqsMap.add(new SequenceI[] { seq });
       }
       SequenceI[][] collatedSeqs = seqsMap.toArray(new SequenceI[0][0]);
-      ssm.setProgressBar(null);
-      ssm.setProgressBar(MessageManager.getString(
-              "status.fetching_3d_structures_for_selected_entries"));
+
+      setProgressBar(MessageManager
+                    .getString("status.fetching_3d_structures_for_selected_entries"), progressId);
       sViewer.viewStructures(pdbEntriesToView, collatedSeqs, alignPanel);
     }
     else
     {
-      ssm.setProgressBar(null);
-      ssm.setProgressBar(MessageManager.formatMessage(
+      setProgressBar(MessageManager.formatMessage(
               "status.fetching_3d_structures_for",
-              pdbEntriesToView[0].getId()));
+              pdbEntriesToView[0].getId()),progressId);
       sViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
     }
+    setProgressBar(null, progressId);
   }
 
   /**
@@ -1000,7 +1004,7 @@ public class StructureChooser extends GStructureChooser
           String searchTerm = txt_search.getText().toLowerCase();
           searchTerm = searchTerm.split(":")[0];
           // System.out.println(">>>>> search term : " + searchTerm);
-          List<FTSDataColumnI> wantedFields = new ArrayList<FTSDataColumnI>();
+          List<FTSDataColumnI> wantedFields = new ArrayList<>();
           FTSRestRequest pdbRequest = new FTSRestRequest();
           pdbRequest.setAllowEmptySeq(false);
           pdbRequest.setResponseSize(1);
@@ -1062,7 +1066,7 @@ public class StructureChooser extends GStructureChooser
 
     public PDBEntryTableModel(List<CachedPDB> pdbEntries)
     {
-      this.pdbEntries = new ArrayList<CachedPDB>(pdbEntries);
+      this.pdbEntries = new ArrayList<>(pdbEntries);
     }
 
     @Override
index c8854a7..31c20ed 100644 (file)
@@ -310,6 +310,8 @@ public abstract class StructureViewerBase extends GStructureViewer
 
   public abstract ViewerType getViewerType();
 
+  protected abstract IProgressIndicator getIProgressIndicator();
+
   /**
    * add a new structure (with associated sequences and chains) to this viewer,
    * retrieving it if necessary first.
@@ -460,7 +462,7 @@ public abstract class StructureViewerBase extends GStructureViewer
      * create the mappings
      */
     apanel.getStructureSelectionManager().setMapping(seq, chains,
-            pdbFilename, DataSourceType.FILE);
+            pdbFilename, DataSourceType.FILE, getIProgressIndicator());
 
     /*
      * alert the FeatureRenderer to show new (PDB RESNUM) features
index 9403057..2727db1 100755 (executable)
@@ -55,6 +55,7 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.image.BufferedImage;
 import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.io.FileOutputStream;
 import java.util.ArrayList;
 import java.util.List;
@@ -63,6 +64,8 @@ import javax.imageio.ImageIO;
 import javax.swing.ButtonGroup;
 import javax.swing.JMenuItem;
 import javax.swing.JRadioButtonMenuItem;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
 
 import org.jibble.epsgraphics.EpsGraphics2D;
 
@@ -141,7 +144,35 @@ public class TreePanel extends GTreePanel
 
     buildAssociatedViewMenu();
 
-    av.addPropertyChangeListener(new java.beans.PropertyChangeListener()
+    final PropertyChangeListener listener = addAlignmentListener();
+
+    /*
+     * remove listener when window is closed, so that this
+     * panel can be garbage collected
+     */
+    addInternalFrameListener(new InternalFrameAdapter()
+    {
+      @Override
+      public void internalFrameClosed(InternalFrameEvent evt)
+      {
+        if (av != null)
+        {
+          av.removePropertyChangeListener(listener);
+        }
+      }
+    });
+
+    TreeLoader tl = new TreeLoader(newTree, inputData);
+    tl.start();
+
+  }
+
+  /**
+   * @return
+   */
+  protected PropertyChangeListener addAlignmentListener()
+  {
+    final PropertyChangeListener listener = new PropertyChangeListener()
     {
       @Override
       public void propertyChange(PropertyChangeEvent evt)
@@ -168,11 +199,9 @@ public class TreePanel extends GTreePanel
           repaint();
         }
       }
-    });
-
-    TreeLoader tl = new TreeLoader(newTree, inputData);
-    tl.start();
-
+    };
+    av.addPropertyChangeListener(listener);
+    return listener;
   }
 
   @Override
index cdbb4fa..2a7743a 100644 (file)
@@ -60,15 +60,6 @@ public class ViewSelectionMenu extends JMenu
 
   private ItemListener _handler;
 
-  @Override
-  protected void finalize() throws Throwable
-  {
-    _selectedviews = null;
-    _handler = null;
-    _allviews = null;
-    super.finalize();
-  }
-
   /**
    * create a new view selection menu. This menu has some standard entries
    * (select all, invert selection), and a checkbox for every view. Mousing over
index 26641b1..f26d6da 100755 (executable)
@@ -606,18 +606,4 @@ public class FileLoader implements Runnable
     return tempStructFile.toString();
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see java.lang.Object#finalize()
-   */
-  @Override
-  protected void finalize() throws Throwable
-  {
-    source = null;
-    alignFrame = null;
-    viewport = null;
-    super.finalize();
-  }
-
 }
index d269e97..65ba74a 100644 (file)
@@ -47,11 +47,4 @@ public class InputStreamParser extends FileParse
     error = false;
   }
 
-  @Override
-  protected void finalize() throws Throwable
-  {
-    dataIn = null;
-    super.finalize();
-  }
-
 }
index 13f41d4..6d819d3 100644 (file)
@@ -26,6 +26,7 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.io.gff.GffConstants;
 import jalview.util.MessageManager;
+import jalview.util.StringUtils;
 import jalview.util.UrlLink;
 
 import java.util.Arrays;
@@ -183,50 +184,11 @@ public class SequenceAnnotationReport
           sb.append(" ").append(feature.end);
         }
 
-        if (feature.getDescription() != null
-                && !feature.description.equals(feature.getType()))
+        String description = feature.getDescription();
+        if (description != null && !description.equals(feature.getType()))
         {
-          String tmpString = feature.getDescription();
-          String tmp2up = tmpString.toUpperCase();
-          int startTag = tmp2up.indexOf("<HTML>");
-          if (startTag > -1)
-          {
-            tmpString = tmpString.substring(startTag + 6);
-            tmp2up = tmp2up.substring(startTag + 6);
-          }
-          int endTag = tmp2up.indexOf("</BODY>");
-          if (endTag > -1)
-          {
-            tmpString = tmpString.substring(0, endTag);
-            tmp2up = tmp2up.substring(0, endTag);
-          }
-          endTag = tmp2up.indexOf("</HTML>");
-          if (endTag > -1)
-          {
-            tmpString = tmpString.substring(0, endTag);
-          }
-
-          if (startTag > -1)
-          {
-            sb.append("; ").append(tmpString);
-          }
-          else
-          {
-            if (tmpString.indexOf("<") > -1 || tmpString.indexOf(">") > -1)
-            {
-              // The description does not specify html is to
-              // be used, so we must remove < > symbols
-              tmpString = tmpString.replaceAll("<", "&lt;");
-              tmpString = tmpString.replaceAll(">", "&gt;");
-
-              sb.append("; ");
-              sb.append(tmpString);
-            }
-            else
-            {
-              sb.append("; ").append(tmpString);
-            }
-          }
+          description = StringUtils.stripHtmlTags(description);
+          sb.append("; ").append(description);
         }
         // check score should be shown
         if (!Float.isNaN(feature.getScore()))
index e381b26..5adc55c 100644 (file)
@@ -6,6 +6,7 @@ import htsjdk.variant.variantcontext.VariantContext;
 import htsjdk.variant.vcf.VCFHeader;
 import htsjdk.variant.vcf.VCFHeaderLine;
 import htsjdk.variant.vcf.VCFHeaderLineCount;
+import htsjdk.variant.vcf.VCFHeaderLineType;
 import htsjdk.variant.vcf.VCFInfoHeaderLine;
 
 import jalview.analysis.AlignmentUtils;
@@ -17,6 +18,9 @@ import jalview.datamodel.GeneLociI;
 import jalview.datamodel.Mapping;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.FeatureAttributeType;
+import jalview.datamodel.features.FeatureSource;
+import jalview.datamodel.features.FeatureSources;
 import jalview.ext.ensembl.EnsemblMap;
 import jalview.ext.htsjdk.VCFReader;
 import jalview.io.gff.Gff3Helper;
@@ -116,6 +120,12 @@ public class VCFLoader
   private int csqAlleleNumberFieldIndex = -1;
   private int csqFeatureFieldIndex = -1;
 
+  /*
+   * a unique identifier under which to save metadata about feature
+   * attributes (selected INFO field data)
+   */
+  private String sourceId;
+
   /**
    * Constructor given an alignment context
    * 
@@ -175,18 +185,19 @@ public class VCFLoader
       reader = new VCFReader(filePath);
 
       header = reader.getFileHeader();
-      VCFHeaderLine ref = header
-              .getOtherHeaderLine(VCFHeader.REFERENCE_KEY);
+
+      sourceId = filePath;
+
+      saveMetadata(sourceId);
 
       /*
        * get offset of CSQ ALLELE_NUM and Feature if declared
        */
       locateCsqFields();
 
-      // check if reference is wrt assembly19 (GRCh37)
-      // todo may need to allow user to specify reference assembly?
-      boolean isRefGrch37 = (ref != null && ref.getValue().contains(
-              "assembly19"));
+      VCFHeaderLine ref = header
+              .getOtherHeaderLine(VCFHeader.REFERENCE_KEY);
+      String vcfAssembly = ref.getValue();
 
       int varCount = 0;
       int seqCount = 0;
@@ -196,7 +207,7 @@ public class VCFLoader
        */
       for (SequenceI seq : al.getSequences())
       {
-        int added = loadSequenceVCF(seq, reader, isRefGrch37);
+        int added = loadSequenceVCF(seq, reader, vcfAssembly);
         if (added > 0)
         {
           seqCount++;
@@ -239,6 +250,47 @@ public class VCFLoader
   }
 
   /**
+   * Reads metadata (such as INFO field descriptions and datatypes) and saves
+   * them for future reference
+   * 
+   * @param sourceId
+   */
+  void saveMetadata(String sourceId)
+  {
+    FeatureSource metadata = new FeatureSource(sourceId);
+
+    for (VCFInfoHeaderLine info : header.getInfoHeaderLines())
+    {
+      String attributeId = info.getID();
+      String desc = info.getDescription();
+      VCFHeaderLineType type = info.getType();
+      FeatureAttributeType attType = null;
+      switch (type)
+      {
+      case Character:
+        attType = FeatureAttributeType.Character;
+        break;
+      case Flag:
+        attType = FeatureAttributeType.Flag;
+        break;
+      case Float:
+        attType = FeatureAttributeType.Float;
+        break;
+      case Integer:
+        attType = FeatureAttributeType.Integer;
+        break;
+      case String:
+        attType = FeatureAttributeType.String;
+        break;
+      }
+      metadata.setAttributeName(attributeId, desc);
+      metadata.setAttributeType(attributeId, attType);
+    }
+
+    FeatureSources.getInstance().addSource(sourceId, metadata);
+  }
+
+  /**
    * Records the position of selected fields defined in the CSQ INFO header (if
    * there is one). CSQ fields are declared in the CSQ INFO Description e.g.
    * <p>
@@ -336,16 +388,17 @@ public class VCFLoader
   /**
    * Tries to add overlapping variants read from a VCF file to the given
    * sequence, and returns the number of variant features added. Note that this
-   * requires the sequence to hold information as to its chromosomal positions
-   * and reference, in order to be able to map the VCF variants to the sequence.
+   * requires the sequence to hold information as to its species, chromosomal
+   * positions and reference assembly, in order to be able to map the VCF
+   * variants to the sequence (or not)
    * 
    * @param seq
    * @param reader
-   * @param isVcfRefGrch37
+   * @param vcfAssembly
    * @return
    */
   protected int loadSequenceVCF(SequenceI seq, VCFReader reader,
-          boolean isVcfRefGrch37)
+          String vcfAssembly)
   {
     int count = 0;
     GeneLociI seqCoords = seq.getGeneLoci();
@@ -357,16 +410,55 @@ public class VCFLoader
       return 0;
     }
 
+    if (!vcfSpeciesMatchesSequence(vcfAssembly, seqCoords.getSpeciesId()))
+    {
+      return 0;
+    }
+
     List<int[]> seqChromosomalContigs = seqCoords.getMap().getToRanges();
     for (int[] range : seqChromosomalContigs)
     {
-      count += addVcfVariants(seq, reader, range, isVcfRefGrch37);
+      count += addVcfVariants(seq, reader, range, vcfAssembly);
     }
 
     return count;
   }
 
   /**
+   * Answers true if the species inferred from the VCF reference identifier
+   * matches that for the sequence
+   * 
+   * @param vcfAssembly
+   * @param speciesId
+   * @return
+   */
+  boolean vcfSpeciesMatchesSequence(String vcfAssembly, String speciesId)
+  {
+    // PROBLEM 1
+    // there are many aliases for species - how to equate one with another?
+    // PROBLEM 2
+    // VCF ##reference header is an unstructured URI - how to extract species?
+    // perhaps check if ref includes any (Ensembl) alias of speciesId??
+    // TODO ask the user to confirm this??
+
+    if (vcfAssembly.contains("Homo_sapiens") // gnomAD exome data example
+            && "HOMO_SAPIENS".equals(speciesId)) // Ensembl species id
+    {
+      return true;
+    }
+
+    if (vcfAssembly.contains("c_elegans") // VEP VCF response example
+            && "CAENORHABDITIS_ELEGANS".equals(speciesId)) // Ensembl
+    {
+      return true;
+    }
+
+    // this is not a sustainable solution...
+
+    return false;
+  }
+
+  /**
    * Queries the VCF reader for any variants that overlap the given chromosome
    * region of the sequence, and adds as variant features. Returns the number of
    * overlapping variants found.
@@ -376,12 +468,12 @@ public class VCFLoader
    * @param range
    *          start-end range of a sequence region in its chromosomal
    *          coordinates
-   * @param isVcfRefGrch37
-   *          true if the VCF is with reference to GRCh37
+   * @param vcfAssembly
+   *          the '##reference' identifier for the VCF reference assembly
    * @return
    */
   protected int addVcfVariants(SequenceI seq, VCFReader reader,
-          int[] range, boolean isVcfRefGrch37)
+          int[] range, String vcfAssembly)
   {
     GeneLociI seqCoords = seq.getGeneLoci();
 
@@ -390,22 +482,24 @@ public class VCFLoader
     String species = seqCoords.getSpeciesId();
 
     /*
-     * map chromosomal coordinates from GRCh38 (sequence) to
-     * GRCh37 (VCF) if necessary
+     * map chromosomal coordinates from sequence to VCF if the VCF
+     * data has a different reference assembly to the sequence
      */
-    // TODO generalise for other assemblies and species
+    // TODO generalise for non-human species
+    // - or get the user to choose in a dialog
+
     int offset = 0;
-    String fromRef = "GRCh38";
-    if (fromRef.equalsIgnoreCase(seqRef) && isVcfRefGrch37)
+    if ("GRCh38".equalsIgnoreCase(seqRef) // Ensembl
+            && vcfAssembly.contains("Homo_sapiens_assembly19")) // gnomAD
     {
       String toRef = "GRCh37";
       int[] newRange = mapReferenceRange(range, chromosome, "human",
-              fromRef, toRef);
+              seqRef, toRef);
       if (newRange == null)
       {
         System.err.println(String.format(
                 "Failed to map %s:%s:%s:%d:%d to %s", species, chromosome,
-                fromRef, range[0], range[1], toRef));
+                seqRef, range[0], range[1], toRef));
         return 0;
       }
       offset = newRange[0] - range[0];
@@ -575,6 +669,7 @@ public class VCFLoader
 
     SequenceFeature sf = new SequenceFeature(type, alleles, featureStart,
             featureEnd, score, FEATURE_GROUP_VCF);
+    sf.setSource(sourceId);
 
     sf.setValue(Gff3Helper.ALLELES, alleles);
 
@@ -856,8 +951,7 @@ public class VCFLoader
      */
     EnsemblMap mapper = new EnsemblMap();
     int[] mapping = mapper.getAssemblyMapping(species, chromosome, fromRef,
-            toRef,
-            queryRange);
+            toRef, queryRange);
 
     if (mapping == null)
     {
index 083cd26..29f3fa9 100644 (file)
@@ -39,19 +39,6 @@ public class JSFunctionExec implements Runnable
     jvlite.setExecutor(this);
   }
 
-  @Override
-  protected void finalize() throws Throwable
-  {
-    jvlite = null;
-    executor = null;
-    if (jsExecQueue != null)
-    {
-      jsExecQueue.clear();
-    }
-    jsExecQueue = null;
-    super.finalize();
-  }
-
   private Vector jsExecQueue;
 
   private Thread executor = null;
index 874bfd3..6071933 100644 (file)
@@ -299,13 +299,6 @@ public class MouseOverStructureListener extends JSFunctionExec
   }
 
   @Override
-  public void finalize() throws Throwable
-  {
-    jvlite = null;
-    super.finalize();
-  }
-
-  @Override
   public void releaseReferences(Object svl)
   {
 
index abc0b3d..a6e0ace 100644 (file)
@@ -39,6 +39,8 @@ import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
+import javax.swing.text.EditorKit;
+import javax.swing.text.html.HTMLEditorKit;
 
 /**
  * DOCUMENT ME!
@@ -85,6 +87,7 @@ public class GCutAndPasteHtmlTransfer extends JInternalFrame
   {
     try
     {
+      textarea.setEditorKit(new HTMLEditorKit());
       setJMenuBar(editMenubar);
       jbInit();
     } catch (Exception e)
@@ -272,4 +275,20 @@ public class GCutAndPasteHtmlTransfer extends JInternalFrame
   {
 
   }
+
+  /**
+   * Adds the given stylesheet rule to the Html editor. However note that CSS
+   * support is limited.
+   * 
+   * @param rule
+   * @see javax.swing.text.html.CSS
+   */
+  public void addStylesheetRule(String rule)
+  {
+    EditorKit editorKit = textarea.getEditorKit();
+    if (editorKit != null)
+    {
+      ((HTMLEditorKit) editorKit).getStyleSheet().addRule(rule);
+    }
+  }
 }
index b973f45..35e2536 100644 (file)
@@ -66,7 +66,7 @@ public class StructureSelectionManager
 
   static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
 
-  private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
+  private List<StructureMapping> mappings = new ArrayList<>();
 
   private boolean processSecondaryStructure = false;
 
@@ -74,20 +74,16 @@ public class StructureSelectionManager
 
   private boolean addTempFacAnnot = false;
 
-  private IProgressIndicator progressIndicator;
-
   private SiftsClient siftsClient = null;
 
-  private long progressSessionId;
-
   /*
    * Set of any registered mappings between (dataset) sequences.
    */
-  private List<AlignedCodonFrame> seqmappings = new ArrayList<AlignedCodonFrame>();
+  private List<AlignedCodonFrame> seqmappings = new ArrayList<>();
 
-  private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
+  private List<CommandListener> commandListeners = new ArrayList<>();
 
-  private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
+  private List<SelectionListener> sel_listeners = new ArrayList<>();
 
   /**
    * @return true if will try to use external services for processing secondary
@@ -175,9 +171,9 @@ public class StructureSelectionManager
    * map between the PDB IDs (or structure identifiers) used by Jalview and the
    * absolute filenames for PDB data that corresponds to it
    */
-  Map<String, String> pdbIdFileName = new HashMap<String, String>();
+  Map<String, String> pdbIdFileName = new HashMap<>();
 
-  Map<String, String> pdbFileNameId = new HashMap<String, String>();
+  Map<String, String> pdbFileNameId = new HashMap<>();
 
   public void registerPDBFile(String idForFile, String absoluteFile)
   {
@@ -228,7 +224,7 @@ public class StructureSelectionManager
     }
     if (instances == null)
     {
-      instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager>();
+      instances = new java.util.IdentityHashMap<>();
     }
     StructureSelectionManager instance = instances.get(context);
     if (instance == null)
@@ -324,9 +320,11 @@ public class StructureSelectionManager
    * @return null or the structure data parsed as a pdb file
    */
   synchronized public StructureFile setMapping(SequenceI[] sequence,
-          String[] targetChains, String pdbFile, DataSourceType protocol)
+          String[] targetChains, String pdbFile, DataSourceType protocol, 
+          IProgressIndicator progress)
   {
-    return setMapping(true, sequence, targetChains, pdbFile, protocol);
+    return computeMapping(true, sequence, targetChains, pdbFile, protocol,
+            progress);
   }
 
   /**
@@ -353,6 +351,16 @@ public class StructureSelectionManager
           SequenceI[] sequenceArray, String[] targetChainIds,
           String pdbFile, DataSourceType sourceType)
   {
+    return computeMapping(forStructureView, sequenceArray, targetChainIds,
+            pdbFile, sourceType, null);
+  }
+
+  synchronized public StructureFile computeMapping(
+          boolean forStructureView, SequenceI[] sequenceArray,
+          String[] targetChainIds, String pdbFile, DataSourceType sourceType,
+          IProgressIndicator progress)
+  {
+    long progressSessionId = System.currentTimeMillis() * 3;
     /*
      * There will be better ways of doing this in the future, for now we'll use
      * the tried and tested MCview pdb mapping
@@ -500,12 +508,14 @@ public class StructureSelectionManager
         pdbFile = "INLINE" + pdb.getId();
       }
 
-      List<StructureMapping> seqToStrucMapping = new ArrayList<StructureMapping>();
+      List<StructureMapping> seqToStrucMapping = new ArrayList<>();
       if (isMapUsingSIFTs && seq.isProtein())
       {
-        setProgressBar(null);
-        setProgressBar(MessageManager
-                .getString("status.obtaining_mapping_with_sifts"));
+        if (progress!=null) {
+          progress.setProgressBar(MessageManager
+                .getString("status.obtaining_mapping_with_sifts"),
+                  progressSessionId);
+        }
         jalview.datamodel.Mapping sqmpping = maxAlignseq
                 .getMappingFromS1(false);
         if (targetChainId != null && !targetChainId.trim().isEmpty())
@@ -538,7 +548,7 @@ public class StructureSelectionManager
         }
         else
         {
-          List<StructureMapping> foundSiftsMappings = new ArrayList<StructureMapping>();
+          List<StructureMapping> foundSiftsMappings = new ArrayList<>();
           for (PDBChain chain : pdb.getChains())
           {
             try
@@ -575,20 +585,25 @@ public class StructureSelectionManager
       }
       else
       {
-        setProgressBar(null);
-        setProgressBar(MessageManager
-                .getString("status.obtaining_mapping_with_nw_alignment"));
+        if (progress != null)
+        {
+          progress.setProgressBar(MessageManager
+                                 .getString("status.obtaining_mapping_with_nw_alignment"),
+                  progressSessionId);
+        }
         StructureMapping nwMapping = getNWMappings(seq, pdbFile, maxChainId,
                 maxChain, pdb, maxAlignseq);
         seqToStrucMapping.add(nwMapping);
         ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
-
       }
-
       if (forStructureView)
       {
         mappings.addAll(seqToStrucMapping);
       }
+      if (progress != null)
+      {
+        progress.setProgressBar(null, progressSessionId);
+      }
     }
     return pdb;
   }
@@ -683,7 +698,7 @@ public class StructureSelectionManager
             .getMappingFromS1(false);
     maxChain.transferRESNUMFeatures(seq, null);
 
-    HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
+    HashMap<Integer, int[]> mapping = new HashMap<>();
     int resNum = -10000;
     int index = 0;
     char insCode = ' ';
@@ -737,7 +752,7 @@ public class StructureSelectionManager
      * Remove mappings to the closed listener's PDB files, but first check if
      * another listener is still interested
      */
-    List<String> pdbs = new ArrayList<String>(Arrays.asList(pdbfiles));
+    List<String> pdbs = new ArrayList<>(Arrays.asList(pdbfiles));
 
     StructureListener sl;
     for (int i = 0; i < listeners.size(); i++)
@@ -758,7 +773,7 @@ public class StructureSelectionManager
      */
     if (pdbs.size() > 0)
     {
-      List<StructureMapping> tmp = new ArrayList<StructureMapping>();
+      List<StructureMapping> tmp = new ArrayList<>();
       for (StructureMapping sm : mappings)
       {
         if (!pdbs.contains(sm.pdbfile))
@@ -844,7 +859,7 @@ public class StructureSelectionManager
                 && sm.pdbchain.equals(atom.getChain()))
         {
           int indexpos = sm.getSeqPos(atom.getPdbResNum());
-          if (lastipos != indexpos && lastseq != sm.sequence)
+          if (lastipos != indexpos || lastseq != sm.sequence)
           {
             results.addResult(sm.sequence, indexpos, indexpos);
             lastipos = indexpos;
@@ -952,7 +967,7 @@ public class StructureSelectionManager
       return;
     }
     int atomNo;
-    List<AtomSpec> atoms = new ArrayList<AtomSpec>();
+    List<AtomSpec> atoms = new ArrayList<>();
     for (StructureMapping sm : mappings)
     {
       if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
@@ -1060,7 +1075,7 @@ public class StructureSelectionManager
 
   public StructureMapping[] getMapping(String pdbfile)
   {
-    List<StructureMapping> tmp = new ArrayList<StructureMapping>();
+    List<StructureMapping> tmp = new ArrayList<>();
     for (StructureMapping sm : mappings)
     {
       if (sm.pdbfile.equals(pdbfile))
@@ -1220,7 +1235,7 @@ public class StructureSelectionManager
     }
   }
 
-  Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
+  Vector<AlignmentViewPanelListener> view_listeners = new Vector<>();
 
   public synchronized void sendViewPosition(
           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
@@ -1343,35 +1358,6 @@ public class StructureSelectionManager
     return null;
   }
 
-  public IProgressIndicator getProgressIndicator()
-  {
-    return progressIndicator;
-  }
-
-  public void setProgressIndicator(IProgressIndicator progressIndicator)
-  {
-    this.progressIndicator = progressIndicator;
-  }
-
-  public long getProgressSessionId()
-  {
-    return progressSessionId;
-  }
-
-  public void setProgressSessionId(long progressSessionId)
-  {
-    this.progressSessionId = progressSessionId;
-  }
-
-  public void setProgressBar(String message)
-  {
-    if (progressIndicator == null)
-    {
-      return;
-    }
-    progressIndicator.setProgressBar(message, progressSessionId);
-  }
-
   public List<AlignedCodonFrame> getSequenceMappings()
   {
     return seqmappings;
index d21eac3..f5dd883 100644 (file)
@@ -967,4 +967,55 @@ public final class MappingUtils
     return (min <= queryRange[0] && max >= queryRange[0]
             && min <= queryRange[1] && max >= queryRange[1]);
   }
+
+  /**
+   * Removes the specified number of positions from the given ranges. Provided
+   * to allow a stop codon to be stripped from a CDS sequence so that it matches
+   * the peptide translation length.
+   * 
+   * @param positions
+   * @param ranges
+   *          a list of (single) [start, end] ranges
+   * @return
+   */
+  public static void removeEndPositions(int positions,
+          List<int[]> ranges)
+  {
+    int toRemove = positions;
+    Iterator<int[]> it = new ReverseListIterator<>(ranges);
+    while (toRemove > 0)
+    {
+      int[] endRange = it.next();
+      if (endRange.length != 2)
+      {
+        /*
+         * not coded for [start1, end1, start2, end2, ...]
+         */
+        System.err
+                .println("MappingUtils.removeEndPositions doesn't handle multiple  ranges");
+        return;
+      }
+
+      int length = endRange[1] - endRange[0] + 1;
+      if (length <= 0)
+      {
+        /*
+         * not coded for a reverse strand range (end < start)
+         */
+        System.err
+                .println("MappingUtils.removeEndPositions doesn't handle reverse strand");
+        return;
+      }
+      if (length > toRemove)
+      {
+        endRange[1] -= toRemove;
+        toRemove = 0;
+      }
+      else
+      {
+        toRemove -= length;
+        it.remove();
+      }
+    }
+  }
 }
index b3456aa..2e8ace8 100644 (file)
@@ -403,4 +403,45 @@ public class StringUtils
     }
     return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
   }
+
+  /**
+   * A helper method that strips off any leading or trailing html and body tags.
+   * If no html tag is found, then also html-encodes angle bracket characters.
+   * 
+   * @param text
+   * @return
+   */
+  public static String stripHtmlTags(String text)
+  {
+    if (text == null)
+    {
+      return null;
+    }
+    String tmp2up = text.toUpperCase();
+    int startTag = tmp2up.indexOf("<HTML>");
+    if (startTag > -1)
+    {
+      text = text.substring(startTag + 6);
+      tmp2up = tmp2up.substring(startTag + 6);
+    }
+    // is omission of "<BODY>" intentional here??
+    int endTag = tmp2up.indexOf("</BODY>");
+    if (endTag > -1)
+    {
+      text = text.substring(0, endTag);
+      tmp2up = tmp2up.substring(0, endTag);
+    }
+    endTag = tmp2up.indexOf("</HTML>");
+    if (endTag > -1)
+    {
+      text = text.substring(0, endTag);
+    }
+  
+    if (startTag == -1 && (text.contains("<") || text.contains(">")))
+    {
+      text = text.replaceAll("<", "&lt;");
+      text = text.replaceAll(">", "&gt;");
+    }
+    return text;
+  }
 }
index b260cab..a0cbff4 100644 (file)
@@ -22,6 +22,7 @@ package jalview.viewmodel;
 
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.analysis.Conservation;
+import jalview.analysis.TreeModel;
 import jalview.api.AlignCalcManagerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
@@ -79,7 +80,7 @@ import java.util.Map;
 public abstract class AlignmentViewport
         implements AlignViewportI, CommandListener, VamsasSource
 {
-  final protected ViewportRanges ranges;
+  protected ViewportRanges ranges;
 
   protected ViewStyleI viewStyle = new ViewStyle();
 
@@ -947,11 +948,15 @@ public abstract class AlignmentViewport
     groupConsensus = null;
     groupConservation = null;
     hconsensus = null;
+    hconservation = null;
     hcomplementConsensus = null;
-    // colour scheme may hold reference to consensus
-    residueShading = null;
-    // TODO remove listeners from changeSupport?
+    gapcounts = null;
+    calculator = null;
+    residueShading = null; // may hold a reference to Consensus
     changeSupport = null;
+    ranges = null;
+    currentTree = null;
+    selectionGroup = null;
     setAlignment(null);
   }
 
@@ -1333,7 +1338,10 @@ public abstract class AlignmentViewport
   public void removePropertyChangeListener(
           java.beans.PropertyChangeListener listener)
   {
-    changeSupport.removePropertyChangeListener(listener);
+    if (changeSupport != null)
+    {
+      changeSupport.removePropertyChangeListener(listener);
+    }
   }
 
   /**
@@ -2869,6 +2877,8 @@ public abstract class AlignmentViewport
    */
   private SearchResultsI searchResults = null;
 
+  protected TreeModel currentTree = null;
+
   @Override
   public boolean hasSearchResults()
   {
@@ -2927,4 +2937,16 @@ public abstract class AlignmentViewport
             + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
     return sq;
   }
+
+  @Override
+  public void setCurrentTree(TreeModel tree)
+  {
+    currentTree = tree;
+  }
+
+  @Override
+  public TreeModel getCurrentTree()
+  {
+    return currentTree;
+  }
 }
index 42d490e..24ff57f 100644 (file)
@@ -402,23 +402,39 @@ public class ViewportRanges extends ViewportProperties
    */
   public boolean scrollUp(boolean up)
   {
+    /*
+     * if in unwrapped mode, scroll up or down one sequence row;
+     * if in wrapped mode, scroll by one visible width of columns
+     */
     if (up)
     {
-      if (startSeq < 1)
+      if (wrappedMode)
       {
-        return false;
+        pageUp();
+      }
+      else
+      {
+        if (startSeq < 1)
+        {
+          return false;
+        }
+        setStartSeq(startSeq - 1);
       }
-
-      setStartSeq(startSeq - 1);
     }
     else
     {
-      if (endSeq >= getVisibleAlignmentHeight() - 1)
+      if (wrappedMode)
       {
-        return false;
+        pageDown();
+      }
+      else
+      {
+        if (endSeq >= getVisibleAlignmentHeight() - 1)
+        {
+          return false;
+        }
+        setStartSeq(startSeq + 1);
       }
-
-      setStartSeq(startSeq + 1);
     }
     return true;
   }
index b972ab8..dd64e77 100644 (file)
@@ -30,6 +30,7 @@ import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.IProgressIndicator;
+import jalview.gui.IProgressIndicatorHandler;
 import jalview.schemes.ResidueProperties;
 import jalview.workers.AlignCalcWorker;
 import jalview.ws.jws2.dm.AAConSettings;
@@ -220,7 +221,26 @@ public abstract class AbstractJabaCalcWorker extends AlignCalcWorker
                 progressId = System.currentTimeMillis());
       }
       rslt = submitToService(seqs);
+      if (guiProgress != null)
+      {
+        guiProgress.registerHandler(progressId,
+                new IProgressIndicatorHandler()
+                {
 
+                  @Override
+                  public boolean cancelActivity(long id)
+                  {
+                    cancelCurrentJob();
+                    return true;
+                  }
+
+                  @Override
+                  public boolean canCancel()
+                  {
+                    return true;
+                  }
+                });
+      }
       boolean finished = false;
       long rpos = 0;
       do
index cb8f75a..2f3c298 100644 (file)
@@ -170,13 +170,11 @@ public class Jws2Instance
     {
       try
       {
-        Closeable svc = (Closeable) service;
-        service = null;
-        svc.close();
-      } catch (Exception e)
+        ((Closeable) service).close();
+      } catch (Throwable t)
       {
+        // ignore
       }
-      ;
     }
     super.finalize();
   }
index 3187fd9..9d3877c 100644 (file)
@@ -27,39 +27,25 @@ import jalview.datamodel.SequenceI;
 import jalview.gui.JvOptionPane;
 import jalview.io.FastaFile;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
 import java.util.Arrays;
 import java.util.Random;
 
 import org.testng.annotations.BeforeClass;
 
 /**
- * Generates, and outputs in Fasta format, a random DNA alignment for given
+ * Generates, and outputs in Fasta format, a random peptide or nucleotide alignment for given
  * sequence length and count. Will regenerate the same alignment each time if
  * the same random seed is used (so may be used for reproducible unit tests).
  * Not guaranteed to reproduce the same results between versions, as the rules
  * may get tweaked to produce more 'realistic' results.
  * 
- * Arguments:
- * <ul>
- * <li>length (number of bases in each sequence)</li>
- * <li>height (number of sequences)</li>
- * <li>a whole number random seed</li>
- * <li>percentage of gaps to include (0-100)</li>
- * <li>percentage chance of variation of each position (0-100)</li>
- * </ul>
- * 
  * @author gmcarstairs
- *
  */
 public class AlignmentGenerator
 {
-  @BeforeClass(alwaysRun = true)
-  public void setUpJvOptionPane()
-  {
-    JvOptionPane.setInteractiveMode(false);
-    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
-  }
-
   private static final char GAP = '-';
 
   private static final char ZERO = '0';
@@ -72,51 +58,76 @@ public class AlignmentGenerator
 
   private Random random;
 
+  private PrintStream ps;
 
   /**
-   * Outputs a DNA 'alignment' where each position is a random choice from
-   * 'GTCA-'.
+   * Outputs a pseudo-randomly generated nucleotide or peptide alignment
+   * Arguments:
+   * <ul>
+   * <li>n (for nucleotide) or p (for peptide)</li>
+   * <li>length (number of bases in each sequence)</li>
+   * <li>height (number of sequences)</li>
+   * <li>a whole number random seed</li>
+   * <li>percentage of gaps to include (0-100)</li>
+   * <li>percentage chance of variation of each position (0-100)</li>
+   * <li>(optional) path to a file to write the alignment to</li>
+   * </ul>
+   * 
    * 
    * @param args
+   * @throws FileNotFoundException
    */
-  public static void main(String[] args)
+  public static void main(String[] args) throws FileNotFoundException
   {
-    if (args.length != 6)
+    if (args.length != 6 && args.length != 7)
     {
       usage();
       return;
     }
+
+    PrintStream ps = System.out;
+    if (args.length == 7)
+    {
+      ps = new PrintStream(new File(args[6]));
+    }
+
     boolean nucleotide = args[0].toLowerCase().startsWith("n");
     int width = Integer.parseInt(args[1]);
     int height = Integer.parseInt(args[2]);
     long randomSeed = Long.valueOf(args[3]);
     int gapPercentage = Integer.valueOf(args[4]);
     int changePercentage = Integer.valueOf(args[5]);
-    AlignmentI al = new AlignmentGenerator(nucleotide).generate(width,
-            height,
-            randomSeed, gapPercentage, changePercentage);
 
-    System.out.println("; " + height + " sequences of " + width
+    ps.println("; " + height + " sequences of " + width
             + " bases with " + gapPercentage + "% gaps and "
             + changePercentage + "% mutations (random seed = " + randomSeed
             + ")");
-    System.out.println(new FastaFile().print(al.getSequencesArray(), true));
+
+    new AlignmentGenerator(nucleotide, ps).generate(width, height,
+            randomSeed, gapPercentage, changePercentage);
+
+    if (ps != System.out)
+    {
+      ps.close();
+    }
   }
 
   /**
-   * Print parameter help.
+   * Prints parameter help
    */
   private static void usage()
   {
     System.out.println("Usage:");
     System.out.println("arg0: n (for nucleotide) or p (for peptide)");
     System.out.println("arg1: number of (non-gap) bases per sequence");
-    System.out.println("arg2: number sequences");
+    System.out.println("arg2: number of sequences");
     System.out
             .println("arg3: an integer as random seed (same seed = same results)");
     System.out.println("arg4: percentage of gaps to (randomly) generate");
     System.out
             .println("arg5: percentage of 'mutations' to (randomly) generate");
+    System.out
+            .println("arg6: (optional) path to output file (default is sysout)");
     System.out.println("Example: AlignmentGenerator n 12 15 387 10 5");
     System.out
             .println("- 15 nucleotide sequences of 12 bases each, approx 10% gaps and 5% mutations, random seed = 387");
@@ -124,16 +135,28 @@ public class AlignmentGenerator
   }
 
   /**
-   * Constructor that sets nucleotide or peptide symbol set
+   * Constructor that sets nucleotide or peptide symbol set, and also writes the
+   * generated alignment to sysout
    */
   public AlignmentGenerator(boolean nuc)
   {
-    BASES = nuc ? NUCS : PEPS;
+    this(nuc, System.out);
+  }
+
+  /**
+   * Constructor that sets nucleotide or peptide symbol set, and also writes the
+   * generated alignment to the specified output stream (if not null). This can
+   * be used to write the alignment to a file or sysout.
+   */
+  public AlignmentGenerator(boolean nucleotide, PrintStream printStream)
+  {
+    BASES = nucleotide ? NUCS : PEPS;
+    ps = printStream;
   }
 
   /**
-   * Outputs a DNA 'alignment' of given width and height, where each position is
-   * a random choice from 'GTCA-'.
+   * Outputs an 'alignment' of given width and height, where each position is a
+   * random choice from the symbol alphabet, or - for gap
    * 
    * @param width
    * @param height
@@ -153,6 +176,12 @@ public class AlignmentGenerator
               seqno + 1, width, changePercentage);
     }
     AlignmentI al = new Alignment(seqs);
+
+    if (ps != null)
+    {
+      ps.println(new FastaFile().print(al.getSequencesArray(), true));
+    }
+
     return al;
   }
 
index d229a39..1bff8bf 100644 (file)
@@ -64,6 +64,8 @@ import org.testng.annotations.Test;
 
 public class AlignmentUtilsTests
 {
+  private static Sequence ts = new Sequence("short",
+          "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm");
 
   @BeforeClass(alwaysRun = true)
   public void setUpJvOptionPane()
@@ -72,9 +74,6 @@ public class AlignmentUtilsTests
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  private static Sequence ts = new Sequence("short",
-          "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm");
-
   @Test(groups = { "Functional" })
   public void testExpandContext()
   {
@@ -2633,4 +2632,71 @@ public class AlignmentUtilsTests
     assertEquals("[ [1, 12] ] 1:1 to [ [158, 164, 210, 214] ]",
             toMap.toString());
   }
+
+  /**
+   * Tests for the method that maps nucleotide to protein based on CDS features
+   */
+  @Test(groups = "Functional")
+  public void testMapCdsToProtein()
+  {
+    SequenceI peptide = new Sequence("pep", "KLQ");
+
+    /*
+     * Case 1: CDS 3 times length of peptide
+     * NB method only checks lengths match, not translation
+     */
+    SequenceI dna = new Sequence("dna", "AACGacgtCTCCT");
+    dna.createDatasetSequence();
+    dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null));
+    dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 13, null));
+    MapList ml = AlignmentUtils.mapCdsToProtein(dna, peptide);
+    assertEquals(3, ml.getFromRatio());
+    assertEquals(1, ml.getToRatio());
+    assertEquals("[[1, 3]]",
+            Arrays.deepToString(ml.getToRanges().toArray()));
+    assertEquals("[[1, 4], [9, 13]]",
+            Arrays.deepToString(ml.getFromRanges().toArray()));
+
+    /*
+     * Case 2: CDS 3 times length of peptide + stop codon
+     * (note code does not currently check trailing codon is a stop codon)
+     */
+    dna = new Sequence("dna", "AACGacgtCTCCTTGA");
+    dna.createDatasetSequence();
+    dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null));
+    dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 16, null));
+    ml = AlignmentUtils.mapCdsToProtein(dna, peptide);
+    assertEquals(3, ml.getFromRatio());
+    assertEquals(1, ml.getToRatio());
+    assertEquals("[[1, 3]]",
+            Arrays.deepToString(ml.getToRanges().toArray()));
+    assertEquals("[[1, 4], [9, 13]]",
+            Arrays.deepToString(ml.getFromRanges().toArray()));
+
+    /*
+     * Case 3: CDS not 3 times length of peptide - no mapping is made
+     */
+    dna = new Sequence("dna", "AACGacgtCTCCTTG");
+    dna.createDatasetSequence();
+    dna.addSequenceFeature(new SequenceFeature("CDS", "", 1, 4, null));
+    dna.addSequenceFeature(new SequenceFeature("CDS", "", 9, 15, null));
+    ml = AlignmentUtils.mapCdsToProtein(dna, peptide);
+    assertNull(ml);
+
+    /*
+     * Case 4: incomplete start codon corresponding to X in peptide
+     */
+    dna = new Sequence("dna", "ACGacgtCTCCTTGG");
+    dna.createDatasetSequence();
+    SequenceFeature sf = new SequenceFeature("CDS", "", 1, 3, null);
+    sf.setPhase("2"); // skip 2 positions (AC) to start of next codon (GCT)
+    dna.addSequenceFeature(sf);
+    dna.addSequenceFeature(new SequenceFeature("CDS", "", 8, 15, null));
+    peptide = new Sequence("pep", "XLQ");
+    ml = AlignmentUtils.mapCdsToProtein(dna, peptide);
+    assertEquals("[[2, 3]]",
+            Arrays.deepToString(ml.getToRanges().toArray()));
+    assertEquals("[[3, 3], [8, 12]]",
+            Arrays.deepToString(ml.getFromRanges().toArray()));
+  }
 }
index 70e59c5..e2e5594 100644 (file)
@@ -64,7 +64,7 @@ public class TestAlignSeq
     s2 = new Sequence("Seq2", "ASDFA");
     s2.setStart(5);
     s2.setEnd(9);
-    s3 = new Sequence("Seq1", "SDFAQQQSSS");
+    s3 = new Sequence("Seq3", "SDFAQQQSSS");
 
   }
 
@@ -125,10 +125,10 @@ public class TestAlignSeq
     };
 
     as.printAlignment(ps);
-    String expected = "Score = 320.0\nLength of alignment = 10\nSequence Seq1 :  3 - 18 (Sequence length = 14)\nSequence Seq1 :  1 - 10 (Sequence length = 10)\n\n"
-            + "Seq1 SDFAQQQRRR\n"
-            + "     |||||||   \n"
-            + "Seq1 SDFAQQQSSS\n\n" + "Percentage ID = 70.00\n";
+    String expected = "Score = 320.0\nLength of alignment = 10\nSequence Seq1/4-13 (Sequence length = 14)\nSequence Seq3/1-10 (Sequence length = 10)\n\n"
+            + "Seq1/4-13 SDFAQQQRRR\n"
+            + "          |||||||   \n"
+            + "Seq3/1-10 SDFAQQQSSS\n\n" + "Percentage ID = 70.00\n\n";
     assertEquals(expected, baos.toString());
   }
 }
index fbeb365..8c9cbc9 100644 (file)
@@ -273,4 +273,47 @@ public class SequenceFeatureTest
             "group");
     assertTrue(sf.isContactFeature());
   }
+
+  @Test(groups = { "Functional" })
+  public void testGetDetailsReport()
+  {
+    // single locus, no group, no score
+    SequenceFeature sf = new SequenceFeature("variant", "G,C", 22, 22, null);
+    String expected = "<br><table><tr><td>Type</td><td>variant</td></tr>"
+            + "<tr><td>Start/end</td><td>22</td></tr>"
+            + "<tr><td>Description</td><td>G,C</td></tr></table>";
+    assertEquals(expected, sf.getDetailsReport());
+
+    // contact feature
+    sf = new SequenceFeature("Disulphide Bond", "a description", 28, 31,
+            null);
+    expected = "<br><table><tr><td>Type</td><td>Disulphide Bond</td></tr>"
+            + "<tr><td>Start/end</td><td>28:31</td></tr>"
+            + "<tr><td>Description</td><td>a description</td></tr></table>";
+    assertEquals(expected, sf.getDetailsReport());
+
+    sf = new SequenceFeature("variant", "G,C", 22, 33,
+            12.5f, "group");
+    sf.setValue("Parent", "ENSG001");
+    sf.setValue("Child", "ENSP002");
+    expected = "<br><table><tr><td>Type</td><td>variant</td></tr>"
+            + "<tr><td>Start/end</td><td>22-33</td></tr>"
+            + "<tr><td>Description</td><td>G,C</td></tr>"
+            + "<tr><td>Score</td><td>12.5</td></tr>"
+            + "<tr><td>Group</td><td>group</td></tr>"
+            + "<tr><td>Child</td><td>ENSP002</td></tr>"
+            + "<tr><td>Parent</td><td>ENSG001</td></tr></table>";
+    assertEquals(expected, sf.getDetailsReport());
+
+    /*
+     * feature with embedded html link in description
+     */
+    String desc = "<html>Fer2 Status: True Positive <a href=\"http://pfam.xfam.org/family/PF00111\">Pfam 8_8</a></html>";
+    sf = new SequenceFeature("Pfam", desc, 8, 83, "Uniprot");
+    expected = "<br><table><tr><td>Type</td><td>Pfam</td></tr>"
+            + "<tr><td>Start/end</td><td>8-83</td></tr>"
+            + "<tr><td>Description</td><td>Fer2 Status: True Positive <a href=\"http://pfam.xfam.org/family/PF00111\">Pfam 8_8</a></td></tr>"
+            + "<tr><td>Group</td><td>Uniprot</td></tr></table>";
+    assertEquals(expected, sf.getDetailsReport());
+  }
 }
diff --git a/test/jalview/gui/FreeUpMemoryTest.java b/test/jalview/gui/FreeUpMemoryTest.java
new file mode 100644 (file)
index 0000000..e93bfac
--- /dev/null
@@ -0,0 +1,216 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.analysis.AlignmentGenerator;
+import jalview.bin.Cache;
+import jalview.bin.Jalview;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceGroup;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class FreeUpMemoryTest
+{
+  private static final int ONE_MB = 1000 * 1000;
+
+  /**
+   * Configure (read-only) Jalview property settings for test
+   */
+  @BeforeClass(alwaysRun = true)
+  public void setUp()
+  {
+    Jalview.main(new String[] { "-nonews", "-props",
+        "test/jalview/testProps.jvprops" });
+    Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty("SHOW_QUALITY",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty("SHOW_CONSERVATION",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty("SHOW_OCCUPANCY",
+            Boolean.TRUE.toString());
+    Cache.applicationProperties.setProperty("SHOW_IDENTITY",
+            Boolean.TRUE.toString());
+  }
+
+  /**
+   * A simple test that memory is released when all windows are closed.
+   * <ul>
+   * <li>generates a reasonably large alignment and loads it</li>
+   * <li>performs various operations on the alignment</li>
+   * <li>closes all windows</li>
+   * <li>requests garbage collection</li>
+   * <li>asserts that the remaining memory footprint (heap usage) is 'not large'
+   * </li>
+   * </ul>
+   * If the test fails, this suggests that a reference to some large object
+   * (perhaps the alignment data, or some annotation / Tree / PCA data) has
+   * failed to be garbage collected. If this is the case, the heap will need to
+   * be inspected manually (suggest using jvisualvm) in order to track down
+   * where large objects are still referenced. The code (for example
+   * AlignmentViewport.dispose()) should then be updated to ensure references to
+   * large objects are set to null when they are no longer required.
+   * 
+   * @throws IOException
+   */
+  @Test(groups = "Memory")
+  public void testFreeMemoryOnClose() throws IOException
+  {
+    File f = generateAlignment();
+    f.deleteOnExit();
+
+    doStuffInJalview(f);
+
+    Desktop.instance.closeAll_actionPerformed(null);
+
+    checkUsedMemory(35L);
+  }
+
+  /**
+   * Requests garbage collection and then checks whether remaining memory in use
+   * is less than the expected value (in Megabytes)
+   * 
+   * @param expectedMax
+   */
+  protected void checkUsedMemory(long expectedMax)
+  {
+    /*
+     * request garbage collection and wait briefly for it to run;
+     * NB there is no guarantee when, or whether, it will do so
+     */
+    System.gc();
+    waitFor(100);
+
+    /*
+     * a second gc() call should not be necessary - but it is!
+     * the test passes with it, and fails without it
+     */
+    System.gc();
+    waitFor(100);
+
+    /*
+     * check used memory is 'reasonably low'
+     */
+    long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB;
+    long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB;
+    long usedMemory = availableMemory - freeMemory;
+
+    /*
+     * sanity check - fails if any frame was added after
+     * closeAll_actionPerformed
+     */
+    assertEquals(Desktop.instance.getAllFrames().length, 0);
+
+    /*
+     * if this assertion fails
+     * - set a breakpoint here
+     * - run jvisualvm to inspect a heap dump of Jalview
+     * - identify large objects in the heap and their referers
+     * - fix code as necessary to null the references on close
+     */
+    System.out.println("Used memory after gc = " + usedMemory + "MB");
+    assertTrue(usedMemory < expectedMax, String.format(
+            "Used memory %d should be less than %d (Recommend running test manually to verify)",
+            usedMemory,
+            expectedMax));
+  }
+
+  /**
+   * Loads an alignment from file and exercises various operations in Jalview
+   * 
+   * @param f
+   */
+  protected void doStuffInJalview(File f)
+  {
+    /*
+     * load alignment, wait for consensus and other threads to complete
+     */
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(),
+            DataSourceType.FILE);
+    while (af.getViewport().isCalcInProgress())
+    {
+      waitFor(200);
+    }
+
+    /*
+     * set a selection group - potential memory leak if it retains
+     * a reference to the alignment
+     */
+    SequenceGroup sg = new SequenceGroup();
+    sg.setStartRes(0);
+    sg.setEndRes(100);
+    AlignmentI al = af.viewport.getAlignment();
+    for (int i = 0; i < al.getHeight(); i++)
+    {
+      sg.addSequence(al.getSequenceAt(i), false);
+    }
+    af.viewport.setSelectionGroup(sg);
+
+    /*
+     * compute Tree and PCA (on all sequences, 100 columns)
+     */
+    af.openTreePcaDialog();
+    CalculationChooser dialog = af.alignPanel.getCalculationDialog();
+    dialog.openPcaPanel("BLOSUM62", dialog.getSimilarityParameters(true));
+    dialog.openTreePanel("BLOSUM62", dialog.getSimilarityParameters(false));
+
+    /*
+     * wait until Tree and PCA have been computed
+     */
+    while (af.viewport.getCurrentTree() == null
+            && dialog.getPcaPanel().isWorking())
+    {
+      waitFor(10);
+    }
+
+    /*
+     * give Swing time to add the PCA panel (?!?)
+     */
+    waitFor(100);
+  }
+
+  /**
+   * Wait for waitMs miliseconds
+   * 
+   * @param waitMs
+   */
+  protected void waitFor(int waitMs)
+  {
+    try
+    {
+      Thread.sleep(waitMs);
+    } catch (InterruptedException e)
+    {
+    }
+  }
+
+  /**
+   * Generates an alignment and saves it in a temporary file, to be loaded by
+   * Jalview. We use a peptide alignment (so Conservation and Quality are
+   * calculated), which is wide enough to ensure Consensus, Conservation and
+   * Occupancy have a significant memory footprint (if not removed from the
+   * heap).
+   * 
+   * @return
+   * @throws IOException
+   */
+  private File generateAlignment() throws IOException
+  {
+    File f = File.createTempFile("MemoryTest", "fa");
+    PrintStream ps = new PrintStream(f);
+    AlignmentGenerator ag = new AlignmentGenerator(false, ps);
+    int width = 100000;
+    int height = 100;
+    ag.generate(width, height, 0, 10, 15);
+    return f;
+  }
+}
diff --git a/test/jalview/gui/PairwiseAlignmentPanelTest.java b/test/jalview/gui/PairwiseAlignmentPanelTest.java
new file mode 100644 (file)
index 0000000..3322ee8
--- /dev/null
@@ -0,0 +1,73 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceGroup;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+
+import javax.swing.JTextArea;
+
+import junit.extensions.PA;
+
+import org.testng.annotations.Test;
+
+public class PairwiseAlignmentPanelTest
+{
+  @Test(groups = "Functional")
+  public void testConstructor_withSelectionGroup()
+  {
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+            "examples/uniref50.fa", DataSourceType.FILE);
+    AlignViewport viewport = af.getViewport();
+    AlignmentI al = viewport.getAlignment();
+
+    /*
+     * select columns 29-36 of sequences 4 and 5 for alignment
+     * Q93XJ9_SOLTU/23-29 L-KAISNV
+     * FER1_PEA/26-32     V-TTTKAF
+     */
+    SequenceGroup sg = new SequenceGroup();
+    sg.addSequence(al.getSequenceAt(3), false);
+    sg.addSequence(al.getSequenceAt(4), false);
+    sg.setStartRes(28);
+    sg.setEndRes(35);
+    viewport.setSelectionGroup(sg);
+
+    PairwiseAlignPanel testee = new PairwiseAlignPanel(viewport);
+
+    String text = ((JTextArea) PA.getValue(testee, "textarea")).getText();
+    String expected = "Score = 80.0\n" + "Length of alignment = 4\n"
+            + "Sequence     FER1_PEA/29-32 (Sequence length = 7)\n"
+            + "Sequence Q93XJ9_SOLTU/23-26 (Sequence length = 7)\n\n"
+            + "    FER1_PEA/29-32 TKAF\n" + "                    ||.\n"
+            + "Q93XJ9_SOLTU/23-26 LKAI\n\n" + "Percentage ID = 50.00\n\n";
+    assertEquals(text, expected);
+  }
+
+  /**
+   * This test aligns the same sequences as testConstructor_withSelectionGroup
+   * but as a complete alignment (no selection). Note that in fact the user is
+   * currently required to make a selection in order to calculate pairwise
+   * alignments, so this case does not arise.
+   */
+  @Test(groups = "Functional")
+  public void testConstructor_noSelectionGroup()
+  {
+    String seqs = ">Q93XJ9_SOLTU/23-29\nL-KAISNV\n>FER1_PEA/26-32\nV-TTTKAF\n";
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqs,
+            DataSourceType.PASTE);
+    AlignViewport viewport = af.getViewport();
+
+    PairwiseAlignPanel testee = new PairwiseAlignPanel(viewport);
+
+    String text = ((JTextArea) PA.getValue(testee, "textarea")).getText();
+    String expected = "Score = 80.0\n" + "Length of alignment = 4\n"
+            + "Sequence     FER1_PEA/29-32 (Sequence length = 7)\n"
+            + "Sequence Q93XJ9_SOLTU/23-26 (Sequence length = 7)\n\n"
+            + "    FER1_PEA/29-32 TKAF\n" + "                    ||.\n"
+            + "Q93XJ9_SOLTU/23-26 LKAI\n\n" + "Percentage ID = 50.00\n\n";
+    assertEquals(text, expected);
+  }
+}
index a1715e9..72a288b 100644 (file)
@@ -29,6 +29,7 @@ import java.awt.GridLayout;
 
 import javax.swing.JLabel;
 import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
 
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
@@ -119,8 +120,15 @@ public class ProgressBarTest
    * @param layout
    * @param msgs
    */
-  private void verifyProgress(GridLayout layout, String[] msgs)
+  private void verifyProgress(final GridLayout layout, final String[] msgs)
   {
+    try
+    {
+    SwingUtilities.invokeAndWait(new Runnable()
+    {
+      @Override
+      public void run()
+      {
     int msgCount = msgs.length;
     assertEquals(1 + msgCount, layout.getRows());
     assertEquals(msgCount, statusPanel.getComponentCount());
@@ -132,5 +140,13 @@ public class ProgressBarTest
       assertEquals(msgs[i++],
               ((JLabel) ((JPanel) c).getComponent(0)).getText());
     }
+      }
+    });
+    } catch (Exception e)
+    {
+      throw new AssertionError(
+              "Unexpected exception waiting for progress bar validation",
+              e);
+    }
   }
 }
index 4a254d2..5607b4b 100644 (file)
@@ -55,7 +55,7 @@ public class VCFLoaderTest
 
   private static final String[] VCF = { "##fileformat=VCFv4.2",
       "##INFO=<ID=AF,Number=A,Type=Float,Description=\"Allele Frequency, for each ALT allele, in the same order as listed\">",
-      "##reference=GRCh38",
+      "##reference=Homo_sapiens/GRCh38",
       "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO",
       // A/T,C variants in position 2 of gene sequence (precedes transcript)
       // should create 2 variant features with respective scores
@@ -191,7 +191,8 @@ public class VCFLoaderTest
     SequenceI gene1 = alignment.findName("gene1");
     int[] to = new int[] { 45051610, 45051634 };
     int[] from = new int[] { gene1.getStart(), gene1.getEnd() };
-    gene1.setGeneLoci("human", "GRCh38", "17", new MapList(from, to, 1, 1));
+    gene1.setGeneLoci("homo_sapiens", "GRCh38", "17", new MapList(from, to,
+            1, 1));
 
     /*
      * map 'transcript1' to chromosome via 'gene1'
@@ -201,7 +202,8 @@ public class VCFLoaderTest
     to = new int[] { 45051612, 45051619, 45051624, 45051633 };
     SequenceI transcript1 = alignment.findName("transcript1");
     from = new int[] { transcript1.getStart(), transcript1.getEnd() };
-    transcript1.setGeneLoci("human", "GRCh38", "17", new MapList(from, to,
+    transcript1.setGeneLoci("homo_sapiens", "GRCh38", "17", new MapList(
+            from, to,
             1, 1));
 
     /*
@@ -210,7 +212,8 @@ public class VCFLoaderTest
     SequenceI gene2 = alignment.findName("gene2");
     to = new int[] { 45051634, 45051610 };
     from = new int[] { gene2.getStart(), gene2.getEnd() };
-    gene2.setGeneLoci("human", "GRCh38", "17", new MapList(from, to, 1, 1));
+    gene2.setGeneLoci("homo_sapiens", "GRCh38", "17", new MapList(from, to,
+            1, 1));
 
     /*
      * map 'transcript2' to chromosome via 'gene2'
@@ -220,7 +223,8 @@ public class VCFLoaderTest
     to = new int[] { 45051633, 45051624, 45051619, 45051612 };
     SequenceI transcript2 = alignment.findName("transcript2");
     from = new int[] { transcript2.getStart(), transcript2.getEnd() };
-    transcript2.setGeneLoci("human", "GRCh38", "17", new MapList(from, to,
+    transcript2.setGeneLoci("homo_sapiens", "GRCh38", "17", new MapList(
+            from, to,
             1, 1));
 
     /*
@@ -248,7 +252,8 @@ public class VCFLoaderTest
     SequenceI gene3 = alignment.findName("gene3");
     to = new int[] { 45051610, 45051634 };
     from = new int[] { gene3.getStart(), gene3.getEnd() };
-    gene3.setGeneLoci("human", "GRCh38", "5", new MapList(from, to, 1, 1));
+    gene3.setGeneLoci("homo_sapiens", "GRCh38", "5", new MapList(from, to,
+            1, 1));
 
     /*
      * map 'transcript3' to chromosome
@@ -256,7 +261,8 @@ public class VCFLoaderTest
     SequenceI transcript3 = alignment.findName("transcript3");
     to = new int[] { 45051612, 45051619, 45051624, 45051633 };
     from = new int[] { transcript3.getStart(), transcript3.getEnd() };
-    transcript3.setGeneLoci("human", "GRCh38", "5", new MapList(from, to,
+    transcript3.setGeneLoci("homo_sapiens", "GRCh38", "5", new MapList(
+            from, to,
             1, 1));
 
     /*
@@ -266,7 +272,8 @@ public class VCFLoaderTest
     to = new int[] { 45051615, 45051617, 45051619, 45051632, 45051634,
         45051634 };
     from = new int[] { transcript4.getStart(), transcript4.getEnd() };
-    transcript4.setGeneLoci("human", "GRCh38", "5", new MapList(from, to,
+    transcript4.setGeneLoci("homo_sapiens", "GRCh38", "5", new MapList(
+            from, to,
             1, 1));
 
     /*
index e9e6c22..77e070c 100644 (file)
@@ -4,7 +4,7 @@
 ##INFO=<ID=AF_Female,Number=R,Type=Float,Description="Allele Frequency among Female genotypes, for each ALT allele, in the same order as listed">
 ##INFO=<ID=AN,Number=1,Type=Integer,Description="Total number of alleles in called genotypes">
 ##INFO=<ID=CSQ,Number=.,Type=String,Description="Consequence annotations from Ensembl VEP. Format: Allele|Consequence|IMPACT|SYMBOL|Gene|Feature_type|Feature|BIOTYPE|PolyPhen">
-##reference=GRCh38
+##reference=/Homo_sapiens/GRCh38
 #CHROM POS     ID      REF     ALT     QUAL    FILTER  INFO
 5      45051610        .       C       A       81.96   RF;AC0  AC=1;AF=0.1;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=A|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,A|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad
 5      45051614        .       C       T       1666.64 RF      AC=1;AF=0.2;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=T|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,T|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad
index aea3687..af02d5e 100644 (file)
@@ -275,11 +275,11 @@ public class AAStructureBindingModelTest
     StructureSelectionManager ssm = new StructureSelectionManager();
 
     ssm.setMapping(new SequenceI[] { seq1a, seq1b }, null, PDB_1,
-            DataSourceType.PASTE);
+            DataSourceType.PASTE, null);
     ssm.setMapping(new SequenceI[] { seq2 }, null, PDB_2,
-            DataSourceType.PASTE);
+            DataSourceType.PASTE, null);
     ssm.setMapping(new SequenceI[] { seq3 }, null, PDB_3,
-            DataSourceType.PASTE);
+            DataSourceType.PASTE, null);
 
     testee = new AAStructureBindingModel(ssm, pdbFiles, seqs, null)
     {
index 87070d7..d4cf98a 100644 (file)
@@ -1238,4 +1238,49 @@ public class MappingUtilsTest
     assertFalse(MappingUtils.rangeContains(null, new int[] { 1, 10 }));
   }
 
+  @Test(groups = "Functional")
+  public void testRemoveEndPositions()
+  {
+    List<int[]> ranges = new ArrayList<>();
+
+    /*
+     * case 1: truncate last range
+     */
+    ranges.add(new int[] { 1, 10 });
+    ranges.add(new int[] { 20, 30 });
+    MappingUtils.removeEndPositions(5, ranges);
+    assertEquals(2, ranges.size());
+    assertEquals(25, ranges.get(1)[1]);
+
+    /*
+     * case 2: remove last range
+     */
+    ranges.clear();
+    ranges.add(new int[] { 1, 10 });
+    ranges.add(new int[] { 20, 22 });
+    MappingUtils.removeEndPositions(3, ranges);
+    assertEquals(1, ranges.size());
+    assertEquals(10, ranges.get(0)[1]);
+
+    /*
+     * case 3: truncate penultimate range
+     */
+    ranges.clear();
+    ranges.add(new int[] { 1, 10 });
+    ranges.add(new int[] { 20, 21 });
+    MappingUtils.removeEndPositions(3, ranges);
+    assertEquals(1, ranges.size());
+    assertEquals(9, ranges.get(0)[1]);
+
+    /*
+     * case 4: remove last two ranges
+     */
+    ranges.clear();
+    ranges.add(new int[] { 1, 10 });
+    ranges.add(new int[] { 20, 20 });
+    ranges.add(new int[] { 30, 30 });
+    MappingUtils.removeEndPositions(3, ranges);
+    assertEquals(1, ranges.size());
+    assertEquals(9, ranges.get(0)[1]);
+  }
 }
index b6f8a25..084219a 100644 (file)
@@ -228,4 +228,26 @@ public class StringUtilsTest
     assertEquals("", StringUtils.toSentenceCase(""));
     assertNull(StringUtils.toSentenceCase(null));
   }
+
+  @Test(groups = { "Functional" })
+  public void testStripHtmlTags()
+  {
+    assertNull(StringUtils.stripHtmlTags(null));
+    assertEquals("", StringUtils.stripHtmlTags(""));
+    assertEquals(
+            "<a href=\"something\">label</href>",
+            StringUtils
+                    .stripHtmlTags("<html><a href=\"something\">label</href></html>"));
+
+    // if no "<html>" tag, < and > get html-encoded (not sure why)
+    assertEquals("&lt;a href=\"something\"&gt;label&lt;/href&gt;",
+            StringUtils.stripHtmlTags("<a href=\"something\">label</href>"));
+
+    // </body> gets removed but not <body> (is this intentional?)
+    assertEquals("<body><p>hello",
+            StringUtils.stripHtmlTags("<html><body><p>hello</body></html>"));
+
+    assertEquals("kdHydro &lt; 12.53",
+            StringUtils.stripHtmlTags("kdHydro < 12.53"));
+  }
 }
index 851b1b7..c0cb4ba 100644 (file)
@@ -763,6 +763,66 @@ public class ViewportRangesTest {
       }
     }
   }
+
+  @Test(groups = { "Functional" })
+  public void testScrollUp_wrapped()
+  {
+    /*
+     * alignment 30 tall and 45 wide
+     */
+    AlignmentI al2 = gen.generate(45, 30, 1, 0, 5);
+
+    /*
+     * wrapped view, 5 sequences high, start at sequence offset 1
+     */
+    ViewportRanges vr = new ViewportRanges(al2);
+    vr.setWrappedMode(true);
+    vr.setViewportStartAndHeight(1, 5);
+
+    /*
+     * offset wrapped view to column 3
+     */
+    vr.setStartEndRes(3, 22);
+
+    int startRes = vr.getStartRes();
+    int width = vr.getViewportWidth();
+    assertEquals(startRes, 3);
+    assertEquals(width, 20);
+
+    // in wrapped mode, we change startRes but not startSeq
+    // scroll down:
+    vr.scrollUp(false);
+    assertEquals(vr.getStartSeq(), 1);
+    assertEquals(vr.getStartRes(), 23);
+
+    // scroll up returns to original position
+    vr.scrollUp(true);
+    assertEquals(vr.getStartSeq(), 1);
+    assertEquals(vr.getStartRes(), 3);
+
+    // scroll up again returns to 'origin'
+    vr.scrollUp(true);
+    assertEquals(vr.getStartSeq(), 1);
+    assertEquals(vr.getStartRes(), 0);
+
+    /*
+     * offset 3 columns once more and do some scroll downs
+     */
+    vr.setStartEndRes(3, 22);
+    vr.scrollUp(false);
+    assertEquals(vr.getStartSeq(), 1);
+    assertEquals(vr.getStartRes(), 23);
+    vr.scrollUp(false);
+    assertEquals(vr.getStartSeq(), 1);
+    assertEquals(vr.getStartRes(), 43);
+
+    /*
+     * scroll down beyond end of alignment does nothing
+     */
+    vr.scrollUp(false);
+    assertEquals(vr.getStartSeq(), 1);
+    assertEquals(vr.getStartRes(), 43);
+  }
 }
 
 // mock listener for property change events