Merge branch 'bug/JAL-3065_DH_keyPair_Exception_nogroovysettings' into releases/Relea...
authorJim Procter <jprocter@issues.jalview.org>
Wed, 5 Sep 2018 08:03:37 +0000 (09:03 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Wed, 5 Sep 2018 08:03:37 +0000 (09:03 +0100)
32 files changed:
.ant-targets-build.xml
.classpath
.gitignore
RELEASE
THIRDPARTYLIBS
build.xml
examples/rna_ss_test.stk [new file with mode: 0644]
examples/testdata/example_annot_file.jva
help/html/releases.html
help/html/whatsNew.html
lib/VAqua4.jar [deleted file]
lib/VAqua5.jar [new file with mode: 0644]
src/jalview/analysis/Rna.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/Annotation.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/ext/ensembl/EnsemblFeatures.java
src/jalview/ext/ensembl/EnsemblLookup.java
src/jalview/ext/ensembl/EnsemblMap.java [new file with mode: 0644]
src/jalview/ext/ensembl/EnsemblRestClient.java
src/jalview/ext/ensembl/EnsemblSeqProxy.java
src/jalview/ext/ensembl/EnsemblXref.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AppVarna.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/SeqCanvas.java
src/jalview/io/JalviewFileChooser.java
src/jalview/io/StockholmFile.java
test/jalview/ext/ensembl/EnsemblRestClientTest.java
test/jalview/ext/ensembl/EnsemblSeqProxyTest.java
test/jalview/gui/SeqCanvasTest.java
test/jalview/io/StockholmFileTest.java

index 7ef21f1..15432a1 100644 (file)
@@ -1,2 +1,31 @@
+build
+buildPropertiesFile
+buildTests
+buildextclients
+buildindices
+castorbinding
+clean
+compileApplet
+distclean
 help
+init
+linkcheck
+makeApplet
+makedist
+makefulldist
+obfuscate
+packageApplet
+prepare
+prepareTests
+preparejnlp
+prepubapplet_1
+pubapplet
+runenv
+signApplet
+sourcedist
+sourcedoc
+sourcescrub
+testclean
+testng
 usage
+writejnlpf
index 3a05b47..549b185 100644 (file)
@@ -49,7 +49,7 @@
        <classpathentry kind="lib" path="lib/jfreesvg-2.1.jar"/>
        <classpathentry kind="lib" path="lib/quaqua-filechooser-only-8.0.jar"/>
        <classpathentry kind="lib" path="lib/htsjdk-1.133.jar"/>
-       <classpathentry kind="lib" path="lib/VAqua4.jar"/>
+       <classpathentry kind="lib" path="lib/VAqua5.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin"/>
        <classpathentry kind="lib" path="lib/xml-apis.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
index 2a55560..86637df 100644 (file)
@@ -1,5 +1,6 @@
 .project
 /dist
+/clover
 /classes
 /tests
 /test-reports
@@ -12,4 +13,4 @@
 TESTNG
 /jalviewApplet.jar
 /benchmarking/lib
-*.class
\ No newline at end of file
+*.class
diff --git a/RELEASE b/RELEASE
index c61d86c..54b71f0 100644 (file)
--- a/RELEASE
+++ b/RELEASE
@@ -1,2 +1,2 @@
-jalview.release=releases/Release_2_10_4_Branch
-jalview.version=2.10.4b1
+jalview.release=releases/Release_2_10_5_Branch
+jalview.version=2.10.5
index d0d9125..7779627 100644 (file)
@@ -50,7 +50,7 @@ jfreesvg-2.1.jar : GPL v3 licensed library from the JFree suite: http://www.jfre
 
 quaqua: v.8.0 (latest stable) by Randel S Hofer. LGPL and BSD Modified license: downloaded from http://www.randelshofer.ch/quaqua/ 
 
-vaqua: v4 (latest stable) by Alan Snyder et al. GPLv2 with Classpathe xception, also includes contributions from Quaqua: ownloaded from http://violetlib.org/vaqua/overview.html
+vaqua: v5 (latest stable) by Alan Snyder et al. GPLv2 with Classpath exception, also includes contributions from Quaqua: downloaded from http://violetlib.org/vaqua/overview.html
 
 lib/htsjdk-1.120-SNAPSHOT.jar: (currently not required for 2.10) built from maven master at https://github.com/samtools/htsjdk MIT License to Broad Institute
 
index 133941a..97f459d 100755 (executable)
--- a/build.xml
+++ b/build.xml
@@ -20,6 +20,9 @@
 <project name="jalviewX" default="usage" basedir="."
  xmlns:if="ant:if"
     xmlns:unless="ant:unless">
+  <taskdef classpath="${clover.jar}" resource="cloverlib.xml" if:set="clover.jar"/>
+  <clover-env if:set="clover.jar"/>
+
   <target name="help" depends="usage" />
   <target name="usage" depends="init">
     <echo message="~~~Jalview Ant build.xml Usage~~~~" />
     <property name="docDir" value="doc" />
     <property name="sourceDir" value="src" />
     <property name="schemaDir" value="schemas" />
-    <property name="outputDir" value="classes" />
+    <property name="outputDir" value="classes" unless:set="clover.jar"/>
+    <property name="outputDir" value="cloverclasses" if:set="clover.jar"/>
     <property name="packageDir" value="dist" />
     <property name="outputJar" value="jalview.jar" />
     <!-- Jalview Applet JMol Jar Dependency -->
       verbose="2">
       <classpath>
         <pathelement location="${testOutputDir}" />
+        <pathelement location="${clover.jar}" if:set="clover.jar"/>
         <path refid="test.classpath" />
       </classpath>
       <jvmarg value="--add-modules=java.se.ee" if:set="java9"/>
         <association mime-type="application-x/ext-file" extensions="jar"/>-->
   </target>
 
-  <target name="-jarsignwithtsa" depends="makedist,preparejnlp" if="timestamp">
+  <target name="-jarsignwithtsa" depends="makedist,preparejnlp" if="timestamp" unless="nosign">
     <signjar storepass="${jalview.keystore.pass}" keypass="${jalview.key.pass}" keystore="${jalview.keystore}" alias="${jalview.key}" lazy="false" verbose="false" sigalg="${jalview.keyalg}" digestalg="${jalview.keydig}"
       tsaproxyhost="${proxyHost}" tsaproxyport="${proxyPort}" tsaurl="${jalview.tsaurl}">
       <fileset dir="${packageDir}">
       </fileset>
     </signjar>
   </target>
-  <target name="-jarsignnotsa" depends="makedist,preparejnlp" unless="timestamp">
+  <target name="-jarsignnotsa" depends="makedist,preparejnlp" if:blank="timestamp" unless="nosign">
     <signjar storepass="${jalview.keystore.pass}" keypass="${jalview.key.pass}" keystore="${jalview.keystore}" alias="${jalview.key}" lazy="false" verbose="false" sigalg="${jalview.keyalg}" digestalg="${jalview.keydig}">
       <fileset dir="${packageDir}">
         <include name="*.jar" />
 </target>
 
 <target name="makedist" depends="build, buildPropertiesFile, linkcheck, buildindices">
+  <fail if="clover.jar">
+    Ignoring request to build jalview distribution with clover-instrumented classes
+  </fail>
   <!-- make the package jar if not already existing -->
   <mkdir dir="${packageDir}" />
   <!-- clean dir if it already existed -->
     <include name="ap_${jsonSimple}" />
   </fileset>
 </target>
-<target name="-signappletnotsa" unless="timestamp" depends="-signapplet">
+<target name="-signappletnotsa" if:blank="timestamp" depends="-signapplet" unless="nosign">
   <signjar storepass="${jalview.keystore.pass}" keypass="${jalview.key.pass}" keystore="${jalview.keystore}" alias="${jalview.key}" lazy="false" verbose="false">
     <fileset refid="signappletjarset" />
   </signjar>
 </target>
 
-<target name="-signapplettsa" if="timestamp" depends="-signapplet">
+<target name="-signapplettsa" if="timestamp" depends="-signapplet" unless="nosign">
   <signjar storepass="${jalview.keystore.pass}" keypass="${jalview.key.pass}" keystore="${jalview.keystore}" alias="${jalview.key}" lazy="false" verbose="false" tsaproxyhost="${proxyHost}" tsaproxyport="${proxyPort}" tsaurl="${jalview.tsaurl}">
     <fileset refid="signappletjarset" />
   </signjar>
diff --git a/examples/rna_ss_test.stk b/examples/rna_ss_test.stk
new file mode 100644 (file)
index 0000000..429612e
--- /dev/null
@@ -0,0 +1,6 @@
+# STOCKHOLM 1.0
+#=GF ID RNA.SS.TEST
+#=GF TP RNA;
+Test.sequence         GUACAAAAAAAAAA
+#=GC SS_cons          <(EHBheb(E)e)>
+//
index 1779247..6b9faa4 100644 (file)
@@ -18,5 +18,5 @@ SEQUENCE_GROUP        Group_A 30      50      *
 SEQUENCE_GROUP Group_B 1       351     2-5
 SEQUENCE_GROUP Group_C 12      14      -1      seq1    seq2    seq3
 PROPERTIES     Group_A description=This is the description     colour=Helix Propensity pidThreshold=0  outlineColour=red       displayBoxes=true       displayText=false       colourText=false        textCol1=black  textCol2=black  textColThreshold=0
-PROPERTIES     Group_B outlineColour=red       colour=None
+PROPERTIES     Group_B outlineColour=green     colour=None
 PROPERTIES     Group_C colour=Clustal
index 75c8e06..9c4c9ed 100755 (executable)
@@ -68,6 +68,89 @@ li:before {
       </td>
     </tr>
     <tr>
+    <td width="60" nowrap>
+      <div align="center">
+        <strong><a name="Jalview.2.10.5">2.10.5</a><br /> <em>4/09/2018</em></strong>
+      </div>
+    </td>
+    <td><div align="left">
+        <em></em>
+        <ul>
+            <li>
+              <!-- JAL-247 -->Hidden sequence markers and representative
+              sequence bolding included when exporting alignment as EPS,
+              SVG, PNG or HTML. <em>Display is configured via the
+                Format menu, or for command-line use via a jalview
+                properties file.</em>
+            </li>
+            <li>
+              <!-- JAL-3076 -->Ensembl client updated to Version 7 REST
+              API and sequence data now imported as JSON
+            </li>
+            <li>
+              <!-- JAL-3065 -->Change in recommended way of starting
+              Jalview via a Java command line: add jars in lib directory
+              to CLASSPATH, rather than via the deprecated java.ext.dirs
+              property.
+            </li>
+          </ul>
+          <em>Development</em>
+          <ul>
+            <li>
+              <!-- JAL-3047 -->Support added to execute test suite
+              instrumented with <a href="http://openclover.org/">Open
+                Clover</a>
+            </li>
+          </ul>
+        </div></td>
+    <td><div align="left">
+        <em></em>
+        <ul>
+            <li>
+              <!-- JAL-3087 -->Corrupted display when switching to
+              wrapped mode when sequence panel's vertical scrollbar is
+              visible.
+            </li>
+            <li>
+              <!-- JAL-3003 -->Alignment is black in exported EPS file
+              when sequences are selected in exported view.</em>
+            </li>
+            <li>
+              <!-- JAL-3059 -->Groups with different coloured borders
+              aren't rendered with correct colour.
+            </li>
+            <li>
+              <!-- JAL-3092 -->Jalview could hang when importing certain
+              types of knotted RNA secondary structure
+            </li>
+            <li>
+              <!-- JAL-3095 -->Sequence highlight and selection in
+              trimmed VARNA 2D structure is incorrect for sequences that
+              do not start at 1
+            </li>
+            <li>
+              <!-- JAL-3061 -->'.' inserted into RNA secondary structure
+              annotation when columns are inserted into an alignment,
+              and when exporting as Stockolm flatfile.
+            </li>
+            <li>
+              <!-- JAL-3053 -->Jalview annotation rows containing upper
+              and lower-case 'E' and 'H' do not automatically get
+              treated as RNA secondary structure
+            </li>
+          </ul>
+          <em>Java 10 Issues</em>
+          <ul>
+            <li>
+              <!-- JAL-2988 -->OSX - Can't save new files via the File
+              or export menus by typing in a name into the Save dialog
+              box.
+            </li>
+          </ul>
+      </div>
+    </td>
+    </tr>
+    <tr>
       <td width="60" nowrap>
         <div align="center">
           <strong><a name="Jalview.2.10.4b1">2.10.4b1</a><br />
@@ -111,7 +194,8 @@ li:before {
               annotation added to view (Windows)
             </li>
             <li>
-              <!-- JAL-3009 -->Jalview Desktop is slow to start up when network connectivity is poor
+              <!-- JAL-3009 -->Jalview Desktop is slow to start up when
+              network connectivity is poor
             </li>
             <li>
               <!-- JAL-1460 -->Drag URL from chrome, firefox, IE to
index ed6f5c2..4dfc54e 100755 (executable)
 </head>
 <body>
   <p>
-    <strong>What's new in Jalview 2.10.4b1 ?</strong>
+    <strong>What's new in Jalview 2.10.5 ?</strong>
   </p>
-  <p>This is the first patch release for Jalview 2.10.4. It includes
-    the following new patches:</p>
+  <p>Jalview 2.10.5 is a minor release that includes critical
+    patches for users working with Ensembl, and RNA secondary structure
+    annotation.</p>
   <ul>
-    <li>HGVS nomenclature used for variant annotation retrieved
-      from Uniprot</li>
-    <li>Uniprot import fails for some sequences (Cannot import
-      features with multiple variant elements)</li>
-    <li>Clustal files with sequence positions in right-hand column
-      are now parsed correctly</li>
-    <li>Wrap view - export to SVG - IDs shown but not alignment
-      area in exported graphic</li>
-    <li>F2/Keyboard mode edits work when Overview window has input
-      focus</li>
-    <li>Windows specific fixes:
-      <ul>
-        <li>Annotation panel set too high when annotation added to
-          view</li>
-        <li>Updated search paths for Chimera default installation</li>
-        <li>Windows File Shortcuts can be dragged onto the Jalview
-          Desktop</li>
-        <li>Drag URL from Chrome, Firefox, IE to Jalview desktop on
-          Windows doesn't open file:<br /> Dragging the currently open
-          URL and links from a page viewed in Firefox or Chrome on
-          Windows is now fully supported.<br />
-        <strong>If you are using Edge</strong>, only links in the page
-          can be dragged.<br />
-        <strong>With Internet Explorer</strong>, only the currently open
-          URL in the browser can be dropped onto Jalview.
-        </li>
-      </ul>
+    <li>EPS, PNG and SVG export now includes hidden sequence
+      markers, and representative sequences are marked in bold.</li>
+    <li>Ensembl Client updated for Ensembl Rest API v7.<br />The
+      latest Ensembl API is not backwards compatible with earlier
+      versions of Jalview, so if you require Ensembl functionality you
+      will need to install this release.
     </li>
-  </ul>
-  <p>Highlights in the 2.10.4 series include:</p>
-  <ul>
-    <li>Numerous efficiency improvements in the renderer and overview when working with large alignments with lots of hidden columns</li>
-    <li>Use of HTTPS when connecting to Uniprot, Ensembl and other EBI web services</li>
-    <li>Critical patches for running Jalview on OSX with Java 10</li>
-    <li>Easier adjustment of the Alignment ID panel and Annotation panel</li>
-    <li>Improved support for mapping between 3D Structures and Uniprot Protein Sequences</li>
-    <li>Improved support for discovering CDS and transcripts for Proteins and Ensembl gene IDs</li>
-    <li>New buttons on the Structure Chooser for adding structures
-      to an existing view, and disabling automatic superposition
-      according to linked alignments</li>
-    <li>Annotation transfer between Chimera and Jalview <em>(formerly only
-        available in 'Experimental' mode)</em></li>
+    <li>Improved support for VIENNA extended dot-bracket notation
+      for RNA secondary structure</li>
+    <li>Positional and selected region highlighting in VARNA
+      'trimmed sequence' view now more reliable</li>
   </ul>
   <p>
-    The full list of bugs fixed in this release can be found in the <a href="releases.html#Jalview.2.10.4">2.10.4
-      Release Notes</a>. 
-  </p>
+    The full list of bugs fixed in this release can be found in the <a
+      href="releases.html#Jalview.2.10.5">2.10.5 Release Notes</a>. The majority of improvement in 2.10.5 are due to Jalview users
+    contacting us via the jalview-discuss email list. Thanks to everyone
+    who took the time to do this !</p>
+    <p><strong>Jalview and Java 10</strong></p>
+  <p>This release addresses a critical bug for OSX users who are
+    running Jalview with Java 10 which can prevent files being saved
+    correctly through the 'Save As' dialog box. The 'Save As' dialog for
+    Jalview's Groovy Console remains affected - we hope to address this
+    in the next major release.</p>
 </body>
 </html>
diff --git a/lib/VAqua4.jar b/lib/VAqua4.jar
deleted file mode 100644 (file)
index c1e7cfc..0000000
Binary files a/lib/VAqua4.jar and /dev/null differ
diff --git a/lib/VAqua5.jar b/lib/VAqua5.jar
new file mode 100644 (file)
index 0000000..b25e343
Binary files /dev/null and b/lib/VAqua5.jar differ
index 0d39abf..e5cda93 100644 (file)
@@ -440,8 +440,8 @@ public class Rna
       /*
        * catch things like <<..<<..>>..<<..>>>> |
        */
-      int j = bps.size() - 1;
-      while (j >= 0)
+      int j = bps.size();
+      while (--j >= 0)
       {
         int popen = bps.get(j).getBP5();
 
@@ -460,7 +460,6 @@ public class Rna
             break;
           }
         }
-        j -= 1;
       }
 
       // Put positions and helix information into the hashtable
index 0098d76..ee9389c 100755 (executable)
@@ -25,6 +25,7 @@ import jalview.analysis.SecStrConsensus.SimpleBP;
 import jalview.analysis.WUSSParseException;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -164,6 +165,64 @@ public class AlignmentAnnotation
   }
 
   /**
+   * Get the RNA Secondary Structure SequenceFeature Array if present
+   */
+  public SequenceFeature[] getRnaSecondaryStructure()
+  {
+    return this._rnasecstr;
+  }
+
+  /**
+   * Check the RNA Secondary Structure is equivalent to one in given
+   * AlignmentAnnotation param
+   */
+  public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that)
+  {
+    return rnaSecondaryStructureEquivalent(that, true);
+  }
+
+  public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that, boolean compareType)
+  {
+    SequenceFeature[] thisSfArray = this.getRnaSecondaryStructure();
+    SequenceFeature[] thatSfArray = that.getRnaSecondaryStructure();
+    if (thisSfArray == null || thatSfArray == null)
+    {
+      return thisSfArray == null && thatSfArray == null;
+    }
+    if (thisSfArray.length != thatSfArray.length)
+    {
+      return false;
+    }
+    Arrays.sort(thisSfArray, new SFSortByEnd()); // probably already sorted
+                                                   // like this
+    Arrays.sort(thatSfArray, new SFSortByEnd()); // probably already sorted
+                                                   // like this
+    for (int i=0; i < thisSfArray.length; i++) {
+      SequenceFeature thisSf = thisSfArray[i];
+      SequenceFeature thatSf = thatSfArray[i];
+      if (compareType) {
+        if (thisSf.getType() == null || thatSf.getType() == null) {
+          if (thisSf.getType() == null && thatSf.getType() == null) {
+            continue;
+          } else {
+            return false;
+          }
+        }
+        if (! thisSf.getType().equals(thatSf.getType())) {
+          return false;
+        }
+      }
+      if (!(thisSf.getBegin() == thatSf.getBegin()
+              && thisSf.getEnd() == thatSf.getEnd()))
+      {
+        return false;
+      }
+    }
+    return true;
+
+  }
+
+  /**
    * map of positions in the associated annotation
    */
   private Map<Integer, Annotation> sequenceMapping;
