develop merge
authortcofoegbu <tcnofoegbu@dundee.ac.uk>
Mon, 25 Apr 2016 15:14:17 +0000 (16:14 +0100)
committertcofoegbu <tcnofoegbu@dundee.ac.uk>
Mon, 25 Apr 2016 15:14:17 +0000 (16:14 +0100)
61 files changed:
.classpath
THIRDPARTYLIBS
doc/AddingGroovySupport.html
examples/groovy/featureCounter.groovy [new file with mode: 0644]
examples/groovy/multipleFeatureAnnotations.groovy [new file with mode: 0644]
help/helpTOC.xml
help/html/features/groovy.html
lib/groovy-all-1.8.2.jar [deleted file]
lib/groovy-all-2.4.6.jar [new file with mode: 0644]
resources/lang/Messages.properties
src/MCview/PDBViewer.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/scoremodels/FeatureScoreModel.java
src/jalview/api/AlignCalcWorkerI.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/datamodel/SeqCigar.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/ext/ensembl/EnsemblSeqProxy.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/CutAndPasteHtmlTransfer.java
src/jalview/gui/CutAndPasteTransfer.java
src/jalview/gui/DasSourceBrowser.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/IdPanel.java
src/jalview/gui/OptsAndParamsPage.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/TreeCanvas.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/gff/GffConstants.java [new file with mode: 0644]
src/jalview/jbgui/GAlignFrame.java
src/jalview/util/ColorUtils.java
src/jalview/util/Comparison.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/workers/AlignCalcWorker.java
src/jalview/workers/AlignmentAnnotationFactory.java [new file with mode: 0644]
src/jalview/workers/AnnotationProviderI.java [new file with mode: 0644]
src/jalview/workers/AnnotationWorker.java [new file with mode: 0644]
src/jalview/workers/ColumnCounterWorker.java [new file with mode: 0644]
src/jalview/workers/ConsensusThread.java
src/jalview/workers/ConservationThread.java
src/jalview/workers/FeatureCounterI.java [new file with mode: 0644]
src/jalview/workers/StrucConsensusThread.java
src/jalview/ws/jws2/AADisorderClient.java
src/jalview/ws/jws2/JPred301Client.java
src/jalview/ws/jws2/RNAalifoldClient.java
test/jalview/analysis/AlignmentUtilsTests.java
test/jalview/analysis/scoremodels/FeatureScoreModelTest.java
test/jalview/datamodel/SeqCigarTest.java
test/jalview/ext/ensembl/EnsemblSeqProxyTest.java
test/jalview/io/FeaturesFileTest.java
test/jalview/io/SequenceAnnotationReportTest.java [new file with mode: 0644]
test/jalview/util/ColorUtilsTest.java
test/jalview/util/ComparisonTest.java
utils/InstallAnywhere/Jalview.iap_xml

index cad9e2b..aad4801 100644 (file)
@@ -39,7 +39,7 @@
        <classpathentry kind="lib" path="lib/jdas-1.0.4.jar"/>
        <classpathentry kind="lib" path="lib/spring-core-3.0.5.RELEASE.jar"/>
        <classpathentry kind="lib" path="lib/spring-web-3.0.5.RELEASE.jar"/>
-       <classpathentry kind="lib" path="lib/groovy-all-1.8.2.jar"/>
+       <classpathentry kind="lib" path="/Users/jprocter/git/jalview/lib/groovy-all-2.4.6.jar"/>
        <classpathentry kind="lib" path="lib/min-jabaws-client-2.1.0.jar" sourcepath="/clustengine"/>
        <classpathentry kind="lib" path="lib/json_simple-1.1.jar" sourcepath="/Users/jimp/Downloads/json_simple-1.1-all.zip"/>
        <classpathentry kind="lib" path="lib/slf4j-api-1.7.7.jar"/>
index c6c817a..caaa7f9 100644 (file)
@@ -21,6 +21,7 @@ commons-discovery.jar
 commons-logging-1.1.1.jar
 commons-logging.jar
 commons-net-3.3.jar
+groovy-all-2.4.6-indy.jar      APL 2.0 License - downloaded and extracted from https://dl.bintray.com/groovy/maven/apache-groovy-binary-2.4.6.zip
 httpclient-4.0.3.jar
 httpcore-4.0.1.jar
 httpmime-4.0.3.jar
index 41e34ce..63e7170 100644 (file)
 </title>
 <body>
 <h1>
-Adding Groovy Support to Jalview
+Groovy Support in Jalview
 </h1>
 <p>
-There is currently no scripting language 
-extension within Jalview, in part because a 
-scripting API has not yet been developed.
-</p>
-<p>It is, however, really easy to embed scripting
-engines like groovy. If groovy is detected on the 
-classpath, a new menu entry on the Desktop's Tools 
-menu will open the GroovyShell.
+  <a href="http://www.groovy-lang.org">Groovy</a> has been bundled with the Jalview desktop since circa 2012. The program supports interactive execution of groovy scripts via the Groovy Console, and command line execution via the '-groovy' option. The main source for documentation about Groovy in Jalview is the <a href="http://www.jalview.org/help/html/features/groovy.html">online help pages</a>.
 </p>
 <p>Here are some scripts to get you started:</p>
 <ul><li>Getting the title, alignment and first sequence from the current alignFrame<br>
@@ -43,13 +36,6 @@ def seq = alignment.getSequenceAt(0);
 </pre>
 </li>
 </ul>
-<h1>Getting Groovy...</h1>
-<p>
-You need the core groovy jars which include the GroovyShell. The easiest way of doing
-this is to add the groovy-all-*.jar to the lib directory whose path is given in the java.ext.dirs property.</p>
-<p>The is obtained from the <em>embedded</em> directory within the <a 
-href="http://dist.codehaus.org/groovy/distributions"/>groovy distribution</a>).
-</p>
 <h2>TODO</h2>
 <p>
 Using Java class methods from Groovy is straightforward, but currently, there isn't a set of easy to use methods for the jalview objects. A Jalview Scripting API needs to be developed to make this easier.</p>
diff --git a/examples/groovy/featureCounter.groovy b/examples/groovy/featureCounter.groovy
new file mode 100644 (file)
index 0000000..08d038d
--- /dev/null
@@ -0,0 +1,87 @@
+import jalview.workers.FeatureCounterI;
+import jalview.workers.AlignmentAnnotationFactory;
+
+/*
+ * Example script that registers two alignment annotation calculators
+ * - one that counts residues in a column with Pfam annotation
+ * - one that counts only charged residues with Pfam annotation
+ * Modify this example as required to count by column any desired value that can be 
+ * derived from the residue and sequence features at each position of an alignment.
+ */
+
+/*
+ * A closure that returns true for any Charged residue
+ */
+def isCharged = { residue ->
+    switch(residue) {
+        case ['D', 'd', 'E', 'e', 'H', 'h', 'K', 'k', 'R', 'r']:
+            return true
+    }
+    false
+} 
+
+/*
+ * A closure that returns 1 if sequence features include type 'Pfam', else 0
+ * Argument should be a list of SequenceFeature 
+ */
+def hasPfam = { features -> 
+    for (sf in features)
+    {
+        /*
+         * Here we inspect the type of the sequence feature.
+         * You can also test sf.description, sf.score, sf.featureGroup,
+         * sf.strand, sf.phase, sf.begin, sf.end
+         * or sf.getValue(attributeName) for GFF 'column 9' properties
+         */
+        if ("Pfam".equals(sf.type))
+        {
+            return true
+        }
+    }
+    false
+}
+
+/*
+ * Closure that counts residues with a Pfam feature annotation
+ * Parameters are
+ * - the name (label) for the alignment annotation
+ * - the description (tooltip) for the annotation
+ * - a closure (groovy function) that tests whether to include a residue
+ * - a closure that tests whether to increment count based on sequence features  
+ */
+def getColumnCounter = { name, desc, residueTester, featureCounter ->
+    [
+     getName: { name }, 
+     getDescription: { desc },
+     getMinColour: { [0, 255, 255] }, // cyan
+     getMaxColour: { [0, 0, 255] }, // blue
+     count: 
+         { res, feats -> 
+            def c = 0
+            if (residueTester.call(res))
+            {
+                if (featureCounter.call(feats))
+                {
+                    c++
+                }
+            }
+            c
+         }
+     ] as FeatureCounterI
+}
+
+/*
+ * Define annotation that counts any residue with Pfam domain annotation
+ */
+def pfamAnnotation = getColumnCounter("Pfam", "Count of residues with Pfam domain annotation", {true}, hasPfam)
+
+/*
+ * Define annotation that counts charged residues with Pfam domain annotation
+ */
+def chargedPfamAnnotation = getColumnCounter("Pfam charged", "Count of charged residues with Pfam domain annotation", isCharged, hasPfam)
+
+/*
+ * Register the annotations
+ */
+AlignmentAnnotationFactory.newCalculator(pfamAnnotation) 
+AlignmentAnnotationFactory.newCalculator(chargedPfamAnnotation)
diff --git a/examples/groovy/multipleFeatureAnnotations.groovy b/examples/groovy/multipleFeatureAnnotations.groovy
new file mode 100644 (file)
index 0000000..592c7f5
--- /dev/null
@@ -0,0 +1,110 @@
+import jalview.workers.AlignmentAnnotationFactory;
+import jalview.workers.AnnotationProviderI;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.util.ColorUtils;
+import jalview.util.Comparison;
+import java.awt.Color;
+
+/*
+ * Example script to compute two alignment annotations
+ * - count of Phosphorylation features
+ * - count of Turn features
+ * To try this, first load example file uniref50.fa and load on features file
+ * exampleFeatures.txt, before running this script
+ *
+ * The script only needs to be run once - it will be registered by Jalview
+ * and recalculated automatically when the alignment changes.
+ */
+
+/*
+ * A closure that returns true if value includes "PHOSPHORYLATION"
+ */
+def phosCounter = { type ->    type.contains("PHOSPHORYLATION") }
+
+/*
+ * A closure that returns true if value includes "TURN"
+ */
+def turnCounter = { type ->    type.contains("TURN") }
+
+/*
+ * A closure that computes and returns an array of Annotation values,
+ * one for each column of the alignment
+ */
+def getAnnotations(al, fr, counter) 
+{
+    def width = al.width
+    def counts = new int[width] 
+    def max = 0
+    
+    /*
+     * count features in each column, record the maximum value
+     */
+    for (col = 0 ; col < width ; col++)
+    {
+        def count = 0
+        for (row = 0 ; row < al.height ; row++)
+        {
+            seq = al.getSequenceAt(row)
+            if (seq != null && col < seq.getLength())
+            {
+                def res = seq.getCharAt(col)
+                if (!Comparison.isGap(res))
+                {
+                    pos = seq.findPosition(col)
+                    features = fr.findFeaturesAtRes(seq, pos)
+                    for (feature in features)
+                    {
+                        if (counter.call(feature.type))
+                        {
+                            count++
+                        }
+                    }
+                }
+            }
+        }
+        counts[col] = count
+        if (count > max)
+        {
+            max = count
+        }
+    }
+    
+    /*
+     * make the Annotation objects, with a graduated colour scale 
+     * (from min value to max value) for the histogram bars 
+     */
+    def zero = '0' as char
+    def anns = new Annotation[width] 
+    for (col = 0 ; col < width ; col++)
+    {
+        def c = counts[col]
+        if (c > 0)
+        {
+            Color color = ColorUtils.getGraduatedColour(c, 0, Color.cyan,
+                max, Color.blue)
+            anns[col] = AlignmentAnnotationFactory.newAnnotation(String.valueOf(c),
+                String.valueOf(c), zero, c, color)
+        }
+    }
+    anns
+}
+
+/*
+ * Define the method that performs the calculations, and builds two
+ * AlignmentAnnotation objects
+ */
+def annotator = 
+    [ calculateAnnotation: { al, fr ->
+        def phosAnns = getAnnotations(al, fr, phosCounter)
+        def ann1 = AlignmentAnnotationFactory.newAlignmentAnnotation("Phosphorylation", "Count of Phosphorylation features", phosAnns)
+        def turnAnns = getAnnotations(al, fr, turnCounter)
+        def ann2 = AlignmentAnnotationFactory.newAlignmentAnnotation("Turn", "Count of Turn features", turnAnns)
+        return [ann1, ann2]
+      } 
+    ] as AnnotationProviderI
+    
+/*
+ * Register the annotation calculator with Jalview
+ */
+AlignmentAnnotationFactory.newCalculator(annotator) 
index 2594738..fe8e1a9 100755 (executable)
@@ -27,7 +27,7 @@
                                <tocitem text="PDB Sequence Fetcher" target="pdbfetcher" />
                                <tocitem text="PDB Structure Chooser" target="pdbchooser" />
                                <tocitem text="Chimera Viewer" target="chimera" />
-                               <tocitem text="Select columns by annotation" target="selectcolbyannot" />
+                               <tocitem text="Select Columns by Annotation" target="selectcolbyannot" />
                                <tocitem text="Latest Release Notes" target="release"/>
                </tocitem>
                
@@ -49,7 +49,7 @@
                <tocitem text="Viewing Trees" target="treeviewer" expand="false" />
                <tocitem text="Fetching Sequences" target="seqfetch" />         
                
-               <tocitem text="Select columns by annotation" target="selectcolbyannot" />
+               <tocitem text="Select Columns by Annotation" target="selectcolbyannot" />
                
                <tocitem text="Nucleic Acid Support" target="nucleicAcids" expand="false">
                        <tocitem text="Viewing RNA structure" target="varna" />
                        <tocitem text="Consensus" target="calcs.consensus" />
                        <tocitem text="RNA Structure Consensus" target="calcs.alstrconsensus" />
                        <tocitem text="Annotations File Format" target="annotations.fileformat" />
-                       <tocitem text="Select Column by Annotation" target="calcs.annotation" />
+                       <tocitem text="Select Columns by Annotation" target="selectcolbyannot" />
                </tocitem>
                <tocitem text="3D Structure Data" target="viewingpdbs" expand="false">
                        <tocitem text="PDB Sequence Fetcher" target="pdbfetcher" />
index adabdf1..9aa341b 100644 (file)
@@ -27,7 +27,7 @@
     <strong>The Groovy Shell</strong>
   </p>
   <p>
-    <a href="http://groovy.codehaus.org/">Groovy</a> is an &quot;<em>agile
+    <a href="http://www.groovy-lang.org/">Groovy</a> is an &quot;<em>agile
       and dynamic language for the Java platform</em>&quot;. The groovy
     scripting language makes it extremely easy to programmatically
     interact with Java programs, in much the same way that Javascript is
     page.
   </p>
   <p>
-    <strong><em>Getting Groovy...</em> </strong><br> Jalview Groovy
-    support is only possible if the core groovy jars which include the
-    GroovyShell are present on the CLASSPATH when Jalview is started.
-  </p>
-  <p>
-    The jars are obtained from the <em>embedded</em> directory within
-    the <a href="http://dist.codehaus.org/groovy/distributions">groovy
-      distribution</a>. The easiest way of adding them to the Jalview
-    classpath is to download and build Jalview from its source
-    distribution, and then add the groovy-all-*.jar to the lib directory
-    whose path is given in the java.ext.dirs property.
-  </p>
-  <p>
-    <strong>Opening Jalview's Groovy Console</strong><br>If groovy
-    is available, then the <strong>Tools&#8594;Groovy
-      Console...</strong> menu entry will be available from the Jalview Desktop's
-    drop-down menu. Selecting this will open the <a
-      href="http://groovy.codehaus.org/Groovy+Console"
-    >Groovy Console</a> which allows you to interactively execute Groovy
+    <strong><em>Getting Groovy...</em> </strong><br> Jalview comes with
+    an embedded installation of Groovy. All you need is to select <strong>Tools&#8594;Groovy
+      Console...</strong> menu option from the Jalview Desktop's
+    drop-down menu. After a short pause, you should then see the <a
+      href="http://groovy-lang.org/groovyconsole.html"
+    >Groovy Console</a> appear. This allows you to interactively execute Groovy
     scripts within the Jalview run-time environment.
   </p>
   <p>
diff --git a/lib/groovy-all-1.8.2.jar b/lib/groovy-all-1.8.2.jar
deleted file mode 100644 (file)
index 85af249..0000000
Binary files a/lib/groovy-all-1.8.2.jar and /dev/null differ
diff --git a/lib/groovy-all-2.4.6.jar b/lib/groovy-all-2.4.6.jar
new file mode 100644 (file)
index 0000000..4301384
Binary files /dev/null and b/lib/groovy-all-2.4.6.jar differ
index d1bd66a..3c79b58 100644 (file)
@@ -1292,4 +1292,7 @@ status.cancelled_image_export_operation = Cancelled {0} export operation.
 info.error_creating_file = Error creating {0} file.
 exception.outofmemory_loading_mmcif_file = Out of memory loading mmCIF File
 info.error_creating_file = Error creating {0} file.
+label.run_groovy = Run Groovy console script
+label.run_groovy_tip = Run the script in the Groovy console over this alignment
+label.couldnt_run_groovy_script = Failed to run Groovy script
 label.uniprot_sequence_fetcher = UniProt Sequence Fetcher
\ No newline at end of file
index da744a4..e032c7a 100755 (executable)
@@ -62,7 +62,6 @@ import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
 import javax.swing.JRadioButtonMenuItem;
