Merge branch 'develop' into Jalview-JS/develop
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 8 Oct 2019 09:49:13 +0000 (10:49 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 8 Oct 2019 09:49:13 +0000 (10:49 +0100)
Conflicts:
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/CrossRef.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/PopupMenu.java

34 files changed:
1  2 
.classpath
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/CrossRef.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/SeqPanel.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/features/FeatureAttributes.java
src/jalview/ext/ensembl/EnsemblSeqProxy.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationExporter.java
src/jalview/gui/CrossRefAction.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/FeatureTypeSettings.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/IdPanel.java
src/jalview/gui/OverviewCanvas.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/io/FeaturesFile.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/vcf/VCFLoader.java
src/jalview/jbgui/GPreferences.java
src/jalview/project/Jalview2XML.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
test/jalview/analysis/AlignmentUtilsTests.java
test/jalview/gui/PopupMenuTest.java
test/jalview/io/vcf/VCFLoaderTest.java
test/jalview/project/Jalview2xmlTests.java

diff --cc .classpath
index 0a7e4b3,0000000..a85d514
mode 100644,000000..100644
--- /dev/null
@@@ -1,206 -1,0 +1,23 @@@
 +<?xml version="1.0" encoding="UTF-8"?>
 +<classpath>
 +      <classpathentry kind="src" output="bin/main" path="src">
 +              <attributes>
 +                      <attribute name="gradle_scope" value="main"/>
 +                      <attribute name="gradle_used_by_scope" value=""/>
 +              </attributes>
 +      </classpathentry>
 +      <classpathentry kind="src" output="bin/test" path="test">
 +              <attributes>
 +                      <attribute name="test" value="true"/>
 +                      <attribute name="gradle_scope" value="test"/>
 +                      <attribute name="gradle_used_by_scope" value=""/>
 +              </attributes>
 +      </classpathentry>
 +      <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
 +              <attributes>
 +                      <attribute name="module" value="true"/>
 +              </attributes>
 +      </classpathentry>
 +      <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
-       <classpathentry kind="lib" path="help"/>
-       <classpathentry kind="lib" path="resources"/>
-       <classpathentry kind="lib" path="j11lib/apache-mime4j-0.6.jar"/>
-       <classpathentry kind="lib" path="j11lib/axis.jar"/>
-       <classpathentry kind="lib" path="j11lib/biojava-core-4.1.0.jar"/>
-       <classpathentry kind="lib" path="j11lib/biojava-ontology-4.1.0.jar"/>
-       <classpathentry kind="lib" path="j11lib/commons-codec-1.3.jar"/>
-       <classpathentry kind="lib" path="j11lib/commons-compress-1.18.jar"/>
-       <classpathentry kind="lib" path="j11lib/commons-discovery.jar"/>
-       <classpathentry kind="lib" path="j11lib/commons-logging-1.1.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/FastInfoset.jar"/>
-       <classpathentry kind="lib" path="j11lib/getdown-core.jar"/>
-       <classpathentry kind="lib" path="j11lib/gmbal-api-only-MODULE.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-ant-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-bsf-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-cli-commons-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-cli-picocli-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-console-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-datetime-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-dateutil-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-docgenerator-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-groovydoc-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-groovysh-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-jaxb-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-jmx-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-json-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-json-direct-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-jsr223-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-macro-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-nio-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-servlet-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-sql-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-swing-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-templates-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-test-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-test-junit5-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-testng-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/groovy-xml-2.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/htsjdk-2.12.0.jar"/>
-       <classpathentry kind="lib" path="j11lib/httpclient-4.0.3.jar"/>
-       <classpathentry kind="lib" path="j11lib/httpcore-4.0.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/httpmime-4.0.3.jar"/>
-       <classpathentry kind="lib" path="j11lib/intervalstore-v1.0.jar"/>
-       <classpathentry kind="lib" path="j11lib/istack-commons-runtime.jar"/>
-       <classpathentry kind="lib" path="j11lib/jabaws-min-client-2.2.0.jar"/>
-       <classpathentry kind="lib" path="j11lib/java-json.jar"/>
-       <classpathentry kind="lib" path="j11lib/javax.activation-api-1.2.0.jar"/>
-       <classpathentry kind="lib" path="j11lib/javax.annotation-api-1.3.2.jar"/>
-       <classpathentry kind="lib" path="j11lib/javax.jws-api-1.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/javax.servlet-api-4.0.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/javax.xml.rpc-api-1.1.2.jar"/>
-       <classpathentry kind="lib" path="j11lib/javax.xml.soap-api-1.4.0.jar"/>
-       <classpathentry kind="lib" path="j11lib/jaxb-api-2.3.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/jaxb-runtime-2.3.2.jar"/>
-       <classpathentry kind="lib" path="j11lib/jaxws-api-2.3.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/jaxws-rt-java9.jar"/>
-       <classpathentry kind="lib" path="j11lib/jersey-client-1.19.4.jar"/>
-       <classpathentry kind="lib" path="j11lib/jersey-core-1.19.4.jar"/>
-       <classpathentry kind="lib" path="j11lib/jersey-json-1.19.4.jar"/>
-       <classpathentry kind="lib" path="j11lib/jetty-http-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="j11lib/jetty-io-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="j11lib/jetty-server-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="j11lib/jetty-util-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="j11lib/jfreesvg-2.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/JGoogleAnalytics_0.3.jar"/>
-       <classpathentry kind="lib" path="j11lib/jhall.jar"/>
-       <classpathentry kind="lib" path="j11lib/Jmol-14.29.17.jar"/>
-       <classpathentry kind="lib" path="j11lib/json_simple-1.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/jsoup-1.8.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/jsr311-api-1.1.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/jswingreader-0.3.jar"/>
-       <classpathentry kind="lib" path="j11lib/libquaqua-8.0.jnilib.jar"/>
-       <classpathentry kind="lib" path="j11lib/libquaqua64-8.0.jnilib.jar"/>
-       <classpathentry kind="lib" path="j11lib/log4j-to-slf4j-2.0-rc2.jar"/>
-       <classpathentry kind="lib" path="j11lib/mail-MODULE.jar"/>
-       <classpathentry kind="lib" path="j11lib/miglayout-4.0-swing.jar"/>
-       <classpathentry kind="lib" path="j11lib/mimepull-1.9.11.jar"/>
-       <classpathentry kind="lib" path="j11lib/policy-2.7.6.jar"/>
-       <classpathentry kind="lib" path="j11lib/quaqua-filechooser-only-8.0.jar"/>
-       <classpathentry kind="lib" path="j11lib/regex.jar"/>
-       <classpathentry kind="lib" path="j11lib/saaj-impl.jar"/>
-       <classpathentry kind="lib" path="j11lib/slf4j-api-1.7.26.jar"/>
-       <classpathentry kind="lib" path="j11lib/slf4j-log4j12-1.7.26.jar"/>
-       <classpathentry kind="lib" path="j11lib/stax-ex-1.8.1.jar"/>
-       <classpathentry kind="lib" path="j11lib/stax2-api-4.2.jar"/>
-       <classpathentry kind="lib" path="j11lib/streambuffer-1.5.7.jar"/>
-       <classpathentry kind="lib" path="j11lib/txw2-2.3.2.jar"/>
-       <classpathentry kind="lib" path="j11lib/vamsas-client.jar"/>
-       <classpathentry kind="lib" path="j11lib/VAqua5-patch.jar"/>
-       <classpathentry kind="lib" path="j11lib/VARNAv3-93.jar"/>
-       <classpathentry kind="lib" path="j11lib/wsdl4j-1.6.3.jar"/>
-       <classpathentry kind="lib" path="j11lib/xercesImpl.jar"/>
-       <classpathentry kind="lib" path="utils/ant-contrib-1.0b3.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/axis-ant.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/classgraph-4.1.6.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/hamcrest-core-1.3.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/jhall.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/jhindexer.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/junit-4.12.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/proguard_5.3.3.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/roxes-ant-tasks-1.2-2004-01-30.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/testnglibs/bsh-2.0b4.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/testnglibs/guava-base-r03.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/testnglibs/guava-collections-r03.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/testnglibs/jcommander.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/testnglibs/junit-4.12.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/testnglibs/snakeyaml.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/testnglibs/testng-sources.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/testnglibs/testng.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="utils/wsdl4j.jar">
-               <attributes>
-                       <attribute name="test" value="true"/>
-               </attributes>
-       </classpathentry>
 +      <classpathentry kind="output" path="bin/main"/>
 +</classpath>
Simple merge
Simple merge
@@@ -20,8 -20,9 +20,7 @@@
   */
  package jalview.analysis;
  
--import static jalview.io.gff.GffConstants.CLINICAL_SIGNIFICANCE;
--
+ import jalview.commands.RemoveGapColCommand;
  import jalview.datamodel.AlignedCodon;
  import jalview.datamodel.AlignedCodonFrame;
  import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
@@@ -37,7 -38,7 +36,6 @@@ import jalview.datamodel.SequenceFeatur
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
  import jalview.datamodel.features.SequenceFeatures;
--import jalview.io.gff.Gff3Helper;
  import jalview.io.gff.SequenceOntologyI;
  import jalview.schemes.ResidueProperties;
  import jalview.util.Comparison;
@@@ -45,10 -46,10 +43,7 @@@ import jalview.util.DBRefUtils
  import jalview.util.IntRangeComparator;
  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;
@@@ -1602,12 -1603,11 +1597,12 @@@ public class AlignmentUtil
        return false;
      }
      String name = seq2.getName();
 -    final DBRefEntry[] xrefs = seq1.getDBRefs();
 +    final List<DBRefEntry> xrefs = seq1.getDBRefs();
      if (xrefs != null)
      {
 -      for (DBRefEntry xref : xrefs)
 +      for (int ix = 0, nx = xrefs.size(); ix < nx; ix++)
        {
-         DBRefEntry xref = xrefs.get(ix);
++        DBRefEntry xref = xrefs.get(ix);
          String xrefName = xref.getSource() + "|" + xref.getAccessionId();
          // case-insensitive test, consistent with DBRefEntry.equalRef()
          if (xrefName.equalsIgnoreCase(name))
      List<DBRefEntry> direct = new ArrayList<>();
      HashSet<String> directSources = new HashSet<>();
  
 -    if (contig.getDBRefs() != null)
 +    List<DBRefEntry> refs = contig.getDBRefs();
 +    if (refs != null)
      {
 -      for (DBRefEntry dbr : contig.getDBRefs())
 +      for (int ib = 0, nb = refs.size(); ib < nb; ib++)
        {
-         DBRefEntry dbr = refs.get(ib);
-         MapList map;
 -        if (dbr.hasMap() && dbr.getMap().getMap().isTripletMap())
++        DBRefEntry dbr = refs.get(ib);
++        MapList map;
 +        if (dbr.hasMap() && (map = dbr.getMap().getMap()).isTripletMap())
          {
 -          MapList map = dbr.getMap().getMap();
            // check if map is the CDS mapping
            if (mapping.getMap().equals(map))
            {
      List<DBRefEntry> propagated = new ArrayList<>();
  
      // and generate appropriate mappings
 -    for (DBRefEntry cdsref : direct)
 +    for (int ic = 0, nc = direct.size(); ic < nc; ic++)
      {
-       DBRefEntry cdsref = direct.get(ic);
-       Mapping m = cdsref.getMap();
++      DBRefEntry cdsref = direct.get(ic);
++      Mapping m = cdsref.getMap();
        // clone maplist and mapping
        MapList cdsposmap = new MapList(
                Arrays.asList(new int[][]
                { new int[] { cdsSeq.getStart(), cdsSeq.getEnd() } }),
 -              cdsref.getMap().getMap().getToRanges(), 3, 1);
 -      Mapping cdsmap = new Mapping(cdsref.getMap().getTo(),
 -              cdsref.getMap().getMap());
 +              m.getMap().getToRanges(), 3, 1);
-       Mapping cdsmap = new Mapping(m.getTo(),m.getMap());
++      Mapping cdsmap = new Mapping(m.getTo(), m.getMap());
  
        // create dbref
        DBRefEntry newref = new DBRefEntry(cdsref.getSource(),
        int phase = 0;
        try
        {
 -        phase = Integer.parseInt(sf.getPhase());
 +      String s = sf.getPhase();
 +      if (s != null) 
 +      {
 +              phase = Integer.parseInt(s);
 +      }
        } catch (NumberFormatException e)
        {
-         // SwingJS -- need to avoid these.
 -        // ignore
++        // leave as zero
        }
        /*
         * phase > 0 on first codon means 5' incomplete - skip to the start
    }
  
    /**
-    * Maps exon features from dna to protein, and computes variants in peptide
-    * product generated by variants in dna, and adds them as sequence_variant
-    * features on the protein sequence. Returns the number of variant features
-    * added.
-    * 
-    * @param dnaSeq
-    * @param peptide
-    * @param dnaToProtein
-    */
-   public static int computeProteinFeatures(SequenceI dnaSeq,
-           SequenceI peptide, MapList dnaToProtein)
-   {
-     while (dnaSeq.getDatasetSequence() != null)
-     {
-       dnaSeq = dnaSeq.getDatasetSequence();
-     }
-     while (peptide.getDatasetSequence() != null)
-     {
-       peptide = peptide.getDatasetSequence();
-     }
-     transferFeatures(dnaSeq, peptide, dnaToProtein, SequenceOntologyI.EXON);
-     /*
-      * compute protein variants from dna variants and codon mappings;
-      * NB - alternatively we could retrieve this using the REST service e.g.
-      * http://rest.ensembl.org/overlap/translation
-      * /ENSP00000288602?feature=transcript_variation;content-type=text/xml
-      * which would be a bit slower but possibly more reliable
-      */
-     /*
-      * 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<DnaVariant>[]> variant : variants.entrySet())
-     {
-       int peptidePos = variant.getKey();
-       List<DnaVariant>[] codonVariants = variant.getValue();
-       count += computePeptideVariants(peptide, peptidePos, codonVariants);
-     }
-     return count;
-   }
-   /**
-    * 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 dnavar : codonVariants[0])
-     {
-       if (dnavar.variant != null)
-       {
-         String alleles = (String) dnavar.variant.getValue(Gff3Helper.ALLELES);
-         if (alleles != null)
-         {
-           for (String base : alleles.split(","))
-           {
-             if (!base1.equalsIgnoreCase(base))
-             {
-               String codon = base.toUpperCase() + base2.toLowerCase()
-                       + base3.toLowerCase();
-               String canonical = base1.toUpperCase() + base2.toLowerCase()
-                       + base3.toLowerCase();
-               if (addPeptideVariant(peptide, peptidePos, residue, dnavar,
-                       codon, canonical))
-               {
-                 count++;
-               }
-             }
-           }
-         }
-       }
-     }
-     /*
-      * variants in second codon base
-      */
-     for (DnaVariant var : codonVariants[1])
-     {
-       if (var.variant != null)
-       {
-         String alleles = (String) var.variant.getValue(Gff3Helper.ALLELES);
-         if (alleles != null)
-         {
-           for (String base : alleles.split(","))
-           {
-             if (!base2.equalsIgnoreCase(base))
-             {
-               String codon = base1.toLowerCase() + base.toUpperCase()
-                       + base3.toLowerCase();
-               String canonical = base1.toLowerCase() + base2.toUpperCase()
-                       + base3.toLowerCase();
-               if (addPeptideVariant(peptide, peptidePos, residue, var,
-                       codon, canonical))
-               {
-                 count++;
-               }
-             }
-           }
-         }
-       }
-     }
-     /*
-      * variants in third codon base
-      */
-     for (DnaVariant var : codonVariants[2])
-     {
-       if (var.variant != null)
-       {
-         String alleles = (String) var.variant.getValue(Gff3Helper.ALLELES);
-         if (alleles != null)
-         {
-           for (String base : alleles.split(","))
-           {
-             if (!base3.equalsIgnoreCase(base))
-             {
-               String codon = base1.toLowerCase() + base2.toLowerCase()
-                       + base.toUpperCase();
-               String canonical = base1.toLowerCase() + base2.toLowerCase()
-                       + base3.toUpperCase();
-               if (addPeptideVariant(peptide, peptidePos, residue, var,
-                       codon, canonical))
-               {
-                 count++;
-               }
-             }
-           }
-         }
-       }
-     }
-     return count;
-   }
-   /**
--   * Helper method that adds a peptide variant feature. 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
--   *          the variant codon e.g. aCg
--   * @param canonical
--   *          the 'normal' codon e.g. aTg
--   * @return true if a feature was added, else false
--   */
--  static boolean addPeptideVariant(SequenceI peptide, int peptidePos,
--          String residue, DnaVariant var, String codon, String canonical)
--  {
--    /*
--     * 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("-") ? null
--            : (codon.length() > CODON_LENGTH ? null
--                    : ResidueProperties.codonTranslate(codon));
--    if (trans == null)
--    {
--      return false;
--    }
--    String desc = canonical + "/" + codon;
--    String featureType = "";
--    if (trans.equals(residue))
--    {
--      featureType = SequenceOntologyI.SYNONYMOUS_VARIANT;
--    }
--    else if (ResidueProperties.STOP.equals(trans))
--    {
--      featureType = SequenceOntologyI.STOP_GAINED;
--    }
--    else
--    {
--      String residue3Char = StringUtils
--              .toSentenceCase(ResidueProperties.aa2Triplet.get(residue));
--      String trans3Char = StringUtils
--              .toSentenceCase(ResidueProperties.aa2Triplet.get(trans));
--      desc = "p." + residue3Char + peptidePos + trans3Char;
--      featureType = SequenceOntologyI.NONSYNONYMOUS_VARIANT;
--    }
--    SequenceFeature sf = new SequenceFeature(featureType, desc, peptidePos,
--            peptidePos, var.getSource());
--
--    StringBuilder attributes = new StringBuilder(32);
--    String id = (String) var.variant.getValue(VARIANT_ID);
--    if (id != null)
--    {
--      if (id.startsWith(SEQUENCE_VARIANT))
--      {
--        id = id.substring(SEQUENCE_VARIANT.length());
--      }
--      sf.setValue(VARIANT_ID, id);
--      attributes.append(VARIANT_ID).append("=").append(id);
--      // TODO handle other species variants JAL-2064
--      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;
--  }
--
--  /**
-    * 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.
-    * <p>
-    * This depends on dna variants being held as a comma-separated list as
-    * property "alleles" on variant features.
-    * 
-    * @param dnaSeq
-    * @param dnaToProtein
-    * @return
-    */
-   @SuppressWarnings("unchecked")
-   static LinkedHashMap<Integer, List<DnaVariant>[]> buildDnaVariantsMap(
-           SequenceI dnaSeq, MapList dnaToProtein)
-   {
-     /*
-      * 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<DnaVariant>[]> variants = new LinkedHashMap<>();
-     List<SequenceFeature> dnaFeatures = dnaSeq.getFeatures()
-             .getFeaturesByOntology(SequenceOntologyI.SEQUENCE_VARIANT);
-     if (dnaFeatures.isEmpty())
-     {
-       return variants;
-     }
-     int dnaStart = dnaSeq.getStart();
-     int[] lastCodon = null;
-     int lastPeptidePostion = 0;
-     /*
-      * build a map of codon variations for peptides
-      */
-     for (SequenceFeature sf : dnaFeatures)
-     {
-       int dnaCol = sf.getBegin();
-       if (dnaCol != sf.getEnd())
-       {
-         // not handling multi-locus variant features
-         continue;
-       }
-       /*
-        * ignore variant if not a SNP
-        */
-       String alls = (String) sf.getValue(Gff3Helper.ALLELES);
-       if (alls == null)
-       {
-         continue; // non-SNP VCF variant perhaps - can't process this
-       }
-       String[] alleles = alls.toUpperCase().split(",");
-       boolean isSnp = true;
-       for (String allele : alleles)
-       {
-         if (allele.trim().length() > 1)
-         {
-           isSnp = false;
-         }
-       }
-       if (!isSnp)
-       {
-         continue;
-       }
-       int[] mapsTo = dnaToProtein.locateInTo(dnaCol, dnaCol);
-       if (mapsTo == null)
-       {
-         // feature doesn't lie within coding region
-         continue;
-       }
-       int peptidePosition = mapsTo[0];
-       List<DnaVariant>[] codonVariants = variants.get(peptidePosition);
-       if (codonVariants == null)
-       {
-         codonVariants = new ArrayList[CODON_LENGTH];
-         codonVariants[0] = new ArrayList<>();
-         codonVariants[1] = new ArrayList<>();
-         codonVariants[2] = new ArrayList<>();
-         variants.put(peptidePosition, codonVariants);
-       }
-       /*
-        * get this peptide's codon positions e.g. [3, 4, 5] or [4, 7, 10]
-        */
-       int[] codon = peptidePosition == lastPeptidePostion ? lastCodon
-               : MappingUtils.flattenRanges(dnaToProtein.locateInFrom(
-                       peptidePosition, peptidePosition));
-       lastPeptidePostion = peptidePosition;
-       lastCodon = codon;
-       /*
-        * save nucleotide (and any variant) for each codon position
-        */
-       for (int codonPos = 0; codonPos < CODON_LENGTH; codonPos++)
-       {
-         String nucleotide = String.valueOf(
-                 dnaSeq.getCharAt(codon[codonPos] - dnaStart)).toUpperCase();
-         List<DnaVariant> codonVariant = codonVariants[codonPos];
-         if (codon[codonPos] == dnaCol)
-         {
-           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));
-           }
-         }
-         else if (codonVariant.isEmpty())
-         {
-           /*
-            * record (possibly non-varying) base value
-            */
-           codonVariant.add(new DnaVariant(nucleotide));
-         }
-       }
-     }
-     return variants;
-   }
-   /**
     * 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.
      SequenceIdMatcher matcher = new SequenceIdMatcher(seqs);
      if (xrefs != null)
      {
-       // BH 2019.01.25 streamlined this triply nested loop to remove all iterators
 -      for (SequenceI xref : xrefs)
++      // BH 2019.01.25 recoded to remove iterators
 +      
 +      for (int ix = 0, nx = xrefs.length; ix < nx; ix++)
        {
 -        DBRefEntry[] dbrefs = xref.getDBRefs();
 +      SequenceI xref = xrefs[ix];
 +        List<DBRefEntry> dbrefs = xref.getDBRefs();
          if (dbrefs != null)
          {
 -          for (DBRefEntry dbref : dbrefs)
 +          for (int ir = 0, nir = dbrefs.size(); ir < nir; ir++)
            {
-                 DBRefEntry dbref = dbrefs.get(ir);
-                 Mapping map = dbref.getMap();
-                 SequenceI mto;
 -            if (dbref.getMap() == null || dbref.getMap().getTo() == null
 -                    || dbref.getMap().getTo().isProtein() != isProtein)
++            DBRefEntry dbref = dbrefs.get(ir);
++            Mapping map = dbref.getMap();
++            SequenceI mto;
 +            if (map == null || (mto = map.getTo()) == null
 +                    || mto.isProtein() != isProtein)
              {
                continue;
              }
@@@ -483,11 -483,9 +483,11 @@@ public class CrossRe
    private void removeAlreadyRetrievedSeqs(List<DBRefEntry> sourceRefs,
            boolean fromDna)
    {
-     List<DBRefEntry> dbrSourceSet = new ArrayList<DBRefEntry>(sourceRefs);
 -    DBRefEntry[] dbrSourceSet = sourceRefs.toArray(new DBRefEntry[0]);
 -    for (SequenceI sq : dataset.getSequences())
++    List<DBRefEntry> dbrSourceSet = new ArrayList<>(sourceRefs);
 +    List<SequenceI> dsSeqs = dataset.getSequences();
 +    for (int ids = 0, nds = dsSeqs.size(); ids < nds; ids++)
      {
 +      SequenceI sq = dsSeqs.get(ids);
        boolean dupeFound = false;
        // !fromDna means we are looking only for nucleotide sequences, not
        // protein
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -143,9 -145,11 +147,11 @@@ public class FeatureSettings extends JP
     */
    Object[][] originalData;
  
 -  private float originalTransparency;
 +  float originalTransparency;
  
-   Map<String, FeatureMatcherSetI> originalFilters;
+   private ViewStyleI originalViewStyle;
+   private Map<String, FeatureMatcherSetI> originalFilters;
  
    final JInternalFrame frame;
  
          if (evt.isPopupTrigger())
          {
            Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
 -          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
 -                  evt.getY());
 +          showPopupMenu(selectedRow, type, colour, evt.getPoint());
          }
-         else if (evt.getClickCount() == 2)
+         else if (evt.getClickCount() == 2
+                 && table.columnAtPoint(pt) == TYPE_COLUMN)
          {
            boolean invertSelection = evt.isAltDown();
            boolean toggleSelection = Platform.isControlDown(evt);
                        javax.swing.event.InternalFrameEvent evt)
                {
                  fr.removePropertyChangeListener(change);
--              };
++              }
              });
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
      inConstruction = false;
@@@ -892,9 -876,9 +893,9 @@@ public class FeatureTypeSettings extend
       * save the colour, and repaint stuff
       */
      fr.setColour(featureType, acg);
-     ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
+     refreshDisplay(updateStructsAndOverview);
  
 -    updateColoursTab();
 +    updateColoursPanel();
    }
  
    /**
       * (note this might now be an empty filter with no conditions)
       */
      fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined);