@@ -294,6 +353,7 @@ public class AlignmentAnnotation
     char firstChar = 0;
     for (int i = 0; i < annotations.length; i++)
     {
+      // DEBUG System.out.println(i + ": " + annotations[i]);
       if (annotations[i] == null)
       {
         continue;
@@ -301,12 +361,15 @@ public class AlignmentAnnotation
       if (annotations[i].secondaryStructure == 'H'
               || annotations[i].secondaryStructure == 'E')
       {
+        // DEBUG System.out.println( "/H|E/ '" +
+        // annotations[i].secondaryStructure + "'");
         hasIcons |= true;
       }
       else
       // Check for RNA secondary structure
       {
-        // System.out.println(annotations[i].secondaryStructure);
+        // DEBUG System.out.println( "/else/ '" +
+        // annotations[i].secondaryStructure + "'");
         // TODO: 2.8.2 should this ss symbol validation check be a function in
         // RNA/ResidueProperties ?
         if (annotations[i].secondaryStructure == '('
@@ -317,10 +380,12 @@ public class AlignmentAnnotation
                 || annotations[i].secondaryStructure == 'B'
                 || annotations[i].secondaryStructure == 'C'
                 || annotations[i].secondaryStructure == 'D'
-                || annotations[i].secondaryStructure == 'E'
+                // || annotations[i].secondaryStructure == 'E' // ambiguous on
+                // its own -- already checked above
                 || annotations[i].secondaryStructure == 'F'
                 || annotations[i].secondaryStructure == 'G'
-                || annotations[i].secondaryStructure == 'H'
+                // || annotations[i].secondaryStructure == 'H' // ambiguous on
+                // its own -- already checked above
                 || annotations[i].secondaryStructure == 'I'
                 || annotations[i].secondaryStructure == 'J'
                 || annotations[i].secondaryStructure == 'K'
@@ -367,7 +432,7 @@ public class AlignmentAnnotation
         // &&
         // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
                 firstChar != ' ' && firstChar != '$' && firstChar != 0xCE
-                && firstChar != '(' && firstChar != '[' && firstChar != '>'
+                && firstChar != '(' && firstChar != '[' && firstChar != '<'
                 && firstChar != '{' && firstChar != 'A' && firstChar != 'B'
                 && firstChar != 'C' && firstChar != 'D' && firstChar != 'E'
                 && firstChar != 'F' && firstChar != 'G' && firstChar != 'H'
@@ -1648,4 +1713,5 @@ public class AlignmentAnnotation
     }
     return aa;
   }
+
 }
index ae29417..f6919cd 100755 (executable)
@@ -210,7 +210,10 @@ public class Annotation
     return ((value == 0f)
             && ((description == null) || (description.trim().length() == 0))
             && ((displayCharacter == null)
-                    || (displayCharacter.trim().length() == 0))
+                    || (displayCharacter.trim().length() == 0)
+                    || (displayCharacter.equals(" ."))) // RNA Stockholm blank
+                                                        // displayCharacter can
+                                                        // end up like this
             && (secondaryStructure == '\0' || (secondaryStructure == ' '))
             && colour == null);
   }