-import javax.swing.SwingUtilities;
 
 public class PDBViewer extends JInternalFrame implements Runnable
 {
@@ -416,8 +415,7 @@ public class PDBViewer extends JInternalFrame implements Runnable
           @Override
           public void mousePressed(MouseEvent evt)
           {
-            if (evt.isControlDown()
-                    || SwingUtilities.isRightMouseButton(evt))
+            if (evt.isPopupTrigger())
             {
               radioItem.removeActionListener(radioItem.getActionListeners()[0]);
 
index 28062c0..42a1201 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.analysis;
 
+import static jalview.io.gff.GffConstants.CLINICAL_SIGNIFICANCE;
+
 import jalview.datamodel.AlignedCodon;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
@@ -38,8 +40,9 @@ import jalview.schemes.ResidueProperties;
 import jalview.util.Comparison;
 import jalview.util.MapList;
 import jalview.util.MappingUtils;
-import jalview.util.StringUtils;
 
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -66,6 +69,31 @@ import java.util.TreeMap;
 public class AlignmentUtils
 {
 
+  private static final String SEQUENCE_VARIANT = "sequence_variant:";
+  private static final String ID = "ID";
+
+  /**
+   * A data model to hold the 'normal' base value at a position, and an optional
+   * sequence variant feature
+   */
+  static class DnaVariant
+  {
+    String base;
+
+    SequenceFeature variant;
+
+    DnaVariant(String nuc)
+    {
+      base = nuc;
+    }
+
+    DnaVariant(String nuc, SequenceFeature var)
+    {
+      base = nuc;
+      variant = var;
+    }
+  }
+
   /**
    * given an existing alignment, create a new alignment including all, or up to
    * flankSize additional symbols from each sequence's dataset sequence
@@ -1744,39 +1772,26 @@ public class AlignmentUtils
      * /ENSP00000288602?feature=transcript_variation;content-type=text/xml
      * which would be a bit slower but possibly more reliable
      */
-    LinkedHashMap<Integer, List<String[][]>> variants = buildDnaVariantsMap(
+
+    /*
+     * build a map with codon variations for each potentially varying peptide
+     */
+    LinkedHashMap<Integer, List<DnaVariant>[]> variants = buildDnaVariantsMap(
             dnaSeq, dnaToProtein);
 
     /*
      * scan codon variations, compute peptide variants and add to peptide sequence
      */
     int count = 0;
-    for (Entry<Integer, List<String[][]>> variant : variants.entrySet())
+    for (Entry<Integer, List<DnaVariant>[]> variant : variants.entrySet())
     {
       int peptidePos = variant.getKey();
-      List<String[][]> codonVariants = variant.getValue();
-      String residue = String.valueOf(peptide.getCharAt(peptidePos - 1)); // 0-based
-      for (String[][] codonVariant : codonVariants)
-      {
-        List<String> peptideVariants = computePeptideVariants(codonVariant,
-                residue);
-        if (!peptideVariants.isEmpty())
-        {
-          String desc = residue
-                  + "->" // include canonical residue in description
-                  + StringUtils
-                          .listToDelimitedString(peptideVariants, ", ");
-          SequenceFeature sf = new SequenceFeature(
-                  SequenceOntologyI.SEQUENCE_VARIANT, desc, peptidePos,
-                  peptidePos, 0f, null);
-          peptide.addSequenceFeature(sf);
-          count++;
-        }
-      }
+      List<DnaVariant>[] codonVariants = variant.getValue();
+      count += computePeptideVariants(peptide, peptidePos, codonVariants);
     }
 
     /*
-     * ugly sort to get sequence features in start position order
+     * sort to get sequence features in start position order
      * - would be better to store in Sequence as a TreeSet or NCList?
      */
     Arrays.sort(peptide.getSequenceFeatures(),
@@ -1794,21 +1809,186 @@ public class AlignmentUtils
   }
 
   /**
-   * Builds a map whose key is position in the protein sequence, and value is an
-   * array of all variants for the coding codon positions
+   * Computes non-synonymous peptide variants from codon variants and adds them
+   * as sequence_variant features on the protein sequence (one feature per
+   * allele variant). Selected attributes (variant id, clinical significance)
+   * are copied over to the new features.
+   * 
+   * @param peptide
+   *          the protein sequence
+   * @param peptidePos
+   *          the position to compute peptide variants for
+   * @param codonVariants
+   *          a list of dna variants per codon position
+   * @return the number of features added
+   */
+  static int computePeptideVariants(SequenceI peptide, int peptidePos,
+          List<DnaVariant>[] codonVariants)
+  {
+    String residue = String.valueOf(peptide.getCharAt(peptidePos - 1));
+    int count = 0;
+    String base1 = codonVariants[0].get(0).base;
+    String base2 = codonVariants[1].get(0).base;
+    String base3 = codonVariants[2].get(0).base;
+
+    /*
+     * variants in first codon base
+     */
+    for (DnaVariant var : codonVariants[0])
+    {
+      if (var.variant != null)
+      {
+        String alleles = (String) var.variant.getValue("alleles");
+        if (alleles != null)
+        {
+          for (String base : alleles.split(","))
+          {
+            String codon = base + base2 + base3;
+            if (addPeptideVariant(peptide, peptidePos, residue, var, codon))
+            {
+              count++;
+            }
+          }
+        }
+      }
+    }
+
+    /*
+     * variants in second codon base
+     */
+    for (DnaVariant var : codonVariants[1])
+    {
+      if (var.variant != null)
+      {
+        String alleles = (String) var.variant.getValue("alleles");
+        if (alleles != null)
+        {
+          for (String base : alleles.split(","))
+          {
+            String codon = base1 + base + base3;
+            if (addPeptideVariant(peptide, peptidePos, residue, var, codon))
+            {
+              count++;
+            }
+          }
+        }
+      }
+    }
+
+    /*
+     * variants in third codon base
+     */
+    for (DnaVariant var : codonVariants[2])
+    {
+      if (var.variant != null)
+      {
+        String alleles = (String) var.variant.getValue("alleles");
+        if (alleles != null)
+        {
+          for (String base : alleles.split(","))
+          {
+            String codon = base1 + base2 + base;
+            if (addPeptideVariant(peptide, peptidePos, residue, var, codon))
+            {
+              count++;
+            }
+          }
+        }
+      }
+    }
+
+    return count;
+  }
+
+  /**
+   * Helper method that adds a peptide variant feature, provided the given codon
+   * translates to a value different to the current residue (is a non-synonymous
+   * variant). ID and clinical_significance attributes of the dna variant (if
+   * present) are copied to the new feature.
+   * 
+   * @param peptide
+   * @param peptidePos
+   * @param residue
+   * @param var
+   * @param codon
+   * @return true if a feature was added, else false
+   */
+  static boolean addPeptideVariant(SequenceI peptide, int peptidePos,
+          String residue, DnaVariant var, String codon)
+  {
+    /*
+     * get peptide translation of codon e.g. GAT -> D
+     * note that variants which are not single alleles,
+     * e.g. multibase variants or HGMD_MUTATION etc
+     * are currently ignored here
+     */
+    String trans = codon.contains("-") ? "-"
+            : (codon.length() > 3 ? null : ResidueProperties
+                    .codonTranslate(codon));
+    if (trans != null && !trans.equals(residue))
+    {
+      String desc = residue + "->" + trans;
+      // set score to 0f so 'graduated colour' option is offered! JAL-2060
+      SequenceFeature sf = new SequenceFeature(
+              SequenceOntologyI.SEQUENCE_VARIANT, desc, peptidePos,
+              peptidePos, 0f, null);
+      StringBuilder attributes = new StringBuilder(32);
+      String id = (String) var.variant.getValue(ID);
+      if (id != null)
+      {
+        if (id.startsWith(SEQUENCE_VARIANT))
+        {
+          id = id.substring(SEQUENCE_VARIANT.length());
+        }
+        sf.setValue(ID, id);
+        attributes.append(ID).append("=").append(id);
+        // TODO handle other species variants
+        StringBuilder link = new StringBuilder(32);
+        try
+        {
+          link.append(desc).append(" ").append(id)
+                  .append("|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=")
+                  .append(URLEncoder.encode(id, "UTF-8"));
+          sf.addLink(link.toString());
+        } catch (UnsupportedEncodingException e)
+        {
+          // as if
+        }
+      }
+      String clinSig = (String) var.variant
+              .getValue(CLINICAL_SIGNIFICANCE);
+      if (clinSig != null)
+      {
+        sf.setValue(CLINICAL_SIGNIFICANCE, clinSig);
+        attributes.append(";").append(CLINICAL_SIGNIFICANCE).append("=")
+                .append(clinSig);
+      }
+      peptide.addSequenceFeature(sf);
+      if (attributes.length() > 0)
+      {
+        sf.setAttributes(attributes.toString());
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Builds a map whose key is position in the protein sequence, and value is a
+   * list of the base and all variants for each corresponding codon position
    * 
    * @param dnaSeq
    * @param dnaToProtein
    * @return
    */
-  static LinkedHashMap<Integer, List<String[][]>> buildDnaVariantsMap(
+  static LinkedHashMap<Integer, List<DnaVariant>[]> buildDnaVariantsMap(
           SequenceI dnaSeq, MapList dnaToProtein)
   {
     /*
-     * map from peptide position to all variant features of the codon for it
-     * LinkedHashMap ensures we add the peptide features in sequence order
+     * map from peptide position to all variants of the codon which codes for it
+     * LinkedHashMap ensures we keep the peptide features in sequence order
      */
-    LinkedHashMap<Integer, List<String[][]>> variants = new LinkedHashMap<Integer, List<String[][]>>();
+    LinkedHashMap<Integer, List<DnaVariant>[]> variants = new LinkedHashMap<Integer, List<DnaVariant>[]>();
     SequenceOntologyI so = SequenceOntologyFactory.getInstance();
 
     SequenceFeature[] dnaFeatures = dnaSeq.getSequenceFeatures();
@@ -1841,10 +2021,13 @@ public class AlignmentUtils
           continue;
         }
         int peptidePosition = mapsTo[0];
-        List<String[][]> codonVariants = variants.get(peptidePosition);
+        List<DnaVariant>[] codonVariants = variants.get(peptidePosition);
         if (codonVariants == null)
         {
-          codonVariants = new ArrayList<String[][]>();
+          codonVariants = new ArrayList[3];
+          codonVariants[0] = new ArrayList<DnaVariant>();
+          codonVariants[1] = new ArrayList<DnaVariant>();
+          codonVariants[2] = new ArrayList<DnaVariant>();
           variants.put(peptidePosition, codonVariants);
         }
 
@@ -1873,106 +2056,46 @@ public class AlignmentUtils
         lastCodon = codon;
 
         /*
-         * save nucleotide (and this variant) for each codon position
+         * save nucleotide (and any variant) for each codon position
          */
-        String[][] codonVariant = new String[3][];
         for (int codonPos = 0; codonPos < 3; codonPos++)
         {
           String nucleotide = String.valueOf(
                   dnaSeq.getCharAt(codon[codonPos] - dnaStart))
                   .toUpperCase();
-          if (codonVariant[codonPos] == null)
+          List<DnaVariant> codonVariant = codonVariants[codonPos];
+          if (codon[codonPos] == dnaCol)
           {
-            /*
-             * record current dna base
-             */
-            codonVariant[codonPos] = new String[] { nucleotide };
+            if (!codonVariant.isEmpty()
+                    && codonVariant.get(0).variant == null)
+            {
+              /*
+               * already recorded base value, add this variant
+               */
+              codonVariant.get(0).variant = sf;
+            }
+            else
+            {
+              /*
+               * add variant with base value
+               */
+              codonVariant.add(new DnaVariant(nucleotide, sf));
+            }
           }
-          if (codon[codonPos] == dnaCol)
+          else if (codonVariant.isEmpty())
           {
             /*
-             * add alleles to dna base (and any previously found alleles)
+             * record (possibly non-varying) base value
              */
-            String[] known = codonVariant[codonPos];
-            String[] dnaVariants = new String[alleles.length + known.length];
-            System.arraycopy(known, 0, dnaVariants, 0, known.length);
-            System.arraycopy(alleles, 0, dnaVariants, known.length,
-                    alleles.length);
-            codonVariant[codonPos] = dnaVariants;
+            codonVariant.add(new DnaVariant(nucleotide));
           }
         }
-        codonVariants.add(codonVariant);
       }
     }
     return variants;
   }
 
   /**
-   * Returns a sorted, non-redundant list of all peptide translations generated
-   * by the given dna variants, excluding the current residue value
-   * 
-   * @param codonVariants
-   *          an array of base values (acgtACGT) for codon positions 1, 2, 3
-   * @param residue
-   *          the current residue translation
-   * @return
-   */
-  static List<String> computePeptideVariants(String[][] codonVariants,
-          String residue)
-  {
-    List<String> result = new ArrayList<String>();
-    for (String base1 : codonVariants[0])
-    {
-      for (String base2 : codonVariants[1])
-      {
-        for (String base3 : codonVariants[2])
-        {
-          String codon = base1 + base2 + base3;
-          /*
-           * get peptide translation of codon e.g. GAT -> D
-           * note that variants which are not single alleles,
-           * e.g. multibase variants or HGMD_MUTATION etc
-           * are ignored here
-           */
-          String peptide = codon.contains("-") ? "-"
-                  : (codon.length() > 3 ? null : ResidueProperties
-                          .codonTranslate(codon));
-          if (peptide != null && !result.contains(peptide)
-                  && !peptide.equalsIgnoreCase(residue))
-          {
-            result.add(peptide);
-          }
-        }
-      }
-    }
-
-    /*
-     * sort alphabetically with STOP at the end
-     */
-    Collections.sort(result, new Comparator<String>()
-    {
-
-      @Override
-      public int compare(String o1, String o2)
-      {
-        if ("STOP".equals(o1))
-        {
-          return 1;
-        }
-        else if ("STOP".equals(o2))
-        {
-          return -1;
-        }
-        else
-        {
-          return o1.compareTo(o2);
-        }
-      }
-    });
-    return result;
-  }
-
-  /**
    * Makes an alignment with a copy of the given sequences, adding in any
    * non-redundant sequences which are mapped to by the cross-referenced
    * sequences.
index 1ca3342..7c81912 100644 (file)
@@ -23,9 +23,8 @@ package jalview.analysis.scoremodels;
 import jalview.api.analysis.ScoreModelI;
 import jalview.api.analysis.ViewBasedAnalysisI;
 import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SeqCigar;
 import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceI;
-import jalview.util.Comparison;
 
 import java.util.ArrayList;
 import java.util.Hashtable;
@@ -48,13 +47,10 @@ public class FeatureScoreModel implements ScoreModelI, ViewBasedAnalysisI
   {
     int nofeats = 0;
     List<String> dft = fr.getDisplayedFeatureTypes();
-
     nofeats = dft.size();
-
-    SequenceI[] sequenceString = seqData.getVisibleAlignment(
-            Comparison.GapChars.charAt(0)).getSequencesArray();
-    int noseqs = sequenceString.length;
-    int cpwidth = seqData.getWidth();
+    SeqCigar[] seqs = seqData.getSequences();
+    int noseqs = seqs.length;
+    int cpwidth = 0;// = seqData.getWidth();
     float[][] distance = new float[noseqs][noseqs];
     if (nofeats == 0)
     {
@@ -67,54 +63,64 @@ public class FeatureScoreModel implements ScoreModelI, ViewBasedAnalysisI
       }
       return distance;
     }
-    float max = 0;
-    for (int cpos = 0; cpos < cpwidth; cpos++)
+    // need to get real position for view position
+    int[] viscont = seqData.getVisibleContigs();
+    for (int vc = 0; vc < viscont.length; vc += 2)
     {
-      // get visible features at cpos under view's display settings and compare
-      // them
-      List<Hashtable<String, SequenceFeature>> sfap = new ArrayList<Hashtable<String, SequenceFeature>>();
-      for (int i = 0; i < noseqs; i++)
-      {
-        Hashtable<String, SequenceFeature> types = new Hashtable<String, SequenceFeature>();
-        List<SequenceFeature> sfs = fr.findFeaturesAtRes(sequenceString[i],
-                sequenceString[i].findPosition(cpos));
-        for (SequenceFeature sf : sfs)
-        {
-          types.put(sf.getType(), sf);
-        }
-        sfap.add(types);
-      }
-      for (int i = 0; i < (noseqs - 1); i++)
+
+      for (int cpos = viscont[vc]; cpos <= viscont[vc + 1]; cpos++)
       {
-        if (cpos == 0)
-        {
-          distance[i][i] = 0f;
-        }
-        for (int j = i + 1; j < noseqs; j++)
+        cpwidth++;
+        // get visible features at cpos under view's display settings and
+        // compare them
+        List<Hashtable<String, SequenceFeature>> sfap = new ArrayList<Hashtable<String, SequenceFeature>>();
+        for (int i = 0; i < noseqs; i++)
         {
-          int sfcommon = 0;
-          // compare the two lists of features...
-          Hashtable<String, SequenceFeature> fi = sfap.get(i), fk, fj = sfap
-                  .get(j);
-          if (fi.size() > fj.size())
+          Hashtable<String, SequenceFeature> types = new Hashtable<String, SequenceFeature>();
+          int spos = seqs[i].findPosition(cpos);
+          if (spos != -1)
           {
-            fk = fj;
+            List<SequenceFeature> sfs = fr.findFeaturesAtRes(
+                    seqs[i].getRefSeq(), spos);
+            for (SequenceFeature sf : sfs)
+            {
+              types.put(sf.getType(), sf);
+            }
           }
-          else
+          sfap.add(types);
+        }
+        for (int i = 0; i < (noseqs - 1); i++)
+        {
+          if (cpos == 0)
           {
-            fk = fi;
-            fi = fj;
+            distance[i][i] = 0f;
           }
-          for (String k : fi.keySet())
+          for (int j = i + 1; j < noseqs; j++)
           {
-            SequenceFeature sfj = fk.get(k);
-            if (sfj != null)
+            int sfcommon = 0;
+            // compare the two lists of features...
+            Hashtable<String, SequenceFeature> fi = sfap.get(i), fk, fj = sfap
+                    .get(j);
+            if (fi.size() > fj.size())
+            {
+              fk = fj;
+            }
+            else
+            {
+              fk = fi;
+              fi = fj;
+            }
+            for (String k : fi.keySet())
             {
-              sfcommon++;
+              SequenceFeature sfj = fk.get(k);
+              if (sfj != null)
+              {
+                sfcommon++;
+              }
             }
+            distance[i][j] += (fi.size() + fk.size() - 2f * sfcommon);
+            distance[j][i] += distance[i][j];
           }
-          distance[i][j] += (fi.size() + fk.size() - 2f * sfcommon);
-          distance[j][i] += distance[i][j];
         }
       }
     }
index 872528b..06dc054 100644 (file)
@@ -22,12 +22,30 @@ package jalview.api;
 
 import jalview.datamodel.AlignmentAnnotation;
 
+/**
+ * Interface describing a worker that calculates alignment annotation(s). The
+ * main (re-)calculation should be performed by the inherited run() method.
+ */
 public interface AlignCalcWorkerI extends Runnable
 {
-
+  /**
+   * Answers true if this worker updates the given annotation (regardless of its
+   * current state)
+   * 
+   * @param annot
+   * @return
+   */
   public boolean involves(AlignmentAnnotation annot);
 
+  /**
+   * Updates the display of calculated annotation values (does not recalculate
+   * the values). This allows for quick redraw of annotations when display
+   * settings are changed.
+   */
   public void updateAnnotation();
 
-  void removeOurAnnotation();
+  /**
+   * Removes any annotation managed by this worker from the alignment
+   */
+  void removeAnnotation();
 }
index 584a69a..40a2ce5 100755 (executable)
@@ -59,8 +59,8 @@ import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.util.Arrays;
 import java.util.Enumeration;
-import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 import java.util.Vector;
 
 public class FeatureSettings extends Panel implements ItemListener,
@@ -201,7 +201,8 @@ public class FeatureSettings extends Panel implements ItemListener,
             60);
   }
 
-  protected void popupSort(final MyCheckbox check, final Hashtable minmax,
+  protected void popupSort(final MyCheckbox check,
+          final Map<String, float[][]> minmax,
           int x, int y)
   {
     final String type = check.type;
@@ -239,7 +240,7 @@ public class FeatureSettings extends Panel implements ItemListener,
     men.add(dens);
     if (minmax != null)
     {
-      final Object typeMinMax = minmax.get(type);
+      final float[][] typeMinMax = minmax.get(type);
       /*
        * final java.awt.CheckboxMenuItem chb = new
        * java.awt.CheckboxMenuItem("Vary Height"); // this is broken at the
@@ -252,7 +253,7 @@ public class FeatureSettings extends Panel implements ItemListener,
        * 
        * }); men.add(chb);
        */
-      if (typeMinMax != null && ((float[][]) typeMinMax)[0] != null)
+      if (typeMinMax != null && typeMinMax[0] != null)
       {
         // graduated colourschemes for those where minmax exists for the
         // positional features
@@ -338,7 +339,7 @@ public class FeatureSettings extends Panel implements ItemListener,
   {
     SequenceFeature[] tmpfeatures;
     String group = null, type;
-    Vector visibleChecks = new Vector();
+    Vector<String> visibleChecks = new Vector<String>();
     AlignmentI alignment = av.getAlignment();
     for (int i = 0; i < alignment.getHeight(); i++)
     {
@@ -404,7 +405,7 @@ public class FeatureSettings extends Panel implements ItemListener,
 
     // now add checkboxes which should be visible,
     // if they have not already been added
-    Enumeration en = visibleChecks.elements();
+    Enumeration<String> en = visibleChecks.elements();
 
     while (en.hasMoreElements())
     {
index d279a26..98b0de5 100644 (file)
@@ -68,10 +68,49 @@ public class SeqCigar extends CigarSimple
   }
 
   /**
+   * 
+   * @param column
+   * @return position in sequence for column (or -1 if no match state exists)
+   */
+  public int findPosition(int column)
+  {
+    int w = 0, ew, p = refseq.findPosition(start);
+    if (column < 0)
+    {
+      return -1;
+    }
+    if (range != null)
+    {
+      for (int i = 0; i < length; i++)
+      {
+        if (operation[i] == M || operation[i] == D)
+        {
+          p += range[i];
+        }
+        if (operation[i] == M || operation[i] == I)
+        {
+          ew = w + range[i];
+          if (column < ew)
+          {
+            if (operation[i] == I)
+            {
+              return -1;
+            }
+            return p - (ew - column);
+          }
+          w = ew;
+        }
+      }
+    }
+    return -1;
+  }
+
+  /**
    * Returns sequence as a string with cigar operations applied to it
    * 
    * @return String
    */
+  @Override
   public String getSequenceString(char GapChar)
   {
     return (length == 0) ? "" : (String) getSequenceAndDeletions(
index 4a7706f..f2eb8ac 100755 (executable)
@@ -39,6 +39,10 @@ public class SequenceFeature
   // private key for Phase designed not to conflict with real GFF data
   private static final String PHASE = "!Phase";
 
+  /*
+   * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as
+   * name1=value1;name2=value2,value3;...etc
+   */
   private static final String ATTRIBUTES = "ATTRIBUTES";
 
   public int begin;
@@ -111,26 +115,57 @@ public class SequenceFeature
     }
   }
 
+  /**
+   * Constructor including a Status value
+   * 
+   * @param type
+   * @param desc
+   * @param status
+   * @param begin
+   * @param end
+   * @param featureGroup
+   */
   public SequenceFeature(String type, String desc, String status,
           int begin, int end, String featureGroup)
   {
+    this(type, desc, begin, end, featureGroup);
+    setStatus(status);
+  }
+
+  /**
+   * Constructor
+   * 
+   * @param type
+   * @param desc
+   * @param begin
+   * @param end
+   * @param featureGroup
+   */
+  SequenceFeature(String type, String desc, int begin, int end,
+          String featureGroup)
+  {
     this.type = type;
     this.description = desc;
-    setValue(STATUS, status);
     this.begin = begin;
     this.end = end;
     this.featureGroup = featureGroup;
   }
 
+  /**
+   * Constructor including a score value
+   * 
+   * @param type
+   * @param desc
+   * @param begin
+   * @param end
+   * @param score
+   * @param featureGroup
+   */
   public SequenceFeature(String type, String desc, int begin, int end,
           float score, String featureGroup)
   {
-    this.type = type;
-    this.description = desc;
-    this.begin = begin;
-    this.end = end;
+    this(type, desc, begin, end, featureGroup);
     this.score = score;
-    this.featureGroup = featureGroup;
   }
 
   /**
index 8fb668a..fb81e66 100644 (file)
@@ -14,6 +14,7 @@ import jalview.io.FastaFile;
 import jalview.io.FileParse;
 import jalview.io.gff.SequenceOntologyFactory;
 import jalview.io.gff.SequenceOntologyI;
+import jalview.util.Comparison;
 import jalview.util.DBRefUtils;
 import jalview.util.MapList;
 
@@ -679,16 +680,21 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
     {
       complement.append(",");
     }
-    if ("HGMD_MUTATION".equalsIgnoreCase(allele))
+
+    /*
+     * some 'alleles' are actually descriptive terms 
+     * e.g. HGMD_MUTATION, PhenCode_variation
+     * - we don't want to 'reverse complement' these
+     */
+    if (!Comparison.isNucleotideSequence(allele, true))
     {
       complement.append(allele);
     }
     else
     {
-      char[] alleles = allele.toCharArray();
-      for (int i = alleles.length - 1; i >= 0; i--)
+      for (int i = allele.length() - 1; i >= 0; i--)
       {
-        complement.append(Dna.getComplement(alleles[i]));
+        complement.append(Dna.getComplement(allele.charAt(i)));
       }
     }
   }
index cb769bb..44d1d94 100644 (file)
@@ -119,6 +119,8 @@ import java.awt.dnd.DropTargetEvent;
 import java.awt.dnd.DropTargetListener;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.awt.event.KeyAdapter;
@@ -129,6 +131,7 @@ import java.awt.print.PageFormat;
 import java.awt.print.PrinterJob;
 import java.beans.PropertyChangeEvent;
 import java.io.File;
+import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -466,6 +469,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       formatMenu.add(vsel);
     }
+    addFocusListener(new FocusAdapter()
+    {
+      @Override
+      public void focusGained(FocusEvent e)
+      {
+        Desktop.setCurrentAlignFrame(AlignFrame.this);
+      }
+    });
 
   }
 
@@ -911,10 +922,21 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             .setSelected(av.getGlobalColourScheme() instanceof jalview.schemes.RNAHelicesColour);
 
     showProducts.setEnabled(canShowProducts());
+    setGroovyEnabled(Desktop.getGroovyConsole() != null);
 
     updateEditMenuBar();
   }
 
+  /**
+   * Set the enabled state of the 'Run Groovy' option in the Calculate menu
+   * 
+   * @param b
+   */
+  public void setGroovyEnabled(boolean b)
+  {
+    runGroovy.setEnabled(b);
+  }
+
   private IProgressIndicator progressBar;
 
   /*
@@ -3619,8 +3641,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           @Override
           public void mousePressed(MouseEvent evt)
           {
-            if (evt.isControlDown()
-                    || SwingUtilities.isRightMouseButton(evt))
+            if (evt.isPopupTrigger())
             {
               radioItem.removeActionListener(radioItem.getActionListeners()[0]);
 
@@ -4124,7 +4145,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       {
         JMenuItem tm = new JMenuItem();
         ScoreModelI sm = ResidueProperties.scoreMatrices.get(pwtype);
-        if (sm.isProtein() == !viewport.getAlignment().isNucleotide())
+        if (sm.isDNA() == viewport.getAlignment().isNucleotide()
+                || sm.isProtein() == !viewport.getAlignment()
+                        .isNucleotide())
         {
           String smn = MessageManager.getStringOrReturn(
                   "label.score_model_", sm.getName());
@@ -5371,7 +5394,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void tabbedPane_mousePressed(MouseEvent e)
   {
-    if (SwingUtilities.isRightMouseButton(e))
+    if (e.isPopupTrigger())
     {
       String msg = MessageManager.getString("label.enter_view_name");
       String reply = JOptionPane.showInternalInputDialog(this, msg, msg,
@@ -6088,6 +6111,45 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
   }
+
+  /**
+   * Try to run a script in the Groovy console, having first ensured that this
+   * AlignFrame is set as currentAlignFrame in Desktop, to allow the script to
+   * be targeted at this alignment.
+   */
+  @Override
+  protected void runGroovy_actionPerformed()
+  {
+    Desktop.setCurrentAlignFrame(this);
+    Object console = Desktop.instance.getGroovyConsole();
+    if (console != null)
+    {
+      /*
+       * use reflection here to avoid compile-time dependency
+       * on Groovy libraries
+       */
+      try
+      {
+        Class<?> gcClass = getClass().getClassLoader().loadClass(
+                "groovy.ui.Console");
+        Method runScript = gcClass.getMethod("runScript");
+        runScript.invoke(console);
+      } catch (Exception ex)
+      {
+        System.err.println((ex.toString()));
+        JOptionPane
+                .showInternalMessageDialog(Desktop.desktop, MessageManager
+                        .getString("label.couldnt_run_groovy_script"),
+                        MessageManager
+                                .getString("label.groovy_support_failed"),
+                        JOptionPane.ERROR_MESSAGE);
+      }
+    }
+    else
+    {
+      System.err.println("Can't run Groovy script as console not found");
+    }
+  }
 }
 
 class PrintThread extends Thread
index 3105ab9..f92d67f 100755 (executable)
@@ -617,7 +617,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
         }
       }
     }
-    if (!SwingUtilities.isRightMouseButton(evt))
+    if (!evt.isPopupTrigger())
     {
       return;
     }
index a70d4b9..bb311ef 100755 (executable)
@@ -54,7 +54,6 @@ import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.Scrollable;
-import javax.swing.SwingUtilities;
 import javax.swing.ToolTipManager;
 
 /**
@@ -511,7 +510,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       }
     }
 
-    if (SwingUtilities.isRightMouseButton(evt) && activeRow != -1)
+    if (evt.isPopupTrigger() && activeRow != -1)
     {
       if (av.getColumnSelection() == null)
       {
index 7d5d8d7..65d8670 100644 (file)
@@ -100,6 +100,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
     });
     SwingUtilities.invokeLater(new Runnable()
     {
+      @Override
       public void run()
       {
         textarea.requestFocus();
@@ -143,6 +144,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
     textarea.setText(text);
   }
 
+  @Override
   public void save_actionPerformed(ActionEvent e)
   {
     JalviewFileChooser chooser = new JalviewFileChooser(
@@ -173,6 +175,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
     }
   }
 
+  @Override
   public void toggleHtml_actionPerformed(ActionEvent e)
   {
     String txt = textarea.getText();
@@ -187,6 +190,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
    * @param e
    *          DOCUMENT ME!
    */
+  @Override
   public void copyItem_actionPerformed(ActionEvent e)
   {
     Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
@@ -210,6 +214,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
    * @param e
    *          DOCUMENT ME!
    */
+  @Override
   public void cancel_actionPerformed(ActionEvent e)
   {
     try
@@ -220,9 +225,10 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
     }
   }
 
+  @Override
   public void textarea_mousePressed(MouseEvent e)
   {
-    if (SwingUtilities.isRightMouseButton(e))
+    if (e.isPopupTrigger())
     {
       JPopupMenu popup = new JPopupMenu(
               MessageManager.getString("action.edit"));
@@ -230,6 +236,7 @@ public class CutAndPasteHtmlTransfer extends GCutAndPasteHtmlTransfer
               MessageManager.getString("action.copy"));
       item.addActionListener(new ActionListener()
       {
+        @Override
         public void actionPerformed(ActionEvent e)
         {
           copyItem_actionPerformed(e);
index 76f1041..1161340 100644 (file)
@@ -358,7 +358,7 @@ public class CutAndPasteTransfer extends GCutAndPasteTransfer
   @Override
   public void textarea_mousePressed(MouseEvent e)
   {
-    if (SwingUtilities.isRightMouseButton(e))
+    if (e.isPopupTrigger())
     {
       JPopupMenu popup = new JPopupMenu(
               MessageManager.getString("action.edit"));
index 53e3c8d..b176672 100644 (file)
@@ -81,6 +81,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     ListSelectionModel rowSM = table.getSelectionModel();
     rowSM.addListSelectionListener(new ListSelectionListener()
     {
+      @Override
       public void valueChanged(ListSelectionEvent e)
       {
         ListSelectionModel lsm = (ListSelectionModel) e.getSource();
@@ -94,10 +95,10 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
 
     table.addMouseListener(new MouseAdapter()
     {
+      @Override
       public void mouseClicked(MouseEvent evt)
       {
-        if (evt.getClickCount() == 2
-                || SwingUtilities.isRightMouseButton(evt))
+        if (evt.getClickCount() == 2 || evt.isPopupTrigger())
         {
           editRemoveLocalSource(evt);
         }
@@ -119,6 +120,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     this(null);
   }
 
+  @Override
   public void paintComponent(java.awt.Graphics g)
   {
     if (sourceRegistry == null)
@@ -145,6 +147,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
 
     javax.swing.SwingUtilities.invokeLater(new Runnable()
     {
+      @Override
       public void run()
       {
         TableSorter sorter = (TableSorter) table.getModel();
@@ -282,6 +285,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     fullDetails.setText(text.toString());
     javax.swing.SwingUtilities.invokeLater(new Runnable()
     {
+      @Override
       public void run()
       {
         fullDetailsScrollpane.getVerticalScrollBar().setValue(0);
@@ -289,6 +293,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     });
   }
 
+  @Override
   public void run()
   {
     loadingDasSources = true;
@@ -358,6 +363,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     return selected;
   }
 
+  @Override
   public void refresh_actionPerformed(ActionEvent e)
   {
     saveProperties(jalview.bin.Cache.applicationProperties);
@@ -411,6 +417,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
 
     javax.swing.SwingUtilities.invokeLater(new Runnable()
     {
+      @Override
       public void run()
       {
         filter1.setSelectedIndex(0);
@@ -420,6 +427,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     });
   }
 
+  @Override
   public void amendLocal(boolean newSource)
   {
     String url = "http://localhost:8080/", nickname = "";
@@ -503,6 +511,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
 
     SwingUtilities.invokeLater(new Runnable()
     {
+      @Override
       public void run()
       {
         scrollPane.getVerticalScrollBar().setValue(
@@ -571,6 +580,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
       refreshTableData(data);
       SwingUtilities.invokeLater(new Runnable()
       {
+        @Override
         public void run()
         {
           scrollPane.getVerticalScrollBar().setValue(
@@ -582,6 +592,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     }
   }
 
+  @Override
   public void valueChanged(ListSelectionEvent evt)
   {
     // Called when the MainTable selection changes
@@ -691,6 +702,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     }
   }
 
+  @Override
   public void reset_actionPerformed(ActionEvent e)
   {
     registryURL.setText(sourceRegistry.getDasRegistryURL());
@@ -743,21 +755,25 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
 
     private Object[][] data;
 
+    @Override
     public int getColumnCount()
     {
       return columnNames.length;
     }
 
+    @Override
     public int getRowCount()
     {
       return data.length;
     }
 
+    @Override
     public String getColumnName(int col)
     {
       return columnNames[col];
     }
 
+    @Override
     public Object getValueAt(int row, int col)
     {
       return data[row][col];
@@ -768,6 +784,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
      * each cell. If we didn't implement this method, then the last column would
      * contain text ("true"/"false"), rather than a check box.
      */
+    @Override
     public Class getColumnClass(int c)
     {
       return getValueAt(0, c).getClass();
@@ -776,6 +793,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     /*
      * Don't need to implement this method unless your table's editable.
      */
+    @Override
     public boolean isCellEditable(int row, int col)
     {
       // Note that the data/cell address is constant,
@@ -787,6 +805,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     /*
      * Don't need to implement this method unless your table's data can change.
      */
+    @Override
     public void setValueAt(Object value, int row, int col)
     {
       data[row][col] = value;
@@ -812,6 +831,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
 
     Thread thr = new Thread(new Runnable()
     {
+      @Override
       public void run()
       {
         // this actually initialises the das source list
index 27f60ea..8e863c1 100644 (file)
@@ -383,7 +383,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       @Override
       public void mousePressed(MouseEvent evt)
       {
-        if (SwingUtilities.isRightMouseButton(evt))
+        if (evt.isPopupTrigger())
         {
           showPasteMenu(evt.getX(), evt.getY());
         }
@@ -2507,9 +2507,21 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       java.lang.reflect.Method setvar = gcClass.getMethod("setVariable",
               new Class[] { String.class, Object.class });
       java.lang.reflect.Method run = gcClass.getMethod("run");
-      Object gc = gccons.newInstance();
-      setvar.invoke(gc, new Object[] { "Jalview", this });
-      run.invoke(gc);
+      groovyConsole = gccons.newInstance();
+      setvar.invoke(groovyConsole, new Object[] { "Jalview", this });
+      run.invoke(groovyConsole);
+      /*
+       * and rebuild alignframe menus to enable 'Run Groovy'
+       */
+
+      AlignFrame[] alignFrames = getAlignFrames();
+      if (alignFrames != null)
+      {
+        for (AlignFrame af : alignFrames)
+        {
+          af.setGroovyEnabled(true);
+        }
+      }
     } catch (Exception ex)
     {
       jalview.bin.Cache.log.error("Groovy Shell Creation failed.", ex);
@@ -2917,6 +2929,13 @@ public class Desktop extends jalview.jbgui.GDesktop implements
    */
   private java.util.concurrent.Semaphore block = new Semaphore(0);
 
+  /*
+   * groovy.ui.Console object - if Groovy jars are present and the 
+   * user has activated the Groovy console. Use via reflection to
+   * avoid compile-time dependency on Groovy libraries.
+   */
+  private static Object groovyConsole;
+
   /**
    * add another dialog thread to the queue
    * 
@@ -3145,4 +3164,9 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     Desktop.currentAlignFrame = currentAlignFrame;
   }
 
+  public static Object getGroovyConsole()
+  {
+    return groovyConsole;
+  }
+
 }
index 6ca0769..6633156 100644 (file)
@@ -58,6 +58,7 @@ import java.util.Arrays;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.Vector;
 
@@ -169,7 +170,7 @@ public class FeatureSettings extends JPanel implements
       public void mousePressed(MouseEvent evt)
       {
         selectedRow = table.rowAtPoint(evt.getPoint());
-        if (SwingUtilities.isRightMouseButton(evt))
+        if (evt.isPopupTrigger())
         {
           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
                   table.getValueAt(selectedRow, 1), fr.getMinMax(),
@@ -277,7 +278,8 @@ public class FeatureSettings extends JPanel implements
   }
 
   protected void popupSort(final int selectedRow, final String type,
-          final Object typeCol, final Hashtable minmax, int x, int y)
+          final Object typeCol, final Map<String, float[][]> minmax, int x,
+          int y)
   {
     JPopupMenu men = new JPopupMenu(MessageManager.formatMessage(
             "label.settings_for_param", new String[] { type }));
@@ -312,7 +314,7 @@ public class FeatureSettings extends JPanel implements
     men.add(dens);
     if (minmax != null)
     {
-      final Object typeMinMax = minmax.get(type);
+      final float[][] typeMinMax = minmax.get(type);
       /*
        * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
        * this is broken at the moment and isn't that useful anyway!
@@ -327,7 +329,7 @@ public class FeatureSettings extends JPanel implements
        * 
        * men.add(chb);
        */
-      if (typeMinMax != null && ((float[][]) typeMinMax)[0] != null)
+      if (typeMinMax != null && typeMinMax[0] != null)
       {
         // if (table.getValueAt(row, column));
         // graduated colourschemes for those where minmax exists for the
index bbffbab..e247793 100755 (executable)
@@ -191,7 +191,7 @@ public class IdPanel extends JPanel implements MouseListener,
      * Ignore single click. Ignore 'left' click followed by 'right' click (user
      * selects a row then its pop-up menu).
      */
-    if (e.getClickCount() < 2 || SwingUtilities.isRightMouseButton(e))
+    if (e.getClickCount() < 2 || e.isPopupTrigger())
     {
       return;
     }
@@ -316,29 +316,33 @@ public class IdPanel extends JPanel implements MouseListener,
 
     int seq = alignPanel.getSeqPanel().findSeq(e);
 
-    if (SwingUtilities.isRightMouseButton(e))
+    if (e.isPopupTrigger())
     {
       Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq);
       // build a new links menu based on the current links + any non-positional
       // features
-      Vector nlinks = new Vector(Preferences.sequenceURLLinks);
-      SequenceFeature sf[] = sq == null ? null : sq.getSequenceFeatures();
-      for (int sl = 0; sf != null && sl < sf.length; sl++)
+      Vector<String> nlinks = new Vector<String>(
+              Preferences.sequenceURLLinks);
+      SequenceFeature sfs[] = sq == null ? null : sq.getSequenceFeatures();
+      if (sfs != null)
       {
-        if (sf[sl].begin == sf[sl].end && sf[sl].begin == 0)
+        for (SequenceFeature sf : sfs)
         {
-          if (sf[sl].links != null && sf[sl].links.size() > 0)
+          if (sf.begin == sf.end && sf.begin == 0)
           {
-            for (int l = 0, lSize = sf[sl].links.size(); l < lSize; l++)
+            if (sf.links != null && sf.links.size() > 0)
             {
-              nlinks.addElement(sf[sl].links.elementAt(l));
+              for (int l = 0, lSize = sf.links.size(); l < lSize; l++)
+              {
+                nlinks.addElement(sf.links.elementAt(l));
+              }
             }
           }
         }
       }
 
-      jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(alignPanel, sq,
-              nlinks, new Vector(Preferences.getGroupURLLinks()));
+      PopupMenu pop = new PopupMenu(alignPanel, sq, nlinks,
+              Preferences.getGroupURLLinks());
       pop.show(this, e.getX(), e.getY());
 
       return;
index 8a0fe77..801cb1f 100644 (file)
@@ -130,9 +130,9 @@ public class OptsAndParamsPage
       add(enabled, BorderLayout.NORTH);
       for (Object str : opt.getPossibleValues())
       {
-        val.addItem((String) str);
+        val.addItem(str);
       }
-      val.setSelectedItem((String) opt.getValue());
+      val.setSelectedItem(opt.getValue());
       if (opt.getPossibleValues().size() > 1)
       {
         setLayout(new GridLayout(1, 2));
@@ -145,6 +145,7 @@ public class OptsAndParamsPage
       setInitialValue();
     }
 
+    @Override
     public void actionPerformed(ActionEvent e)
     {
       if (e.getSource() != enabled)
@@ -206,32 +207,37 @@ public class OptsAndParamsPage
       return opt;
     }
 
+    @Override
     public void mouseClicked(MouseEvent e)
     {
-      if (javax.swing.SwingUtilities.isRightMouseButton(e))
+      if (e.isPopupTrigger())
       {
         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
       }
     }
 
+    @Override
     public void mouseEntered(MouseEvent e)
     {
       // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void mouseExited(MouseEvent e)
     {
       // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void mousePressed(MouseEvent e)
     {
       // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void mouseReleased(MouseEvent e)
     {
       // TODO Auto-generated method stub
@@ -412,6 +418,7 @@ public class OptsAndParamsPage
         showDesc.addActionListener(new ActionListener()
         {
 
+          @Override
           public void actionPerformed(ActionEvent e)
           {
             descisvisible = !descisvisible;
@@ -451,6 +458,7 @@ public class OptsAndParamsPage
       validate();
     }
 
+    @Override
     public void actionPerformed(ActionEvent e)
     {
       if (adjusting)
@@ -526,45 +534,51 @@ public class OptsAndParamsPage
       lastVal = null;
     }
 
+    @Override
     public void mouseClicked(MouseEvent e)
     {
-      if (javax.swing.SwingUtilities.isRightMouseButton(e))
+      if (e.isPopupTrigger())
       {
         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
       }
     }
 
+    @Override
     public void mouseEntered(MouseEvent e)
     {
       // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void mouseExited(MouseEvent e)
     {
       // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void mousePressed(MouseEvent e)
     {
       // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void mouseReleased(MouseEvent e)
     {
       // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void stateChanged(ChangeEvent e)
     {
       if (!adjusting)
       {
         valueField.setText(""
-                + ((integ) ? ("" + (int) slider.getValue())
-                        : ("" + (float) (slider.getValue() / 1000f))));
+                + ((integ) ? ("" + slider.getValue())
+                        : ("" + slider.getValue() / 1000f)));
         checkIfModified();
       }
 
index ab8c398..1e0772a 100644 (file)
@@ -24,6 +24,7 @@ import jalview.analysis.AAFrequency;
 import jalview.analysis.AlignmentAnnotationUtils;
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.Conservation;
+import jalview.bin.Cache;
 import jalview.commands.ChangeCaseCommand;
 import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
@@ -54,6 +55,7 @@ import jalview.schemes.TaylorColourScheme;
 import jalview.schemes.TurnColourScheme;
 import jalview.schemes.UserColourScheme;
 import jalview.schemes.ZappoColourScheme;
+import jalview.util.DBRefUtils;
 import jalview.util.GroupUrlLink;
 import jalview.util.GroupUrlLink.UrlStringTooLongException;
 import jalview.util.MessageManager;
@@ -62,6 +64,7 @@ import jalview.util.UrlLink;
 import java.awt.Color;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Hashtable;
@@ -90,8 +93,6 @@ public class PopupMenu extends JPopupMenu
 {
   private static final String ALL_ANNOTATIONS = "All";
 
-  private static final String COMMA = ",";
-
   JMenu groupMenu = new JMenu();
 
   JMenuItem groupName = new JMenuItem();
@@ -216,7 +217,7 @@ public class PopupMenu extends JPopupMenu
    * @param seq
    *          DOCUMENT ME!
    */
-  public PopupMenu(final AlignmentPanel ap, Sequence seq, Vector links)
+  public PopupMenu(final AlignmentPanel ap, Sequence seq, List<String> links)
   {
     this(ap, seq, links, null);
   }
@@ -229,7 +230,7 @@ public class PopupMenu extends JPopupMenu
    * @param groupLinks
    */
   public PopupMenu(final AlignmentPanel ap, final SequenceI seq,
-          Vector links, Vector groupLinks)
+          List<String> links, List<String> groupLinks)
   {
     // /////////////////////////////////////////////////////////
     // If this is activated from the sequence panel, the user may want to
@@ -608,124 +609,130 @@ public class PopupMenu extends JPopupMenu
 
     if (links != null && links.size() > 0)
     {
+      addFeatureLinks(seq, links);
+    }
+  }
 
-      JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
-      Vector linkset = new Vector();
-      for (int i = 0; i < links.size(); i++)
+  /**
+   * Adds a 'Link' menu item with a sub-menu item for each hyperlink provided.
+   * 
+   * @param seq
+   * @param links
+   */
+  void addFeatureLinks(final SequenceI seq, List<String> links)
+  {
+    JMenu linkMenu = new JMenu(MessageManager.getString("action.link"));
+    List<String> linkset = new ArrayList<String>();
+    for (String link : links)
+    {
+      UrlLink urlLink = null;
+      try
       {
-        String link = links.elementAt(i).toString();
-        UrlLink urlLink = null;
-        try
-        {
-          urlLink = new UrlLink(link);
-        } catch (Exception foo)
-        {
-          jalview.bin.Cache.log.error("Exception for URLLink '" + link
-                  + "'", foo);
-          continue;
-        }
-        ;
-        if (!urlLink.isValid())
+        urlLink = new UrlLink(link);
+      } catch (Exception foo)
+      {
+        Cache.log.error("Exception for URLLink '" + link + "'", foo);
+        continue;
+      }
+      ;
+      if (!urlLink.isValid())
+      {
+        Cache.log.error(urlLink.getInvalidMessage());
+        continue;
+      }
+      final String label = urlLink.getLabel();
+      if (seq != null && urlLink.isDynamic())
+      {
+
+        // collect matching db-refs
+        DBRefEntry[] dbr = DBRefUtils.selectRefs(seq.getDBRefs(),
+                new String[] { urlLink.getTarget() });
+        // collect id string too
+        String id = seq.getName();
+        String descr = seq.getDescription();
+        if (descr != null && descr.length() < 1)
         {
-          jalview.bin.Cache.log.error(urlLink.getInvalidMessage());
-          continue;
+          descr = null;
         }
-        final String label = urlLink.getLabel();
-        if (seq != null && urlLink.isDynamic())
-        {
 
-          // collect matching db-refs
-          DBRefEntry[] dbr = jalview.util.DBRefUtils.selectRefs(
-                  seq.getDBRefs(), new String[] { urlLink.getTarget() });
-          // collect id string too
-          String id = seq.getName();
-          String descr = seq.getDescription();
-          if (descr != null && descr.length() < 1)
-          {
-            descr = null;
-          }
-
-          if (dbr != null)
+        if (dbr != null)
+        {
+          for (int r = 0; r < dbr.length; r++)
           {
-            for (int r = 0; r < dbr.length; r++)
+            if (id != null && dbr[r].getAccessionId().equals(id))
             {
-              if (id != null && dbr[r].getAccessionId().equals(id))
-              {
-                // suppress duplicate link creation for the bare sequence ID
-                // string with this link
-                id = null;
-              }
-              // create Bare ID link for this RUL
-              String[] urls = urlLink.makeUrls(dbr[r].getAccessionId(),
-                      true);
-              if (urls != null)
-              {
-                for (int u = 0; u < urls.length; u += 2)
-                {
-                  if (!linkset.contains(urls[u] + "|" + urls[u + 1]))
-                  {
-                    linkset.addElement(urls[u] + "|" + urls[u + 1]);
-                    addshowLink(linkMenu, label + "|" + urls[u],
-                            urls[u + 1]);
-                  }
-                }
-              }
+              // suppress duplicate link creation for the bare sequence ID
+              // string with this link
+              id = null;
             }
-          }
-          if (id != null)
-          {
-            // create Bare ID link for this RUL
-            String[] urls = urlLink.makeUrls(id, true);
+            // create Bare ID link for this URL
+            String[] urls = urlLink.makeUrls(dbr[r].getAccessionId(), true);
             if (urls != null)
             {
               for (int u = 0; u < urls.length; u += 2)
               {
                 if (!linkset.contains(urls[u] + "|" + urls[u + 1]))
                 {
-                  linkset.addElement(urls[u] + "|" + urls[u + 1]);
-                  addshowLink(linkMenu, label, urls[u + 1]);
+                  linkset.add(urls[u] + "|" + urls[u + 1]);
+                  addshowLink(linkMenu, label + "|" + urls[u], urls[u + 1]);
                 }
               }
             }
           }
-          // Create urls from description but only for URL links which are regex
-          // links
-          if (descr != null && urlLink.getRegexReplace() != null)
+        }
+        if (id != null)
+        {
+          // create Bare ID link for this URL
+          String[] urls = urlLink.makeUrls(id, true);
+          if (urls != null)
           {
-            // create link for this URL from description where regex matches
-            String[] urls = urlLink.makeUrls(descr, true);
-            if (urls != null)
+            for (int u = 0; u < urls.length; u += 2)
             {
-              for (int u = 0; u < urls.length; u += 2)
+              if (!linkset.contains(urls[u] + "|" + urls[u + 1]))
               {
-                if (!linkset.contains(urls[u] + "|" + urls[u + 1]))
-                {
-                  linkset.addElement(urls[u] + "|" + urls[u + 1]);
-                  addshowLink(linkMenu, label, urls[u + 1]);
-                }
+                linkset.add(urls[u] + "|" + urls[u + 1]);
+                addshowLink(linkMenu, label, urls[u + 1]);
               }
             }
           }
         }
-        else
+        // Create urls from description but only for URL links which are regex
+        // links
+        if (descr != null && urlLink.getRegexReplace() != null)
         {
-          if (!linkset.contains(label + "|" + urlLink.getUrl_prefix()))
+          // create link for this URL from description where regex matches
+          String[] urls = urlLink.makeUrls(descr, true);
+          if (urls != null)
           {
-            linkset.addElement(label + "|" + urlLink.getUrl_prefix());
-            // Add a non-dynamic link
-            addshowLink(linkMenu, label, urlLink.getUrl_prefix());
+            for (int u = 0; u < urls.length; u += 2)
+            {
+              if (!linkset.contains(urls[u] + "|" + urls[u + 1]))
+              {
+                linkset.add(urls[u] + "|" + urls[u + 1]);
+                addshowLink(linkMenu, label, urls[u + 1]);
+              }
+            }
           }
         }
       }
-      if (sequence != null)
-      {
-        sequenceMenu.add(linkMenu);
-      }
       else
       {
-        add(linkMenu);
+        if (!linkset.contains(label + "|" + urlLink.getUrl_prefix()))
+        {
+          linkset.add(label + "|" + urlLink.getUrl_prefix());
+          // Add a non-dynamic link
+          addshowLink(linkMenu, label, urlLink.getUrl_prefix());
+        }
       }
     }
+    if (sequence != null)
+    {
+      sequenceMenu.add(linkMenu);
+    }
+    else
+    {
+      add(linkMenu);
+    }
   }
 
   /**
@@ -855,7 +862,7 @@ public class PopupMenu extends JPopupMenu
     showOrHideMenu.add(item);
   }
 
-  private void buildGroupURLMenu(SequenceGroup sg, Vector groupLinks)
+  private void buildGroupURLMenu(SequenceGroup sg, List<String> groupLinks)
   {
 
     // TODO: usability: thread off the generation of group url content so root
@@ -864,19 +871,15 @@ public class PopupMenu extends JPopupMenu
     // ID/regex match URLs
     groupLinksMenu = new JMenu(
             MessageManager.getString("action.group_link"));
+    // three types of url that might be created.
     JMenu[] linkMenus = new JMenu[] { null,
         new JMenu(MessageManager.getString("action.ids")),
         new JMenu(MessageManager.getString("action.sequences")),
-        new JMenu(MessageManager.getString("action.ids_sequences")) }; // three
-                                                                       // types
-                                                                       // of url
-                                                                       // that
-                                                                       // might
-                                                                       // be
-    // created.
+        new JMenu(MessageManager.getString("action.ids_sequences")) };
+
     SequenceI[] seqs = ap.av.getSelectionAsNewSequence();
     String[][] idandseqs = GroupUrlLink.formStrings(seqs);
-    Hashtable commonDbrefs = new Hashtable();
+    Hashtable<String, Object[]> commonDbrefs = new Hashtable<String, Object[]>();
     for (int sq = 0; sq < seqs.length; sq++)
     {
 
@@ -896,7 +899,7 @@ public class PopupMenu extends JPopupMenu
         for (int d = 0; d < dbr.length; d++)
         {
           String src = dbr[d].getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
-          Object[] sarray = (Object[]) commonDbrefs.get(src);
+          Object[] sarray = commonDbrefs.get(src);
           if (sarray == null)
           {
             sarray = new Object[2];
@@ -921,30 +924,29 @@ public class PopupMenu extends JPopupMenu
     // now create group links for all distinct ID/sequence sets.
     boolean addMenu = false; // indicates if there are any group links to give
                              // to user
-    for (int i = 0; i < groupLinks.size(); i++)
+    for (String link : groupLinks)
     {
-      String link = groupLinks.elementAt(i).toString();
       GroupUrlLink urlLink = null;
       try
       {
         urlLink = new GroupUrlLink(link);
       } catch (Exception foo)
       {
-        jalview.bin.Cache.log.error("Exception for GroupURLLink '" + link
+        Cache.log.error("Exception for GroupURLLink '" + link
                 + "'", foo);
         continue;
       }
       ;
       if (!urlLink.isValid())
       {
-        jalview.bin.Cache.log.error(urlLink.getInvalidMessage());
+        Cache.log.error(urlLink.getInvalidMessage());
         continue;
       }
       final String label = urlLink.getLabel();
       boolean usingNames = false;
       // Now see which parts of the group apply for this URL
       String ltarget = urlLink.getTarget(); // jalview.util.DBRefUtils.getCanonicalName(urlLink.getTarget());
-      Object[] idset = (Object[]) commonDbrefs.get(ltarget.toUpperCase());
+      Object[] idset = commonDbrefs.get(ltarget.toUpperCase());
       String[] seqstr, ids; // input to makeUrl
       if (idset != null)
       {
@@ -1062,7 +1064,7 @@ public class PopupMenu extends JPopupMenu
             new Object[] { urlgenerator.getUrl_prefix(),
                 urlgenerator.getNumberInvolved(urlstub) }));
     // TODO: put in info about what is being sent.
-    item.addActionListener(new java.awt.event.ActionListener()
+    item.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -1076,7 +1078,7 @@ public class PopupMenu extends JPopupMenu
             try
             {
               showLink(urlgenerator.constructFrom(urlstub));
-            } catch (UrlStringTooLongException e)
+            } catch (UrlStringTooLongException e2)
             {
             }
           }
index c7e1332..6b2d3c4 100755 (executable)
@@ -30,6 +30,7 @@ import jalview.jbgui.GPreferences;
 import jalview.jbgui.GSequenceLink;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 import jalview.ws.sifts.SiftsSettings;
 
 import java.awt.BorderLayout;
@@ -40,7 +41,7 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.MouseEvent;
 import java.io.File;
-import java.util.Collection;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.StringTokenizer;
 import java.util.Vector;
@@ -96,7 +97,7 @@ public class Preferences extends GPreferences
    * Holds name and link separated with | character. Sequence ID must be
    * $SEQUENCE_ID$ or $SEQUENCE_ID=/.possible | chars ./=$
    */
-  public static Vector sequenceURLLinks;
+  public static Vector<String> sequenceURLLinks;
 
   /**
    * Holds name and link separated with | character. Sequence IDS and Sequences
@@ -106,14 +107,14 @@ public class Preferences extends GPreferences
    * (TODO: proper escape for using | to separate ids or sequences
    */
 
-  public static Vector groupURLLinks;
+  public static List<String> groupURLLinks;
   static
   {
     String string = Cache
             .getDefault(
                     "SEQUENCE_LINKS",
                     "EMBL-EBI Search|http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$SEQUENCE_ID$");
-    sequenceURLLinks = new Vector();
+    sequenceURLLinks = new Vector<String>();
 
     try
     {
@@ -152,10 +153,10 @@ public class Preferences extends GPreferences
      * .properties file as '|' separated strings
      */
 
-    groupURLLinks = new Vector();
+    groupURLLinks = new ArrayList<String>();
   }
 
-  Vector nameLinks, urlLinks;
+  Vector<String> nameLinks, urlLinks;
 
   JInternalFrame frame;
 
@@ -176,7 +177,8 @@ public class Preferences extends GPreferences
     wsPrefs = new WsPreferences();
     wsTab.add(wsPrefs, BorderLayout.CENTER);
     int width = 500, height = 450;
-    if (new jalview.util.Platform().isAMac())
+    new jalview.util.Platform();
+    if (Platform.isAMac())
     {
       width = 570;
       height = 480;
@@ -329,8 +331,8 @@ public class Preferences extends GPreferences
     /*
      * Set Connections tab defaults
      */
-    nameLinks = new Vector();
-    urlLinks = new Vector();
+    nameLinks = new Vector<String>();
+    urlLinks = new Vector<String>();
     for (int i = 0; i < sequenceURLLinks.size(); i++)
     {
       String link = sequenceURLLinks.elementAt(i).toString();
@@ -533,7 +535,7 @@ public class Preferences extends GPreferences
     if (nameLinks.size() > 0)
     {
       StringBuffer links = new StringBuffer();
-      sequenceURLLinks = new Vector();
+      sequenceURLLinks = new Vector<String>();
       for (int i = 0; i < nameLinks.size(); i++)
       {
         sequenceURLLinks.addElement(nameLinks.elementAt(i) + "|"
@@ -849,7 +851,7 @@ public class Preferences extends GPreferences
     super.showunconserved_actionPerformed(e);
   }
 
-  public static Collection getGroupURLLinks()
+  public static List<String> getGroupURLLinks()
   {
     return groupURLLinks;
   }
index 41de58f..3ab681c 100755 (executable)
@@ -39,7 +39,6 @@ import java.awt.event.MouseMotionListener;
 import javax.swing.JMenuItem;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
-import javax.swing.SwingUtilities;
 import javax.swing.ToolTipManager;
 
 /**
@@ -107,7 +106,7 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
     min = res;
     max = res;
 
-    if (SwingUtilities.isRightMouseButton(evt))
+    if (evt.isPopupTrigger())
     {
       rightMouseButtonPressed(evt, res);
     }
index cce2ee0..3fbb809 100644 (file)
@@ -21,6 +21,7 @@
 package jalview.gui;
 
 import jalview.api.AlignViewportI;
+import jalview.bin.Cache;
 import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
 import jalview.commands.EditCommand.Edit;
@@ -54,11 +55,12 @@ import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.awt.event.MouseWheelEvent;
 import java.awt.event.MouseWheelListener;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Vector;
 
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
 import javax.swing.ToolTipManager;
 
 /**
@@ -509,6 +511,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
   void insertNucAtCursor(boolean group, String nuc)
   {
+    // TODO not called - delete?
     groupEditing = group;
     startseq = seqCanvas.cursorY;
     lastres = seqCanvas.cursorX;
@@ -599,7 +602,7 @@ public class SeqPanel extends JPanel implements MouseListener,
   {
     lastMousePress = evt.getPoint();
 
-    if (javax.swing.SwingUtilities.isMiddleMouseButton(evt))
+    if (SwingUtilities.isMiddleMouseButton(evt))
     {
       mouseWheelPressed = true;
       return;
@@ -1171,8 +1174,7 @@ public class SeqPanel extends JPanel implements MouseListener,
           {
             for (int j = 0; j < startres - lastres; j++)
             {
-              if (!jalview.util.Comparison.isGap(groupSeqs[g]
-                      .getCharAt(fixedRight - j)))
+              if (!Comparison.isGap(groupSeqs[g].getCharAt(fixedRight - j)))
               {
                 blank = false;
                 break;
@@ -1234,7 +1236,7 @@ public class SeqPanel extends JPanel implements MouseListener,
               continue;
             }
 
-            if (!jalview.util.Comparison.isGap(groupSeqs[g].getCharAt(j)))
+            if (!Comparison.isGap(groupSeqs[g].getCharAt(j)))
             {
               // Not a gap, block edit not valid
               endEditing();
@@ -1366,7 +1368,7 @@ public class SeqPanel extends JPanel implements MouseListener,
 
       for (blankColumn = fixedColumn; blankColumn > j; blankColumn--)
       {
-        if (jalview.util.Comparison.isGap(seq[s].getCharAt(blankColumn)))
+        if (Comparison.isGap(seq[s].getCharAt(blankColumn)))
         {
           // Theres a space, so break and insert the gap
           break;
@@ -1597,24 +1599,24 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     }
 
-    if (javax.swing.SwingUtilities.isRightMouseButton(evt))
+    if (evt.isPopupTrigger())
     {
       List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
               .findFeaturesAtRes(sequence.getDatasetSequence(),
                       sequence.findPosition(res));
-      Vector links = new Vector();
+      List<String> links = new ArrayList<String>();
       for (SequenceFeature sf : allFeatures)
       {
         if (sf.links != null)
         {
-          for (int j = 0; j < sf.links.size(); j++)
+          for (String link : sf.links)
           {
-            links.addElement(sf.links.elementAt(j));
+            links.add(link);
           }
         }
       }
 
-      jalview.gui.PopupMenu pop = new jalview.gui.PopupMenu(ap, null, links);
+      PopupMenu pop = new PopupMenu(ap, null, links);
       pop.show(this, evt.getX(), evt.getY());
       return;
     }
@@ -1955,7 +1957,7 @@ public class SeqPanel extends JPanel implements MouseListener,
     {
       if (av.getAlignment() == null)
       {
-        jalview.bin.Cache.log.warn("alignviewport av SeqSetId="
+        Cache.log.warn("alignviewport av SeqSetId="
                 + av.getSequenceSetId() + " ViewId=" + av.getViewId()
                 + " 's alignment is NULL! returning immediately.");
         return;
index 90c74be..f21c5e7 100755 (executable)
@@ -59,7 +59,6 @@ import java.util.Vector;
 import javax.swing.JColorChooser;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
-import javax.swing.SwingUtilities;
 import javax.swing.ToolTipManager;
 
 /**
@@ -791,7 +790,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
   {
     if (highlightNode != null)
     {
-      if (SwingUtilities.isRightMouseButton(evt))
+      if (evt.isPopupTrigger())
       {
         Color col = JColorChooser.showDialog(this,
                 MessageManager.getString("label.select_subtree_colour"),
index 65fd72f..1a639f1 100755 (executable)
@@ -330,7 +330,7 @@ public class AppletFormatAdapter
       }
       else if (format.equals(IdentifyFile.FeaturesFile))
       {
-        alignFile = new FeaturesFile(inFile, type);
+        alignFile = new FeaturesFile(true, inFile, type);
       }
       return buildAlignmentFrom(alignFile);
     } catch (Exception e)
index d3a1d09..2d76d6b 100644 (file)
@@ -23,11 +23,13 @@ package jalview.io;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.io.gff.GffConstants;
+import jalview.util.DBRefUtils;
 import jalview.util.UrlLink;
 
 import java.util.ArrayList;
-import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 
 /**
  * generate HTML reports for a sequence
@@ -44,165 +46,189 @@ public class SequenceAnnotationReport
   }
 
   /**
-   * appends the features at rpos to the given stringbuffer ready for display in
-   * a tooltip
+   * Append text for the list of features to the tooltip
    * 
    * @param tooltipText2
-   * @param linkImageURL
    * @param rpos
    * @param features
-   *          TODO refactor to Jalview 'utilities' somehow.
+   * @param minmax
    */
   public void appendFeatures(final StringBuffer tooltipText2, int rpos,
-          List<SequenceFeature> features)
+          List<SequenceFeature> features, Map<String, float[][]> minmax)
   {
-    appendFeatures(tooltipText2, rpos, features, null);
+    if (features != null)
+    {
+      for (SequenceFeature feature : features)
+      {
+        appendFeature(tooltipText2, rpos, minmax, feature);
+      }
+    }
   }
 
-  public void appendFeatures(final StringBuffer tooltipText2, int rpos,
-          List<SequenceFeature> features, Hashtable minmax)
+  /**
+   * Appends text for one sequence feature to the string buffer
+   * 
+   * @param sb
+   * @param rpos
+   * @param minmax
+   *          {{min, max}, {min, max}} positional and non-positional feature
+   *          scores for this type
+   * @param feature
+   */
+  void appendFeature(final StringBuffer sb, int rpos,
+          Map<String, float[][]> minmax, SequenceFeature feature)
   {
-    String tmpString;
-    if (features != null)
+    if ("disulfide bond".equals(feature.getType()))
     {
-      for (SequenceFeature feature : features)
+      if (feature.getBegin() == rpos || feature.getEnd() == rpos)
       {
-        if (feature.getType().equals("disulfide bond"))
+        if (sb.length() > 6)
         {
-          if (feature.getBegin() == rpos || feature.getEnd() == rpos)
-          {
-            if (tooltipText2.length() > 6)
-            {
-              tooltipText2.append("<br>");
-            }
-            tooltipText2.append("disulfide bond " + feature.getBegin()
-                    + ":" + feature.getEnd());
-          }
+          sb.append("<br>");
         }
-        else
+        sb.append("disulfide bond ").append(feature.getBegin()).append(":")
+                .append(feature.getEnd());
+      }
+    }
+    else
+    {
+      if (sb.length() > 6)
+      {
+        sb.append("<br>");
+      }
+      // TODO: remove this hack to display link only features
+      boolean linkOnly = feature.getValue("linkonly") != null;
+      if (!linkOnly)
+      {
+        sb.append(feature.getType()).append(" ");
+        if (rpos != 0)
         {
-          if (tooltipText2.length() > 6)
+          // we are marking a positional feature
+          sb.append(feature.begin);
+        }
+        if (feature.begin != feature.end)
+        {
+          sb.append(" " + feature.end);
+        }
+
+        if (feature.getDescription() != null
+                && !feature.description.equals(feature.getType()))
+        {
+          String tmpString = feature.getDescription();
+          String tmp2up = tmpString.toUpperCase();
+          final int startTag = tmp2up.indexOf("<HTML>");
+          if (startTag > -1)
           {
-            tooltipText2.append("<br>");
+            tmpString = tmpString.substring(startTag + 6);
+            tmp2up = tmp2up.substring(startTag + 6);
           }
-          // TODO: remove this hack to display link only features
-          boolean linkOnly = feature.getValue("linkonly") != null;
-          if (!linkOnly)
+          // TODO strips off </body> but not <body> - is that intended?
+          int endTag = tmp2up.indexOf("</BODY>");
+          if (endTag > -1)
           {
-            tooltipText2.append(feature.getType() + " ");
-            if (rpos != 0)
-            {
-              // we are marking a positional feature
-              tooltipText2.append(feature.begin);
-            }
-            if (feature.begin != feature.end)
-            {
-              tooltipText2.append(" " + feature.end);
-            }
-
-            if (feature.getDescription() != null
-                    && !feature.description.equals(feature.getType()))
-            {
-              tmpString = feature.getDescription();
-              String tmp2up = tmpString.toUpperCase();
-              int startTag = tmp2up.indexOf("<HTML>");
-              if (startTag > -1)
-              {
-                tmpString = tmpString.substring(startTag + 6);
-                tmp2up = tmp2up.substring(startTag + 6);
-              }
-              int endTag = tmp2up.indexOf("</BODY>");
-              if (endTag > -1)
-              {
-                tmpString = tmpString.substring(0, endTag);
-                tmp2up = tmp2up.substring(0, endTag);
-              }
-              endTag = tmp2up.indexOf("</HTML>");
-              if (endTag > -1)
-              {
-                tmpString = tmpString.substring(0, endTag);
-              }
-
-              if (startTag > -1)
-              {
-                tooltipText2.append("; " + tmpString);
-              }
-              else
-              {
-                if (tmpString.indexOf("<") > -1
-                        || tmpString.indexOf(">") > -1)
-                {
-                  // The description does not specify html is to
-                  // be used, so we must remove < > symbols
-                  tmpString = tmpString.replaceAll("<", "&lt;");
-                  tmpString = tmpString.replaceAll(">", "&gt;");
-
-                  tooltipText2.append("; ");
-                  tooltipText2.append(tmpString);
+            tmpString = tmpString.substring(0, endTag);
+            tmp2up = tmp2up.substring(0, endTag);
+          }
+          endTag = tmp2up.indexOf("</HTML>");
+          if (endTag > -1)
+          {
+            tmpString = tmpString.substring(0, endTag);
+          }
 
-                }
-                else
-                {
-                  tooltipText2.append("; " + tmpString);
-                }
-              }
-            }
-            // check score should be shown
-            if (!Float.isNaN(feature.getScore()))
+          if (startTag > -1)
+          {
+            sb.append("; ").append(tmpString);
+          }
+          else
+          {
+            if (tmpString.indexOf("<") > -1
+                    || tmpString.indexOf(">") > -1)
             {
-              float[][] rng = (minmax == null) ? null : ((float[][]) minmax
-                      .get(feature.getType()));
-              if (rng != null && rng[0] != null && rng[0][0] != rng[0][1])
-              {
-                tooltipText2.append(" Score=" + feature.getScore());
-              }
+              // The description does not specify html is to
+              // be used, so we must remove < > symbols
+              tmpString = tmpString.replaceAll("<", "&lt;");
+              tmpString = tmpString.replaceAll(">", "&gt;");
+              sb.append("; ").append(tmpString);
             }
-            if (feature.getValue("status") != null)
+            else
             {
-              String status = feature.getValue("status").toString();
-              if (status.length() > 0)
-              {
-                tooltipText2.append("; (" + feature.getValue("status")
-                        + ")");
-              }
+              sb.append("; ").append(tmpString);
             }
           }
         }
-        if (feature.links != null)
+
+        /*
+         * score should be shown if there is one, and min != max
+         * for this feature type (e.g. not all 0)
+         */
+        if (!Float.isNaN(feature.getScore()))
         {
-          if (linkImageURL != null)
+          float[][] rng = (minmax == null) ? null : minmax.get(feature
+                  .getType());
+          if (rng != null && rng[0] != null && rng[0][0] != rng[0][1])
           {
-            tooltipText2.append(" <img src=\"" + linkImageURL + "\">");
+            sb.append(" Score=").append(
+                    String.valueOf(feature.getScore()));
           }
-          else
+        }
+        String status = (String) feature.getValue("status");
+        if (status != null && status.length() > 0)
+        {
+          sb.append("; (").append(status).append(")");
+        }
+        String clinSig = (String) feature
+                .getValue(GffConstants.CLINICAL_SIGNIFICANCE);
+        if (clinSig != null)
+        {
+          sb.append("; ").append(clinSig);
+        }
+      }
+    }
+    appendLinks(sb, feature);
+  }
+
+  /**
+   * Format and appends any hyperlinks for the sequence feature to the string
+   * buffer
+   * 
+   * @param sb
+   * @param feature
+   */
+  void appendLinks(final StringBuffer sb, SequenceFeature feature)
+  {
+    if (feature.links != null)
+    {
+      if (linkImageURL != null)
+      {
+        sb.append(" <img src=\"" + linkImageURL + "\">");
+      }
+      else
+      {
+        for (String urlstring : feature.links)
+        {
+          try
           {
-            for (String urlstring : feature.links)
+            for (String[] urllink : createLinksFrom(null, urlstring))
             {
-              try
-              {
-                for (String[] urllink : createLinksFrom(null, urlstring))
-                {
-                  tooltipText2.append("<br/> <a href=\""
-                          + urllink[3]
-                          + "\" target=\""
-                          + urllink[0]
-                          + "\">"
-                          + (urllink[0].toLowerCase().equals(
-                                  urllink[1].toLowerCase()) ? urllink[0]
-                                  : (urllink[0] + ":" + urllink[1]))
-                          + "</a></br>");
-                }
-              } catch (Exception x)
-              {
-                System.err.println("problem when creating links from "
-                        + urlstring);
-                x.printStackTrace();
-              }
+              sb.append("<br/> <a href=\""
+                      + urllink[3]
+                      + "\" target=\""
+                      + urllink[0]
+                      + "\">"
+                      + (urllink[0].toLowerCase().equals(
+                              urllink[1].toLowerCase()) ? urllink[0]
+                              : (urllink[0] + ":" + urllink[1]))
+                      + "</a></br>");
             }
+          } catch (Exception x)
+          {
+            System.err.println("problem when creating links from "
+                    + urlstring);
+            x.printStackTrace();
           }
-
         }
       }
+
     }
   }
 
@@ -213,63 +239,72 @@ public class SequenceAnnotationReport
    * @return String[][] { String[] { link target, link label, dynamic component
    *         inserted (if any), url }}
    */
-  public String[][] createLinksFrom(SequenceI seq, String link)
+  String[][] createLinksFrom(SequenceI seq, String link)
   {
-    ArrayList<String[]> urlSets = new ArrayList<String[]>();
-    ArrayList<String> uniques = new ArrayList<String>();
+    List<String[]> urlSets = new ArrayList<String[]>();
+    List<String> uniques = new ArrayList<String>();
     UrlLink urlLink = new UrlLink(link);
     if (!urlLink.isValid())
     {
       System.err.println(urlLink.getInvalidMessage());
       return null;
     }
-    final String target = urlLink.getTarget(); // link.substring(0,
-    // link.indexOf("|"));
-    final String label = urlLink.getLabel();
     if (seq != null && urlLink.isDynamic())
     {
-
-      // collect matching db-refs
-      DBRefEntry[] dbr = jalview.util.DBRefUtils.selectRefs(seq.getDBRefs(),
-              new String[] { target });
-      // collect id string too
-      String id = seq.getName();
-      String descr = seq.getDescription();
-      if (descr != null && descr.length() < 1)
+      urlSets.addAll(createDynamicLinks(seq, urlLink, uniques));
+    }
+    else
+    {
+      String target = urlLink.getTarget();
+      String label = urlLink.getLabel();
+      String unq = label + "|" + urlLink.getUrl_prefix();
+      if (!uniques.contains(unq))
       {
-        descr = null;
+        uniques.add(unq);
+        urlSets.add(new String[] { target, label, null,
+            urlLink.getUrl_prefix() });
       }
-      if (dbr != null)
+    }
+
+    return urlSets.toArray(new String[][] {});
+  }
+
+  /**
+   * Formats and returns a list of dynamic href links
+   * 
+   * @param seq
+   * @param urlLink
+   * @param uniques
+   */
+  List<String[]> createDynamicLinks(SequenceI seq, UrlLink urlLink,
+          List<String> uniques)
+  {
+    List<String[]> result = new ArrayList<String[]>();
+    final String target = urlLink.getTarget();
+    final String label = urlLink.getLabel();
+
+    // collect matching db-refs
+    DBRefEntry[] dbr = DBRefUtils.selectRefs(seq.getDBRefs(),
+            new String[] { target });
+    // collect id string too
+    String id = seq.getName();
+    String descr = seq.getDescription();
+    if (descr != null && descr.length() < 1)
+    {
+      descr = null;
+    }
+    if (dbr != null)
+    {
+      for (int r = 0; r < dbr.length; r++)
       {
-        for (int r = 0; r < dbr.length; r++)
+        if (id != null && dbr[r].getAccessionId().equals(id))
         {
-          if (id != null && dbr[r].getAccessionId().equals(id))
-          {
-            // suppress duplicate link creation for the bare sequence ID
-            // string with this link
-            id = null;
-          }
-          // create Bare ID link for this RUL
-          String[] urls = urlLink.makeUrls(dbr[r].getAccessionId(), true);
-          if (urls != null)
-          {
-            for (int u = 0; u < urls.length; u += 2)
-            {
-              String unq = urls[u] + "|" + urls[u + 1];
-              if (!uniques.contains(unq))
-              {
-                urlSets.add(new String[] { target, label, urls[u],
-                    urls[u + 1] });
-                uniques.add(unq);
-              }
-            }
-          }
+          // suppress duplicate link creation for the bare sequence ID
+          // string with this link
+          id = null;
         }
-      }
-      if (id != null)
-      {
-        // create Bare ID link for this RUL
-        String[] urls = urlLink.makeUrls(id, true);
+        // create Bare ID link for this URL
+        String[] urls = urlLink.makeUrls(dbr[r].getAccessionId(), true);
         if (urls != null)
         {
           for (int u = 0; u < urls.length; u += 2)
@@ -277,51 +312,56 @@ public class SequenceAnnotationReport
             String unq = urls[u] + "|" + urls[u + 1];
             if (!uniques.contains(unq))
             {
-              urlSets.add(new String[] { target, label, urls[u],
+              result.add(new String[] { target, label, urls[u],
                   urls[u + 1] });
               uniques.add(unq);
             }
           }
         }
       }
-      if (descr != null && urlLink.getRegexReplace() != null)
+    }
+    if (id != null)
+    {
+      // create Bare ID link for this URL
+      String[] urls = urlLink.makeUrls(id, true);
+      if (urls != null)
       {
-        // create link for this URL from description only if regex matches
-        String[] urls = urlLink.makeUrls(descr, true);
-        if (urls != null)
+        for (int u = 0; u < urls.length; u += 2)
         {
-          for (int u = 0; u < urls.length; u += 2)
+          String unq = urls[u] + "|" + urls[u + 1];
+          if (!uniques.contains(unq))
           {
-            String unq = urls[u] + "|" + urls[u + 1];
-            if (!uniques.contains(unq))
-            {
-              urlSets.add(new String[] { target, label, urls[u],
-                  urls[u + 1] });
-              uniques.add(unq);
-            }
+            result.add(new String[] { target, label, urls[u],
+                urls[u + 1] });
+            uniques.add(unq);
           }
         }
       }
-
     }
-    else
+    if (descr != null && urlLink.getRegexReplace() != null)
     {
-      String unq = label + "|" + urlLink.getUrl_prefix();
-      if (!uniques.contains(unq))
+      // create link for this URL from description only if regex matches
+      String[] urls = urlLink.makeUrls(descr, true);
+      if (urls != null)
       {
-        uniques.add(unq);
-        // Add a non-dynamic link
-        urlSets.add(new String[] { target, label, null,
-            urlLink.getUrl_prefix() });
+        for (int u = 0; u < urls.length; u += 2)
+        {
+          String unq = urls[u] + "|" + urls[u + 1];
+          if (!uniques.contains(unq))
+          {
+            result.add(new String[] { target, label, urls[u],
+                urls[u + 1] });
+            uniques.add(unq);
+          }
+        }
       }
     }
-
-    return urlSets.toArray(new String[][] {});
+    return result;
   }
 
   public void createSequenceAnnotationReport(final StringBuffer tip,
           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
-          Hashtable minmax)
+          Map<String, float[][]> minmax)
   {
     createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats,
             true, minmax);
@@ -329,7 +369,7 @@ public class SequenceAnnotationReport
 
   public void createSequenceAnnotationReport(final StringBuffer tip,
           SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
-          boolean tableWrap, Hashtable minmax)
+          boolean tableWrap, Map<String, float[][]> minmax)
   {
     String tmp;
     tip.append("<i>");
diff --git a/src/jalview/io/gff/GffConstants.java b/src/jalview/io/gff/GffConstants.java
new file mode 100644 (file)
index 0000000..545c6e3
--- /dev/null
@@ -0,0 +1,23 @@
+package jalview.io.gff;
+
+/**
+ * A class to hold constants shared by creators and consumers of GFF or feature
+ * data
+ */
+public class GffConstants
+{
+  // SequenceOntology terms are to be found in SequenceOntologyI
+
+  /*
+   * clinical_significance may be an attribute of 
+   * sequence_variant data from Ensembl
+   */
+  public static final String CLINICAL_SIGNIFICANCE = "clinical_significance";
+
+  /*
+   * not instantiable
+   */
+  private GffConstants()
+  {
+  }
+}
index 205b9c6..b2eb094 100755 (executable)
@@ -55,7 +55,6 @@ import javax.swing.JPanel;
 import javax.swing.JRadioButtonMenuItem;
 import javax.swing.JTabbedPane;
 import javax.swing.KeyStroke;
-import javax.swing.SwingUtilities;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.MenuEvent;
 import javax.swing.event.MenuListener;
@@ -179,6 +178,8 @@ public class GAlignFrame extends JInternalFrame
 
   protected JMenu showProducts = new JMenu();
 
+  protected JMenuItem runGroovy = new JMenuItem();
+
   protected JMenuItem rnahelicesColour = new JMenuItem();
 
   protected JCheckBoxMenuItem autoCalculate = new JCheckBoxMenuItem();
@@ -293,8 +294,7 @@ public class GAlignFrame extends JInternalFrame
           @Override
           public void mousePressed(MouseEvent evt)
           {
-            if (evt.isControlDown()
-                    || SwingUtilities.isRightMouseButton(evt))
+            if (evt.isPopupTrigger())
             {
               radioItem.removeActionListener(radioItem.getActionListeners()[0]);
 
@@ -1726,6 +1726,17 @@ public class GAlignFrame extends JInternalFrame
     // for show products actions see AlignFrame.canShowProducts
     showProducts.setText(MessageManager.getString("label.get_cross_refs"));
 
+    runGroovy.setText(MessageManager.getString("label.run_groovy"));
+    runGroovy.setToolTipText(MessageManager.getString("label.run_groovy_tip"));
+    runGroovy.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        runGroovy_actionPerformed();
+      }
+    });
+
     JMenuItem openFeatureSettings = new JMenuItem(
             MessageManager.getString("action.feature_settings"));
     openFeatureSettings.addActionListener(new ActionListener()
@@ -2160,6 +2171,7 @@ public class GAlignFrame extends JInternalFrame
     alignFrameMenuBar.add(colourMenu);
     alignFrameMenuBar.add(calculateMenu);
     alignFrameMenuBar.add(webService);
+
     fileMenu.add(fetchSequence);
     fileMenu.add(addSequenceMenu);
     fileMenu.add(reload);
@@ -2178,6 +2190,9 @@ public class GAlignFrame extends JInternalFrame
     fileMenu.add(associatedData);
     fileMenu.addSeparator();
     fileMenu.add(closeMenuItem);
+
+    pasteMenu.add(pasteNew);
+    pasteMenu.add(pasteThis);
     editMenu.add(undoMenuItem);
     editMenu.add(redoMenuItem);
     editMenu.add(cut);
@@ -2198,6 +2213,13 @@ public class GAlignFrame extends JInternalFrame
     // editMenu.addSeparator();
     editMenu.add(padGapsMenuitem);
 
+    showMenu.add(showAllColumns);
+    showMenu.add(showAllSeqs);
+    showMenu.add(showAllhidden);
+    hideMenu.add(hideSelColumns);
+    hideMenu.add(hideSelSequences);
+    hideMenu.add(hideAllSelection);
+    hideMenu.add(hideAllButSelection);
     viewMenu.add(newView);
     viewMenu.add(expandViews);
     viewMenu.add(gatherViews);
@@ -2268,6 +2290,12 @@ public class GAlignFrame extends JInternalFrame
     colourMenu.add(modifyPID);
     colourMenu.add(annotationColour);
     colourMenu.add(rnahelicesColour);
+
+    sort.add(sortIDMenuItem);
+    sort.add(sortLengthMenuItem);
+    sort.add(sortGroupMenuItem);
+    sort.add(sortPairwiseMenuItem);
+    sort.add(sortByTreeMenu);
     calculateMenu.add(sort);
     calculateMenu.add(calculateTree);
     calculateMenu.addSeparator();
@@ -2281,17 +2309,14 @@ public class GAlignFrame extends JInternalFrame
     calculateMenu.add(autoCalculate);
     calculateMenu.add(sortByTree);
     calculateMenu.addSeparator();
+    calculateMenu.add(expandAlignment);
     calculateMenu.add(extractScores);
+    calculateMenu.addSeparator();
+    calculateMenu.add(runGroovy);
+
     webServiceNoServices = new JMenuItem(
             MessageManager.getString("label.no_services"));
     webService.add(webServiceNoServices);
-    pasteMenu.add(pasteNew);
-    pasteMenu.add(pasteThis);
-    sort.add(sortIDMenuItem);
-    sort.add(sortLengthMenuItem);
-    sort.add(sortGroupMenuItem);
-    sort.add(sortPairwiseMenuItem);
-    sort.add(sortByTreeMenu);
     exportImageMenu.add(htmlMenuItem);
     exportImageMenu.add(epsFile);
     exportImageMenu.add(createPNG);
@@ -2303,13 +2328,6 @@ public class GAlignFrame extends JInternalFrame
     this.getContentPane().add(statusPanel, java.awt.BorderLayout.SOUTH);
     statusPanel.add(statusBar, null);
     this.getContentPane().add(tabbedPane, java.awt.BorderLayout.CENTER);
-    showMenu.add(showAllColumns);
-    showMenu.add(showAllSeqs);
-    showMenu.add(showAllhidden);
-    hideMenu.add(hideSelColumns);
-    hideMenu.add(hideSelSequences);
-    hideMenu.add(hideAllSelection);
-    hideMenu.add(hideAllButSelection);
 
     formatMenu.add(font);
     formatMenu.addSeparator();
@@ -2337,7 +2355,6 @@ public class GAlignFrame extends JInternalFrame
     selectMenu.add(grpsFromSelection);
     selectMenu.add(deleteGroups);
     selectMenu.add(annotationColumn);
-    calculateMenu.add(expandAlignment);
     // TODO - determine if the listenToViewSelections button is needed : see bug
     // JAL-574
     // selectMenu.addSeparator();
@@ -2355,6 +2372,14 @@ public class GAlignFrame extends JInternalFrame
   }
 
   /**
+   * Try to run script in a Groovy console, having first ensured that this
+   * alignframe is set as currentAlignFrame in Desktop
+   */
+  protected void runGroovy_actionPerformed()
+  {
+
+  }
+  /**
    * Adds the given action listener and key accelerator to the given menu item.
    * Also saves in a lookup table to support lookup of action by key stroke.
    * 
index b8cd563..31d1ded 100644 (file)
@@ -103,4 +103,43 @@ public class ColorUtils
     return col == null ? null : col.brighter().brighter().brighter();
   }
 
+  /**
+   * Returns a color between minColour and maxColour; the RGB values are in
+   * proportion to where 'value' lies between minValue and maxValue
+   * 
+   * @param value
+   * @param minValue
+   * @param minColour
+   * @param maxValue
+   * @param maxColour
+   * @return
+   */
+  public static Color getGraduatedColour(float value, float minValue,
+          Color minColour, float maxValue, Color maxColour)
+  {
+    if (minValue == maxValue)
+    {
+      return minColour;
+    }
+    if (value < minValue)
+    {
+      value = minValue;
+    }
+    if (value > maxValue)
+    {
+      value = maxValue;
+    }
+
+    /*
+     * prop = proportion of the way value is from minValue to maxValue
+     */
+    float prop = (value - minValue) / (maxValue - minValue);
+    float r = minColour.getRed() + prop
+            * (maxColour.getRed() - minColour.getRed());
+    float g = minColour.getGreen() + prop
+            * (maxColour.getGreen() - minColour.getGreen());
+    float b = minColour.getBlue() + prop
+            * (maxColour.getBlue() - minColour.getBlue());
+    return new Color(r / 255, g / 255, b / 255);
+  }
 }
index 8902e2c..5605a53 100644 (file)
@@ -286,7 +286,7 @@ public class Comparison
    * @param letters
    * @return
    */
-  public static final boolean areNucleotide(char[][] letters)
+  static final boolean areNucleotide(char[][] letters)
   {
     int ntCount = 0;
     int aaCount = 0;
@@ -300,16 +300,11 @@ public class Comparison
       // to save a lengthy calculation
       for (char c : seq)
       {
-        if ('a' <= c && c <= 'z')
-        {
-          c -= TO_UPPER_CASE;
-        }
-
-        if (c == 'A' || c == 'G' || c == 'C' || c == 'T' || c == 'U')
+        if (isNucleotide(c))
         {
           ntCount++;
         }
-        else if (!Comparison.isGap(c))
+        else if (!isGap(c))
         {
           aaCount++;
         }
@@ -332,6 +327,59 @@ public class Comparison
   }
 
   /**
+   * Answers true if the character is one of aAcCgGtTuU
+   * 
+   * @param c
+   * @return
+   */
+  public static boolean isNucleotide(char c)
+  {
+    if ('a' <= c && c <= 'z')
+    {
+      c -= TO_UPPER_CASE;
+    }
+
+    switch (c)
+    {
+    case 'A':
+    case 'C':
+    case 'G':
+    case 'T':
+    case 'U':
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Answers true if every character in the string is one of aAcCgGtTuU, or
+   * (optionally) a gap character (dot, dash, space), else false
+   * 
+   * @param s
+   * @param allowGaps
+   * @return
+   */
+  public static boolean isNucleotideSequence(String s, boolean allowGaps)
+  {
+    if (s == null)
+    {
+      return false;
+    }
+    for (int i = 0; i < s.length(); i++)
+    {
+      char c = s.charAt(i);
+      if (!isNucleotide(c))
+      {
+        if (!allowGaps || !isGap(c))
+        {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
    * Convenience overload of isNucleotide
    * 
    * @param seqs
index 5b55e05..848f565 100644 (file)
@@ -68,6 +68,12 @@ public abstract class FeatureRendererModel implements
 
   protected AlignmentViewport av;
 
+  /*
+   * map holds per feature type, {{min, max}, {min, max}} feature score
+   * values for positional and non-positional features respectively
+   */
+  private Map<String, float[][]> minmax = new Hashtable<String, float[][]>();
+
   @Override
   public AlignViewportI getViewport()
   {
@@ -194,9 +200,7 @@ public abstract class FeatureRendererModel implements
     renderOrder = neworder;
   }
 
-  protected Hashtable minmax = new Hashtable();
-
-  public Hashtable getMinMax()
+  public Map<String, float[][]> getMinMax()
   {
     return minmax;
   }
@@ -210,7 +214,7 @@ public abstract class FeatureRendererModel implements
    */
   protected final byte[] normaliseScore(SequenceFeature sequenceFeature)
   {
-    float[] mm = ((float[][]) minmax.get(sequenceFeature.type))[0];
+    float[] mm = minmax.get(sequenceFeature.type)[0];
     final byte[] r = new byte[] { 0, (byte) 255 };
     if (mm != null)
     {
@@ -341,7 +345,7 @@ public abstract class FeatureRendererModel implements
     }
     if (minmax == null)
     {
-      minmax = new Hashtable();
+      minmax = new Hashtable<String, float[][]>();
     }
     AlignmentI alignment = av.getAlignment();
     for (int i = 0; i < alignment.getHeight(); i++)
@@ -396,7 +400,7 @@ public abstract class FeatureRendererModel implements
         if (!Float.isNaN(features[index].score))
         {
           int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
-          float[][] mm = (float[][]) minmax.get(features[index].getType());
+          float[][] mm = minmax.get(features[index].getType());
           if (mm == null)
           {
             mm = new float[][] { null, null };
@@ -819,7 +823,7 @@ public abstract class FeatureRendererModel implements
     changeSupport.removePropertyChangeListener(listener);
   }
 
-  public Set getAllFeatureColours()
+  public Set<String> getAllFeatureColours()
   {
     return featureColours.keySet();
   }
index bca3145..48e3604 100644 (file)
@@ -46,7 +46,7 @@ public abstract class AlignCalcWorker implements AlignCalcWorkerI
 
   protected AlignmentViewPanel ap;
 
-  protected List<AlignmentAnnotation> ourAnnots = null;
+  protected List<AlignmentAnnotation> ourAnnots;
 
   public AlignCalcWorker(AlignViewportI alignViewport,
           AlignmentViewPanel alignPanel)
@@ -68,17 +68,18 @@ public abstract class AlignCalcWorker implements AlignCalcWorkerI
 
   }
 
+  @Override
   public boolean involves(AlignmentAnnotation i)
   {
     return ourAnnots != null && ourAnnots.contains(i);
   }
 
   /**
-   * permanently remove from the alignment all annotation rows managed by this
+   * Permanently removes from the alignment all annotation rows managed by this
    * worker
    */
   @Override
-  public void removeOurAnnotation()
+  public void removeAnnotation()
   {
     if (ourAnnots != null && alignViewport != null)
     {
@@ -90,6 +91,7 @@ public abstract class AlignCalcWorker implements AlignCalcWorkerI
           alignment.deleteAnnotation(aa, true);
         }
       }
+      ourAnnots.clear();
     }
   }
   // TODO: allow GUI to query workers associated with annotation to add items to
diff --git a/src/jalview/workers/AlignmentAnnotationFactory.java b/src/jalview/workers/AlignmentAnnotationFactory.java
new file mode 100644 (file)
index 0000000..37f3ca5
--- /dev/null
@@ -0,0 +1,117 @@
+package jalview.workers;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+
+import java.awt.Color;
+
+/**
+ * Factory class with methods which allow clients (including external scripts
+ * such as Groovy) to 'register and forget' an alignment annotation calculator. <br>
+ * Currently supports two flavours of calculator:
+ * <ul>
+ * <li>a 'feature counter' which can count any desired property derivable from
+ * residue value and any sequence features at each position of the alignment</li>
+ * <li>a 'general purpose' calculator which computes one more complete
+ * AlignmentAnnotation objects</li>
+ * </ul>
+ */
+public class AlignmentAnnotationFactory
+{
+  /**
+   * Constructs and registers a new alignment annotation worker
+   * 
+   * @param counter
+   *          provider of feature counts per alignment position
+   */
+  public static void newCalculator(FeatureCounterI counter)
+  {
+    if (Desktop.getCurrentAlignFrame() != null)
+    {
+      newCalculator(Desktop.getCurrentAlignFrame(), counter);
+    }
+    else
+    {
+      System.err
+              .println("Can't register calculator as no alignment window has focus");
+    }
+  }
+
+  /**
+   * Constructs and registers a new alignment annotation worker
+   * 
+   * @param af
+   *          the AlignFrame for which the annotation is to be calculated
+   * @param counter
+   *          provider of feature counts per alignment position
+   */
+  public static void newCalculator(AlignFrame af, FeatureCounterI counter)
+  {
+    new ColumnCounterWorker(af, counter);
+  }
+
+  /**
+   * Constructs and registers a new alignment annotation worker
+   * 
+   * @param calculator
+   *          provider of AlignmentAnnotation for the alignment
+   */
+  public static void newCalculator(AnnotationProviderI calculator)
+  {
+    if (Desktop.getCurrentAlignFrame() != null)
+    {
+      newCalculator(Desktop.getCurrentAlignFrame(), calculator);
+    }
+    else
+    {
+      System.err
+              .println("Can't register calculator as no alignment window has focus");
+    }
+  }
+
+  /**
+   * Constructs and registers a new alignment annotation worker
+   * 
+   * @param af
+   *          the AlignFrame for which the annotation is to be calculated
+   * @param calculator
+   *          provider of AlignmentAnnotation for the alignment
+   */
+  public static void newCalculator(AlignFrame af,
+          AnnotationProviderI calculator)
+  {
+    new AnnotationWorker(af, calculator);
+  }
+
+  /**
+   * Factory method to construct an Annotation object
+   * 
+   * @param displayChar
+   * @param desc
+   * @param secondaryStructure
+   * @param val
+   * @param color
+   * @return
+   */
+  public static Annotation newAnnotation(String displayChar, String desc,
+          char secondaryStructure, float val, Color color)
+  {
+    return new Annotation(displayChar, desc, secondaryStructure, val, color);
+  }
+
+  /**
+   * Factory method to construct an AlignmentAnnotation object
+   * 
+   * @param name
+   * @param desc
+   * @param anns
+   * @return
+   */
+  public static AlignmentAnnotation newAlignmentAnnotation(String name,
+          String desc, Annotation[] anns)
+  {
+    return new AlignmentAnnotation(name, desc, anns);
+  }
+}
diff --git a/src/jalview/workers/AnnotationProviderI.java b/src/jalview/workers/AnnotationProviderI.java
new file mode 100644 (file)
index 0000000..653ff04
--- /dev/null
@@ -0,0 +1,17 @@
+package jalview.workers;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.gui.FeatureRenderer;
+
+import java.util.List;
+
+/**
+ * Interface to be satisfied by any class which computes one or more alignment
+ * annotations
+ */
+public interface AnnotationProviderI
+{
+  List<AlignmentAnnotation> calculateAnnotation(AlignmentI al,
+          FeatureRenderer fr);
+}
diff --git a/src/jalview/workers/AnnotationWorker.java b/src/jalview/workers/AnnotationWorker.java
new file mode 100644 (file)
index 0000000..fbf7531
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.workers;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.FeatureRenderer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class to create and update one or more alignment annotations, given a
+ * 'calculator'.
+ * 
+ */
+class AnnotationWorker extends AlignCalcWorker
+{
+  /*
+   * the provider of the annotation calculations
+   */
+  AnnotationProviderI counter;
+
+  /**
+   * Constructor
+   * 
+   * @param af
+   * @param counter
+   */
+  public AnnotationWorker(AlignFrame af, AnnotationProviderI counter)
+  {
+    super(af.getViewport(), af.alignPanel);
+    ourAnnots = new ArrayList<AlignmentAnnotation>();
+    this.counter = counter;
+    calcMan.registerWorker(this);
+  }
+
+  @Override
+  public void run()
+  {
+    try
+    {
+      calcMan.notifyStart(this);
+
+      while (!calcMan.notifyWorking(this))
+      {
+        try
+        {
+          Thread.sleep(200);
+        } catch (InterruptedException ex)
+        {
+          ex.printStackTrace();
+        }
+      }
+      if (alignViewport.isClosed())
+      {
+        abortAndDestroy();
+        return;
+      }
+
+      removeAnnotations();
+      AlignmentI alignment = alignViewport.getAlignment();
+      if (alignment != null)
+      {
+        try
+        {
+          List<AlignmentAnnotation> anns = counter.calculateAnnotation(
+                  alignment, new FeatureRenderer((AlignmentPanel) ap));
+          for (AlignmentAnnotation ann : anns)
+          {
+            ann.showAllColLabels = true;
+            ann.graph = AlignmentAnnotation.BAR_GRAPH;
+            ourAnnots.add(ann);
+            alignment.addAnnotation(ann);
+          }
+        } catch (IndexOutOfBoundsException x)
+        {
+          // probable race condition. just finish and return without any fuss.
+          return;
+        }
+      }
+    } catch (OutOfMemoryError error)
+    {
+      ap.raiseOOMWarning("calculating annotations", error);
+      calcMan.workerCannotRun(this);
+    } finally
+    {
+      calcMan.workerComplete(this);
+    }
+
+    if (ap != null)
+    {
+      ap.adjustAnnotationHeight();
+      ap.paintAlignment(true);
+    }
+
+  }
+
+  /**
+   * Remove all our annotations before re-calculating them
+   */
+  void removeAnnotations()
+  {
+    for (AlignmentAnnotation ann : ourAnnots)
+    {
+      alignViewport.getAlignment().deleteAnnotation(ann);
+    }
+    ourAnnots.clear();
+  }
+
+  @Override
+  public void updateAnnotation()
+  {
+    // do nothing
+  }
+}
diff --git a/src/jalview/workers/ColumnCounterWorker.java b/src/jalview/workers/ColumnCounterWorker.java
new file mode 100644 (file)
index 0000000..6f4a4f3
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.workers;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.FeatureRenderer;
+import jalview.util.ColorUtils;
+import jalview.util.Comparison;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class to compute an alignment annotation with column counts of any
+ * properties of interest of positions in an alignment. <br>
+ * This is designed to be extensible, by supplying to the constructor an object
+ * that computes a count for each residue position, based on the residue value
+ * and any sequence features at that position.
+ * 
+ */
+class ColumnCounterWorker extends AlignCalcWorker
+{
+  FeatureCounterI counter;
+
+  /**
+   * Constructor registers the annotation for the given alignment frame
+   * 
+   * @param af
+   * @param counter
+   */
+  public ColumnCounterWorker(AlignFrame af, FeatureCounterI counter)
+  {
+    super(af.getViewport(), af.alignPanel);
+    ourAnnots = new ArrayList<AlignmentAnnotation>();
+    this.counter = counter;
+    calcMan.registerWorker(this);
+  }
+
+  /**
+   * method called under control of AlignCalcManager to recompute the annotation
+   * when the alignment changes
+   */
+  @Override
+  public void run()
+  {
+    try
+    {
+      calcMan.notifyStart(this);
+
+      while (!calcMan.notifyWorking(this))
+      {
+        try
+        {
+          Thread.sleep(200);
+        } catch (InterruptedException ex)
+        {
+          ex.printStackTrace();
+        }
+      }
+      if (alignViewport.isClosed())
+      {
+        abortAndDestroy();
+        return;
+      }
+
+      removeAnnotation();
+      if (alignViewport.getAlignment() != null)
+      {
+        try
+        {
+          computeAnnotations();
+        } catch (IndexOutOfBoundsException x)
+        {
+          // probable race condition. just finish and return without any fuss.
+          return;
+        }
+      }
+    } catch (OutOfMemoryError error)
+    {
+      ap.raiseOOMWarning("calculating feature counts", error);
+      calcMan.workerCannotRun(this);
+    } finally
+    {
+      calcMan.workerComplete(this);
+    }
+
+    if (ap != null)
+    {
+      ap.adjustAnnotationHeight();
+      ap.paintAlignment(true);
+    }
+
+  }
+
+  /**
+   * Scan each column of the alignment to calculate a count by feature type. Set
+   * the count as the value of the alignment annotation for that feature type.
+   */
+  void computeAnnotations()
+  {
+    FeatureRenderer fr = new FeatureRenderer((AlignmentPanel) ap);
+    // TODO use the commented out code once JAL-2075 is fixed
+    // to get adequate performance on genomic length sequence
+    AlignmentI alignment = alignViewport.getAlignment();
+    // AlignmentView alignmentView = alignViewport.getAlignmentView(false);
+    // AlignmentI alignment = alignmentView.getVisibleAlignment(' ');
+
+    // int width = alignmentView.getWidth();
+    int width = alignment.getWidth();
+    int height = alignment.getHeight();
+    int[] counts = new int[width];
+    int max = 0;
+
+    for (int col = 0; col < width; col++)
+    {
+      int count = 0;
+      for (int row = 0; row < height; row++)
+      {
+        count += countFeaturesAt(alignment, col, row, fr);
+      }
+      counts[col] = count;
+      max = Math.max(count, max);
+    }
+
+    Annotation[] anns = new Annotation[width];
+    /*
+     * add non-zero counts as annotations
+     */
+    for (int i = 0; i < counts.length; i++)
+    {
+      int count = counts[i];
+      if (count > 0)
+      {
+        Color color = ColorUtils.getGraduatedColour(count, 0, Color.cyan,
+                max, Color.blue);
+        anns[i] = new Annotation(String.valueOf(count),
+                String.valueOf(count), '0', count, color);
+      }
+    }
+
+    /*
+     * construct the annotation, save it and add it to the displayed alignment
+     */
+    AlignmentAnnotation ann = new AlignmentAnnotation(counter.getName(),
+            counter.getDescription(), anns);
+    ann.showAllColLabels = true;
+    ann.graph = AlignmentAnnotation.BAR_GRAPH;
+    ourAnnots.add(ann);
+    alignViewport.getAlignment().addAnnotation(ann);
+  }
+
+  /**
+   * Returns a count of any feature types present at the specified position of
+   * the alignment
+   * 
+   * @param alignment
+   * @param col
+   * @param row
+   * @param fr
+   */
+  int countFeaturesAt(AlignmentI alignment, int col, int row,
+          FeatureRenderer fr)
+  {
+    SequenceI seq = alignment.getSequenceAt(row);
+    if (seq == null)
+    {
+      return 0;
+    }
+    if (col >= seq.getLength())
+    {
+      return 0;// sequence doesn't extend this far
+    }
+    char res = seq.getCharAt(col);
+    if (Comparison.isGap(res))
+    {
+      return 0;
+    }
+    int pos = seq.findPosition(col);
+
+    /*
+     * compute a count for any displayed features at residue
+     */
+    // NB have to adjust pos if using AlignmentView.getVisibleAlignment
+    // see JAL-2075
+    List<SequenceFeature> features = fr.findFeaturesAtRes(seq, pos);
+    int count = this.counter.count(String.valueOf(res), features);
+    return count;
+  }
+
+  /**
+   * Method called when the user changes display options that may affect how the
+   * annotation is rendered, but do not change its values. Currently no such
+   * options affect user-defined annotation, so this method does nothing.
+   */
+  @Override
+  public void updateAnnotation()
+  {
+    // do nothing
+  }
+}
index a8698e6..14e2a31 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.workers;
 
 import jalview.analysis.AAFrequency;
-import jalview.api.AlignCalcWorkerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.AlignmentAnnotation;
@@ -32,8 +31,7 @@ import jalview.schemes.ColourSchemeI;
 
 import java.util.Hashtable;
 
-public class ConsensusThread extends AlignCalcWorker implements
-        AlignCalcWorkerI
+public class ConsensusThread extends AlignCalcWorker
 {
   public ConsensusThread(AlignViewportI alignViewport,
           AlignmentViewPanel alignPanel)
index 236bccf..1075e4d 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.workers;
 
 import jalview.analysis.Conservation;
-import jalview.api.AlignCalcWorkerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.AlignmentAnnotation;
@@ -30,8 +29,7 @@ import jalview.datamodel.AlignmentI;
 import java.util.ArrayList;
 import java.util.List;
 
-public class ConservationThread extends AlignCalcWorker implements
-        AlignCalcWorkerI
+public class ConservationThread extends AlignCalcWorker
 {
 
   private int ConsPercGaps = 25; // JBPNote : This should be a configurable
diff --git a/src/jalview/workers/FeatureCounterI.java b/src/jalview/workers/FeatureCounterI.java
new file mode 100644 (file)
index 0000000..aa4a283
--- /dev/null
@@ -0,0 +1,63 @@
+package jalview.workers;
+
+import jalview.datamodel.SequenceFeature;
+
+import java.util.List;
+
+/**
+ * An interface for a type that returns counts of any value of interest at a
+ * sequence position that can be determined from the sequence character and any
+ * features present at that position
+ * 
+ */
+public interface FeatureCounterI
+{
+  /**
+   * Returns a count of some property of interest, for example
+   * <ul>
+   * <li>the number of variant features at the position</li>
+   * <li>the number of Cath features of status 'True Positive'</li>
+   * <li>1 if the residue is hydrophobic, else 0</li>
+   * <li>etc</li>
+   * </ul>
+   * 
+   * @param residue
+   *          the residue (or gap) at the position
+   * @param a
+   *          list of any sequence features which include the position
+   */
+  int count(String residue, List<SequenceFeature> features);
+
+  /**
+   * Returns a name for the annotation that this is counting, for use as the
+   * displayed label
+   * 
+   * @return
+   */
+  String getName();
+
+  /**
+   * Returns a description for the annotation, for display as a tooltip
+   * 
+   * @return
+   */
+  String getDescription();
+
+  /**
+   * Returns the colour (as [red, green, blue] values in the range 0-255) to use
+   * for the minimum value on histogram bars. If this is different to
+   * getMaxColour(), then bars will have a graduated colour.
+   * 
+   * @return
+   */
+  int[] getMinColour();
+
+  /**
+   * Returns the colour (as [red, green, blue] values in the range 0-255) to use
+   * for the maximum value on histogram bars. If this is the same as
+   * getMinColour(), then bars will have a single colour (not graduated).
+   * 
+   * @return
+   */
+  int[] getMaxColour();
+}
index e0b3833..3483dac 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.workers;
 
 import jalview.analysis.StructureFrequency;
-import jalview.api.AlignCalcWorkerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.AlignmentAnnotation;
@@ -31,8 +30,7 @@ import jalview.datamodel.SequenceI;
 
 import java.util.Hashtable;
 
-public class StrucConsensusThread extends AlignCalcWorker implements
-        AlignCalcWorkerI
+public class StrucConsensusThread extends AlignCalcWorker
 {
   public StrucConsensusThread(AlignViewportI alignViewport,
           AlignmentViewPanel alignPanel)
index f929b1e..f4b1c31 100644 (file)
@@ -20,7 +20,6 @@
  */
 package jalview.ws.jws2;
 
-import jalview.api.AlignCalcWorkerI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.GraphLine;
@@ -46,8 +45,7 @@ import compbio.data.sequence.Score;
 import compbio.data.sequence.ScoreManager.ScoreHolder;
 import compbio.metadata.Argument;
 
-public class AADisorderClient extends JabawsCalcWorker implements
-        AlignCalcWorkerI
+public class AADisorderClient extends JabawsCalcWorker
 {
 
   private static final String THRESHOLD = "THRESHOLD";
index e4d6329..c15f256 100644 (file)
@@ -20,7 +20,6 @@
  */
 package jalview.ws.jws2;
 
-import jalview.api.AlignCalcWorkerI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.gui.AlignFrame;
@@ -43,9 +42,7 @@ import compbio.data.sequence.JpredAlignment;
 import compbio.metadata.Argument;
 
 public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
-        implements AlignCalcWorkerI
 {
-
   /**
    * 
    * @return default args for this service when run as dynamic web service
@@ -87,6 +84,7 @@ public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
     return (seqs.size() > 1);
   }
 
+  @Override
   public String getServiceActionText()
   {
     return "calculating consensus secondary structure prediction using JPred service";
@@ -112,6 +110,7 @@ public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
    * update the consensus annotation from the sequence profile data using
    * current visualization settings.
    */
+  @Override
   public void updateResultAnnotation(boolean immediate)
   {
     if (immediate || !calcMan.isWorking(this) && msascoreset != null)
index 41aa223..9ca6d2e 100644 (file)
@@ -20,7 +20,6 @@
  */
 package jalview.ws.jws2;
 
-import jalview.api.AlignCalcWorkerI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
 import jalview.gui.AlignFrame;
@@ -50,8 +49,7 @@ import compbio.metadata.Argument;
  * 
  */
 
-public class RNAalifoldClient extends JabawsCalcWorker implements
-        AlignCalcWorkerI
+public class RNAalifoldClient extends JabawsCalcWorker
 {
 
   String methodName;
@@ -75,6 +73,7 @@ public class RNAalifoldClient extends JabawsCalcWorker implements
     initViewportParams();
   }
 
+  @Override
   public String getCalcId()
   {
     return CALC_ID;
index 810ef5f..860d979 100644 (file)
@@ -26,6 +26,7 @@ import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 
+import jalview.analysis.AlignmentUtils.DnaVariant;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
@@ -1733,56 +1734,62 @@ public class AlignmentUtilsTests
     /*
      * first with no variants on dna
      */
-    LinkedHashMap<Integer, List<String[][]>> variantsMap = AlignmentUtils
+    LinkedHashMap<Integer, List<DnaVariant>[]> variantsMap = AlignmentUtils
             .buildDnaVariantsMap(dna, map);
     assertTrue(variantsMap.isEmpty());
 
     /*
      * single allele codon 1, on base 1
      */
-    SequenceFeature sf = new SequenceFeature("sequence_variant", "", 1, 1,
+    SequenceFeature sf1 = new SequenceFeature("sequence_variant", "", 1, 1,
             0f, null);
-    sf.setValue("alleles", "T");
-    sf.setValue("ID", "sequence_variant:rs758803211");
-    dna.addSequenceFeature(sf);
+    sf1.setValue("alleles", "T");
+    sf1.setValue("ID", "sequence_variant:rs758803211");
+    dna.addSequenceFeature(sf1);
 
     /*
      * two alleles codon 2, on bases 2 and 3 (distinct variants)
      */
-    sf = new SequenceFeature("sequence_variant", "", 5, 5, 0f, null);
-    sf.setValue("alleles", "T");
-    sf.setValue("ID", "sequence_variant:rs758803212");
-    dna.addSequenceFeature(sf);
-    sf = new SequenceFeature("sequence_variant", "", 6, 6, 0f, null);
-    sf.setValue("alleles", "G");
-    sf.setValue("ID", "sequence_variant:rs758803213");
-    dna.addSequenceFeature(sf);
+    SequenceFeature sf2 = new SequenceFeature("sequence_variant", "", 5, 5,
+            0f, null);
+    sf2.setValue("alleles", "T");
+    sf2.setValue("ID", "sequence_variant:rs758803212");
+    dna.addSequenceFeature(sf2);
+    SequenceFeature sf3 = new SequenceFeature("sequence_variant", "", 6, 6,
+            0f, null);
+    sf3.setValue("alleles", "G");
+    sf3.setValue("ID", "sequence_variant:rs758803213");
+    dna.addSequenceFeature(sf3);
 
     /*
      * two alleles codon 3, both on base 2 (one variant)
      */
-    sf = new SequenceFeature("sequence_variant", "", 8, 8, 0f, null);
-    sf.setValue("alleles", "C, G");
-    sf.setValue("ID", "sequence_variant:rs758803214");
-    dna.addSequenceFeature(sf);
+    SequenceFeature sf4 = new SequenceFeature("sequence_variant", "", 8, 8,
+            0f, null);
+    sf4.setValue("alleles", "C, G");
+    sf4.setValue("ID", "sequence_variant:rs758803214");
+    dna.addSequenceFeature(sf4);
 
     // no alleles on codon 4
 
     /*
      * alleles on codon 5 on all 3 bases (distinct variants)
      */
-    sf = new SequenceFeature("sequence_variant", "", 13, 13, 0f, null);
-    sf.setValue("alleles", "C, G"); // (C duplicates given base value)
-    sf.setValue("ID", "sequence_variant:rs758803215");
-    dna.addSequenceFeature(sf);
-    sf = new SequenceFeature("sequence_variant", "", 14, 14, 0f, null);
-    sf.setValue("alleles", "g, a"); // should force to upper-case
-    sf.setValue("ID", "sequence_variant:rs758803216");
-    dna.addSequenceFeature(sf);
-    sf = new SequenceFeature("sequence_variant", "", 15, 15, 0f, null);
-    sf.setValue("alleles", "A, T");
-    sf.setValue("ID", "sequence_variant:rs758803217");
-    dna.addSequenceFeature(sf);
+    SequenceFeature sf5 = new SequenceFeature("sequence_variant", "", 13,
+            13, 0f, null);
+    sf5.setValue("alleles", "C, G"); // (C duplicates given base value)
+    sf5.setValue("ID", "sequence_variant:rs758803215");
+    dna.addSequenceFeature(sf5);
+    SequenceFeature sf6 = new SequenceFeature("sequence_variant", "", 14,
+            14, 0f, null);
+    sf6.setValue("alleles", "g, a"); // should force to upper-case
+    sf6.setValue("ID", "sequence_variant:rs758803216");
+    dna.addSequenceFeature(sf6);
+    SequenceFeature sf7 = new SequenceFeature("sequence_variant", "", 15,
+            15, 0f, null);
+    sf7.setValue("alleles", "A, T");
+    sf7.setValue("ID", "sequence_variant:rs758803217");
+    dna.addSequenceFeature(sf7);
 
     /*
      * build map - expect variants on positions 1, 2, 3, 5
@@ -1791,39 +1798,68 @@ public class AlignmentUtilsTests
     assertEquals(4, variantsMap.size());
 
     /*
-     * one variant on protein position 1
-     */
-    assertEquals(1, variantsMap.get(1).size());
-    assertTrue(Arrays.deepEquals(new String[][] { { "A", "T" }, { "T" },
-        { "G" } }, variantsMap.get(1).get(0)));
-
-    /*
-     * two variants on protein position 2
-     */
-    assertEquals(2, variantsMap.get(2).size());
-    assertTrue(Arrays.deepEquals(new String[][] { { "A" }, { "A", "T" },
-        { "A" } }, variantsMap.get(2).get(0)));
-    assertTrue(Arrays.deepEquals(new String[][] { { "A" }, { "A" },
-        { "A", "G" } }, variantsMap.get(2).get(1)));
-
-    /*
-     * one variant on protein position 3
-     */
-    assertEquals(1, variantsMap.get(3).size());
-    assertTrue(Arrays.deepEquals(new String[][] { { "T" },
-        { "T", "C", "G" }, { "T" } }, variantsMap.get(3).get(0)));
+     * protein residue 1: variant on codon (ATG) base 1, not on 2 or 3
+     */
+    List<DnaVariant>[] pep1Variants = variantsMap.get(1);
+    assertEquals(3, pep1Variants.length);
+    assertEquals(1, pep1Variants[0].size());
+    assertEquals("A", pep1Variants[0].get(0).base); // codon[1] base
+    assertSame(sf1, pep1Variants[0].get(0).variant); // codon[1] variant
+    assertEquals(1, pep1Variants[1].size());
+    assertEquals("T", pep1Variants[1].get(0).base); // codon[2] base
+    assertNull(pep1Variants[1].get(0).variant); // no variant here
+    assertEquals(1, pep1Variants[2].size());
+    assertEquals("G", pep1Variants[2].get(0).base); // codon[3] base
+    assertNull(pep1Variants[2].get(0).variant); // no variant here
+
+    /*
+     * protein residue 2: variants on codon (AAA) bases 2 and 3
+     */
+    List<DnaVariant>[] pep2Variants = variantsMap.get(2);
+    assertEquals(3, pep2Variants.length);
+    assertEquals(1, pep2Variants[0].size());
+    // codon[1] base recorded while processing variant on codon[2]
+    assertEquals("A", pep2Variants[0].get(0).base);
+    assertNull(pep2Variants[0].get(0).variant); // no variant here
+    // codon[2] base and variant:
+    assertEquals(1, pep2Variants[1].size());
+    assertEquals("A", pep2Variants[1].get(0).base);
+    assertSame(sf2, pep2Variants[1].get(0).variant);
+    // codon[3] base was recorded when processing codon[2] variant
+    // and then the variant for codon[3] added to it
+    assertEquals(1, pep2Variants[2].size());
+    assertEquals("A", pep2Variants[2].get(0).base);
+    assertSame(sf3, pep2Variants[2].get(0).variant);
+
+    /*
+     * protein residue 3: variants on codon (TTT) base 2 only
+     */
+    List<DnaVariant>[] pep3Variants = variantsMap.get(3);
+    assertEquals(3, pep3Variants.length);
+    assertEquals(1, pep3Variants[0].size());
+    assertEquals("T", pep3Variants[0].get(0).base); // codon[1] base
+    assertNull(pep3Variants[0].get(0).variant); // no variant here
+    assertEquals(1, pep3Variants[1].size());
+    assertEquals("T", pep3Variants[1].get(0).base); // codon[2] base
+    assertSame(sf4, pep3Variants[1].get(0).variant); // codon[2] variant
+    assertEquals(1, pep3Variants[2].size());
+    assertEquals("T", pep3Variants[2].get(0).base); // codon[3] base
+    assertNull(pep3Variants[2].get(0).variant); // no variant here
 
     /*
      * three variants on protein position 5
-     * duplicated bases are not removed here, handled in computePeptideVariants
-     */
-    assertEquals(3, variantsMap.get(5).size());
-    assertTrue(Arrays.deepEquals(new String[][] { { "C", "C", "G" },
-        { "C" }, { "C" } }, variantsMap.get(5).get(0)));
-    assertTrue(Arrays.deepEquals(new String[][] { { "C" },
-        { "C", "G", "A" }, { "C" } }, variantsMap.get(5).get(1)));
-    assertTrue(Arrays.deepEquals(new String[][] { { "C" }, { "C" },
-        { "C", "A", "T" } }, variantsMap.get(5).get(2)));
+     */
+    List<DnaVariant>[] pep5Variants = variantsMap.get(5);
+    assertEquals(3, pep5Variants.length);
+    assertEquals(1, pep5Variants[0].size());
+    assertEquals("C", pep5Variants[0].get(0).base); // codon[1] base
+    assertSame(sf5, pep5Variants[0].get(0).variant); // codon[1] variant
+    assertEquals(1, pep5Variants[1].size());
+    assertEquals("C", pep5Variants[1].get(0).base); // codon[2] base
+    assertSame(sf6, pep5Variants[1].get(0).variant); // codon[2] variant
+    assertEquals(1, pep5Variants[2].size());
+    assertEquals("C", pep5Variants[2].get(0).base); // codon[3] base
+    assertSame(sf7, pep5Variants[2].get(0).variant); // codon[3] variant
   }
 
   /**
@@ -1833,67 +1869,159 @@ public class AlignmentUtilsTests
   @Test(groups = "Functional")
   public void testComputePeptideVariants()
   {
-    String[][] codonVariants = new String[][] { { "A" }, { "G" }, { "T" } };
-  
     /*
-     * AGT codes for S - this is not included in the variants returned
+     * scenario: AAATTTCCC codes for KFP, with variants
+     *           GAA -> E
+     *           CAA -> Q
+     *           AAG synonymous
+     *           AAT -> N
+     *              TTC synonymous
+     *                 CAC,CGC -> H,R (as one variant)
      */
-    List<String> variants = AlignmentUtils.computePeptideVariants(codonVariants, "S");
-    assertEquals("[]", variants.toString());
-  
-    // S is reported if it differs from the current value (A):
-    variants = AlignmentUtils.computePeptideVariants(codonVariants, "A");
-    assertEquals("[S]", variants.toString());
-  
-    /*
-     * synonymous variant is not reported
-     */
-    codonVariants = new String[][] { { "A" }, { "G" }, { "C", "T" } };
-    // AGC and AGT both code for S
-    variants = AlignmentUtils.computePeptideVariants(codonVariants, "s");
-    assertEquals("[]", variants.toString());
-  
+    SequenceI peptide = new Sequence("pep/10-12", "KFP");
+
     /*
-     * equivalent variants are only reported once
+     * two distinct variants for codon 1 position 1
+     * second one has clinical significance
      */
-    codonVariants = new String[][] { { "C" }, { "T" },
-        { "A", "C", "G", "T" } };
-    // CTA CTC CTG CTT all code for L
-    variants = AlignmentUtils.computePeptideVariants(codonVariants, "S");
-    assertEquals("[L]", variants.toString());
-  
+    SequenceFeature sf1 = new SequenceFeature("sequence_variant", "", 1, 1,
+            0f, null);
+    sf1.setValue("alleles", "A,G"); // GAA -> E
+    sf1.setValue("ID", "var1.125A>G");
+    SequenceFeature sf2 = new SequenceFeature("sequence_variant", "", 1, 1,
+            0f, null);
+    sf2.setValue("alleles", "A,C"); // CAA -> Q
+    sf2.setValue("ID", "var2");
+    sf2.setValue("clinical_significance", "Dodgy");
+    SequenceFeature sf3 = new SequenceFeature("sequence_variant", "", 3, 3,
+            0f, null);
+    sf3.setValue("alleles", "A,G"); // synonymous
+    sf3.setValue("ID", "var3");
+    sf3.setValue("clinical_significance", "None");
+    SequenceFeature sf4 = new SequenceFeature("sequence_variant", "", 3, 3,
+            0f, null);
+    sf4.setValue("alleles", "A,T"); // AAT -> N
+    sf4.setValue("ID", "sequence_variant:var4"); // prefix gets stripped off
+    sf4.setValue("clinical_significance", "Benign");
+    SequenceFeature sf5 = new SequenceFeature("sequence_variant", "", 6, 6,
+            0f, null);
+    sf5.setValue("alleles", "T,C"); // synonymous
+    sf5.setValue("ID", "var5");
+    sf5.setValue("clinical_significance", "Bad");
+    SequenceFeature sf6 = new SequenceFeature("sequence_variant", "", 8, 8,
+            0f, null);
+    sf6.setValue("alleles", "C,A,G"); // CAC,CGC -> H,R
+    sf6.setValue("ID", "var6");
+    sf6.setValue("clinical_significance", "Good");
+
+    List<DnaVariant> codon1Variants = new ArrayList<DnaVariant>();
+    List<DnaVariant> codon2Variants = new ArrayList<DnaVariant>();
+    List<DnaVariant> codon3Variants = new ArrayList<DnaVariant>();
+    List<DnaVariant> codonVariants[] = new ArrayList[3];
+    codonVariants[0] = codon1Variants;
+    codonVariants[1] = codon2Variants;
+    codonVariants[2] = codon3Variants;
+
     /*
-     * vary codons 1 and 2; variant products are sorted and non-redundant
+     * compute variants for protein position 1
      */
-    codonVariants = new String[][] { { "a", "C" }, { "g", "T" }, { "A" } };
-    // aga ata cga cta code for R, I, R, L
-    variants = AlignmentUtils.computePeptideVariants(codonVariants, "S");
-    assertEquals("[I, L, R]", variants.toString());
-  
+    codon1Variants.add(new DnaVariant("A", sf1));
+    codon1Variants.add(new DnaVariant("A", sf2));
+    codon2Variants.add(new DnaVariant("A"));
+    codon2Variants.add(new DnaVariant("A"));
+    codon3Variants.add(new DnaVariant("A", sf3));
+    codon3Variants.add(new DnaVariant("A", sf4));
+    AlignmentUtils.computePeptideVariants(peptide, 1, codonVariants);
+
     /*
-     * vary codons 2 and 3
+     * compute variants for protein position 2
      */
-    codonVariants = new String[][] { { "a" }, { "g", "T" }, { "A", "c" } };
-    // aga agc ata atc code for R, S, I, I
-    variants = AlignmentUtils.computePeptideVariants(codonVariants, "S");
-    assertEquals("[I, R]", variants.toString());
-  
+    codon1Variants.clear();
+    codon2Variants.clear();
+    codon3Variants.clear();
+    codon1Variants.add(new DnaVariant("T"));
+    codon2Variants.add(new DnaVariant("T"));
+    codon3Variants.add(new DnaVariant("T", sf5));
+    AlignmentUtils.computePeptideVariants(peptide, 2, codonVariants);
+
     /*
-     * vary codons 1 and 3
+     * compute variants for protein position 3
      */
-    codonVariants = new String[][] { { "a", "t" }, { "a" }, { "t", "g" } };
-    // aat aag tat tag code for N, K, Y, STOP - STOP sorted to end
-    variants = AlignmentUtils.computePeptideVariants(codonVariants, "S");
-    assertEquals("[K, N, Y, STOP]", variants.toString());
-  
+    codon1Variants.clear();
+    codon2Variants.clear();
+    codon3Variants.clear();
+    codon1Variants.add(new DnaVariant("C"));
+    codon2Variants.add(new DnaVariant("C", sf6));
+    codon3Variants.add(new DnaVariant("C"));
+    AlignmentUtils.computePeptideVariants(peptide, 3, codonVariants);
+
     /*
-     * vary codons 1, 2 and 3
+     * verify added sequence features for
+     * var1 K -> E
+     * var2 K -> Q
+     * var4 K -> N
+     * var6 P -> H
+     * var6 P -> R
      */
-    codonVariants = new String[][] { { "a", "t" }, { "G", "C" },
-        { "t", "g" } };
-    // agt agg act acg tgt tgg tct tcg code for S, R, T, T, C, W, S, S
-    variants = AlignmentUtils.computePeptideVariants(codonVariants, "S");
-    assertEquals("[C, R, T, W]", variants.toString());
+    SequenceFeature[] sfs = peptide.getSequenceFeatures();
+    assertEquals(5, sfs.length);
+    SequenceFeature sf = sfs[0];
+    assertEquals(1, sf.getBegin());
+    assertEquals(1, sf.getEnd());
+    assertEquals("K->E", sf.getDescription());
+    assertEquals("var1.125A>G", sf.getValue("ID"));
+    assertNull(sf.getValue("clinical_significance"));
+    assertEquals("ID=var1.125A>G", sf.getAttributes());
+    assertEquals(1, sf.links.size());
+    // link to variation is urlencoded
+    assertEquals(
+            "K->E var1.125A>G|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var1.125A%3EG",
+            sf.links.get(0));
+    sf = sfs[1];
+    assertEquals(1, sf.getBegin());
+    assertEquals(1, sf.getEnd());
+    assertEquals("K->Q", sf.getDescription());
+    assertEquals("var2", sf.getValue("ID"));
+    assertEquals("Dodgy", sf.getValue("clinical_significance"));
+    assertEquals("ID=var2;clinical_significance=Dodgy", sf.getAttributes());
+    assertEquals(1, sf.links.size());
+    assertEquals(
+            "K->Q var2|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var2",
+            sf.links.get(0));
+    sf = sfs[2];
+    assertEquals(1, sf.getBegin());
+    assertEquals(1, sf.getEnd());
+    assertEquals("K->N", sf.getDescription());
+    assertEquals("var4", sf.getValue("ID"));
+    assertEquals("Benign", sf.getValue("clinical_significance"));
+    assertEquals("ID=var4;clinical_significance=Benign", sf.getAttributes());
+    assertEquals(1, sf.links.size());
+    assertEquals(
+            "K->N var4|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var4",
+            sf.links.get(0));
+    sf = sfs[3];
+    assertEquals(3, sf.getBegin());
+    assertEquals(3, sf.getEnd());
+    assertEquals("P->H", sf.getDescription());
+    assertEquals("var6", sf.getValue("ID"));
+    assertEquals("Good", sf.getValue("clinical_significance"));
+    assertEquals("ID=var6;clinical_significance=Good", sf.getAttributes());
+    assertEquals(1, sf.links.size());
+    assertEquals(
+            "P->H var6|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var6",
+            sf.links.get(0));
+    // var5 generates two distinct protein variant features
+    sf = sfs[4];
+    assertEquals(3, sf.getBegin());
+    assertEquals(3, sf.getEnd());
+    assertEquals("P->R", sf.getDescription());
+    assertEquals("var6", sf.getValue("ID"));
+    assertEquals("Good", sf.getValue("clinical_significance"));
+    assertEquals("ID=var6;clinical_significance=Good", sf.getAttributes());
+    assertEquals(1, sf.links.size());
+    assertEquals(
+            "P->R var6|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var6",
+            sf.links.get(0));
   }
 
   /**
index 2dc2ac3..029483f 100644 (file)
@@ -27,7 +27,7 @@ import jalview.gui.AlignFrame;
 import jalview.io.FileLoader;
 import jalview.io.FormatAdapter;
 
-import org.testng.AssertJUnit;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 public class FeatureScoreModelTest
@@ -40,14 +40,13 @@ public class FeatureScoreModelTest
 
   int[] sf3 = new int[] { -1, -1, -1, -1, -1, -1, 76, 77 };
 
-  @Test(groups = { "Functional" })
-  public void testFeatureScoreModel() throws Exception
+  public AlignFrame getTestAlignmentFrame()
   {
     AlignFrame alf = new FileLoader(false).LoadFileWaitTillLoaded(
             alntestFile, FormatAdapter.PASTE);
     AlignmentI al = alf.getViewport().getAlignment();
-    AssertJUnit.assertEquals(4, al.getHeight());
-    AssertJUnit.assertEquals(5, al.getWidth());
+    Assert.assertEquals(al.getHeight(), 4);
+    Assert.assertEquals(al.getWidth(), 5);
     for (int i = 0; i < 4; i++)
     {
       SequenceI ds = al.getSequenceAt(i).getDatasetSequence();
@@ -72,21 +71,68 @@ public class FeatureScoreModelTest
     alf.getFeatureRenderer().setVisible("sf2");
     alf.getFeatureRenderer().setVisible("sf3");
     alf.getFeatureRenderer().findAllFeatures(true);
-    AssertJUnit.assertEquals("Number of feature types", 3, alf
-            .getFeatureRenderer().getDisplayedFeatureTypes().size());
-    AssertJUnit.assertTrue(alf.getCurrentView().areFeaturesDisplayed());
+    Assert.assertEquals(alf.getFeatureRenderer().getDisplayedFeatureTypes()
+            .size(), 3, "Number of feature types");
+    Assert.assertTrue(alf.getCurrentView().areFeaturesDisplayed());
+    return alf;
+  }
+
+  @Test(groups = { "Functional" })
+  public void testFeatureScoreModel() throws Exception
+  {
+    AlignFrame alf = getTestAlignmentFrame();
     FeatureScoreModel fsm = new FeatureScoreModel();
-    AssertJUnit.assertTrue(fsm.configureFromAlignmentView(alf
+    Assert.assertTrue(fsm.configureFromAlignmentView(alf
             .getCurrentView().getAlignPanel()));
     alf.selectAllSequenceMenuItem_actionPerformed(null);
     float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
             true));
-    AssertJUnit.assertTrue("FER1_MESCR should be identical with RAPSA (2)",
-            dm[0][2] == 0f);
-    AssertJUnit
-            .assertTrue(
-                    "FER1_MESCR should be further from SPIOL (1) than it is from RAPSA (2)",
-                    dm[0][1] > dm[0][2]);
+    Assert.assertTrue(dm[0][2] == 0f,
+            "FER1_MESCR (0) should be identical with RAPSA (2)");
+    Assert.assertTrue(dm[0][1] > dm[0][2],
+            "FER1_MESCR (0) should be further from SPIOL (1) than it is from RAPSA (2)");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testFeatureScoreModel_hiddenFirstColumn() throws Exception
+  {
+    AlignFrame alf = getTestAlignmentFrame();
+    // hiding first two columns shouldn't affect the tree
+    alf.getViewport().hideColumns(0, 1);
+    FeatureScoreModel fsm = new FeatureScoreModel();
+    Assert.assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
+            .getAlignPanel()));
+    alf.selectAllSequenceMenuItem_actionPerformed(null);
+    float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
+            true));
+    Assert.assertTrue(dm[0][2] == 0f,
+            "FER1_MESCR (0) should be identical with RAPSA (2)");
+    Assert.assertTrue(dm[0][1] > dm[0][2],
+            "FER1_MESCR (0) should be further from SPIOL (1) than it is from RAPSA (2)");
+  }
 
+  @Test(groups = { "Functional" })
+  public void testFeatureScoreModel_HiddenColumns() throws Exception
+  {
+    AlignFrame alf = getTestAlignmentFrame();
+    // hide columns and check tree changes
+    alf.getViewport().hideColumns(3, 4);
+    alf.getViewport().hideColumns(0, 1);
+    FeatureScoreModel fsm = new FeatureScoreModel();
+    Assert.assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
+            .getAlignPanel()));
+    alf.selectAllSequenceMenuItem_actionPerformed(null);
+    float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
+            true));
+    Assert.assertTrue(dm[0][2] == 0f,
+            "After hiding last two columns FER1_MESCR (0) should still be identical with RAPSA (2)");
+    Assert.assertTrue(dm[0][1] == 0f,
+            "After hiding last two columns FER1_MESCR (0) should now also be identical with SPIOL (1)");
+    for (int s=0;s<3;s++)
+    {
+      Assert.assertTrue(dm[s][3] > 0f, "After hiding last two columns "
+              + alf.getViewport().getAlignment().getSequenceAt(s).getName()
+              + "(" + s + ") should still be distinct from FER1_MAIZE (3)");
+    }
   }
 }
index 705c773..8d3c878 100644 (file)
@@ -23,6 +23,8 @@ package jalview.datamodel;
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
 
+import jalview.util.Comparison;
+
 import org.testng.annotations.Test;
 
 /**
@@ -30,6 +32,29 @@ import org.testng.annotations.Test;
  */
 public class SeqCigarTest
 {
+  @Test(groups = { "Functional" })
+  public void testFindPosition()
+  {
+    SequenceI oseq = new Sequence("MySeq", "ASD---ASD---ASD", 37, 45);
+    oseq.createDatasetSequence();
+    SeqCigar cs = new SeqCigar(oseq);
+    assertEquals(oseq.getSequenceAsString(), cs.getSequenceString('-'));
+    for (int c = 0, cLen = oseq.getLength(); c < cLen; c++)
+    {
+      int os_p = oseq.findPosition(c);
+      int cigar_p = cs.findPosition(c);
+      if (Comparison.isGap(oseq.getCharAt(c)))
+      {
+        assertEquals("Expected gap at position " + os_p + " column " + c,
+                -1, cigar_p);
+      }
+      else
+      {
+        assertEquals("Positions don't match for at column " + c, os_p,
+                cigar_p);
+      }
+    }
+  }
   /*
    * refactored 'as is' from main method
    * 
index 6df479c..71f0212 100644 (file)
@@ -267,7 +267,8 @@ public class EnsemblSeqProxyTest
     sb = new StringBuilder();
     EnsemblSeqProxy.reverseComplementAllele(sb, "-GATt"); // revcomp=aATC-
     EnsemblSeqProxy.reverseComplementAllele(sb, "hgmd_mutation");
-    assertEquals("aATC-,hgmd_mutation", sb.toString());
+    EnsemblSeqProxy.reverseComplementAllele(sb, "PhenCode_variation");
+    assertEquals("aATC-,hgmd_mutation,PhenCode_variation", sb.toString());
   }
 
   /**
index 6da160d..385e049 100644 (file)
@@ -275,7 +275,8 @@ public class FeaturesFileTest
     sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
     assertEquals(1, sfs.length);
     sf = sfs[0];
-    assertEquals("uniprot", sf.description);
+    // ID used for description if available
+    assertEquals("$23", sf.description);
     assertEquals(55, sf.begin);
     assertEquals(130, sf.end);
     assertEquals("uniprot", sf.featureGroup);
diff --git a/test/jalview/io/SequenceAnnotationReportTest.java b/test/jalview/io/SequenceAnnotationReportTest.java
new file mode 100644 (file)
index 0000000..f551571
--- /dev/null
@@ -0,0 +1,166 @@
+package jalview.io;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+import jalview.datamodel.SequenceFeature;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+public class SequenceAnnotationReportTest
+{
+  @Test(groups = "Functional")
+  public void testAppendFeature_disulfideBond()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuffer sb = new StringBuffer();
+    sb.append("123456");
+    SequenceFeature sf = new SequenceFeature("disulfide bond", "desc", 1,
+            3, 1.2f, "group");
+
+    // residuePos == 2 does not match start or end of feature, nothing done:
+    sar.appendFeature(sb, 2, null, sf);
+    assertEquals("123456", sb.toString());
+
+    // residuePos == 1 matches start of feature, text appended (but no <br>)
+    // feature score is not included
+    sar.appendFeature(sb, 1, null, sf);
+    assertEquals("123456disulfide bond 1:3", sb.toString());
+
+    // residuePos == 3 matches end of feature, text appended
+    // <br> is prefixed once sb.length() > 6
+    sar.appendFeature(sb, 3, null, sf);
+    assertEquals("123456disulfide bond 1:3<br>disulfide bond 1:3",
+            sb.toString());
+  }
+
+  @Test(groups = "Functional")
+  public void testAppendFeature_status()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuffer sb = new StringBuffer();
+    SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3,
+            Float.NaN, "group");
+    sf.setStatus("Confirmed");
+  
+    sar.appendFeature(sb, 1, null, sf);
+    assertEquals("METAL 1 3; Fe2-S; (Confirmed)", sb.toString());
+  }
+
+  @Test(groups = "Functional")
+  public void testAppendFeature_withScore()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuffer sb = new StringBuffer();
+    SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3, 1.3f,
+            "group");
+
+    Map<String, float[][]> minmax = new Hashtable<String, float[][]>();
+    sar.appendFeature(sb, 1, minmax, sf);
+    /*
+     * map has no entry for this feature type - score is not shown:
+     */
+    assertEquals("METAL 1 3; Fe2-S", sb.toString());
+
+    /*
+     * map has entry for this feature type - score is shown:
+     */
+    minmax.put("METAL", new float[][] { { 0f, 1f }, null });
+    sar.appendFeature(sb, 1, minmax, sf);
+    // <br> is appended to a buffer > 6 in length
+    assertEquals("METAL 1 3; Fe2-S<br>METAL 1 3; Fe2-S Score=1.3",
+            sb.toString());
+
+    /*
+     * map has min == max for this feature type - score is not shown:
+     */
+    minmax.put("METAL", new float[][] { { 2f, 2f }, null });
+    sb.setLength(0);
+    sar.appendFeature(sb, 1, minmax, sf);
+    assertEquals("METAL 1 3; Fe2-S", sb.toString());
+  }
+
+  @Test(groups = "Functional")
+  public void testAppendFeature_noScore()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuffer sb = new StringBuffer();
+    SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3,
+            Float.NaN, "group");
+  
+    sar.appendFeature(sb, 1, null, sf);
+    assertEquals("METAL 1 3; Fe2-S", sb.toString());
+  }
+
+  @Test(groups = "Functional")
+  public void testAppendFeature_clinicalSignificance()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuffer sb = new StringBuffer();
+    SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3,
+            Float.NaN, "group");
+    sf.setValue("clinical_significance", "Benign");
+  
+    sar.appendFeature(sb, 1, null, sf);
+    assertEquals("METAL 1 3; Fe2-S; Benign", sb.toString());
+  }
+
+  @Test(groups = "Functional")
+  public void testAppendFeature_withScoreStatusClinicalSignificance()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuffer sb = new StringBuffer();
+    SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3, 1.3f,
+            "group");
+    sf.setStatus("Confirmed");
+    sf.setValue("clinical_significance", "Benign");
+    Map<String, float[][]> minmax = new Hashtable<String, float[][]>();
+    minmax.put("METAL", new float[][] { { 0f, 1f }, null });
+    sar.appendFeature(sb, 1, minmax, sf);
+
+    assertEquals("METAL 1 3; Fe2-S Score=1.3; (Confirmed); Benign",
+            sb.toString());
+  }
+
+  @Test(groups = "Functional")
+  public void testAppendFeature_DescEqualsType()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuffer sb = new StringBuffer();
+    SequenceFeature sf = new SequenceFeature("METAL", "METAL", 1, 3,
+            Float.NaN, "group");
+  
+    // description is not included if it duplicates type:
+    sar.appendFeature(sb, 1, null, sf);
+    assertEquals("METAL 1 3", sb.toString());
+
+    sb.setLength(0);
+    sf.setDescription("Metal");
+    // test is case-sensitive:
+    sar.appendFeature(sb, 1, null, sf);
+    assertEquals("METAL 1 3; Metal", sb.toString());
+  }
+
+  @Test(groups = "Functional")
+  public void testAppendFeature_stripHtml()
+  {
+    SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+    StringBuffer sb = new StringBuffer();
+    SequenceFeature sf = new SequenceFeature("METAL",
+            "<html><body>hello<em>world</em></body></html>", 1, 3,
+            Float.NaN, "group");
+  
+    sar.appendFeature(sb, 1, null, sf);
+    // !! strips off </body> but not <body> ??
+    assertEquals("METAL 1 3; <body>hello<em>world</em>",
+            sb.toString());
+
+    sb.setLength(0);
+    sf.setDescription("<br>&kHD>6");
+    sar.appendFeature(sb, 1, null, sf);
+    // if no <html> tag, html-encodes > and < (only):
+    assertEquals("METAL 1 3; &lt;br&gt;&kHD&gt;6", sb.toString());
+  }
+}
index 2b12a72..a82b9c0 100644 (file)
@@ -72,4 +72,65 @@ public class ColorUtilsTest
     assertEquals("#800080", ColorUtils.toTkCode(new Color(128, 0, 128))); // purple
     assertEquals("#00ff00", ColorUtils.toTkCode(new Color(0, 255, 0))); // lime
   }
+
+  @Test(groups = { "Functional" })
+  public void testGetGraduatedColour()
+  {
+    Color minColour = new Color(100, 100, 100);
+    Color maxColour = new Color(180, 200, 220);
+
+    /*
+     * value half-way between min and max
+     */
+    Color col = ColorUtils.getGraduatedColour(20f, 10f, minColour, 30f,
+            maxColour);
+    assertEquals(140, col.getRed());
+    assertEquals(150, col.getGreen());
+    assertEquals(160, col.getBlue());
+
+    /*
+     * value two-thirds of the way between min and max
+     */
+    col = ColorUtils
+            .getGraduatedColour(30f, 10f, minColour, 40f, maxColour);
+    assertEquals(153, col.getRed());
+    // Color constructor rounds float value to nearest int
+    assertEquals(167, col.getGreen());
+    assertEquals(180, col.getBlue());
+
+    /*
+     * value = min
+     */
+    col = ColorUtils
+            .getGraduatedColour(10f, 10f, minColour, 30f, maxColour);
+    assertEquals(minColour, col);
+
+    /*
+     * value = max
+     */
+    col = ColorUtils
+            .getGraduatedColour(30f, 10f, minColour, 30f, maxColour);
+    assertEquals(maxColour, col);
+
+    /*
+     * value < min
+     */
+    col = ColorUtils.getGraduatedColour(0f, 10f, minColour, 30f, maxColour);
+    assertEquals(minColour, col);
+
+    /*
+     * value > max
+     */
+    col = ColorUtils
+            .getGraduatedColour(40f, 10f, minColour, 30f,
+            maxColour);
+    assertEquals(maxColour, col);
+
+    /*
+     * min = max
+     */
+    col = ColorUtils
+            .getGraduatedColour(40f, 10f, minColour, 10f, maxColour);
+    assertEquals(minColour, col);
+  }
 }
index 0c2c998..9aab66c 100644 (file)
@@ -49,7 +49,7 @@ public class ComparisonTest
    * AGCTU. Test is not case-sensitive and ignores gaps.
    */
   @Test(groups = { "Functional" })
-  public void testIsNucleotide()
+  public void testIsNucleotide_sequences()
   {
     SequenceI seq = new Sequence("eightypercent", "agctuAGCPV");
     assertFalse(Comparison.isNucleotide(new SequenceI[] { seq }));
@@ -130,6 +130,23 @@ public class ComparisonTest
             0.001f);
   }
 
+  @Test(groups = { "Functional" })
+  public void testIsNucleotide()
+  {
+    assertTrue(Comparison.isNucleotide('a'));
+    assertTrue(Comparison.isNucleotide('A'));
+    assertTrue(Comparison.isNucleotide('c'));
+    assertTrue(Comparison.isNucleotide('C'));
+    assertTrue(Comparison.isNucleotide('g'));
+    assertTrue(Comparison.isNucleotide('G'));
+    assertTrue(Comparison.isNucleotide('t'));
+    assertTrue(Comparison.isNucleotide('T'));
+    assertTrue(Comparison.isNucleotide('u'));
+    assertTrue(Comparison.isNucleotide('U'));
+    assertFalse(Comparison.isNucleotide('-'));
+    assertFalse(Comparison.isNucleotide('P'));
+  }
+
   /**
    * Test the percentage identity calculation for two sequences
    */
@@ -158,4 +175,17 @@ public class ComparisonTest
     assertEquals(87.5f, Comparison.PID(seq1, seq2, 0, length, false, true),
             0.001f);
   }
+
+  @Test(groups = { "Functional" })
+  public void testIsNucleotideSequence()
+  {
+    assertFalse(Comparison.isNucleotideSequence(null, true));
+    assertTrue(Comparison.isNucleotideSequence("", true));
+    assertTrue(Comparison.isNucleotideSequence("aAgGcCtTuU", true));
+    assertTrue(Comparison.isNucleotideSequence("aAgGcCtTuU", false));
+    assertFalse(Comparison.isNucleotideSequence("xAgGcCtTuU", false));
+    assertFalse(Comparison.isNucleotideSequence("aAgGcCtTuUx", false));
+    assertTrue(Comparison.isNucleotideSequence("a A-g.GcCtTuU", true));
+    assertFalse(Comparison.isNucleotideSequence("a A-g.GcCtTuU", false));
+  }
 }
index 83d1a98..428b998 100755 (executable)
@@ -2025,7 +2025,7 @@ and any path to a file to save to the file]]></string>
                                                                <string><![CDATA[664]]></string>
                                                        </property>
                                                        <property name="sourceName">
-                                                               <string><![CDATA[groovy-all-1.8.2.jar]]></string>
+                                                               <string><![CDATA[groovy-all-2.4.6-indy.jar]]></string>
                                                        </property>
                                                        <property name="overrideUnixPermissions">
                                                                <boolean>false</boolean>
@@ -2043,7 +2043,7 @@ and any path to a file to save to the file]]></string>
                                                                <boolean>true</boolean>
                                                        </property>
                                                        <property name="destinationName">
-                                                               <string><![CDATA[groovy-all-1.8.2.jar]]></string>
+                                                               <string><![CDATA[groovy-all-2.4.6-indy.jar]]></string>
                                                        </property>
                                                        <property name="fileSize">
                                                                <long>6149494</long>