-     ap.paintAlignment(true, true);
+     refreshDisplay(true);
  
 -    updateFiltersTab();
 +    updateFiltersPanel();
    }
+   /**
+    * Repaints alignment, structure and overview (if shown). If there is a
+    * complementary view which is showing this view's features, then also
+    * repaints that.
+    * 
+    * @param updateStructsAndOverview
+    */
+   void refreshDisplay(boolean updateStructsAndOverview)
+   {
+     ap.paintAlignment(true, updateStructsAndOverview);
+     AlignViewportI complement = ap.getAlignViewport().getCodingComplement();
+     if (complement != null && complement.isShowComplementFeatures())
+     {
+       AlignFrame af2 = Desktop.getAlignFrameFor(complement);
+       af2.alignPanel.paintAlignment(true, updateStructsAndOverview);
+     }
+   }
  }
Simple merge
Simple merge
Simple merge
@@@ -51,11 -52,10 +53,12 @@@ import jalview.util.Comparison
  import jalview.util.GroupUrlLink;
  import jalview.util.GroupUrlLink.UrlStringTooLongException;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  import jalview.util.StringUtils;
  import jalview.util.UrlLink;
+ import jalview.viewmodel.seqfeatures.FeatureRendererModel;
  
 +import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
@@@ -786,42 -863,19 +869,44 @@@ public class PopupMenu extends JPopupMe
    /**
     * Opens a panel showing a text report of feature dteails
     * 
+    * @param seqName
+    * 
     * @param sf
     */