index 9c4087e..f17bd33 100755 (executable)
@@ -22,6 +22,7 @@ package jalview.datamodel;
 
 import jalview.datamodel.features.FeatureLocationI;
 
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -157,7 +158,7 @@ public class SequenceFeature implements FeatureLocationI
 
     if (sf.otherDetails != null)
     {
-      otherDetails = new HashMap<String, Object>();
+      otherDetails = new HashMap<>();
       for (Entry<String, Object> entry : sf.otherDetails.entrySet())
       {
         otherDetails.put(entry.getKey(), entry.getValue());
@@ -165,7 +166,7 @@ public class SequenceFeature implements FeatureLocationI
     }
     if (sf.links != null && sf.links.size() > 0)
     {
-      links = new Vector<String>();
+      links = new Vector<>();
       for (int i = 0, iSize = sf.links.size(); i < iSize; i++)
       {
         links.addElement(sf.links.elementAt(i));
@@ -332,7 +333,7 @@ public class SequenceFeature implements FeatureLocationI
   {
     if (links == null)
     {
-      links = new Vector<String>();
+      links = new Vector<>();
     }
 
     if (!links.contains(labelLink))
@@ -394,7 +395,7 @@ public class SequenceFeature implements FeatureLocationI
     {
       if (otherDetails == null)
       {
-        otherDetails = new HashMap<String, Object>();
+        otherDetails = new HashMap<>();
       }
 
       otherDetails.put(key, value);
@@ -536,3 +537,21 @@ public class SequenceFeature implements FeatureLocationI
     return begin == 0 && end == 0;
   }
 }
+
+class SFSortByEnd implements Comparator<SequenceFeature>
+{
+  @Override
+  public int compare(SequenceFeature a, SequenceFeature b)
+  {
+    return a.getEnd() - b.getEnd();
+  }
+}
+
+class SFSortByBegin implements Comparator<SequenceFeature>
+{
+  @Override
+  public int compare(SequenceFeature a, SequenceFeature b)
+  {
+    return a.getBegin() - b.getBegin();
+  }
+}
index cb6f548..582eac6 100644 (file)
@@ -22,9 +22,11 @@ package jalview.ext.ensembl;
 
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
+import jalview.io.DataSourceType;
 import jalview.io.FeaturesFile;
 import jalview.io.FileParse;
 
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -84,12 +86,13 @@ class EnsemblFeatures extends EnsemblRestClient
     // TODO: use a vararg String... for getSequenceRecords instead?
     List<String> queries = new ArrayList<>();
     queries.add(query);
-    FileParse fp = getSequenceReader(queries);
-    if (fp == null || !fp.isValid())
+    BufferedReader fp = getSequenceReader(queries);
+    if (fp == null)
     {
       return null;
     }
-    FeaturesFile fr = new FeaturesFile(fp);
+    FeaturesFile fr = new FeaturesFile(
+            new FileParse(fp, null, DataSourceType.URL));
     return new Alignment(fr.getSeqsAsArray());
   }
 
@@ -140,13 +143,13 @@ class EnsemblFeatures extends EnsemblRestClient
    * describes the required encoding of the response.
    */
   @Override
-  protected String getRequestMimeType(boolean multipleIds)
+  protected String getRequestMimeType()
   {
     return "text/x-gff3";
   }
 
   /**
-   * Returns the MIME type for GFF3.
+   * Returns the MIME type for GFF3
    */
   @Override
   protected String getResponseMimeType()
index 877331d..d7f1b07 100644 (file)
@@ -110,18 +110,6 @@ public class EnsemblLookup extends EnsemblRestClient
     return true;
   }
 
-  @Override
-  protected String getRequestMimeType(boolean multipleIds)
-  {
-    return "application/json";
-  }
-
-  @Override
-  protected String getResponseMimeType()
-  {
-    return "application/json";
-  }
-
   /**
    * Returns the gene id related to the given identifier, which may be for a
    * gene, transcript or protein
diff --git a/src/jalview/ext/ensembl/EnsemblMap.java b/src/jalview/ext/ensembl/EnsemblMap.java
new file mode 100644 (file)
index 0000000..c94528e
--- /dev/null
@@ -0,0 +1,213 @@
+package jalview.ext.ensembl;
+
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.DBRefSource;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.List;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+public class EnsemblMap extends EnsemblRestClient
+{
+  private static final String MAPPED = "mapped";
+
+  private static final String MAPPINGS = "mappings";
+
+  /**
+   * Default constructor (to use rest.ensembl.org)
+   */
+  public EnsemblMap()
+  {
+    super();
+  }
+
+  /**
+   * Constructor given the target domain to fetch data from
+   * 
+   * @param
+   */
+  public EnsemblMap(String domain)
+  {
+    super(domain);
+  }
+
+  @Override
+  public String getDbName()
+  {
+    return DBRefSource.ENSEMBL;
+  }
+
+  @Override
+  public AlignmentI getSequenceRecords(String queries) throws Exception
+  {
+    return null; // not used
+  }
+
+  /**
+   * Constructs a URL of the format <code>
+   * http://rest.ensembl.org/map/human/GRCh38/17:45051610..45109016:1/GRCh37?content-type=application/json
+   * </code>
+   * 
+   * @param species
+   * @param chromosome
+   * @param fromRef
+   * @param toRef
+   * @param startPos
+   * @param endPos
+   * @return
+   * @throws MalformedURLException
+   */
+  protected URL getAssemblyMapUrl(String species, String chromosome, String fromRef,
+          String toRef, int startPos, int endPos)
+          throws MalformedURLException
+  {
+    /*
+     * start-end might be reverse strand - present forwards to the service
+     */
+    boolean forward = startPos <= endPos;
+    int start = forward ? startPos : endPos;
+    int end = forward ? endPos : startPos;
+    String strand = forward ? "1" : "-1";
+    String url = String.format(
+            "%s/map/%s/%s/%s:%d..%d:%s/%s?content-type=application/json",
+            getDomain(), species, fromRef, chromosome, start, end, strand,
+            toRef);
+    return new URL(url);
+  }
+
+  @Override
+  protected boolean useGetRequest()
+  {
+    return true;
+  }
+
+  @Override
+  protected URL getUrl(List<String> ids) throws MalformedURLException
+  {
+    return null; // not used
+  }
+
+  /**
+   * Calls the REST /map service to get the chromosomal coordinates (start/end)
+   * in 'toRef' that corresponding to the (start/end) queryRange in 'fromRef'
+   * 
+   * @param species
+   * @param chromosome
+   * @param fromRef
+   * @param toRef
+   * @param queryRange
+   * @return
+   * @see http://rest.ensemblgenomes.org/documentation/info/assembly_map
+   */
+  public int[] getAssemblyMapping(String species, String chromosome,
+          String fromRef, String toRef, int[] queryRange)
+  {
+    URL url = null;
+    BufferedReader br = null;
+
+    try
+    {
+      url = getAssemblyMapUrl(species, chromosome, fromRef, toRef, queryRange[0],
+              queryRange[1]);
+      br = getHttpResponse(url, null);
+      return (parseAssemblyMappingResponse(br));
+    } catch (Throwable t)
+    {
+      System.out.println("Error calling " + url + ": " + t.getMessage());
+      return null;
+    } finally
+    {
+      if (br != null)
+      {
+        try
+        {
+          br.close();
+        } catch (IOException e)
+        {
+          // ignore
+        }
+      }
+    }
+  }
+
+  /**
+   * Parses the JSON response from the /map/&lt;species&gt;/ REST service. The
+   * format is (with some fields omitted)
+   * 
+   * <pre>
+   *  {"mappings": 
+   *    [{
+   *       "original": {"end":45109016,"start":45051610},
+   *       "mapped"  : {"end":43186384,"start":43128978} 
+   *  }] }
+   * </pre>
+   * 
+   * @param br
+   * @return
+   */
+  protected int[] parseAssemblyMappingResponse(BufferedReader br)
+  {
+    int[] result = null;
+    JSONParser jp = new JSONParser();
+
+    try
+    {
+      JSONObject parsed = (JSONObject) jp.parse(br);
+      JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
+
+      Iterator rvals = mappings.iterator();
+      while (rvals.hasNext())
+      {
+        // todo check for "mapped"
+        JSONObject val = (JSONObject) rvals.next();
+        JSONObject mapped = (JSONObject) val.get(MAPPED);
+        int start = Integer.parseInt(mapped.get("start").toString());
+        int end = Integer.parseInt(mapped.get("end").toString());
+        String strand = mapped.get("strand").toString();
+        if ("1".equals(strand))
+        {
+          result = new int[] { start, end };
+        }
+        else
+        {
+          result = new int[] { end, start };
+        }
+      }
+    } catch (IOException | ParseException | NumberFormatException e)
+    {
+      // ignore
+    }
+    return result;
+  }
+
+  /**
+   * Constructs a URL to the /map/cds/<id> or /map/cdna/<id> REST service. The
+   * REST call is to either ensembl or ensemblgenomes, as determined from the
+   * division, e.g. Ensembl or EnsemblProtists.
+   * 
+   * @param domain
+   * @param accession
+   * @param start
+   * @param end
+   * @param cdsOrCdna
+   * @return
+   * @throws MalformedURLException
+   */
+  URL getIdMapUrl(String domain, String accession, int start, int end,
+          String cdsOrCdna) throws MalformedURLException
+  {
+    String url = String
+            .format("%s/map/%s/%s/%d..%d?include_original_region=1&content-type=application/json",
+                    domain, cdsOrCdna, accession, start, end);
+    return new URL(url);
+  }
+
+}
index b19f557..e6b1264 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.ext.ensembl;
 
-import jalview.io.DataSourceType;
-import jalview.io.FileParse;
 import jalview.util.StringUtils;
 
 import java.io.BufferedReader;
@@ -153,22 +151,28 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
   protected abstract boolean useGetRequest();
 
   /**
-   * Return the desired value for the Content-Type request header
-   * 
-   * @param multipleIds
+   * Returns the desired value for the Content-Type request header. Default is
+   * application/json, override if required to vary this.
    * 
    * @return
    * @see https://github.com/Ensembl/ensembl-rest/wiki/HTTP-Headers
    */
-  protected abstract String getRequestMimeType(boolean multipleIds);
+  protected String getRequestMimeType()
+  {
+    return "application/json";
+  }
 
   /**
-   * Return the desired value for the Accept request header
+   * Return the desired value for the Accept request header. Default is
+   * application/json, override if required to vary this.
    * 
    * @return
    * @see https://github.com/Ensembl/ensembl-rest/wiki/HTTP-Headers
    */
-  protected abstract String getResponseMimeType();
+  protected String getResponseMimeType()
+  {
+    return "application/json";
+  }
 
   /**
    * Checks Ensembl's REST 'ping' endpoint, and returns true if response
@@ -222,25 +226,20 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
   }
 
   /**
-   * returns a reader to a Fasta response from the Ensembl sequence endpoint
+   * Returns a reader to a (Json) response from the Ensembl sequence endpoint.
+   * If the request failed the return value may be null.
    * 
    * @param ids
    * @return
    * @throws IOException
    */
-  protected FileParse getSequenceReader(List<String> ids) throws IOException
+  protected BufferedReader getSequenceReader(List<String> ids)
+          throws IOException
   {
     URL url = getUrl(ids);
 
     BufferedReader reader = getHttpResponse(url, ids);
-    if (reader == null)
-    {
-      // request failed
-      return null;
-    }
-    FileParse fp = new FileParse(reader, url.toString(),
-            DataSourceType.URL);
-    return fp;
+    return reader;
   }
 
   /**
@@ -332,8 +331,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
     boolean multipleIds = ids != null && ids.size() > 1;
     connection.setRequestMethod(
             multipleIds ? HttpMethod.POST : HttpMethod.GET);
-    connection.setRequestProperty("Content-Type",
-            getRequestMimeType(multipleIds));
+    connection.setRequestProperty("Content-Type", getRequestMimeType());
     connection.setRequestProperty("Accept", getResponseMimeType());
 
     connection.setUseCaches(false);
index b2ebb1a..c903de3 100644 (file)
@@ -28,12 +28,11 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.DBRefSource;
 import jalview.datamodel.Mapping;
+import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.SequenceFeatures;
 import jalview.exceptions.JalviewException;
-import jalview.io.FastaFile;
-import jalview.io.FileParse;
 import jalview.io.gff.SequenceOntologyFactory;
 import jalview.io.gff.SequenceOntologyI;
 import jalview.util.Comparison;
@@ -41,6 +40,7 @@ import jalview.util.DBRefUtils;
 import jalview.util.IntRangeComparator;
 import jalview.util.MapList;
 
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -49,6 +49,10 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
 /**
  * Base class for Ensembl sequence fetchers
  * 
@@ -386,50 +390,44 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
       inProgress = false;
       throw new JalviewException("ENSEMBL Rest API not available.");
     }
-    FileParse fp = getSequenceReader(ids);
-    if (fp == null)
+    BufferedReader br = getSequenceReader(ids);
+    if (br == null)
     {
       return alignment;
     }
 
-    FastaFile fr = new FastaFile(fp);
-    if (fr.hasWarningMessage())
+    List<SequenceI> seqs = parseSequenceJson(br);
+
+    if (seqs.isEmpty())
     {
-      System.out.println(
-              String.format("Warning when retrieving %d ids %s\n%s",
-                      ids.size(), ids.toString(), fr.getWarningMessage()));
+      throw new IOException("No data returned for " + ids);
     }
-    else if (fr.getSeqs().size() != ids.size())
+
+    if (seqs.size() != ids.size())
     {
       System.out.println(String.format(
               "Only retrieved %d sequences for %d query strings",
-              fr.getSeqs().size(), ids.size()));
+              seqs.size(), ids.size()));
     }
 
-    if (fr.getSeqs().size() == 1 && fr.getSeqs().get(0).getLength() == 0)
+    if (!seqs.isEmpty())
     {
-      /*
-       * POST request has returned an empty FASTA file e.g. for invalid id
-       */
-      throw new IOException("No data returned for " + ids);
-    }
-
-    if (fr.getSeqs().size() > 0)
-    {
-      AlignmentI seqal = new Alignment(fr.getSeqsAsArray());
-      for (SequenceI sq : seqal.getSequences())
+      AlignmentI seqal = new Alignment(
+              seqs.toArray(new SequenceI[seqs.size()]));
+      for (SequenceI seq : seqs)
       {
-        if (sq.getDescription() == null)
+        if (seq.getDescription() == null)
         {
-          sq.setDescription(getDbName());
+          seq.setDescription(getDbName());
         }
-        String name = sq.getName();
+        String name = seq.getName();
         if (ids.contains(name)
                 || ids.contains(name.replace("ENSP", "ENST")))
         {
-          DBRefEntry dbref = DBRefUtils.parseToDbRef(sq, getDbSource(),
+          // TODO JAL-3077 use true accession version in dbref
+          DBRefEntry dbref = DBRefUtils.parseToDbRef(seq, getDbSource(),
                   getEnsemblDataVersion(), name);
-          sq.addDBRef(dbref);
+          seq.addDBRef(dbref);
         }
       }
       if (alignment == null)
@@ -445,6 +443,49 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
   }
 
   /**
+   * Parses a JSON response into a list of sequences
+   * 
+   * @param br
+   * @return
+   * @see http://rest.ensembl.org/documentation/info/sequence_id
+   */
+  protected List<SequenceI> parseSequenceJson(BufferedReader br)
+  {
+    JSONParser jp = new JSONParser();
+    List<SequenceI> result = new ArrayList<>();
+    try
+    {
+      /*
+       * for now, assumes only one sequence returned; refactor if needed
+       * in future to handle a JSONArray with more than one
+       */
+      final JSONObject val = (JSONObject) jp.parse(br);
+      Object s = val.get("desc");
+      String desc = s == null ? null : s.toString();
+      s = val.get("id");
+      String id = s == null ? null : s.toString();
+      s = val.get("seq");
+      String seq = s == null ? null : s.toString();
+      Sequence sequence = new Sequence(id, seq);
+      if (desc != null)
+      {
+        sequence.setDescription(desc);
+      }
+      // todo JAL-3077 make a DBRefEntry with true accession version
+      // s = val.get("version");
+      // String version = s == null ? "0" : s.toString();
+      // DBRefEntry dbref = new DBRefEntry(getDbSource(), version, id);
+      // sequence.addDBRef(dbref);
+      result.add(sequence);
+    } catch (ParseException | IOException e)
+    {
+      System.err.println("Error processing JSON response: " + e.toString());
+      // ignore
+    }
+    return result;
+  }
+
+  /**
    * Returns the URL for the REST call
    * 
    * @return
@@ -465,7 +506,8 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
     }
     // @see https://github.com/Ensembl/ensembl-rest/wiki/Output-formats
     urlstring.append("?type=").append(getSourceEnsemblType().getType());
-    urlstring.append(("&Accept=text/x-fasta"));
+    urlstring.append(("&Accept=application/json"));
+    urlstring.append(("&Content-Type=application/json"));
 
     String objectType = getObjectType();
     if (objectType != null)
@@ -505,18 +547,6 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
     return false;
   }
 
-  @Override
-  protected String getRequestMimeType(boolean multipleIds)
-  {
-    return multipleIds ? "application/json" : "text/x-fasta";
-  }
-
-  @Override
-  protected String getResponseMimeType()
-  {
-    return "text/x-fasta";
-  }
-
   /**
    * 
    * @return the configured sequence return type for this source
index 27c448e..77768a6 100644 (file)
@@ -88,18 +88,6 @@ class EnsemblXref extends EnsemblRestClient
     return true;
   }
 
-  @Override
-  protected String getRequestMimeType(boolean multipleIds)
-  {
-    return "application/json";
-  }
-
-  @Override
-  protected String getResponseMimeType()
-  {
-    return "application/json";
-  }
-
   /**
    * Calls the Ensembl xrefs REST endpoint and retrieves any cross-references
    * ("primary_id") for the given identifier (Ensembl accession id) and database
@@ -113,8 +101,8 @@ class EnsemblXref extends EnsemblRestClient
    */
   public List<DBRefEntry> getCrossReferences(String identifier)
   {
-    List<DBRefEntry> result = new ArrayList<DBRefEntry>();
-    List<String> ids = new ArrayList<String>();
+    List<DBRefEntry> result = new ArrayList<>();
+    List<String> ids = new ArrayList<>();
     ids.add(identifier);
 
     BufferedReader br = null;
@@ -163,7 +151,7 @@ class EnsemblXref extends EnsemblRestClient
           throws IOException
   {
     JSONParser jp = new JSONParser();
-    List<DBRefEntry> result = new ArrayList<DBRefEntry>();
+    List<DBRefEntry> result = new ArrayList<>();
     try
     {
       JSONArray responses = (JSONArray) jp.parse(br);
index 2c5684a..c46a2ab 100644 (file)
@@ -48,6 +48,7 @@ import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
+import java.awt.Graphics2D;
 import java.awt.event.AdjustmentEvent;
 import java.awt.event.AdjustmentListener;
 import java.awt.event.ComponentAdapter;
@@ -942,30 +943,16 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param pg
-   *          DOCUMENT ME!
-   * @param pwidth
-   *          DOCUMENT ME!
-   * @param pheight
-   *          DOCUMENT ME!
-   * @param pi
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   * 
-   * @throws PrinterException
-   *           DOCUMENT ME!
-   */
-  /**
    * Draws the alignment image, including sequence ids, sequences, and
    * annotation labels and annotations if shown, on either one or two Graphics
-   * context.
+   * contexts.
    * 
    * @param pageWidth
+   *          in pixels
    * @param pageHeight
-   * @param pi
+   *          in pixels
+   * @param pageIndex
+   *          (0, 1, ...)
    * @param idGraphics
    *          the graphics context for sequence ids and annotation labels
    * @param alignmentGraphics
@@ -974,7 +961,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * @return
    * @throws PrinterException
    */
-  public int printUnwrapped(int pageWidth, int pageHeight, int pi,
+  public int printUnwrapped(int pageWidth, int pageHeight, int pageIndex,
           Graphics idGraphics, Graphics alignmentGraphics)
           throws PrinterException
   {
@@ -988,8 +975,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
             : idWidth;
 
     FontMetrics fm = getFontMetrics(av.getFont());
-    int charHeight = av.getCharHeight();
-    int scaleHeight = charHeight + fm.getDescent();
+    final int charHeight = av.getCharHeight();
+    final int scaleHeight = charHeight + fm.getDescent();
 
     idGraphics.setColor(Color.white);
     idGraphics.fillRect(0, 0, pageWidth, pageHeight);
@@ -998,29 +985,20 @@ public class AlignmentPanel extends GAlignmentPanel implements
     /*
      * How many sequences and residues can we fit on a printable page?
      */
-    int totalRes = (pageWidth - idWidth) / av.getCharWidth();
+    final int totalRes = (pageWidth - idWidth) / av.getCharWidth();
 
-    int totalSeq = (pageHeight - scaleHeight) / charHeight - 1;
+    final int totalSeq = (pageHeight - scaleHeight) / charHeight - 1;
 
-    int alignmentWidth = av.getAlignment().getWidth();
-    int pagesWide = (alignmentWidth / totalRes) + 1;
+    final int alignmentWidth = av.getAlignment().getWidth();
+    final int pagesWide = (alignmentWidth / totalRes) + 1;
 
-    final int startRes = (pi % pagesWide) * totalRes;
-    int endRes = (startRes + totalRes) - 1;
+    final int startRes = (pageIndex % pagesWide) * totalRes;
+    final int endRes = Math.min(startRes + totalRes - 1,
+            alignmentWidth - 1);
 
-    if (endRes > (alignmentWidth - 1))
-    {
-      endRes = alignmentWidth - 1;
-    }
-
-    final int startSeq = (pi / pagesWide) * totalSeq;
-    int endSeq = startSeq + totalSeq;
-
-    int alignmentHeight = av.getAlignment().getHeight();
-    if (endSeq > alignmentHeight)
-    {
-      endSeq = alignmentHeight;
-    }
+    final int startSeq = (pageIndex / pagesWide) * totalSeq;
+    final int alignmentHeight = av.getAlignment().getHeight();
+    final int endSeq = Math.min(startSeq + totalSeq, alignmentHeight);
 
     int pagesHigh = ((alignmentHeight / totalSeq) + 1) * pageHeight;
 
@@ -1031,7 +1009,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     pagesHigh /= pageHeight;
 
-    if (pi >= (pagesWide * pagesHigh))
+    if (pageIndex >= (pagesWide * pagesHigh))
     {
       return Printable.NO_SUCH_PAGE;
     }
@@ -1050,47 +1028,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
      * then reset to top left (0, 0)
      */
     idGraphics.translate(0, scaleHeight);
-    idGraphics.setFont(getIdPanel().getIdCanvas().getIdfont());
-    Color currentColor = null;
-    Color currentTextColor = null;
-
-    SequenceI seq;
-    for (int i = startSeq; i < endSeq; i++)
-    {
-      seq = av.getAlignment().getSequenceAt(i);
-      if ((av.getSelectionGroup() != null)
-              && av.getSelectionGroup().getSequences(null).contains(seq))
-      {
-        /*
-         * gray out ids of sequences in selection group (if any)
-         */
-        currentColor = Color.gray;
-        currentTextColor = Color.black;
-      }
-      else
-      {
-        currentColor = av.getSequenceColour(seq);
-        currentTextColor = Color.black;
-      }
-
-      idGraphics.setColor(currentColor);
-      idGraphics.fillRect(0, (i - startSeq) * charHeight, idWidth,
-              charHeight);
-
-      idGraphics.setColor(currentTextColor);
+    IdCanvas idCanvas = getIdPanel().getIdCanvas();
+    List<SequenceI> selection = av.getSelectionGroup() == null ? null
+            : av.getSelectionGroup().getSequences(null);
+    idCanvas.drawIds((Graphics2D) idGraphics, av, startSeq, endSeq - 1,
+            selection);
 
-      int xPos = 0;
-      String displayId = seq.getDisplayId(av.getShowJVSuffix());
-      if (av.isRightAlignIds())
-      {
-        fm = idGraphics.getFontMetrics();
-        xPos = idWidth - fm.stringWidth(displayId) - 4;
-      }
-
-      idGraphics.drawString(displayId, xPos,
-              (((i - startSeq) * charHeight) + charHeight)
-                      - (charHeight / 5));
-    }
     idGraphics.setFont(av.getFont());
     idGraphics.translate(0, -scaleHeight);
 
@@ -1100,7 +1043,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
      */
     alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight);
     getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics, startRes,
-            endRes, startSeq, endSeq);
+            endRes, startSeq, endSeq - 1);
     alignmentGraphics.translate(-alignmentGraphicsOffset, 0);
 
     if (av.isShowAnnotation() && (endSeq == alignmentHeight))
@@ -1130,31 +1073,27 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
-   * DOCUMENT ME!
+   * Prints one page of an alignment in wrapped mode. Returns
+   * Printable.PAGE_EXISTS (0) if a page was drawn, or Printable.NO_SUCH_PAGE if
+   * no page could be drawn (page number out of range).
    * 
-   * @param pg
-   *          DOCUMENT ME!
-   * @param pwidth
-   *          DOCUMENT ME!
-   * @param pheight
-   *          DOCUMENT ME!
-   * @param pi
-   *          DOCUMENT ME!
+   * @param pageWidth
+   * @param pageHeight
+   * @param pageNumber
+   *          (0, 1, ...)
+   * @param g
    * 
-   * @return DOCUMENT ME!
+   * @return
    * 
    * @throws PrinterException
-   *           DOCUMENT ME!
    */
-  public int printWrappedAlignment(int pwidth, int pheight, int pi,
-          Graphics pg) throws PrinterException
+  public int printWrappedAlignment(int pageWidth, int pageHeight, int pageNumber,
+          Graphics g) throws PrinterException
   {
     int annotationHeight = 0;
-    AnnotationLabels labels = null;
     if (av.isShowAnnotation())
     {
       annotationHeight = getAnnotationPanel().adjustPanelHeight();
-      labels = new AnnotationLabels(av);
     }
 
     int hgap = av.getCharHeight();
@@ -1176,64 +1115,39 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
 
     int resWidth = getSeqPanel().seqCanvas
-            .getWrappedCanvasWidth(pwidth - idWidth);
+            .getWrappedCanvasWidth(pageWidth - idWidth);
 
     int totalHeight = cHeight * (maxwidth / resWidth + 1);
 
-    pg.setColor(Color.white);
-    pg.fillRect(0, 0, pwidth, pheight);
-    pg.setFont(av.getFont());
+    g.setColor(Color.white);
+    g.fillRect(0, 0, pageWidth, pageHeight);
+    g.setFont(av.getFont());
+    g.setColor(Color.black);
 
-    // //////////////
-    // Draw the ids
-    pg.setColor(Color.black);
-
-    pg.translate(0, -pi * pheight);
-
-    pg.setClip(0, pi * pheight, pwidth, pheight);
-
-    int ypos = hgap;
-
-    do
-    {
-      for (int i = 0; i < av.getAlignment().getHeight(); i++)
-      {
-        pg.setFont(getIdPanel().getIdCanvas().getIdfont());
-        SequenceI s = av.getAlignment().getSequenceAt(i);
-        String string = s.getDisplayId(av.getShowJVSuffix());
-        int xPos = 0;
-        if (av.isRightAlignIds())
-        {
-          FontMetrics fm = pg.getFontMetrics();
-          xPos = idWidth - fm.stringWidth(string) - 4;
-        }
-        pg.drawString(string, xPos,
-                ((i * av.getCharHeight()) + ypos + av.getCharHeight())
-                        - (av.getCharHeight() / 5));
-      }
-      if (labels != null)
-      {
-        pg.translate(-3, ypos
-                + (av.getAlignment().getHeight() * av.getCharHeight()));
+    /*
+     * method: print the whole wrapped alignment, but with a clip region that
+     * is restricted to the requested page; this supports selective print of 
+     * single  pages or ranges, (at the cost of some repeated processing in 
+     * the 'normal' case, when all pages are printed)
+     */
+    g.translate(0, -pageNumber * pageHeight);
 
-        pg.setFont(av.getFont());
-        labels.drawComponent(pg, idWidth);
-        pg.translate(+3, -ypos
-                - (av.getAlignment().getHeight() * av.getCharHeight()));
-      }
+    g.setClip(0, pageNumber * pageHeight, pageWidth, pageHeight);
 
-      ypos += cHeight;
-    } while (ypos < totalHeight);
+    /*
+     * draw sequence ids and annotation labels (if shown)
+     */
+    IdCanvas idCanvas = getIdPanel().getIdCanvas();
+    idCanvas.drawIdsWrapped((Graphics2D) g, av, 0, totalHeight);
 
-    pg.translate(idWidth, 0);
+    g.translate(idWidth, 0);
 
-    getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(pg, pwidth - idWidth,
+    getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(g, pageWidth - idWidth,
             totalHeight, 0);
 
-    if ((pi * pheight) < totalHeight)
+    if ((pageNumber * pageHeight) < totalHeight)
     {
       return Printable.PAGE_EXISTS;
-
     }
     else
     {
index ea16f23..3a64716 100644 (file)
@@ -120,6 +120,15 @@ public class AppVarna extends JInternalFrame
       }
     }
 
+    /**
+     * highlight a region from start to end (inclusive) on rna
+     * 
+     * @param rna
+     * @param start
+     *          - first base pair index (from 0)
+     * @param end
+     *          - last base pair index (from 0)
+     */
     public void highlightRegion(RNA rna, int start, int end)
     {
       clearLastSelection();
@@ -397,7 +406,8 @@ public class AppVarna extends JInternalFrame
     RnaModel rnaModel = models.get(rna);
     if (rnaModel.seq == sequence)
     {
-      int highlightPos = rnaModel.gapped ? index : position - 1;
+      int highlightPos = rnaModel.gapped ? index
+              : position - sequence.getStart();
       mouseOverHighlighter.highlightRegion(rna, highlightPos, highlightPos);
       vab.updateSelectedRNA(rna);
     }
@@ -418,15 +428,28 @@ public class AppVarna extends JInternalFrame
     {
       return;
     }
-    if (seqsel != null && seqsel.getSize() > 0)
+
+    RnaModel rnaModel = models.get(rna);
+
+    if (seqsel != null && seqsel.getSize() > 0
+            && seqsel.contains(rnaModel.seq))
     {
       int start = seqsel.getStartRes(), end = seqsel.getEndRes();
-      ShiftList shift = offsets.get(rna);
-      if (shift != null)
+      if (rnaModel.gapped)
       {
-        start = shift.shift(start);
-        end = shift.shift(end);
+        ShiftList shift = offsets.get(rna);
+        if (shift != null)
+        {
+          start = shift.shift(start);
+          end = shift.shift(end);
+        }
       }
+      else
+      {
+        start = rnaModel.seq.findPosition(start) - rnaModel.seq.getStart();
+        end = rnaModel.seq.findPosition(end) - rnaModel.seq.getStart();
+      }
+
       selectionHighlighter.highlightRegion(rna, start, end);
       selectionHighlighter.getLastHighlight()
               .setOutlineColor(seqsel.getOutlineColour());
index cd7b0b7..cf88c90 100755 (executable)
@@ -63,10 +63,6 @@ public class IdCanvas extends JPanel implements ViewportListenerI
 
   List<SequenceI> searchResults;
 
-  FontMetrics fm;
-
-  AnnotationLabels labels = null;
-
   AnnotationPanel ap;
 
   private Font idfont;
@@ -88,7 +84,7 @@ public class IdCanvas extends JPanel implements ViewportListenerI
   /**
    * DOCUMENT ME!
    * 
-   * @param gg
+   * @param g
    *          DOCUMENT ME!
    * @param hiddenRows
    *          true - check and display hidden row marker if need be
@@ -101,7 +97,7 @@ public class IdCanvas extends JPanel implements ViewportListenerI
    * @param ypos
    *          DOCUMENT ME!
    */
-  public void drawIdString(Graphics2D gg, boolean hiddenRows, SequenceI s,
+  public void drawIdString(Graphics2D g, boolean hiddenRows, SequenceI s,
           int i, int starty, int ypos)
   {
     int xPos = 0;
@@ -110,39 +106,40 @@ public class IdCanvas extends JPanel implements ViewportListenerI
 
     if ((searchResults != null) && searchResults.contains(s))
     {
-      gg.setColor(Color.black);
-      gg.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
+      g.setColor(Color.black);
+      g.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
               charHeight);
-      gg.setColor(Color.white);
+      g.setColor(Color.white);
     }
     else if ((av.getSelectionGroup() != null)
             && av.getSelectionGroup().getSequences(null).contains(s))
     {
-      gg.setColor(Color.lightGray);
-      gg.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
+      g.setColor(Color.lightGray);
+      g.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
               charHeight);
-      gg.setColor(Color.white);
+      g.setColor(Color.white);
     }
     else
     {
-      gg.setColor(av.getSequenceColour(s));
-      gg.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
+      g.setColor(av.getSequenceColour(s));
+      g.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
               charHeight);
-      gg.setColor(Color.black);
+      g.setColor(Color.black);
     }
 
     if (av.isRightAlignIds())
     {
+      FontMetrics fm = g.getFontMetrics();
       xPos = panelWidth
               - fm.stringWidth(s.getDisplayId(av.getShowJVSuffix())) - 4;
     }
 
-    gg.drawString(s.getDisplayId(av.getShowJVSuffix()), xPos,
+    g.drawString(s.getDisplayId(av.getShowJVSuffix()), xPos,
             (((i - starty + 1) * charHeight) + ypos) - (charHeight / 5));
 
     if (hiddenRows)
     {
-      drawMarker(i, starty, ypos);
+      drawMarker(g, av, i, starty, ypos);
     }
 
   }
@@ -199,7 +196,7 @@ public class IdCanvas extends JPanel implements ViewportListenerI
 
     gg.translate(0, transY);
 
-    drawIds(ss, es);
+    drawIds(gg, av, ss, es, searchResults);
 
     gg.translate(0, -transY);
 
@@ -255,159 +252,167 @@ public class IdCanvas extends JPanel implements ViewportListenerI
     gg.setColor(Color.white);
     gg.fillRect(0, 0, getWidth(), imgHeight);
     
-    drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq());
+    drawIds(gg, av, av.getRanges().getStartSeq(), av.getRanges().getEndSeq(), searchResults);
     
     g.drawImage(image, 0, 0, this);
   }
 
   /**
-   * DOCUMENT ME!
+   * Draws sequence ids from sequence index startSeq to endSeq (inclusive), with
+   * the font and other display settings configured on the viewport. Ids of
+   * sequences included in the selection are coloured grey, otherwise the
+   * current id colour for the sequence id is used.
    * 
-   * @param starty
-   *          DOCUMENT ME!
-   * @param endy
-   *          DOCUMENT ME!
+   * @param g
+   * @param alignViewport
+   * @param startSeq
+   * @param endSeq
+   * @param selection
    */
-  void drawIds(int starty, int endy)
+  void drawIds(Graphics2D g, AlignViewport alignViewport, final int startSeq,
+          final int endSeq, List<SequenceI> selection)
   {
-    if (av.isSeqNameItalics())
+    Font font = alignViewport.getFont();
+    if (alignViewport.isSeqNameItalics())
     {
-      setIdfont(new Font(av.getFont().getName(), Font.ITALIC,
-              av.getFont().getSize()));
+      setIdfont(new Font(font.getName(), Font.ITALIC,
+              font.getSize()));
     }
     else
     {
-      setIdfont(av.getFont());
+      setIdfont(font);
     }
 
-    gg.setFont(getIdfont());
-    fm = gg.getFontMetrics();
+    g.setFont(getIdfont());
+    FontMetrics fm = g.getFontMetrics();
 
-    if (av.antiAlias)
+    if (alignViewport.antiAlias)
     {
-      gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
               RenderingHints.VALUE_ANTIALIAS_ON);
     }
 
     Color currentColor = Color.white;
     Color currentTextColor = Color.black;
 
-    boolean hasHiddenRows = av.hasHiddenRows();
+    boolean hasHiddenRows = alignViewport.hasHiddenRows();
 
-    if (av.getWrapAlignment())
+    if (alignViewport.getWrapAlignment())
     {
-      drawIdsWrapped(starty, hasHiddenRows);
+      drawIdsWrapped(g, alignViewport, startSeq, getHeight());
       return;
     }
 
-    // No need to hang on to labels if we're not wrapped
-    labels = null;
-
     // Now draw the id strings
     int panelWidth = getWidth();
     int xPos = 0;
 
-    SequenceI sequence;
     // Now draw the id strings
-    for (int i = starty; i <= endy; i++)
+    for (int i = startSeq; i <= endSeq; i++)
     {
-      sequence = av.getAlignment().getSequenceAt(i);
+      SequenceI sequence = alignViewport.getAlignment().getSequenceAt(i);
 
       if (sequence == null)
       {
         continue;
       }
 
-      if (hasHiddenRows || av.isDisplayReferenceSeq())
+      if (hasHiddenRows || alignViewport.isDisplayReferenceSeq())
       {
-        setHiddenFont(sequence);
+        g.setFont(getHiddenFont(sequence, alignViewport));
       }
 
       // Selected sequence colours
-      if ((searchResults != null) && searchResults.contains(sequence))
+      if (selection != null && selection.contains(sequence))
       {
         currentColor = Color.black;
         currentTextColor = Color.white;
       }
-      else if ((av.getSelectionGroup() != null) && av.getSelectionGroup()
-              .getSequences(null).contains(sequence))
+      else if ((alignViewport.getSelectionGroup() != null) && alignViewport
+              .getSelectionGroup().getSequences(null).contains(sequence))
       {
         currentColor = Color.lightGray;
         currentTextColor = Color.black;
       }
       else
       {
-        currentColor = av.getSequenceColour(sequence);
+        currentColor = alignViewport.getSequenceColour(sequence);
         currentTextColor = Color.black;
       }
 
-      gg.setColor(currentColor);
+      g.setColor(currentColor);
 
-      gg.fillRect(0, (i - starty) * av.getCharHeight(), getWidth(),
-              av.getCharHeight());
+      int charHeight = alignViewport.getCharHeight();
+      g.fillRect(0, (i - startSeq) * charHeight,
+              getWidth(), charHeight);
 
-      gg.setColor(currentTextColor);
+      g.setColor(currentTextColor);
 
-      String string = sequence.getDisplayId(av.getShowJVSuffix());
+      String string = sequence
+              .getDisplayId(alignViewport.getShowJVSuffix());
 
-      if (av.isRightAlignIds())
+      if (alignViewport.isRightAlignIds())
       {
         xPos = panelWidth - fm.stringWidth(string) - 4;
       }
 
-      gg.drawString(string, xPos,
-              (((i - starty) * av.getCharHeight()) + av.getCharHeight())
-                      - (av.getCharHeight() / 5));
+      g.drawString(string, xPos, (((i - startSeq) * charHeight) + charHeight)
+              - (charHeight / 5));
 
       if (hasHiddenRows)
       {
-        drawMarker(i, starty, 0);
+        drawMarker(g, alignViewport, i, startSeq, 0);
       }
     }
   }
 
   /**
-   * Draws sequence ids in wrapped mode
+   * Draws sequence ids, and annotation labels if annotations are shown, in
+   * wrapped mode
    * 
-   * @param starty
-   * @param hasHiddenRows
+   * @param g
+   * @param alignViewport
+   * @param startSeq
    */
-  protected void drawIdsWrapped(int starty, boolean hasHiddenRows)
+  void drawIdsWrapped(Graphics2D g, AlignViewport alignViewport,
+          int startSeq, int pageHeight)
   {
-    int maxwidth = av.getAlignment().getWidth();
-    int alheight = av.getAlignment().getHeight();
+    int alignmentWidth = alignViewport.getAlignment().getWidth();
+    final int alheight = alignViewport.getAlignment().getHeight();
 
-    if (av.hasHiddenColumns())
+    if (alignViewport.hasHiddenColumns())
     {
-      maxwidth = av.getAlignment().getHiddenColumns()
-              .absoluteToVisibleColumn(maxwidth) - 1;
+      alignmentWidth = alignViewport.getAlignment().getHiddenColumns()
+              .absoluteToVisibleColumn(alignmentWidth) - 1;
     }
 
     int annotationHeight = 0;
 
-    if (av.isShowAnnotation())
+    AnnotationLabels labels = null;
+    if (alignViewport.isShowAnnotation())
     {
       if (ap == null)
       {
-        ap = new AnnotationPanel(av);
+        ap = new AnnotationPanel(alignViewport);
       }
-
       annotationHeight = ap.adjustPanelHeight();
-      if (labels == null)
-      {
-        labels = new AnnotationLabels(av);
-      }
+      labels = new AnnotationLabels(alignViewport);
     }
 
-    int hgap = av.getCharHeight();
-    if (av.getScaleAboveWrapped())
+    final int charHeight = alignViewport.getCharHeight();
+    int hgap = charHeight;
+    if (alignViewport.getScaleAboveWrapped())
     {
-      hgap += av.getCharHeight();
+      hgap += charHeight;
     }
 
-    int cHeight = alheight * av.getCharHeight() + hgap + annotationHeight;
+    /*
+     * height of alignment + gap + annotations (if shown)
+     */
+    int cHeight = alheight * charHeight + hgap
+            + annotationHeight;
 
-    ViewportRanges ranges = av.getRanges();
+    ViewportRanges ranges = alignViewport.getRanges();
 
     int rowSize = ranges.getViewportWidth();
 
@@ -415,49 +420,58 @@ public class IdCanvas extends JPanel implements ViewportListenerI
      * draw repeating sequence ids until out of sequence data or
      * out of visible space, whichever comes first
      */
+    boolean hasHiddenRows = alignViewport.hasHiddenRows();
     int ypos = hgap;
-    int row = ranges.getStartRes();
-    while ((ypos <= getHeight()) && (row < maxwidth))
+    int rowStartRes = ranges.getStartRes();
+    while ((ypos <= pageHeight) && (rowStartRes < alignmentWidth))
     {
-      for (int i = starty; i < alheight; i++)
+      for (int i = startSeq; i < alheight; i++)
       {
-        SequenceI s = av.getAlignment().getSequenceAt(i);
-        if (hasHiddenRows || av.isDisplayReferenceSeq())
+        SequenceI s = alignViewport.getAlignment().getSequenceAt(i);
+        if (hasHiddenRows || alignViewport.isDisplayReferenceSeq())
         {
-          setHiddenFont(s);
+          g.setFont(getHiddenFont(s, alignViewport));
         }
         else
         {
-          gg.setFont(getIdfont());
+          g.setFont(getIdfont());
         }
-
-        drawIdString(gg, hasHiddenRows, s, i, 0, ypos);
+        drawIdString(g, hasHiddenRows, s, i, 0, ypos);
       }
 
-      if (labels != null && av.isShowAnnotation())
+      if (labels != null && alignViewport.isShowAnnotation())
       {
-        gg.translate(0, ypos + (alheight * av.getCharHeight()));
-        labels.drawComponent(gg, getWidth());
-        gg.translate(0, -ypos - (alheight * av.getCharHeight()));
+        g.translate(0, ypos + (alheight * charHeight));
+        labels.drawComponent(g, getWidth());
+        g.translate(0, -ypos - (alheight * charHeight));
       }
 
       ypos += cHeight;
-      row += rowSize;
+      rowStartRes += rowSize;
     }
   }
 
-  void drawMarker(int i, int starty, int yoffset)
+  /**
+   * Draws a marker (a blue right-pointing triangle) between sequences to
+   * indicate hidden sequences.
+   * 
+   * @param g
+   * @param alignViewport
+   * @param seqIndex
+   * @param starty
+   * @param yoffset
+   */
+  void drawMarker(Graphics2D g, AlignViewport alignViewport, int seqIndex, int starty, int yoffset)
   {
-
-    SequenceI[] hseqs = av.getAlignment()
+    SequenceI[] hseqs = alignViewport.getAlignment()
             .getHiddenSequences().hiddenSequences;
     // Use this method here instead of calling hiddenSeq adjust
     // 3 times.
     int hSize = hseqs.length;
 
-    int hiddenIndex = i;
-    int lastIndex = i - 1;
-    int nextIndex = i + 1;
+    int hiddenIndex = seqIndex;
+    int lastIndex = seqIndex - 1;
+    int nextIndex = seqIndex + 1;
 
     for (int j = 0; j < hSize; j++)
     {
@@ -478,52 +492,56 @@ public class IdCanvas extends JPanel implements ViewportListenerI
       }
     }
 
+    /*
+     * are we below or above the hidden sequences?
+     */
     boolean below = (hiddenIndex > lastIndex + 1);
     boolean above = (nextIndex > hiddenIndex + 1);
 
-    gg.setColor(Color.blue);
+    g.setColor(Color.blue);
+    int charHeight = av.getCharHeight();
+
+    /*
+     * vertices of the triangle, below or above hidden seqs
+     */
+    int[] xPoints = new int[]
+    { getWidth() - charHeight,
+        getWidth() - charHeight, getWidth() };
+    int yShift = seqIndex - starty;
+
     if (below)
     {
-      gg.fillPolygon(
-              new int[]
-              { getWidth() - av.getCharHeight(),
-                  getWidth() - av.getCharHeight(), getWidth() },
-              new int[]
-              { (i - starty) * av.getCharHeight() + yoffset,
-                  (i - starty) * av.getCharHeight() + yoffset
-                          + av.getCharHeight() / 4,
-                  (i - starty) * av.getCharHeight() + yoffset },
-              3);
+      int[] yPoints = new int[] { yShift * charHeight + yoffset,
+          yShift * charHeight + yoffset + charHeight / 4,
+          yShift * charHeight + yoffset };
+      g.fillPolygon(xPoints, yPoints, 3);
     }
     if (above)
     {
-      gg.fillPolygon(
-              new int[]
-              { getWidth() - av.getCharHeight(),
-                  getWidth() - av.getCharHeight(), getWidth() },
-              new int[]
-              { (i - starty + 1) * av.getCharHeight() + yoffset,
-                  (i - starty + 1) * av.getCharHeight() + yoffset
-                          - av.getCharHeight() / 4,
-                  (i - starty + 1) * av.getCharHeight() + yoffset },
-              3);
-
+      yShift++;
+      int[] yPoints = new int[] { yShift * charHeight + yoffset,
+          yShift * charHeight + yoffset - charHeight / 4,
+          yShift * charHeight + yoffset };
+      g.fillPolygon(xPoints, yPoints, 3);
     }
   }
 
-  void setHiddenFont(SequenceI seq)
+  /**
+   * Answers the standard sequence id font, or a bold font if the sequence is
+   * set as reference or a hidden group representative
+   * 
+   * @param seq
+   * @param alignViewport
+   * @return
+   */
+  private Font getHiddenFont(SequenceI seq, AlignViewport alignViewport)
   {
-    Font bold = new Font(av.getFont().getName(), Font.BOLD,
-            av.getFont().getSize());
-
     if (av.isReferenceSeq(seq) || av.isHiddenRepSequence(seq))
     {
-      gg.setFont(bold);
-    }
-    else
-    {
-      gg.setFont(getIdfont());
+      return new Font(av.getFont().getName(), Font.BOLD,
+              av.getFont().getSize());
     }
+    return getIdfont();
   }
 
   /**
index 8f315bd..5c404f0 100755 (executable)
@@ -32,7 +32,6 @@ import jalview.util.Comparison;
 import jalview.viewmodel.ViewportListenerI;
 import jalview.viewmodel.ViewportRanges;
 
-import java.awt.AlphaComposite;
 import java.awt.BasicStroke;
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -365,31 +364,27 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     width -= (width % charWidth);
     height -= (height % charHeight);
     
-    // selectImage is the selection group outline image
-    BufferedImage selectImage = drawSelectionGroup(
-            ranges.getStartRes(), ranges.getEndRes(),
-            ranges.getStartSeq(), ranges.getEndSeq());
-    
     if ((img != null) && (fastPaint
             || (getVisibleRect().width != g.getClipBounds().width)
             || (getVisibleRect().height != g.getClipBounds().height)))
     {
-      BufferedImage lcimg = buildLocalImage(selectImage);
-      g.drawImage(lcimg, 0, 0, this);
+      g.drawImage(img, 0, 0, this);
+
+      drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
+              ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
+
       fastPaint = false;
     }
-    else if ((width > 0) && (height > 0))
+    else if (width > 0 && height > 0)
     {
-      // img is a cached version of the last view we drew, if any
-      // if we have no img or the size has changed, make a new one
+      /*
+       * img is a cached version of the last view we drew, if any
+       * if we have no img or the size has changed, make a new one
+       */
       if (img == null || width != img.getWidth()
               || height != img.getHeight())
       {
-        img = setupImage();
-        if (img == null)
-        {
-          return;
-        }
+        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
         gg = (Graphics2D) img.getGraphics();
         gg.setFont(av.getFont());
       }
@@ -412,11 +407,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
                 ranges.getStartSeq(), ranges.getEndSeq(), 0);
       }