-   protected void showFeatureDetails(SequenceFeature sf)
+   protected void showFeatureDetails(String seqName, SequenceFeature sf)
    {
 -    CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
 -    // it appears Java's CSS does not support border-collapse :-(
 -    cap.addStylesheetRule("table { border-collapse: collapse;}");
 -    cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
 -    cap.setText(sf.getDetailsReport(seqName));
 -
 -    Desktop.addInternalFrame(cap,
 +    JInternalFrame details;
 +    if (Platform.isJS())
 +    {
 +      details = new JInternalFrame();
 +      JPanel panel = new JPanel(new BorderLayout());
 +      panel.setOpaque(true);
 +      panel.setBackground(Color.white);
 +      // TODO JAL-3026 set style of table correctly for feature details
 +      JLabel reprt = new JLabel(MessageManager
 +              .formatMessage("label.html_content", new Object[]
-               { sf.getDetailsReport() }));
++              { sf.getDetailsReport(seqName) }));
 +      reprt.setBackground(Color.WHITE);
 +      reprt.setOpaque(true);
 +      panel.add(reprt, BorderLayout.CENTER);
 +      details.setContentPane(panel);
 +      details.pack();
 +    }
 +    else
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +    {
 +      CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
 +      // it appears Java's CSS does not support border-collaps :-(
 +      cap.addStylesheetRule("table { border-collapse: collapse;}");
 +      cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
-       cap.setText(sf.getDetailsReport());
++      cap.setText(sf.getDetailsReport(seqName));
 +      details = cap;
 +    }
 +    Desktop.addInternalFrame(details,
              MessageManager.getString("label.feature_details"), 500, 500);
    }
  
          Cache.log.error("Exception for GroupURLLink '" + link + "'", foo);
          continue;
        }