-    
-      // lcimg is a local *copy* of img which we'll draw selectImage on top of
-      BufferedImage lcimg = buildLocalImage(selectImage);
-      g.drawImage(lcimg, 0, 0, this);
 
+      drawSelectionGroup(gg, ranges.getStartRes(),
+              ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
+
+      g.drawImage(img, 0, 0, this);
     }
 
     if (av.cursorMode)
@@ -445,14 +440,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   {
     drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
 
-    BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
+    drawSelectionGroup((Graphics2D) g1, startRes, endRes,
             startSeq, endSeq);
-    if (selectImage != null)
-    {
-      ((Graphics2D) g1).setComposite(AlphaComposite
-              .getInstance(AlphaComposite.SRC_OVER));
-      g1.drawImage(selectImage, 0, 0, this);
-    }
   }
 
   /**
@@ -470,104 +459,16 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
           int canvasHeight, int startRes)
   {
-    SequenceGroup group = av.getSelectionGroup();
-
     drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
 
+    SequenceGroup group = av.getSelectionGroup();
     if (group != null)
     {
-      BufferedImage selectImage = null;
-      try
-      {
-        selectImage = new BufferedImage(canvasWidth, canvasHeight,
-                BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
-      } catch (OutOfMemoryError er)
-      {
-        System.gc();
-        System.err.println("Print image OutOfMemory Error.\n" + er);
-        new OOMWarning("Creating wrapped alignment image for printing", er);
-      }
-      if (selectImage != null)
-      {
-        Graphics2D g2 = selectImage.createGraphics();
-        setupSelectionGroup(g2, selectImage);
-        drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
+      drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
                 startRes);
-
-        g2.setComposite(
-                AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
-        g.drawImage(selectImage, 0, 0, this);
-        g2.dispose();
-      }
     }
   }
 
-  /*
-   * Make a local image by combining the cached image img
-   * with any selection
-   */
-  private BufferedImage buildLocalImage(BufferedImage selectImage)
-  {
-    // clone the cached image
-         BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
-                   img.getType());
-
-    // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
-    // img.getType());
-    Graphics2D g2d = lcimg.createGraphics();
-    g2d.drawImage(img, 0, 0, null);
-
-    // overlay selection group on lcimg
-    if (selectImage != null)
-    {
-      g2d.setComposite(
-              AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
-      g2d.drawImage(selectImage, 0, 0, this);
-    }
-
-    g2d.dispose();
-
-    return lcimg;
-  }
-
-  /*
-   * Set up a buffered image of the correct height and size for the sequence canvas
-   */
-  private BufferedImage setupImage()
-  {
-    BufferedImage lcimg = null;
-
-    int charWidth = av.getCharWidth();
-    int charHeight = av.getCharHeight();
-    
-    int width = getWidth();
-    int height = getHeight();
-
-    width -= (width % charWidth);
-    height -= (height % charHeight);
-
-    if ((width < 1) || (height < 1))
-    {
-      return null;
-    }
-
-    try
-    {
-       lcimg = new BufferedImage(width, height,
-                BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
-    } catch (OutOfMemoryError er)
-    {
-      System.gc();
-      System.err.println(
-              "Group image OutOfMemory Redraw Error.\n" + er);
-      new OOMWarning("Creating alignment image for display", er);
-
-      return null;
-    }
-
-    return lcimg;
-  }
-
   /**
    * Returns the visible width of the canvas in residues, after allowing for
    * East or West scales (if shown)
@@ -658,7 +559,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     calculateWrappedGeometry(canvasWidth, canvasHeight);
 
     /*
-     * draw one width at a time (including any scales or annotation shown),
+     * draw one width at a time (excluding any scales or annotation shown),
      * until we have run out of either alignment or vertical space available
      */
     int ypos = wrappedSpaceAboveAlignment;
@@ -709,7 +610,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
      */
     wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
     // add sequences
-    wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
+    wrappedRepeatHeightPx += av.getAlignment().getHeight()
             * charHeight;
     // add annotations panel height if shown
     wrappedRepeatHeightPx += getAnnotationHeight();
@@ -964,6 +865,10 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     // chop the wrapped alignment extent up into panel-sized blocks and treat
     // each block as if it were a block from an unwrapped alignment
+    g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
+            BasicStroke.JOIN_ROUND, 3f, new float[]
+            { 5f, 3f }, 0f));
+    g.setColor(Color.RED);
     while ((ypos <= canvasHeight) && (startx < maxwidth))
     {
       // set end value to be start + width, or maxwidth, whichever is smaller
@@ -988,6 +893,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       // update horizontal offset
       startx += cWidth;
     }
+    g.setStroke(new BasicStroke());
   }
 
   int getAnnotationHeight()
@@ -1154,14 +1060,21 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   }
 
+  /**
+   * Draws the outlines of any groups defined on the alignment (excluding the
+   * current selection group, if any)
+   * 
+   * @param g1
+   * @param startRes
+   * @param endRes
+   * @param startSeq
+   * @param endSeq
+   * @param offset
+   */
   void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
           int startSeq, int endSeq, int offset)
   {
     Graphics2D g = (Graphics2D) g1;
-    //
-    // ///////////////////////////////////
-    // Now outline any areas if necessary
-    // ///////////////////////////////////
 
     SequenceGroup group = null;
     int groupIndex = -1;
@@ -1174,18 +1087,15 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     if (group != null)
     {
-      g.setStroke(new BasicStroke());
-      g.setColor(group.getOutlineColour());
-      
       do
       {
+        g.setColor(group.getOutlineColour());
+
         drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
                 endSeq, offset);
 
         groupIndex++;
 
-        g.setStroke(new BasicStroke());
-
         if (groupIndex >= av.getAlignment().getGroups().size())
         {
           break;
@@ -1199,33 +1109,28 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   }
 
-
-  /*
-   * Draw the selection group as a separate image and overlay
+  /**
+   * Draws the outline of the current selection group (if any)
+   * 
+   * @param g
+   * @param startRes
+   * @param endRes
+   * @param startSeq
+   * @param endSeq
    */
-  private BufferedImage drawSelectionGroup(int startRes, int endRes,
+  private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
           int startSeq, int endSeq)
   {
-    // get a new image of the correct size
-    BufferedImage selectionImage = setupImage();
-
-    if (selectionImage == null)
-    {
-      return null;
-    }
-
     SequenceGroup group = av.getSelectionGroup();
     if (group == null)
     {
-      // nothing to draw
-      return null;
+      return;
     }
 
-    // set up drawing colour
-    Graphics2D g = (Graphics2D) selectionImage.getGraphics();
-
-    setupSelectionGroup(g, selectionImage);
-
+    g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
+            BasicStroke.JOIN_ROUND, 3f, new float[]
+            { 5f, 3f }, 0f));
+    g.setColor(Color.RED);
     if (!av.getWrapAlignment())
     {
       drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
@@ -1236,9 +1141,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       drawWrappedSelection(g, group, getWidth(), getHeight(),
               av.getRanges().getStartRes());
     }
-
-    g.dispose();
-    return selectionImage;
+    g.setStroke(new BasicStroke());
   }
 
   /**
@@ -1329,33 +1232,23 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   }
 
 
-  /*
-   * Set up graphics for selection group
-   */
-  private void setupSelectionGroup(Graphics2D g,
-          BufferedImage selectionImage)
-  {
-    // set background to transparent
-    g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
-    g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
-
-    // set up foreground to draw red dashed line
-    g.setComposite(AlphaComposite.Src);
-    g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
-            BasicStroke.JOIN_ROUND, 3f, new float[]
-    { 5f, 3f }, 0f));
-    g.setColor(Color.RED);
-  }
-
-  /*
+  /**
    * Draw a selection group over an unwrapped alignment
-   * @param g graphics object to draw with
-   * @param group selection group
-   * @param startRes start residue of area to draw
-   * @param endRes end residue of area to draw
-   * @param startSeq start sequence of area to draw
-   * @param endSeq end sequence of area to draw
-   * @param offset vertical offset (used when called from wrapped alignment code)
+   * 
+   * @param g
+   *          graphics object to draw with
+   * @param group
+   *          selection group
+   * @param startRes
+   *          start residue of area to draw
+   * @param endRes
+   *          end residue of area to draw
+   * @param startSeq
+   *          start sequence of area to draw
+   * @param endSeq
+   *          end sequence of area to draw
+   * @param offset
+   *          vertical offset (used when called from wrapped alignment code)
    */
   private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
           int startRes, int endRes, int startSeq, int endSeq, int offset)