--      ;
        if (!urlLink.isValid())
        {
          Cache.log.error(urlLink.getInvalidMessage());
      createSequenceDetailsReport(ap.av.getSequenceSelection());
    }
  
-   protected void sequenceDetails_actionPerformed()
-   {
-     createSequenceDetailsReport(new SequenceI[] { sequence });
-   }
    public void createSequenceDetailsReport(SequenceI[] sequences)
    {
 -    CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
      StringBuilder contents = new StringBuilder(128);
 +    contents.append("<html><body>");
      for (SequenceI seq : sequences)
      {
        contents.append("<p><h2>" + MessageManager.formatMessage(
     */
    protected void outline_actionPerformed()
    {
 -    SequenceGroup sg = getGroup();
 -    Color col = JColorChooser.showDialog(this,
 -            MessageManager.getString("label.select_outline_colour"),
 -            Color.BLUE);
 -
 -    if (col != null)
 +    String title = MessageManager
 +            .getString("label.select_outline_colour");
 +    ColourChooserListener listener = new ColourChooserListener()
      {
 -      sg.setOutlineColour(col);
 -    }
 -
 -    refresh();
 +      @Override
 +      public void colourSelected(Color c)
 +      {
 +        getGroup().setOutlineColour(c);
 +        refresh();
-       };
++      }
 +    };
 +    JalviewColourChooser.showColourChooser(Desktop.getDesktop(),
 +            title, Color.BLUE, listener);
    }
  
    /**
      }
    }
  
-   public void colourByStructure(String pdbid)
-   {
-     Annotation[] anots = ap.av.getStructureSelectionManager()
-             .colourSequenceFromStructure(sequence, pdbid);
-     AlignmentAnnotation an = new AlignmentAnnotation("Structure",
-             "Coloured by " + pdbid, anots);
-     ap.av.getAlignment().addAnnotation(an);
-     an.createSequenceMapping(sequence, 0, true);
-     // an.adjustForAlignment();
-     ap.av.getAlignment().setAnnotationIndex(an, 0);
-     ap.adjustAnnotationHeight();
-     sequence.addAlignmentAnnotation(an);
-   }
 -  public void editSequence_actionPerformed(ActionEvent actionEvent)
 +  /**
 +   * Shows a dialog where sequence characters may be edited. Any changes are
 +   * applied, and added as an available 'Undo' item in the edit commands
 +   * history.
 +   */
 +  public void editSequence_actionPerformed()
    {
      SequenceGroup sg = ap.av.getSelectionGroup();
  
        }
  
        EditNameDialog dialog = new EditNameDialog(
-               sequence.getSequenceAsString(sg.getStartRes(),
-                       sg.getEndRes() + 1),
 -              seq.getSequenceAsString(sg.getStartRes(),
 -                      sg.getEndRes() + 1),
 -              null, MessageManager.getString("label.edit_sequence"), null,
++              seq.getSequenceAsString(sg.getStartRes(), sg.getEndRes() + 1),
 +              null, MessageManager.getString("label.edit_sequence"), null);
 +      dialog.showDialog(ap.alignFrame,
                MessageManager.getString("label.edit_sequence"),
 -              ap.alignFrame);
 -
 -      if (dialog.accept)
 -      {
 -        EditCommand editCommand = new EditCommand(
 -                MessageManager.getString("label.edit_sequences"),
 -                Action.REPLACE,
 -                dialog.getName().replace(' ', ap.av.getGapCharacter()),
 -                sg.getSequencesAsArray(ap.av.getHiddenRepSequences()),
 -                sg.getStartRes(), sg.getEndRes() + 1, ap.av.getAlignment());
 -
 -        ap.alignFrame.addHistoryItem(editCommand);
 -
 -        ap.av.firePropertyChange("alignment", null,
 -                ap.av.getAlignment().getSequences());
 -      }
 +              new Runnable()
 +              {
 +                @Override
 +                public void run()
 +                {
 +                  EditCommand editCommand = new EditCommand(
 +                          MessageManager.getString("label.edit_sequences"),
 +                          Action.REPLACE,
 +                          dialog.getName().replace(' ',
 +                                  ap.av.getGapCharacter()),
 +                          sg.getSequencesAsArray(
 +                                  ap.av.getHiddenRepSequences()),
 +                          sg.getStartRes(), sg.getEndRes() + 1,
 +                          ap.av.getAlignment());
 +                  ap.alignFrame.addHistoryItem(editCommand);
 +                  ap.av.firePropertyChange("alignment", null,
 +                          ap.av.getAlignment().getSequences());
 +                }
 +              });
      }
    }
  