@@ -1393,8 +1286,16 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     }
   }
 
-  /*
-   * Draw the selection group as a separate image and overlay
+  /**
+   * Draws part of a selection group outline
+   * 
+   * @param g
+   * @param group
+   * @param startRes
+   * @param endRes
+   * @param startSeq
+   * @param endSeq
+   * @param verticalOffset
    */
   private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
           int startRes, int endRes, int startSeq, int endSeq,
index c49e34f..cb42662 100755 (executable)
@@ -285,6 +285,7 @@ public class JalviewFileChooser extends JFileChooser
 
     setDialogType(SAVE_DIALOG);
 
+    this.setSelectedFile(null);
     int ret = showDialog(parent, MessageManager.getString("action.save"));
 
     if (getFileFilter() instanceof JalviewFileFilter)
index f5b5177..0e73af1 100644 (file)
@@ -83,6 +83,14 @@ public class StockholmFile extends AlignFile
   public static final Regex DETECT_BRACKETS = new Regex(
           "(<|>|\\[|\\]|\\(|\\)|\\{|\\})");
 
+  // WUSS extended symbols. Avoid ambiguity with protein SS annotations by using NOT_RNASS first.
+  public static final String RNASS_BRACKETS = "<>[](){}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
+
+  // use the following regex to decide an annotations (whole) line is NOT an RNA
+  // SS (it contains only E,H,e,h and other non-brace/non-alpha chars)
+  private static final Regex NOT_RNASS = new Regex(
+          "^[^<>[\\](){}A-DF-Za-df-z]*$");
+
   StringBuffer out; // output buffer
 
   AlignmentI al;
@@ -197,7 +205,7 @@ public class StockholmFile extends AlignFile
     String version;
     // String id;
     Hashtable seqAnn = new Hashtable(); // Sequence related annotations
-    LinkedHashMap<String, String> seqs = new LinkedHashMap<String, String>();
+    LinkedHashMap<String, String> seqs = new LinkedHashMap<>();
     Regex p, r, rend, s, x;
     // Temporary line for processing RNA annotation
     // String RNAannot = "";
@@ -658,7 +666,7 @@ public class StockholmFile extends AlignFile
               strucAnn = new Hashtable();
             }
 
-            Vector<AlignmentAnnotation> newStruc = new Vector<AlignmentAnnotation>();
+            Vector<AlignmentAnnotation> newStruc = new Vector<>();
             parseAnnotationRow(newStruc, type, ns);
             for (AlignmentAnnotation alan : newStruc)
             {
@@ -710,7 +718,7 @@ public class StockholmFile extends AlignFile
   private void guessDatabaseFor(Sequence seqO, String dbr, String dbsource)
   {
     DBRefEntry dbrf = null;
-    List<DBRefEntry> dbrs = new ArrayList<DBRefEntry>();
+    List<DBRefEntry> dbrs = new ArrayList<>();
     String seqdb = "Unknown", sdbac = "" + dbr;
     int st = -1, en = -1, p;
     if ((st = sdbac.indexOf("/")) > -1)
@@ -824,9 +832,14 @@ public class StockholmFile extends AlignFile
     }
     boolean ss = false, posterior = false;
     type = id2type(type);
+
+    boolean isrnass = false;
     if (type.equalsIgnoreCase("secondary structure"))
     {
       ss = true;
+      isrnass = !NOT_RNASS.search(annots); // sorry about the double negative
+                                           // here (it's easier for dealing with
+                                           // other non-alpha-non-brace chars)
     }
     if (type.equalsIgnoreCase("posterior probability"))
     {
@@ -844,7 +857,7 @@ public class StockholmFile extends AlignFile
       {
         // if (" .-_".indexOf(pos) == -1)
         {
-          if (DETECT_BRACKETS.search(pos))
+          if (isrnass && RNASS_BRACKETS.indexOf(pos) >= 0)
           {
             ann.secondaryStructure = Rna.getRNASecStrucState(pos).charAt(0);
             ann.displayCharacter = "" + pos.charAt(0);
@@ -1114,22 +1127,36 @@ public class StockholmFile extends AlignFile
     String ch = (annot == null)
             ? ((sequenceI == null) ? "-"
                     : Character.toString(sequenceI.getCharAt(k)))
-            : annot.displayCharacter;
+            : (annot.displayCharacter == null
+                    ? String.valueOf(annot.secondaryStructure)
+                    : annot.displayCharacter);
+    if (ch == null)
+    {
+      ch = " ";
+    }
     if (key != null && key.equals("SS"))
     {
+      char ssannotchar = ' ';
+      boolean charset = false;
       if (annot == null)
       {
         // sensible gap character
-        return ' ';
+        ssannotchar = ' ';
+        charset = true;
       }
       else
       {
         // valid secondary structure AND no alternative label (e.g. ' B')
         if (annot.secondaryStructure > ' ' && ch.length() < 2)
         {
-          return annot.secondaryStructure;
+          ssannotchar = annot.secondaryStructure;
+          charset = true;
         }
       }
+      if (charset)
+      {
+        return (ssannotchar == ' ' && isrna) ? '.' : ssannotchar;
+      }
     }
 
     if (ch.length() == 0)
@@ -1144,7 +1171,9 @@ public class StockholmFile extends AlignFile
     {
       seq = ch.charAt(1);
     }
-    return seq;
+
+    return (seq == ' ' && key != null && key.equals("SS") && isrna) ? '.'
+            : seq;
   }
 
   public String print()
index cc3a3db..460d16c 100644 (file)
@@ -79,19 +79,6 @@ public class EnsemblRestClientTest
       {
         return false;
       }
-
-      @Override
-      protected String getRequestMimeType(boolean b)
-      {
-        return null;
-      }
-
-      @Override
-      protected String getResponseMimeType()
-      {
-        return null;
-      }
-
     };
   }
 
index e2af26b..72f5a34 100644 (file)
@@ -25,14 +25,13 @@ import static org.testng.AssertJUnit.assertFalse;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 
-import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.SequenceFeatures;
 import jalview.gui.JvOptionPane;
 import jalview.io.DataSourceType;
 import jalview.io.FastaFile;
-import jalview.io.FileParse;
 import jalview.io.gff.SequenceOntologyFactory;
 import jalview.io.gff.SequenceOntologyLite;
 
@@ -127,7 +126,11 @@ public class EnsemblSeqProxyTest
                   + "LKKALMMRGLIPECCAVYRIQDGEKKPIGWDTDISWLTGEELHVEVLENVPLTTHNFVRK\n"
                   + "TFFTLAFCDFCRKLLFQGFRCQTCGYKFHQRCSTEVPLMCVNYDQLDLLFVSKFFEHHPI\n"
                   + "PQEEASLAETALTSGSSPSAPASDSIGPQILTSPSPSKSIPIPQPFRPADEDHRNQFGQR\n"
-                  + "DRSSSAPNVHINTIEPVNIDDLIRDQGFRGDGGSTTGLSATPPASLPGSLTNVKALQKSP\n"
+                  + "DRSSSAPNVHINTIEPVNIDDLIRDQGFRGDG\n"
+                  // ? insertion added in ENSP00000288602.11, not in P15056
+                  + "APLNQLMRCLRKYQSRTPSPLLHSVPSEIVFDFEPGPVFR\n"
+                  // end insertion
+                  + "GSTTGLSATPPASLPGSLTNVKALQKSP\n"
                   + "GPQRERKSSSSSEDRNRMKTLGRRDSSDDWEIPDGQITVGQRIGSGSFGTVYKGKWHGDV\n"
                   + "AVKMLNVTAPTPQQLQAFKNEVGVLRKTRHVNILLFMGYSTKPQLAIVTQWCEGSSLYHH\n"
                   + "LHIIETKFEMIKLIDIARQTAQGMDYLHAKSIIHRDLKSNNIFLHEDLTVKIGDFGLATV\n"
@@ -155,22 +158,21 @@ public class EnsemblSeqProxyTest
   }
 
   @Test(dataProvider = "ens_seqs", suiteName = "live")
-  public void testGetOneSeqs(EnsemblRestClient proxy, String sq,
+  public void testGetSequenceRecords(EnsemblSeqProxy proxy, String sq,
           String fastasq) throws Exception
   {
-    FileParse fp = proxy.getSequenceReader(Arrays
-            .asList(new String[] { sq }));
-    SequenceI[] sqs = new FastaFile(fp).getSeqsAsArray();
     FastaFile trueRes = new FastaFile(fastasq, DataSourceType.PASTE);
-    SequenceI[] trueSqs = trueRes.getSeqsAsArray();
-    Assert.assertEquals(sqs.length, trueSqs.length,
+    SequenceI[] expected = trueRes.getSeqsAsArray();
+    AlignmentI retrieved = proxy.getSequenceRecords(sq);
+
+    Assert.assertEquals(retrieved.getHeight(), expected.length,
             "Different number of sequences retrieved for query " + sq);
-    Alignment ral = new Alignment(sqs);
-    for (SequenceI tr : trueSqs)
+
+    for (SequenceI tr : expected)
     {
       SequenceI[] rseq;
       Assert.assertNotNull(
-              rseq = ral.findSequenceMatch(tr.getName()),
+              rseq = retrieved.findSequenceMatch(tr.getName()),
               "Couldn't find sequences matching expected sequence "
                       + tr.getName());
       Assert.assertEquals(rseq.length, 1,
@@ -181,7 +183,6 @@ public class EnsemblSeqProxyTest
               "Sequences differ for " + tr.getName() + "\n" + "Exp:"
                       + tr.getSequenceAsString() + "\n" + "Got:"
                       + rseq[0].getSequenceAsString());
-
     }
   }
 
index ee1270e..bcb1cfd 100644 (file)
@@ -48,17 +48,17 @@ public class SeqCanvasTest
     AlignmentI al = av.getAlignment();
     assertEquals(al.getWidth(), 157);
     assertEquals(al.getHeight(), 15);
+    av.getRanges().setStartEndSeq(0, 14);
+
+    SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
 
     av.setWrapAlignment(true);
-    av.getRanges().setStartEndSeq(0, 14);
     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
     int charHeight = av.getCharHeight();
     int charWidth = av.getCharWidth();
     assertEquals(charHeight, 17);
     assertEquals(charWidth, 12);
 
-    SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
-
     /*
      * first with scales above, left, right
      */
@@ -298,4 +298,42 @@ public class SeqCanvasTest
     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
   }
+
+  /**
+   * Test simulates loading an unwrapped alignment, shrinking it vertically so
+   * not all sequences are visible, then changing to wrapped mode. The ranges
+   * endSeq should be unchanged, but the vertical repeat height should include
+   * all sequences.
+   */
+  @Test(groups = "Functional")
+  public void testCalculateWrappedGeometry_fromScrolled()
+  {
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+            "examples/uniref50.fa", DataSourceType.FILE);
+    AlignViewport av = af.getViewport();
+    AlignmentI al = av.getAlignment();
+    assertEquals(al.getWidth(), 157);
+    assertEquals(al.getHeight(), 15);
+    av.getRanges().setStartEndSeq(0, 3);
+    av.setShowAnnotation(false);
+    av.setScaleAboveWrapped(true);
+
+    SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
+
+    av.setWrapAlignment(true);
+    av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+    assertEquals(charHeight, 17);
+    assertEquals(charWidth, 12);
+
+    int canvasWidth = 400;
+    int canvasHeight = 300;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+
+    assertEquals(av.getRanges().getEndSeq(), 3); // unchanged
+    int repeatingHeight = (int) PA.getValue(testee,
+            "wrappedRepeatHeightPx");
+    assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
+  }
 }
index 4273e6c..e86c8ad 100644 (file)
@@ -38,6 +38,8 @@ import java.util.BitSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
@@ -54,7 +56,8 @@ public class StockholmFileTest
   }
 
   static String PfamFile = "examples/PF00111_seed.stk",
-          RfamFile = "examples/RF00031_folded.stk";
+          RfamFile = "examples/RF00031_folded.stk",
+          RnaSSTestFile = "examples/rna_ss_test.stk";
 
   @Test(groups = { "Functional" })
   public void pfamFileIO() throws Exception
@@ -230,8 +233,8 @@ public class StockholmFileTest
     // we might want to revise this in future
     int aa_new_size = (aa_new == null ? 0 : aa_new.length);
     int aa_original_size = (aa_original == null ? 0 : aa_original.length);
-    Map<Integer, BitSet> orig_groups = new HashMap<Integer, BitSet>();
-    Map<Integer, BitSet> new_groups = new HashMap<Integer, BitSet>();
+    Map<Integer, BitSet> orig_groups = new HashMap<>();
+    Map<Integer, BitSet> new_groups = new HashMap<>();
 
     if (aa_new != null && aa_original != null)
     {
@@ -623,13 +626,13 @@ public class StockholmFileTest
   {
     for (char ch : new char[] { '{', '}', '[', ']', '(', ')', '<', '>' })
     {
-      Assert.assertTrue(StockholmFile.DETECT_BRACKETS.matchAt("" + ch, 0),
-              "Didn't recognise " + ch + " as a WUSS bracket");
+      Assert.assertTrue(StockholmFile.RNASS_BRACKETS.indexOf(ch) >= 0,
+              "Didn't recognise '" + ch + "' as a WUSS bracket");
     }
-    for (char ch : new char[] { '@', '!', 'V', 'Q', '*', ' ', '-', '.' })
+    for (char ch : new char[] { '@', '!', '*', ' ', '-', '.' })
     {
-      Assert.assertFalse(StockholmFile.DETECT_BRACKETS.matchAt("" + ch, 0),
-              "Shouldn't recognise " + ch + " as a WUSS bracket");
+      Assert.assertFalse(StockholmFile.RNASS_BRACKETS.indexOf(ch) >= 0,
+              "Shouldn't recognise '" + ch + "' as a WUSS bracket");
     }
   }
   private static void roundTripSSForRNA(String aliFile, String annFile)
@@ -654,4 +657,191 @@ public class StockholmFileTest
     testAlignmentEquivalence(al, newAl, true, true, true);
 
   }
+
+  // this is the single sequence alignment and the SS annotations equivalent to
+  // the ones in file RnaSSTestFile
+  String aliFileRnaSS = ">Test.sequence/1-14\n"
+          + "GUACAAAAAAAAAA";
+  String annFileRnaSSAlphaChars = "JALVIEW_ANNOTATION\n"
+          + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n"
+          + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|(,(|E,E|H,H|B,B|h,h|e,e|b,b|(,(|E,E|),)|e,e|),)|>,>|\t2.0\n"
+          + "\n"
+          + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n"
+          + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;";
+  String wrongAnnFileRnaSSAlphaChars = "JALVIEW_ANNOTATION\n"
+          + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n"
+          + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|(,(|H,H|E,E|B,B|h,h|e,e|b,b|(,(|E,E|),)|e,e|),)|>,>|\t2.0\n"
+          + "\n"
+          + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n"
+          + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;";
+  @Test(groups = { "Functional" })
+  public void stockholmFileRnaSSAlphaChars() throws Exception
+  {
+    AppletFormatAdapter af = new AppletFormatAdapter();
+    AlignmentI al = af.readFile(RnaSSTestFile, DataSourceType.FILE,
+            jalview.io.FileFormat.Stockholm);
+    Iterable<AlignmentAnnotation> aai = al.findAnnotations(null, null,
+            "Secondary Structure");
+    AlignmentAnnotation aa = aai.iterator().next();
+    Assert.assertTrue(aa.isRNA(),
+            "'" + RnaSSTestFile + "' not recognised as RNA SS");
+    Assert.assertTrue(aa.isValidStruc(),
+            "'" + RnaSSTestFile + "' not recognised as valid structure");
+    Annotation[] as = aa.annotations;
+    char[] As = new char[as.length];
+    for (int i = 0; i < as.length; i++)
+    {
+      As[i] = as[i].secondaryStructure;
+    }
+    char[] shouldBe = { '<', '(', 'E', 'H', 'B', 'h', 'e', 'b', '(', 'E',
+        ')', 'e', ')', '>' };
+    Assert.assertTrue(
+            Arrays.equals(As, shouldBe),
+            "Annotation is " + new String(As) + " but should be "
+                    + new String(shouldBe));
+
+    // this should result in the same RNA SS Annotations
+    AlignmentI newAl = new AppletFormatAdapter().readFile(
+            aliFileRnaSS,
+            DataSourceType.PASTE, jalview.io.FileFormat.Fasta);
+    AnnotationFile aaf = new AnnotationFile();
+    aaf.readAnnotationFile(newAl, annFileRnaSSAlphaChars,
+            DataSourceType.PASTE);
+
+    Assert.assertTrue(
+            testRnaSSAnnotationsEquivalent(al.getAlignmentAnnotation()[0],
+                    newAl.getAlignmentAnnotation()[0]),
+            "RNA SS Annotations SHOULD be pair-wise equivalent (but apparently aren't): \n"
+                    + "RNA SS A 1:" + al.getAlignmentAnnotation()[0] + "\n"
+                    + "RNA SS A 2:" + newAl.getAlignmentAnnotation()[0]);
+
+    // this should NOT result in the same RNA SS Annotations
+    newAl = new AppletFormatAdapter().readFile(
+            aliFileRnaSS, DataSourceType.PASTE,
+            jalview.io.FileFormat.Fasta);
+    aaf = new AnnotationFile();
+    aaf.readAnnotationFile(newAl, wrongAnnFileRnaSSAlphaChars,
+            DataSourceType.PASTE);
+
+    boolean mismatch = testRnaSSAnnotationsEquivalent(al.getAlignmentAnnotation()[0],
+            newAl.getAlignmentAnnotation()[0]);
+    Assert.assertFalse(mismatch,
+            "RNA SS Annotations SHOULD NOT be pair-wise equivalent (but apparently are): \n"
+                    + "RNA SS A 1:" + al.getAlignmentAnnotation()[0] + "\n"
+                    + "RNA SS A 2:" + newAl.getAlignmentAnnotation()[0]);
+  }
+
+  private static boolean testRnaSSAnnotationsEquivalent(
+          AlignmentAnnotation a1,
+          AlignmentAnnotation a2)
+  {
+    return a1.rnaSecondaryStructureEquivalent(a2);
+  }
+
+  String annFileRnaSSWithSpaceChars = "JALVIEW_ANNOTATION\n"
+          + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n"
+          + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|.,.|H,H| , |B,B|h,h| , |b,b|(,(|E,E|.,.|e,e|),)|>,>|\t2.0\n"
+          + "\n"
+          + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n"
+          + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;";
+  String annFileRnaSSWithoutSpaceChars = "JALVIEW_ANNOTATION\n"
+          + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n"
+          + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|.,.|H,H|.,.|B,B|h,h|.,.|b,b|(,(|E,E|.,.|e,e|),)|>,>|\t2.0\n"
+          + "\n"
+          + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n"
+          + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;";
+
+  String wrongAnnFileRnaSSWithoutSpaceChars = "JALVIEW_ANNOTATION\n"
+          + "# Created: Thu Aug 02 14:54:57 BST 2018\n" + "\n"
+          + "NO_GRAPH\tSecondary Structure\tSecondary Structure\t<,<|.,.|H,H|Z,Z|B,B|h,h|z,z|b,b|(,(|E,E|.,.|e,e|),)|>,>|\t2.0\n"
+          + "\n"
+          + "ROWPROPERTIES\tSecondary Structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false\n"
+          + "\n" + "\n" + "ALIGNMENT\tID=RNA.SS.TEST\tTP=RNA;";
+
+  @Test(groups = { "Functional" })
+  public void stockholmFileRnaSSSpaceChars() throws Exception
+  {
+    AlignmentI alWithSpaces = new AppletFormatAdapter().readFile(
+            aliFileRnaSS, DataSourceType.PASTE,
+            jalview.io.FileFormat.Fasta);
+    AnnotationFile afWithSpaces = new AnnotationFile();
+    afWithSpaces.readAnnotationFile(alWithSpaces,
+            annFileRnaSSWithSpaceChars, DataSourceType.PASTE);
+
+    Iterable<AlignmentAnnotation> aaiWithSpaces = alWithSpaces
+            .findAnnotations(null, null, "Secondary Structure");
+    AlignmentAnnotation aaWithSpaces = aaiWithSpaces.iterator().next();
+    Assert.assertTrue(aaWithSpaces.isRNA(),
+            "'" + aaWithSpaces + "' not recognised as RNA SS");
+    Assert.assertTrue(aaWithSpaces.isValidStruc(),
+            "'" + aaWithSpaces + "' not recognised as valid structure");
+    Annotation[] annWithSpaces = aaWithSpaces.annotations;
+    char[] As = new char[annWithSpaces.length];
+    for (int i = 0; i < annWithSpaces.length; i++)
+    {
+      As[i] = annWithSpaces[i].secondaryStructure;
+    }
+    // check all spaces and dots are spaces in the internal representation
+    char[] shouldBe = { '<', ' ', 'H', ' ', 'B', 'h', ' ', 'b', '(', 'E',
+        ' ', 'e', ')', '>' };
+    Assert.assertTrue(Arrays.equals(As, shouldBe), "Annotation is "
+            + new String(As) + " but should be " + new String(shouldBe));
+
+    // this should result in the same RNA SS Annotations
+    AlignmentI alWithoutSpaces = new AppletFormatAdapter().readFile(
+            aliFileRnaSS, DataSourceType.PASTE,
+            jalview.io.FileFormat.Fasta);
+    AnnotationFile afWithoutSpaces = new AnnotationFile();
+    afWithoutSpaces.readAnnotationFile(alWithoutSpaces,
+            annFileRnaSSWithoutSpaceChars,
+            DataSourceType.PASTE);
+
+    Assert.assertTrue(
+            testRnaSSAnnotationsEquivalent(
+                    alWithSpaces.getAlignmentAnnotation()[0],
+                    alWithoutSpaces.getAlignmentAnnotation()[0]),
+            "RNA SS Annotations SHOULD be pair-wise equivalent (but apparently aren't): \n"
+                    + "RNA SS A 1:"
+                    + alWithSpaces.getAlignmentAnnotation()[0]
+                            .getRnaSecondaryStructure()
+                    + "\n" + "RNA SS A 2:"
+                    + alWithoutSpaces.getAlignmentAnnotation()[0]
+                            .getRnaSecondaryStructure());
+
+    // this should NOT result in the same RNA SS Annotations
+    AlignmentI wrongAlWithoutSpaces = new AppletFormatAdapter().readFile(
+            aliFileRnaSS, DataSourceType.PASTE,
+            jalview.io.FileFormat.Fasta);
+    AnnotationFile wrongAfWithoutSpaces = new AnnotationFile();
+    wrongAfWithoutSpaces.readAnnotationFile(wrongAlWithoutSpaces,
+            wrongAnnFileRnaSSWithoutSpaceChars,
+            DataSourceType.PASTE);
+
+    Assert.assertFalse(
+            testRnaSSAnnotationsEquivalent(
+                    alWithSpaces.getAlignmentAnnotation()[0],
+                    wrongAlWithoutSpaces.getAlignmentAnnotation()[0]),
+            "RNA SS Annotations SHOULD NOT be pair-wise equivalent (but apparently are): \n"
+                    + "RNA SS A 1:"
+                    + alWithSpaces.getAlignmentAnnotation()[0]
+                            .getRnaSecondaryStructure()
+                    + "\n" + "RNA SS A 2:"
+                    + wrongAlWithoutSpaces.getAlignmentAnnotation()[0]
+                            .getRnaSecondaryStructure());
+
+    // check no spaces in the output
+    // TODO: create a better 'save as <format>' pattern
+    alWithSpaces.getAlignmentAnnotation()[0].visible = true;
+    StockholmFile sf = new StockholmFile(alWithSpaces);
+
+    String stockholmFile = sf.print(alWithSpaces.getSequencesArray(), true);
+    Pattern noSpacesInRnaSSAnnotation = Pattern
+            .compile("\\n#=GC SS_cons\\s+\\S{14}\\n");
+    Matcher m = noSpacesInRnaSSAnnotation.matcher(stockholmFile);
+    boolean matches = m.find();
+    Assert.assertTrue(matches,
+            "StockholmFile output does not contain expected output (may contain spaces):\n"
+                    + stockholmFile);
+
+  }
 }