@@@ -53,11 -53,10 +53,11 @@@ import javax.swing.JPanel
   * Wrapped mode, but not the scale above in Unwrapped mode.
   * 
   */
 -public class SeqCanvas extends JComponent implements ViewportListenerI
 +@SuppressWarnings("serial")
 +public class SeqCanvas extends JPanel implements ViewportListenerI
  {
-   /*
-    * pixels gap between sequences and annotations when in wrapped mode
+   /**
+    * vertical gap in pixels between sequences and annotations when in wrapped mode
     */
    static final int SEQS_ANNOTATION_GAP = 3;
  
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -1157,34 -1161,100 +1162,127 @@@ public abstract class FeatureRendererMo
      return filter == null ? true : filter.matches(sf);
    }
  
 +  /**
 +   * Answers true unless the specified group is set to hidden. Defaults to true
 +   * if group visibility is not set.
 +   * 
 +   * @param group
 +   * @return
 +   */
 +  public boolean isGroupVisible(String group)
 +  {
 +    if (!featureGroups.containsKey(group))
 +    {
 +      return true;
 +    }
 +    return featureGroups.get(group);
 +  }
 +
 +  /**
 +   * Orders features in render precedence (last in order is last to render, so
 +   * displayed on top of other features)
 +   * 
 +   * @param order
 +   */
 +  public void orderFeatures(Comparator<String> order)
 +  {
 +    Arrays.sort(renderOrder, order);
 +  }
 +
    @Override
+   public MappedFeatures findComplementFeaturesAtResidue(SequenceI sequence,
+           int pos)
+   {
+     SequenceI ds = sequence.getDatasetSequence();
+     if (ds == null)
+     {
+       ds = sequence;
+     }
+     final char residue = ds.getCharAt(pos - ds.getStart());
+     List<SequenceFeature> found = new ArrayList<>();
+     List<AlignedCodonFrame> mappings = this.av.getAlignment()
+             .getCodonFrame(sequence);
+     /*
+      * fudge: if no mapping found, check the complementary alignment
+      * todo: only store in one place? StructureSelectionManager?
+      */
+     if (mappings.isEmpty())
+     {
+       mappings = this.av.getCodingComplement().getAlignment()
+               .getCodonFrame(sequence);
+     }
+     /*
+      * todo: direct lookup of CDS for peptide and vice-versa; for now,
+      * have to search through an unordered list of mappings for a candidate
+      */
+     Mapping mapping = null;
+     SequenceI mapFrom = null;
+     for (AlignedCodonFrame acf : mappings)
+     {
+       mapping = acf.getMappingForSequence(sequence);
+       if (mapping == null || !mapping.getMap().isTripletMap())
+       {
+         continue; // we are only looking for 3:1 or 1:3 mappings
+       }
+       SearchResultsI sr = new SearchResults();
+       acf.markMappedRegion(ds, pos, sr);
+       for (SearchResultMatchI match : sr.getResults())
+       {
+         int fromRes = match.getStart();
+         int toRes = match.getEnd();
+         mapFrom = match.getSequence();
+         List<SequenceFeature> fs = findFeaturesAtResidue(
+                 match.getSequence(), fromRes, toRes);
+         for (SequenceFeature sf : fs)
+         {
+           if (!found.contains(sf))
+           {
+             found.add(sf);
+           }
+         }
+       }
+       /*
+        * just take the first mapped features we find
+        */
+       if (!found.isEmpty())
+       {
+         break;
+       }
+     }
+     if (found.isEmpty())
+     {
+       return null;
+     }
+     /*
+      * sort by renderorder, inefficiently
+      */
+     List<SequenceFeature> result = new ArrayList<>();
+     for (String type : renderOrder)
+     {
+       for (SequenceFeature sf : found)
+       {
+         if (type.equals(sf.getType()))
+         {
+           result.add(sf);
+           if (result.size() == found.size())
+           {
+             return new MappedFeatures(mapping, mapFrom, pos, residue,
+                     result);
+           }
+         }
+       }
+     }
+     
+     return new MappedFeatures(mapping, mapFrom, pos, residue, result);
+   }
+   @Override
    public boolean isVisible(SequenceFeature feature)
    {
      if (feature == null)
Simple merge