JAL-3210 Merge branch 'develop' into trialMerge
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 16 Jul 2019 07:14:32 +0000 (08:14 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 16 Jul 2019 07:14:32 +0000 (08:14 +0100)
Conflicts:
.classpath
.project
.settings/org.eclipse.jdt.core.prefs
.settings/org.eclipse.jdt.ui.prefs
build.xml
resources/lang/Messages_es.properties
src/MCview/AppletPDBCanvas.java
src/MCview/Atom.java
src/MCview/PDBCanvas.java
src/jalview/analysis/AAFrequency.java
src/jalview/appletgui/EmbmenuFrame.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/SeqPanel.java
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/datamodel/DBRefEntry.java
src/jalview/datamodel/Sequence.java
src/jalview/ext/ensembl/EnsemblMap.java
src/jalview/ext/paradise/Annotate3D.java
src/jalview/fts/service/pdb/PDBFTSRestClient.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/FeatureTypeSettings.java
src/jalview/gui/WebserviceInfo.java
src/jalview/io/BackupFiles.java
src/jalview/io/FileLoader.java
src/jalview/jbgui/GDesktop.java
src/jalview/jbgui/GPreferences.java
src/jalview/project/Jalview2XML.java
src/jalview/util/ImageMaker.java
src/jalview/util/Platform.java
src/jalview/ws/SequenceFetcher.java

76 files changed:
1  2 
.classpath
.project
.settings/org.eclipse.jdt.core.prefs
.settings/org.eclipse.jdt.ui.prefs
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/analysis/AAFrequency.java
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/Conservation.java
src/jalview/analysis/StructureFrequency.java
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/EmbmenuFrame.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/SeqPanel.java
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/bin/JalviewLite.java
src/jalview/commands/EditCommand.java
src/jalview/controller/AlignViewController.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/DBRefEntry.java
src/jalview/datamodel/Sequence.java
src/jalview/ext/ensembl/EnsemblFeatures.java
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/ext/ensembl/EnsemblLookup.java
src/jalview/ext/ensembl/EnsemblMap.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/paradise/Annotate3D.java
src/jalview/fts/core/FTSRestClient.java
src/jalview/fts/service/pdb/PDBFTSRestClient.java
src/jalview/gui/APQHandlers.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AppJmol.java
src/jalview/gui/BlogReader.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/CrossRefAction.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/FeatureTypeSettings.java
src/jalview/gui/JvSwingUtils.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/Preferences.java
src/jalview/gui/SplitFrame.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/WebserviceInfo.java
src/jalview/io/AnnotationFile.java
src/jalview/io/BackupFiles.java
src/jalview/io/FeaturesFile.java
src/jalview/io/FileLoader.java
src/jalview/io/JPredFile.java
src/jalview/io/ModellerDescription.java
src/jalview/io/NewickFile.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/vamsas/Tree.java
src/jalview/io/vcf/VCFLoader.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GDesktop.java
src/jalview/jbgui/GPreferences.java
src/jalview/project/Jalview2XML.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/util/ImageMaker.java
src/jalview/util/Platform.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/ws/DBRefFetcher.java
src/jalview/ws/SequenceFetcher.java
src/org/jibble/epsgraphics/EpsGraphics2D.java
test/jalview/analysis/AlignmentUtilsTests.java
test/jalview/io/vcf/VCFLoaderTest.java
test/jalview/project/Jalview2xmlTests.java
test/jalview/ws/SequenceFetcherTest.java

diff --combined .classpath
index 004d432,0000000..0a7e4b3
mode 100644,000000..100644
--- /dev/null
@@@ -1,74 -1,0 +1,206 @@@
 +<?xml version="1.0" encoding="UTF-8"?>
 +<classpath>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="src" path="src2"/>
-       <classpathentry kind="src" path="test"/>
-       <classpathentry kind="src" path="utils"/>
-       <classpathentry kind="lib" path="lib/activation.jar"/>
-       <classpathentry kind="lib" path="lib/axis.jar" sourcepath="D:/axis-1_2RC2-src/axis-1_2RC2"/>
-       <classpathentry kind="lib" path="lib/commons-discovery.jar"/>
-       <classpathentry kind="lib" path="lib/jaxrpc.jar"/>
-       <classpathentry kind="lib" path="lib/jhall.jar"/>
-       <classpathentry kind="lib" path="lib/mail.jar"/>
-       <classpathentry kind="lib" path="lib/regex.jar"/>
-       <classpathentry kind="lib" path="lib/saaj.jar"/>
-       <classpathentry kind="lib" path="lib/wsdl4j.jar"/>
-       <classpathentry kind="lib" path="lib/xercesImpl.jar"/>
-       <classpathentry kind="lib" path="lib/JGoogleAnalytics_0.3.jar" sourcepath="/JGoogleAnalytics/src/main/java"/>
-       <classpathentry kind="lib" path="lib/vamsas-client.jar"/>
-       <classpathentry kind="lib" path="lib/commons-logging-1.1.1.jar"/>
-       <classpathentry kind="lib" path="lib/apache-mime4j-0.6.jar" sourcepath="G:/InstallsDir/Sources for Development/apache-mime4j-0.6-src.zip"/>
-       <classpathentry kind="lib" path="lib/httpclient-4.0.3.jar">
-               <attributes>
-                       <attribute name="javadoc_location" value="file:/D:/InstallsDir/Sources for Development/httpconnect/httpcomponents-client-4.0.3/javadoc/"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="lib/httpcore-4.0.1.jar">
-               <attributes>
-                       <attribute name="javadoc_location" value="file:/D:/InstallsDir/Sources for Development/httpconnect/httpcomponents-client-4.0.3/javadoc/"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="lib/httpmime-4.0.3.jar">
-               <attributes>
-                       <attribute name="javadoc_location" value="file:/D:/InstallsDir/Sources for Development/httpconnect/httpcomponents-client-4.0.3/javadoc/"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="lib" path="lib/miglayout-4.0-swing.jar"/>
-       <classpathentry kind="lib" path="lib/jswingreader-0.3.jar" sourcepath="/jswingreader"/>
-       <classpathentry kind="lib" path="lib/commons-codec-1.3.jar"/>
-       <classpathentry kind="lib" path="lib/jabaws-min-client-2.2.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"/>
-       <classpathentry kind="lib" path="lib/jsoup-1.8.1.jar"/>
-       <classpathentry kind="lib" path="lib/log4j-to-slf4j-2.0-rc2.jar"/>
-       <classpathentry kind="lib" path="lib/slf4j-log4j12-1.7.7.jar"/>
-       <classpathentry kind="lib" path="lib/VARNAv3-93.jar"/>
-       <classpathentry kind="lib" path="lib/jfreesvg-2.1.jar"/>
-       <classpathentry kind="lib" path="lib/quaqua-filechooser-only-8.0.jar"/>
-       <classpathentry kind="lib" path="lib/VAqua5-patch.jar"/>
-       <classpathentry kind="lib" path="utils/classgraph-4.1.6.jar"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin"/>
-       <classpathentry kind="lib" path="lib/xml-apis.jar"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Plugin.jar"/>
-       <classpathentry kind="lib" path="lib/jersey-client-1.19.jar"/>
-       <classpathentry kind="lib" path="lib/jersey-core-1.19.jar"/>
-       <classpathentry kind="lib" path="lib/jsr311-api-1.1.1.jar"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin.jar"/>
-       <classpathentry kind="lib" path="lib/jetty-server-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="lib/servlet-api-3.1.jar"/>
-       <classpathentry kind="lib" path="lib/jetty-util-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="lib/jetty-http-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="lib/jetty-io-9.2.10.v20150310.jar"/>
-       <classpathentry kind="lib" path="lib/java-json.jar"/>
-       <classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
-       <classpathentry kind="lib" path="lib/biojava-core-4.1.0.jar"/>
-       <classpathentry kind="lib" path="lib/biojava-ontology-4.1.0.jar"/>
-       <classpathentry kind="lib" path="lib/htsjdk-2.12.0.jar"/>
-       <classpathentry kind="lib" path="lib/groovy-all-2.4.12-indy.jar"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
-       <classpathentry exported="true" kind="con" path="GROOVY_DSL_SUPPORT"/>
-       <classpathentry kind="lib" path="lib/Jmol-14.29.17.jar"/>
-       <classpathentry kind="lib" path="lib/intervalstore-v1.0.jar"/>
-       <classpathentry kind="output" path="classes"/>
++      <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>
diff --combined .project
index 6c40472,0000000..d4d0c20
mode 100644,000000..100644
--- /dev/null
+++ b/.project
@@@ -1,37 -1,0 +1,23 @@@
 +<?xml version="1.0" encoding="UTF-8"?>
 +<projectDescription>
-       <name>Jalview Release 2.7-old</name>
++      <name>Jalview with gradle build</name>
 +      <comment></comment>
-       <projects>
-       </projects>
++      <projects/>
++      <natures>
++              <nature>org.eclipse.jdt.core.javanature</nature>
++              <nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
++              <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
++      </natures>
 +      <buildSpec>
 +              <buildCommand>
 +                      <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.wst.common.project.facet.core.builder</name>
-                       <arguments>
-                       </arguments>
++                      <arguments/>
 +              </buildCommand>
 +              <buildCommand>
-                       <name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
-                       <triggers>full,incremental,</triggers>
-                       <arguments>
-                               <dictionary>
-                                       <key>LaunchConfigHandle</key>
-                                       <value>&lt;project&gt;/.externalToolBuilders/Jalview Release indices [Builder].launch</value>
-                               </dictionary>
-                       </arguments>
++                      <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
++                      <arguments/>
 +              </buildCommand>
 +      </buildSpec>
-       <natures>
-               <nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
-               <nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-               <nature>org.eclipse.wst.common.project.facet.core.nature</nature>
-               <nature>de.tud.st.ispace.builder.ISpaceNature</nature>
-               <nature>org.eclipse.jem.beaninfo.BeanInfoNature</nature>
-       </natures>
++      <linkedResources/>
++      <filteredResources/>
 +</projectDescription>
index 32ad05c,0000000..11ddce1
mode 100644,000000..100644
--- /dev/null
@@@ -1,421 -1,0 +1,299 @@@
- eclipse.preferences.version=1
- org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
- org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
- org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
- org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
- org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
- org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
- org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
- org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
- org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
- org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
- org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
- org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
- org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
- org.eclipse.jdt.core.compiler.compliance=1.8
- org.eclipse.jdt.core.compiler.debug.lineNumber=generate
- org.eclipse.jdt.core.compiler.debug.localVariable=generate
- org.eclipse.jdt.core.compiler.debug.sourceFile=generate
- org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
- org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
- org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
- org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
- org.eclipse.jdt.core.compiler.problem.deadCode=warning
- org.eclipse.jdt.core.compiler.problem.deprecation=warning
- org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
- org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
- org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
- org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
- org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
- org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
- org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
- org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
- org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
- org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
- org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
- org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
- org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
- org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
- org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
- org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
- org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
- org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
- org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
- org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
- org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
- org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
- org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
- org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
- org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
- org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
- org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
- org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
- org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
- org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
- org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
- org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
- org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
- org.eclipse.jdt.core.compiler.problem.nullReference=warning
- org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
- org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
- org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
- org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
- org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
- org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
- org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
- org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
- org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
- org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
- org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
- org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
- org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
- org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
- org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
- org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
- org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
- org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
- org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
- org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
- org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning
- org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
- org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
- org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
- org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
- org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
- org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
- org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
- org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
- org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
- org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
- org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
- org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
- org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
- org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
- org.eclipse.jdt.core.compiler.problem.unusedImport=warning
- org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
- org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
- org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
- org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
- org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
- org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
- org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
- org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
- org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
- org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
- org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
- org.eclipse.jdt.core.compiler.release=disabled
- org.eclipse.jdt.core.compiler.source=1.8
- org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
- org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
- org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
- org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false
- org.eclipse.jdt.core.formatter.align_with_spaces=false
- org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
- org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=52
- org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
- org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
- org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
- org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
- org.eclipse.jdt.core.formatter.alignment_for_assignment=0
- org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
- org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
- org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16
- org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
- org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
- org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
- org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
- org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
- org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
- org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
- org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
- org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
- org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
- org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
- org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
- org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
- org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
- org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
- org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
- org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
- org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
- org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
- org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
++#
++#Tue Jul 16 08:09:58 BST 2019
++org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
++org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
++org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
++org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
++org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
++org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
- org.eclipse.jdt.core.formatter.blank_lines_after_package=1
- org.eclipse.jdt.core.formatter.blank_lines_before_field=1
- org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
++org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
++org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
++org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
++org.eclipse.jdt.core.formatter.indentation.size=8
++org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
++org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
++org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
++org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
++org.eclipse.jdt.core.formatter.continuation_indentation=4
 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
- org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
- org.eclipse.jdt.core.formatter.blank_lines_before_method=1
- org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
- org.eclipse.jdt.core.formatter.blank_lines_before_package=0
- org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
++org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
++org.eclipse.jdt.core.formatter.blank_lines_after_package=1
++org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
++org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
++org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
++org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
++org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
++org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
++org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
++org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
++org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
++org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
++org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
++org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
++org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
++org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
- org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=next_line
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
++org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
++org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
++org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
++org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
++org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
 +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=next_line
- org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=next_line_on_wrap
++org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
++org.eclipse.jdt.core.formatter.comment.line_length=80
++org.eclipse.jdt.core.formatter.use_on_off_tags=true
++org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
++org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
++org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
++org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
++org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
++org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
++org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
++org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
++org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
++org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
++org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
++org.eclipse.jdt.core.compiler.debug.localVariable=generate
++org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
++org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
++org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
++org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
 +org.eclipse.jdt.core.formatter.brace_position_for_block=next_line
- org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=next_line
 +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=next_line
- org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=next_line
- org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=next_line
 +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
- org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line
- org.eclipse.jdt.core.formatter.brace_position_for_switch=next_line
- org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line
- org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false
- org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false
++org.eclipse.jdt.core.formatter.compact_else_if=true
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
++org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
++org.eclipse.jdt.core.compiler.debug.lineNumber=generate
++org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
++org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
++org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
++org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
 +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
++org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
++org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
 +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
- org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false
- org.eclipse.jdt.core.formatter.comment.format_block_comments=false
- org.eclipse.jdt.core.formatter.comment.format_header=false
- org.eclipse.jdt.core.formatter.comment.format_html=true
- org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
- org.eclipse.jdt.core.formatter.comment.format_line_comments=true
- org.eclipse.jdt.core.formatter.comment.format_source_code=true
- org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
- org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
- org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
- org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
- org.eclipse.jdt.core.formatter.comment.line_length=80
- org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
- org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
- org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
- org.eclipse.jdt.core.formatter.compact_else_if=true
- org.eclipse.jdt.core.formatter.continuation_indentation=4
- org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
- org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
- org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
- org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
++org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
++org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
++org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
 +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
- org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
- org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
- org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
- org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
- org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
- org.eclipse.jdt.core.formatter.indent_empty_lines=false
- org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
- org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
- org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
- org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
- org.eclipse.jdt.core.formatter.indentation.size=2
- org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
- org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
- org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
- org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
- org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
- org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
- org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
- org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
- org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
- org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
- org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
- org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
- org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
- org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert
- org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
- org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
- org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
- org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
- org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
- org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
- org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
- org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
- org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
- org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
- org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
- org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
- org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
- org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
- org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
- org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
- org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
++org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
 +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
- org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
- org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
++org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
++org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
++org.eclipse.jdt.core.formatter.comment.format_line_comments=true
 +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
- org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
- org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
- org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
++org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
++org.eclipse.jdt.core.formatter.alignment_for_assignment=0
++org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
++org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
++org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
 +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
++org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
++org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
++org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
++org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
++org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
++org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
++org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=next_line
++org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=next_line
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
++org.eclipse.jdt.core.formatter.comment.format_header=false
++org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
++org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
++org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
++org.eclipse.jdt.core.formatter.join_wrapped_lines=true
++org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
++org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
++org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
++org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
++org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=next_line
++org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
++org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
++org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
++org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
++org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
++org.eclipse.jdt.core.compiler.source=11
 +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
- org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
- org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
++org.eclipse.jdt.core.formatter.tabulation.size=2
++org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
++org.eclipse.jdt.core.formatter.comment.format_source_code=true
++org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
++org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
 +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
++org.eclipse.jdt.core.formatter.blank_lines_before_field=1
++org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
++org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
++org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
++org.eclipse.jdt.core.formatter.blank_lines_before_method=1
++org.eclipse.jdt.core.compiler.debug.sourceFile=generate
++org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
++org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
++org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
++org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
++org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
++org.eclipse.jdt.core.formatter.brace_position_for_switch=next_line
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
++org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
++org.eclipse.jdt.core.formatter.comment.format_html=true
++org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
++org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
++org.eclipse.jdt.core.formatter.indent_empty_lines=false
++eclipse.preferences.version=1
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
 +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
++org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=52
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
++org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=true
++org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
++org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert
 +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
- org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
- org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
++org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
++org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
++org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
++org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
 +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
 +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
 +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
- org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
- org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
- org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
++org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
++org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
++org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
++org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
++org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
++org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
++org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
 +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
++org.eclipse.jdt.core.formatter.comment.format_block_comments=false
++org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
++org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
++org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
 +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
++org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
++org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
 +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
++org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
++org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
++org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
++org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=next_line_on_wrap
++org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
++org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
++org.eclipse.jdt.core.compiler.compliance=11
++org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
++org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
 +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
 +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
++org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
++org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
++org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line
++org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=next_line
++org.eclipse.jdt.core.formatter.blank_lines_before_package=0
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
 +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
- org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
- org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
- org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
- org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
- org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
- org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
- org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
- org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
- org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
- org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
- org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
- org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
- org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
- org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
++org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
++org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
++org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
 +org.eclipse.jdt.core.formatter.join_lines_in_comments=true
- org.eclipse.jdt.core.formatter.join_wrapped_lines=true
- org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
- org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=true
- org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
- org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false
- org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false
- org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
- org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
- org.eclipse.jdt.core.formatter.lineSplit=76
- org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
- org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
- org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
- org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
- org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
- org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
- org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
- org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
- org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
- org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
- org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
- org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
- org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
- org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
- org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
++org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
++org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
++org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
 +org.eclipse.jdt.core.formatter.tabulation.char=space
- org.eclipse.jdt.core.formatter.tabulation.size=2
- org.eclipse.jdt.core.formatter.use_on_off_tags=true
- org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
- org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
- org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
- org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
- org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
- org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
- org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
++org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
++org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
++org.eclipse.jdt.core.formatter.lineSplit=76
++org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
++org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
@@@ -1,7 -1,7 +1,7 @@@
  eclipse.preferences.version=1
  editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
  formatter_profile=_Jalview
- formatter_settings_version=14
+ formatter_settings_version=16
  org.eclipse.jdt.ui.ignorelowercasenames=true
  org.eclipse.jdt.ui.importorder=jalview;java;javax;org;com;
  org.eclipse.jdt.ui.ondemandthreshold=99
@@@ -41,7 -41,6 +41,7 @@@ sp_cleanup.qualify_static_member_access
  sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
  sp_cleanup.remove_private_constructors=true
  sp_cleanup.remove_redundant_modifiers=false
 +sp_cleanup.remove_redundant_semicolons=true
  sp_cleanup.remove_redundant_type_arguments=true
  sp_cleanup.remove_trailing_whitespaces=false
  sp_cleanup.remove_trailing_whitespaces_all=true
@@@ -32,6 -32,7 +32,7 @@@ action.load_project = Load Projec
  action.save_project = Save Project
  action.save_project_as = Save Project as...
  action.quit = Quit
+ label.quit_jalview = Quit Jalview?
  action.expand_views = Expand Views
  action.gather_views = Gather Views
  action.page_setup = Page Setup...
@@@ -359,8 -360,7 +360,8 @@@ label.open_saved_vamsas_session = Open 
  label.groovy_console = Groovy Console...
  label.lineart = Lineart
  label.dont_ask_me_again = Don't ask me again
 -label.select_eps_character_rendering_style = Select EPS character rendering style
 +label.select_character_rendering_style = {0} character rendering style
 +label.select_character_style_title = {0} Rendering options
  label.invert_selection = Invert Selection
  label.optimise_order = Optimise Order
  label.seq_sort_by_score = Sequence sort by Score
@@@ -409,7 -409,7 +410,7 @@@ label.input_alignment_from_url = Input 
  label.input_alignment = Input Alignment
  label.couldnt_import_as_vamsas_session = Couldn't import {0} as a new vamsas session.
  label.vamsas_document_import_failed = Vamsas Document Import Failed
 -label.couldnt_locate = Could not locate {0}
 +label.couldnt_locate = Couldn''t locate {0}
  label.url_not_found = URL not found
  label.new_sequence_url_link = New sequence URL link
  label.cannot_edit_annotations_in_wrapped_view = Cannot edit annotations in wrapped view
@@@ -595,7 -595,7 +596,7 @@@ label.check_for_questionnaires = Check 
  label.check_for_latest_version = Check for latest version
  label.url_linkfrom_sequence_id = URL link from Sequence ID
  label.use_proxy_server = Use a proxy server
 -label.eps_rendering_style = EPS rendering style
 +label.rendering_style = {0} rendering style
  label.append_start_end = Append /start-end (/15-380)
  label.full_sequence_id = Full Sequence Id
  label.smooth_font = Smooth Font
@@@ -676,7 -676,7 +677,7 @@@ label.sequence_details_for = Sequence D
  label.sequence_name = Sequence Name
  label.sequence_description = Sequence Description
  label.edit_sequence_name_description = Edit Sequence Name/Description
 -label.spaces_converted_to_backslashes = Spaces have been converted to _
 +label.spaces_converted_to_underscores = Spaces have been converted to _
  label.no_spaces_allowed_sequence_name = No spaces allowed in Sequence Name
  label.select_outline_colour = Select Outline Colour
  label.web_browser_not_found_unix = Unixers\: Couldn't find default web browser.\nAdd the full path to your browser in Preferences."
@@@ -878,6 -878,8 +879,6 @@@ label.save_feature_colours = Save Featu
  label.select_startup_file = Select startup file
  label.select_default_browser = Select default web browser
  label.save_tree_as_newick = Save tree as newick file
 -label.create_eps_from_tree = Create EPS file from tree
 -label.create_png_from_tree = Create PNG image from tree
  label.save_colour_scheme = Save colour scheme
  label.edit_params_for = Edit parameters for {0}
  label.choose_filename_for_param_file = Choose a filename for this parameter file
@@@ -933,6 -935,8 +934,6 @@@ error.call_setprogressbar_before_regist
  label.cancelled_params = Cancelled {0}
  error.implementation_error_cannot_show_view_alignment_frame = Implementation error: cannot show a view from another alignment in an AlignFrame.
  error.implementation_error_dont_know_about_threshold_setting = Implementation error: don't know about threshold setting for current AnnotationColourGradient.
 -error.eps_generation_not_implemented = EPS Generation not yet implemented
 -error.png_generation_not_implemented = PNG Generation not yet implemented
  error.try_join_vamsas_session_another = Trying to join a vamsas session when another is already connected
  error.invalid_vamsas_session_id = Invalid vamsas session id
  label.groovy_support_failed = Jalview Groovy Support Failed
@@@ -994,7 -998,8 +995,8 @@@ label.toggled = Toggle
  label.marked = Marked
  label.containing = containing
  label.not_containing = not containing
- label.no_feature_of_type_found = No features of type {0} found.
+ label.no_feature_of_type_found = No features of type {0} found
+ label.no_feature_found_selection = No features of type {0} found in selection
  label.submission_params = Submission {0}
  label.empty_alignment_job = Empty Alignment Job
  label.add_new_sbrs_service = Add a new Simple Bioinformatics Rest Service
@@@ -1003,7 -1008,7 +1005,7 @@@ label.pca_recalculating = Recalculatin
  label.pca_calculating = Calculating PCA
  label.select_foreground_colour = Choose foreground colour
  label.select_colour_for_text = Select Colour for Text
 -label.adjunst_foreground_text_colour_threshold = Adjust Foreground Text Colour Threshold
 +label.adjust_foreground_text_colour_threshold = Adjust Foreground Text Colour Threshold
  label.select_subtree_colour = Select Sub-Tree Colour
  label.create_new_sequence_features = Create New Sequence Feature(s)
  label.amend_delete_features = Amend/Delete Features for {0}
@@@ -1075,6 -1080,7 +1077,6 @@@ error.implementation_error_cannot_find_
  exception.jobsubmission_invalid_params_set = Invalid parameter set. Check Jalview implementation
  exception.notvaliddata_group_contains_less_than_min_seqs = Group contains less than {0} sequences.
  exception.outofmemory_loading_pdb_file = Out of memory loading PDB File
 -exception.eps_coudnt_write_output_file = Could not write to the output file: {0}
  exception.eps_method_not_supported = Method not currently supported by EpsGraphics2D version {0}
  exception.eps_unable_to_get_inverse_matrix = Unable to get inverse of matrix: {0}
  warn.job_cannot_be_cancelled_close_window = This job cannot be cancelled.\nJust close the window.
@@@ -1099,7 -1105,8 +1101,7 @@@ status.searching_for_sequences_from = S
  status.finished_searching_for_sequences_from = Finished searching for sequences from {0}
  label.eps_file = EPS file
  label.png_image = PNG image
 -status.saving_file = Saving {0}
 -status.export_complete = {0} Export completed.
 +status.export_complete = {0} Export completed
  status.fetching_pdb = Fetching PDB {0}
  status.refreshing_news = Refreshing news
  status.importing_vamsas_session_from = Importing VAMSAS session from {0}
@@@ -1219,13 -1226,11 +1221,10 @@@ label.structure_chooser_filter_time = S
  label.structure_chooser_no_of_structures = Structure Chooser - {0} Found ({1})
  info.no_pdb_entry_found_for = No PDB entry found for {0}
  exception.unable_to_detect_internet_connection = Jalview is unable to detect an internet connection
- exception.fts_rest_service_no_longer_available = {0} rest services no longer available!
- exception.resource_not_be_found = The requested resource could not be found
- exception.fts_server_error = There seems to be an error from the {0} server
  exception.fts_server_unreachable = Jalview is unable to reach the {0} server. \nPlease ensure that you are connected to the internet and try again.
  label.nw_mapping = Needleman & Wunsch Alignment
  label.sifts_mapping = SIFTs Mapping
  label.mapping_method = Sequence \u27f7 Structure mapping method
 -status.waiting_for_user_to_select_output_file = Waiting for user to select {0} file
  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
@@@ -1237,8 -1242,6 +1236,6 @@@ action.next_page= >
  action.prev_page= << 
  label.next_page_tooltip=Next Page
  label.prev_page_tooltip=Previous Page
- exception.bad_request=Bad request. There is a problem with your input.
- exception.service_not_available=Service not available. The server is being updated, try again later.
  status.launching_3d_structure_viewer = Launching 3D Structure viewer...
  status.fetching_3d_structures_for_selected_entries = Fetching 3D Structures for selected entries...
  status.fetching_dbrefs_for_sequences_without_valid_refs = Fetching db refs for {0} sequence(s) without valid db ref required for SIFTS mapping
@@@ -1306,10 -1309,11 +1303,11 @@@ label.numeric_required = The value shou
  label.filter = Filter
  label.filters = Filters
  label.join_conditions = Join conditions with
+ label.delete_condition = Delete this condition
  label.score = Score
  label.colour_by_label = Colour by label
  label.variable_colour = Variable colour...
 -label.select_colour = Select colour
 +label.select_colour_for = Select colour for {0}
  option.enable_disable_autosearch = When ticked, search is performed automatically
  option.autosearch = Autosearch
  label.retrieve_ids = Retrieve IDs
@@@ -1330,13 -1334,6 +1328,13 @@@ label.most_bound_molecules = Most Boun
  label.most_polymer_residues = Most Polymer Residues
  label.cached_structures = Cached Structures
  label.free_text_search = Free Text Search
 +label.annotation_name = Annotation Name
 +label.annotation_description = Annotation Description 
 +label.edit_annotation_name_description = Edit Annotation Name/Description
 +label.alignment = alignment
 +label.pca = PCA
 +label.create_image_of = Create {0} image of {1}
 +label.click_to_edit = Click to edit, right-click for menu
  label.backupfiles_confirm_delete = Confirm delete
  label.backupfiles_confirm_delete_old_files = Delete the following older backup files? (see the Backups tab in Preferences for more options)
  label.backupfiles_confirm_save_file = Confirm save file
@@@ -1352,12 -1349,13 +1350,13 @@@ label.append_to_filename = Append to fi
  label.append_to_filename_tooltip = %n in the text will be replaced by the backup number. The text will appear after the filename. See the summary box above.
  label.index_digits = Number of digits to use for the backup number (%n)
  label.summary_of_backups_scheme = Summary of backup scheme
+ label.scheme_examples = Scheme examples
  label.increment_index = Increase appended text numbers - newest file has largest number.
  label.reverse_roll = "Roll" appended text numbers - newest backup file is always number 1.
  label.keep_files = Deleting old backup files
  label.keep_all_backup_files = Do not delete old backup files
  label.keep_only_this_number_of_backup_files = Keep only this number of most recent backup files
- label.autodelete_old_backup_files = Autodelete old backup files:
+ label.autodelete_old_backup_files = Auto-delete old backup files:
  label.always_ask = Always ask
  label.auto_delete = Automatically delete
  label.filename = filename
@@@ -1367,10 -1365,18 +1366,18 @@@ label.configuration = Configuratio
  label.configure_feature_tooltip = Click to configure variable colour or filters
  label.schemes = Schemes
  label.customise = Customise
+ label.custom = Custom
  label.default = Default
  label.single_file = Single backup
  label.keep_all_versions = Keep all versions
  label.rolled_backups = Rolled backup files
+ label.customise_description = Select Customise, make changes, and click on OK to save your own custom scheme
+ label.custom_description = Your own saved scheme
+ label.default_description = Keep the last three versions of the file
+ label.single_file_description = Keep the last version of the file
+ label.keep_all_versions_description = Keep all previous versions of the file
+ label.rolled_backups_description = Keep the last nine versions of the file from _bak.1 (newest) to _bak.9 (oldest)
+ label.cancel_changes_description = Cancel changes made to your last saved Custom scheme
  label.previously_saved_scheme = Previously saved scheme
  label.no_backup_files = NO BACKUP FILES
  label.include_backup_files = Include backup files
@@@ -32,6 -32,7 +32,7 @@@ action.load_project = Cargar proyect
  action.save_project = Guardar proyecto
  action.save_project_as = Guardar proyecto como...
  action.quit = Salir
+ label.quit_jalview = Salir de Jalview?
  action.expand_views = Expandir vistas
  action.gather_views = Capturar vistas
  action.page_setup = Configuración de la página
@@@ -327,8 -328,7 +328,8 @@@ label.open_saved_vamsas_session = Abri
  label.groovy_console = Consola Groovy 
  label.lineart = Lineart
  label.dont_ask_me_again = No volver a preguntar
 -label.select_eps_character_rendering_style = Seleccionar el carácter EPS como estilo de visualización 
 +label.select_character_rendering_style = Estilo de visualización para carácter {0} 
 +label.select_character_style_title = Opciones de visualización {0}
  label.invert_selection = Invertir selección
  label.optimise_order = Optimizar orden
  label.seq_sort_by_score = Ordenar las secuencias por puntuación
@@@ -550,7 -550,7 +551,7 @@@ label.check_for_questionnaires = Compro
  label.check_for_latest_version = Comprobar la Ãºltima versión
  label.url_linkfrom_sequence_id = URL del enlace del ID de la secuencia
  label.use_proxy_server = Utilizar un servidor proxy
 -label.eps_rendering_style = Estilo de visualización EPS
 +label.rendering_style = Estilo de visualización {0}
  label.append_start_end = Añadir /inicio-fin (/15-380)
  label.full_sequence_id = ID de la secuencia completo
  label.smooth_font = Fuente alargada
@@@ -626,7 -626,7 +627,7 @@@ label.sequence_details_for = Detalles d
  label.sequence_name = Nombre de la secuencia
  label.sequence_description = Descripción de la secuencia
  label.edit_sequence_name_description = Editar el nombre/descripción de la secuencia
 -label.spaces_converted_to_backslashes = Los espacios se han convertido en _
 +label.spaces_converted_to_underscores = Los espacios se han convertido en _
  label.no_spaces_allowed_sequence_name = No se permiten espacios en el nombre de la secuencia
  label.select_outline_colour = Seleccionar el color del límite
  label.web_browser_not_found_unix = Unixers\: No es posible encontrar el navegador web por defecto.\nA\u00F1ada la ruta completa de su navegador en la pesta\u00F1a de Preferencias.
@@@ -803,6 -803,8 +804,6 @@@ label.save_feature_colours = Guardar es
  label.select_startup_file = Seleccionar fichero de arranque
  label.select_default_browser = Seleccionar navegador web por defecto
  label.save_tree_as_newick = Guardar Ã¡rbol como fichero newick
 -label.create_eps_from_tree = Crear un fichero EPS a partir de un Ã¡rbol
 -label.create_png_from_tree = Crear una imagen PNG a partir de un Ã¡rbol
  label.save_colour_scheme = Guardar esquema cromático
  label.edit_params_for = Editar los parámetros de {0}
  label.choose_filename_for_param_file = Escoja un nombre de fichero para este fichero de parámetros
@@@ -858,6 -860,8 +859,6 @@@ error.call_setprogressbar_before_regist
  label.cancelled_params = {0} cancelado
  error.implementation_error_cannot_show_view_alignment_frame = Error de implementación: no es posible mostrar una vista de otro alineamiento en un AlignFrame.
  error.implementation_error_dont_know_about_threshold_setting = Error de implementación: no se conoce la configuración del umbral para el AnnotationColourGradient actual.
 -error.eps_generation_not_implemented = La generación de EPS no se ha implementado todavía
 -error.png_generation_not_implemented = La generación de PNG no se ha implementado todavía
  error.try_join_vamsas_session_another = Tratando de establecer una sesión VAMSAS cuando ya había otra conectada
  error.invalid_vamsas_session_id = Identificador de sesión VAMSAS no válido
  label.groovy_support_failed = El soporte Groovy de Jalview ha fallado
@@@ -919,7 -923,8 +920,8 @@@ label.toggled = Invertid
  label.marked = Marcada
  label.containing = conteniendo
  label.not_containing = no conteniendo
- label.no_feature_of_type_found = No se han encontrado características del tipo {0}.
+ label.no_feature_of_type_found = No se han encontrado características del tipo {0}
+ label.no_feature_found_selection = No se han encontrado características del tipo {0} en la región seleccionada
  label.submission_params = Envío {0}
  label.empty_alignment_job = Trabajo de alineamiento vacío
  label.add_new_sbrs_service = Añadir un nuevo SBRS
@@@ -928,7 -933,7 +930,7 @@@ label.pca_recalculating = Recalculando 
  label.pca_calculating = Calculando ACP
  label.select_foreground_colour = Escoger color del primer plano
  label.select_colour_for_text = Seleccione el color del texto
 -label.adjunst_foreground_text_colour_threshold = Ajustar el umbral del color del texto en primer plano
 +label.adjust_foreground_text_colour_threshold = Ajustar el umbral del color del texto en primer plano
  label.select_subtree_colour = Seleccioanr el color del sub-árbol
  label.create_new_sequence_features = Crear nueva(s) característica(s) de secuencia
  label.amend_delete_features = Arrelgar/Borrar características de {0}
@@@ -1000,6 -1005,7 +1002,6 @@@ error.implementation_error_cannot_find_
  exception.jobsubmission_invalid_params_set = Conjunto de parámetros no válido. Comprueba la implementación de Jalview
  exception.notvaliddata_group_contains_less_than_min_seqs = El grupo contiene menos de {0} secuencias.
  exception.outofmemory_loading_pdb_file = Sin memoria al cargar el fichero PDB
 -exception.eps_coudnt_write_output_file = No es posible escribir el fichero de salida: {0}
  exception.eps_method_not_supported = Método actualmente no suportado por la versión {0} de EpsGraphics2D
  exception.eps_unable_to_get_inverse_matrix = Imposible obtener la inversa de la matrix: {0}
  warn.job_cannot_be_cancelled_close_window = Este trabajo no se puede cancelar.\nSimplemente, cierre la ventana.
@@@ -1021,7 -1027,8 +1023,7 @@@ status.searching_for_sequences_from = B
  status.finished_searching_for_sequences_from = Finalizada la búsqueda de secuencias en {0}
  label.eps_file = Fichero EPS
  label.png_image = Imagen PNG
 -status.saving_file = Guardando {0}
 -status.export_complete = Exportación completada.
 +status.export_complete = Exportación completada
  status.fetching_pdb = Recuperando PDB {0}
  status.refreshing_news = Refrescando noticias
  status.importing_vamsas_session_from = Importando sesión VAMSAS de {0}
@@@ -1193,7 -1200,6 +1195,6 @@@ tooltip.rnalifold_settings=Modificar l
  label.show_selected_annotations=Mostrar anotaciones seleccionadas
  status.colouring_chimera=Coloreando Chimera
  label.configure_displayed_columns=Configurar Columnas Mostradas
- exception.resource_not_be_found=El recurso solicitado no se ha encontrado
  label.aacon_calculations=cálculos AACon
  label.pdb_web-service_error=Error de servicio web PDB
  exception.unable_to_detect_internet_connection=Jalview no puede detectar una conexión a Internet
@@@ -1218,12 -1224,10 +1219,10 @@@ exception.fts_server_unreachable=Jalvie
  exception.outofmemory_loading_mmcif_file=Sin memoria al cargar el fichero mmCIF
  label.hide_columns_not_containing=Ocultar las columnas que no contengan
  label.pdb_sequence_fetcher=Recuperador de secuencias PDB
- exception.fts_server_error=Parece que hay un error desde el servidor {0}
- exception.service_not_available=Servicio no disponible. El servidor se está actualizando, vuelva a intentarlo más tarde.
+ status.waiting_for_user_to_select_output_file=Esperando que el usuario seleccione el fichero {0}
  action.prev_page=<< 
  status.cancelled_image_export_operation=Operación de exportación {0} cancelada
  label.couldnt_run_groovy_script=No se ha podido ejecutar el script Groovy
- exception.bad_request=Solicitud incorrecta. Hay un problema con su entrada.
  label.run_groovy=Ejecutar script Groovy desde la consola
  action.next_page=>> 
  label.uniprot_sequence_fetcher=Recuperador de secuencias UniProt
@@@ -1239,7 -1243,6 +1238,6 @@@ label.next_page_tooltip=Página siguient
  label.sifts_mapping=Mapeado SIFTs
  label.mapping_method=Método de mapeo de secuencia \u27F7 estructura
  info.error_creating_file=Error al crear fichero {0}
- exception.fts_rest_service_no_longer_available= Servicios Rest {0} ya no están disponibles! 
  status.launching_3d_structure_viewer=Lanzando visualizador de estructura 3D...
  status.obtaining_mapping_with_sifts=Obteniendo mapeo por SIFTS
  status.fetching_3d_structures_for=Buscando la estructura 3D para {0}
@@@ -1306,11 -1309,12 +1304,12 @@@ label.matchCondition_ge = >
  label.numeric_required = Valor numérico requerido
  label.filter = Filtro
  label.filters = Filtros
+ label.delete_condition = Borrar esta condición
  label.join_conditions = Combinar condiciones con
  label.score = Puntuación
  label.colour_by_label = Colorear por texto
  label.variable_colour = Color variable...
 -label.select_colour = Seleccionar color
 +label.select_colour_for = Seleccionar color para {0}
  option.enable_disable_autosearch = Marcar para buscar automáticamente
  option.autosearch = Auto búsqueda
  label.retrieve_ids = Recuperar IDs
@@@ -1331,13 -1335,6 +1330,13 @@@ label.most_bound_molecules = Más Molécu
  label.most_polymer_residues = Más Residuos de Polímeros
  label.cached_structures = Estructuras en Caché
  label.free_text_search = Búsqueda de texto libre
 +label.annotation_name = Nombre de la anotación
 +label.annotation_description = Descripción de la anotación 
 +label.edit_annotation_name_description = Editar el nombre/descripción de la anotación
 +label.alignment = alineamiento
 +label.pca = ACP
 +label.create_image_of = Crear imagen {0} de {1}
 +label.click_to_edit = Haga clic para editar, clic en el botón derecho para ver el menú  
  label.backupfiles_confirm_delete = Confirmar borrar
  label.backupfiles_confirm_delete_old_files = Â¿Borrar los siguientes archivos? (ver la pestaña 'Copias' de la ventana de Preferencias para más opciones)
  label.backupfiles_confirm_save_file = Confirmar guardar archivo
@@@ -1353,6 -1350,7 +1352,7 @@@ label.append_to_filename = Adjuntar tex
  label.append_to_filename_tooltip = %n en el texto será reemplazado por el número de respaldo. El texto será después del nombre del archivo. Vea el cuadro de resumen arriba.
  label.index_digits = Número de dígitos a utilizar para el número de respaldo.
  label.summary_of_backups_scheme = Resumen del esquema de copias de seguridad
+ label.scheme_examples = Ejemplos de esquema
  label.increment_index = Aumente los números de texto adjuntos: el archivo más nuevo tiene el número más grande
  label.reverse_roll = Ciclos de texto adjuntos: el respaldo más reciente es siempre el número 1
  label.keep_files = Borrando los respaldos antiguos
@@@ -1367,11 -1365,19 +1367,19 @@@ label.braced_newest = (mas nuevo
  label.configuration = Configuración
  label.configure_feature_tooltip = Haga clic para configurar el color o los filtros
  label.schemes = Esquemas
- label.customise = Personalizado
+ label.customise = Personalizar
+ label.custom = Personal
  label.default = Defecto
  label.single_file = Solo uno respaldo
  label.keep_all_versions = Mantener todas las versiones
  label.rolled_backups = Ciclos respaldos
+ label.customise_description = Seleccione Personalizar, haga cambios y haga clic en OK para guardar su propio esquema personalizado
+ label.custom_description = Tu propio esquema guardado
+ label.default_description = Conserve las Ãºltimas tres versiones del archivo
+ label.single_file_description = Conserve la Ãºltima versión del archivo
+ label.keep_all_versions_description = Mantener todas las versiones anteriores del archivo
+ label.rolled_backups_description = Mantenga las Ãºltimas nueve versiones del archivo desde _bak.1 (más reciente) a _bak.9 (más antigua)
+ label.cancel_changes_description = Cancelar los cambios realizados en su Ãºltimo esquema personalizado guardado
  label.previously_saved_scheme = Esquema previamente guardado
  label.no_backup_files = NO ARCHIVOS DE RESPALDOS
  label.include_backup_files = Incluir archivos de respaldos
@@@ -398,7 -398,7 +398,7 @@@ public class AAFrequenc
     * contains
     * 
     * <pre>
 -   *    [profileType, numberOfValues, nonGapCount, charValue1, percentage1, charValue2, percentage2, ...]
 +   *    [profileType, numberOfValues, totalPercent, charValue1, percentage1, charValue2, percentage2, ...]
     * in descending order of percentage value
     * </pre>
     * 
     */
    public static int[] extractProfile(ProfileI profile, boolean ignoreGaps)
    {
 -    int[] rtnval = new int[64];
      ResidueCount counts = profile.getCounts();
      if (counts == null)
      {
      char[] symbols = symbolCounts.symbols;
      int[] values = symbolCounts.values;
      QuickSort.sort(values, symbols);
 -    int nextArrayPos = 2;
      int totalPercentage = 0;
      final int divisor = ignoreGaps ? profile.getNonGapped()
              : profile.getHeight();
      /*
       * traverse the arrays in reverse order (highest counts first)
       */
 +    int[] result = new int[3 + 2 * symbols.length];
 +    int nextArrayPos = 3;
 +    int nonZeroCount = 0;
 +
      for (int i = symbols.length - 1; i >= 0; i--)
      {
        int theChar = symbols[i];
        int charCount = values[i];
 -
 -      rtnval[nextArrayPos++] = theChar;
        final int percentage = (charCount * 100) / divisor;
 -      rtnval[nextArrayPos++] = percentage;
 +      if (percentage == 0)
 +      {
 +        /*
 +         * this count (and any remaining) round down to 0% - discard
 +         */
 +        break;
 +      }
 +      nonZeroCount++;
 +      result[nextArrayPos++] = theChar;
 +      result[nextArrayPos++] = percentage;
        totalPercentage += percentage;
      }
 -    rtnval[0] = symbols.length;
 -    rtnval[1] = totalPercentage;
 -    int[] result = new int[rtnval.length + 1];
 +
 +    /*
 +     * truncate array if any zero values were discarded
 +     */
 +    if (nonZeroCount < symbols.length)
 +    {
 +      int[] tmp = new int[3 + 2 * nonZeroCount];
 +      System.arraycopy(result, 0, tmp, 0, tmp.length);
 +      result = tmp;
 +    }
 +
 +    /*
 +     * fill in 'header' values
 +     */
      result[0] = AlignmentAnnotation.SEQUENCE_PROFILE;
 -    System.arraycopy(rtnval, 0, result, 1, rtnval.length);
 +    result[1] = nonZeroCount;
 +    result[2] = totalPercentage;
  
      return result;
    }
     * contains
     * 
     * <pre>
 -   *    [profileType, numberOfValues, totalCount, charValue1, percentage1, charValue2, percentage2, ...]
 +   *    [profileType, numberOfValues, totalPercentage, charValue1, percentage1, charValue2, percentage2, ...]
     * in descending order of percentage value, where the character values encode codon triplets
     * </pre>
     * 
     * @param hashtable
     * @return
     */
 -  public static int[] extractCdnaProfile(Hashtable hashtable,
 +  public static int[] extractCdnaProfile(
 +          Hashtable<String, Object> hashtable,
            boolean ignoreGaps)
    {
      // this holds #seqs, #ungapped, and then codon count, indexed by encoded
        {
          break; // nothing else of interest here
        }
 +      final int percentage = codonCount * 100 / divisor;
 +      if (percentage == 0)
 +      {
 +        /*
 +         * this (and any remaining) values rounded down to 0 - discard
 +         */
 +        break;
 +      }
        distinctValuesCount++;
        result[j++] = codons[i];
 -      final int percentage = codonCount * 100 / divisor;
        result[j++] = percentage;
        totalPercentage += percentage;
      }
     *          the consensus data stores to be populated (one per column)
     */
    public static void calculateCdna(AlignmentI alignment,
 -          Hashtable[] hconsensus)
 +          Hashtable<String, Object>[] hconsensus)
    {
      final char gapCharacter = alignment.getGapCharacter();
      List<AlignedCodonFrame> mappings = alignment.getCodonFrames();
      for (int col = 0; col < cols; col++)
      {
        // todo would prefer a Java bean for consensus data
 -      Hashtable<String, int[]> columnHash = new Hashtable<>();
 +      Hashtable<String, Object> columnHash = new Hashtable<>();
        // #seqs, #ungapped seqs, counts indexed by (codon encoded + 1)
        int[] codonCounts = new int[66];
        codonCounts[0] = alignment.getSequences().size();
            {
              codonCounts[codonEncoded + 2]++;
              ungappedCount++;
+             break;
            }
          }
        }
     */
    public static void completeCdnaConsensus(
            AlignmentAnnotation consensusAnnotation,
 -          Hashtable[] consensusData, boolean showProfileLogo, int nseqs)
 +          Hashtable<String, Object>[] consensusData, boolean showProfileLogo,
 +          int nseqs)
    {
      if (consensusAnnotation == null
              || consensusAnnotation.annotations == null
      consensusAnnotation.scaleColLabel = true;
      for (int col = 0; col < consensusData.length; col++)
      {
 -      Hashtable hci = consensusData[col];
 +      Hashtable<String, Object> hci = consensusData[col];
        if (hci == null)
        {
          // gapped protein column?
@@@ -143,8 -143,8 +143,8 @@@ public class AlignmentSorte
      }
  
      // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
 -    List<SequenceI> asq;
 -    synchronized (asq = align.getSequences())
 +    List<SequenceI> asq = align.getSequences();
 +    synchronized (asq)
      {
        for (int i = 0; i < len; i++)
        {
    public static void setOrder(AlignmentI align, SequenceI[] seqs)
    {
      // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
 -    List<SequenceI> algn;
 -    synchronized (algn = align.getSequences())
 +    List<SequenceI> algn = align.getSequences();
 +    synchronized (algn)
      {
 -      List<SequenceI> tmp = new ArrayList<SequenceI>();
 +      List<SequenceI> tmp = new ArrayList<>();
  
        for (int i = 0; i < seqs.length; i++)
        {
    {
      // MAINTAINS ORIGNAL SEQUENCE ORDER,
      // ORDERS BY GROUP SIZE
 -    List<SequenceGroup> groups = new ArrayList<SequenceGroup>();
 +    List<SequenceGroup> groups = new ArrayList<>();
  
      if (groups.hashCode() != lastGroupHash)
      {
  
      // NOW ADD SEQUENCES MAINTAINING ALIGNMENT ORDER
      // /////////////////////////////////////////////
 -    List<SequenceI> seqs = new ArrayList<SequenceI>();
 +    List<SequenceI> seqs = new ArrayList<>();
  
      for (int i = 0; i < groups.size(); i++)
      {
      // tmp2 = tmp.retainAll(mask);
      // return tmp2.addAll(mask.removeAll(tmp2))
  
 -    ArrayList<SequenceI> seqs = new ArrayList<SequenceI>();
 +    ArrayList<SequenceI> seqs = new ArrayList<>();
      int i, idx;
      boolean[] tmask = new boolean[mask.size()];
  
    {
      int nSeq = align.getHeight();
  
 -    List<SequenceI> tmp = new ArrayList<SequenceI>();
 +    List<SequenceI> tmp = new ArrayList<>();
  
      tmp = _sortByTree(tree.getTopNode(), tmp, align.getSequences());
  
  
      for (int i = 0; i < alignment.length; i++)
      {
-       ids[i] = (new Float(alignment[i].getName().substring(8)))
+       ids[i] = (Float.valueOf(alignment[i].getName().substring(8)))
                .floatValue();
      }
  
@@@ -1602,12 -1602,11 +1602,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);
          String xrefName = xref.getSource() + "|" + xref.getAccessionId();
          // case-insensitive test, consistent with DBRefEntry.equalRef()
          if (xrefName.equalsIgnoreCase(name))
            // need to
            // synthesize an xref.
  
 -          for (DBRefEntry primRef : dnaDss.getPrimaryDBRefs())
 +          List<DBRefEntry> primrefs = dnaDss.getPrimaryDBRefs();
 +          for (int ip = 0, np = primrefs.size(); ip < np; ip++)
            {
 +                DBRefEntry primRef = primrefs.get(ip);
              /*
               * create a cross-reference from CDS to the source sequence's
               * primary reference and vice versa
  
              dnaSeq.addDBRef(new DBRefEntry(source, version, cdsSeq
                      .getName(), new Mapping(cdsSeqDss, dnaToCdsMap)));
 -
              // problem here is that the cross-reference is synthesized -
              // cdsSeq.getName() may be like 'CDS|dnaaccession' or
              // 'CDS|emblcdsacc'
                      .getInverse()));
              proteinProduct.addDBRef(proteinToCdsRef);
            }
 -
            /*
             * transfer any features on dna that overlap the CDS
             */
        return;
      }
  
-     MapList newMap = targetToFrom.traverse(fromLoci.getMap());
+     MapList newMap = targetToFrom.traverse(fromLoci.getMapping());
  
      if (newMap != null)
      {
      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++)
        {
 -        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))
            {
          }
        }
      }
 -    DBRefEntry[] onSource = DBRefUtils.selectRefs(
 +    List<DBRefEntry> onSource = DBRefUtils.selectRefs(
              proteinProduct.getDBRefs(),
              directSources.toArray(new String[0]));
      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();
        // 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());
  
        // 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)
        {
 -        // ignore
 +        // SwingJS -- need to avoid these.
        }
        /*
         * phase > 0 on first codon means 5' incomplete - skip to the start
      /*
       * variants in first codon base
       */
 -    for (DnaVariant var : codonVariants[0])
 +    for (DnaVariant dnavar : codonVariants[0])
      {
 -      if (var.variant != null)
 +      if (dnavar.variant != null)
        {
 -        String alleles = (String) var.variant.getValue(Gff3Helper.ALLELES);
 +        String alleles = (String) dnavar.variant.getValue(Gff3Helper.ALLELES);
          if (alleles != null)
          {
            for (String base : alleles.split(","))
                        + base3.toLowerCase();
                String canonical = base1.toUpperCase() + base2.toLowerCase()
                        + base3.toLowerCase();
 -              if (addPeptideVariant(peptide, peptidePos, residue, var,
 +              if (addPeptideVariant(peptide, peptidePos, residue, dnavar,
                        codon, canonical))
                {
                  count++;
      SequenceIdMatcher matcher = new SequenceIdMatcher(seqs);
      if (xrefs != null)
      {
 -      for (SequenceI xref : xrefs)
 +      // BH 2019.01.25 streamlined this triply nested loop to remove all 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++)
            {
 -            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;
              }
 -            SequenceI mappedTo = dbref.getMap().getTo();
 +            SequenceI mappedTo = mto;
              SequenceI match = matcher.findIdMatch(mappedTo);
              if (match == null)
              {
@@@ -30,7 -30,6 +30,7 @@@ import jalview.datamodel.Sequence
  import jalview.datamodel.SequenceI;
  import jalview.schemes.ResidueProperties;
  import jalview.util.Comparison;
 +import jalview.util.Format;
  
  import java.awt.Color;
  import java.util.List;
@@@ -55,8 -54,6 +55,8 @@@ public class Conservatio
  
    private static final int GAP_INDEX = -1;
  
 +  private static final Format FORMAT_3DP = new Format("%2.5f");
 +
    SequenceI[] sequences;
  
    int start;
  
        max = Math.max(max, bigtot);
  
-       quality.addElement(new Double(bigtot));
+       quality.addElement(Double.valueOf(bigtot));
      }
  
      double newmax = -Double.MAX_VALUE;
        tmp = ((max - tmp) * (size - cons2GapCounts[j])) / size;
  
        // System.out.println(tmp+ " " + j);
-       quality.setElementAt(new Double(tmp), j);
+       quality.setElementAt(Double.valueOf(tmp), j);
  
        if (tmp > newmax)
        {
          value = quality.elementAt(i).floatValue();
          float vprop = value - qmin;
          vprop /= qmax;
 -        quality2.annotations[i] = new Annotation(" ", String.valueOf(value),
 +        String description = FORMAT_3DP.form(value);
 +        quality2.annotations[i] = new Annotation(" ", description,
                  ' ', value, new Color(minR + (maxR * vprop),
                          minG + (maxG * vprop), minB + (maxB * vprop)));
        }
@@@ -27,6 -27,7 +27,6 @@@ import jalview.datamodel.SequenceI
  import jalview.util.Comparison;
  import jalview.util.Format;
  
 -import java.util.ArrayList;
  import java.util.Hashtable;
  
  /**
@@@ -93,11 -94,11 +93,11 @@@ public class StructureFrequenc
     * @param rnaStruc
     */
    public static final void calculate(SequenceI[] sequences, int start,
 -          int end, Hashtable[] result, boolean profile,
 +          int end, Hashtable<String, Object>[] result, boolean profile,
            AlignmentAnnotation rnaStruc)
    {
  
 -    Hashtable residueHash;
 +    Hashtable<String, Object> residueHash;
      String maxResidue;
      char[] struc = rnaStruc.getRNAStruc().toCharArray();
  
          }
        }
  
 -      residueHash = new Hashtable();
 +      residueHash = new Hashtable<>();
        if (profile)
        {
          // TODO 1-dim array with jsize in [0], nongapped in [1]; or Pojo
            maxResidue = "{";
          }
        }
-       residueHash.put(MAXCOUNT, new Integer(count));
+       residueHash.put(MAXCOUNT, Integer.valueOf(count));
        residueHash.put(MAXRESIDUE, maxResidue);
  
        percentage = ((float) count * 100) / jSize;
-       residueHash.put(PID_GAPS, new Float(percentage));
+       residueHash.put(PID_GAPS, Float.valueOf(percentage));
  
        percentage = ((float) count * 100) / nongap;
-       residueHash.put(PID_NOGAPS, new Float(percentage));
+       residueHash.put(PID_NOGAPS, Float.valueOf(percentage));
  
        if (result[i] == null)
        {
          maxResidue = maxResidue.equals("(") ? ")"
                  : maxResidue.equals("[") ? "]" : "}";
  
 -        residueHash = new Hashtable();
 +        residueHash = new Hashtable<>();
          if (profile)
          {
            residueHash.put(PROFILE,
            residueHash.put(PAIRPROFILE, pairs);
          }
  
-         residueHash.put(MAXCOUNT, new Integer(count));
+         residueHash.put(MAXCOUNT, Integer.valueOf(count));
          residueHash.put(MAXRESIDUE, maxResidue);
  
          percentage = ((float) count * 100) / jSize;
-         residueHash.put(PID_GAPS, new Float(percentage));
+         residueHash.put(PID_GAPS, Float.valueOf(percentage));
  
          percentage = ((float) count * 100) / nongap;
-         residueHash.put(PID_NOGAPS, new Float(percentage));
+         residueHash.put(PID_NOGAPS, Float.valueOf(percentage));
  
          result[bpEnd] = residueHash;
        }
     * @param includeAllConsSymbols
     */
    public static void completeConsensus(AlignmentAnnotation consensus,
 -          Hashtable[] hconsensus, int iStart, int width,
 +          Hashtable<String, Object>[] hconsensus, int iStart, int width,
            boolean ignoreGapsInConsensusCalculation,
            boolean includeAllConsSymbols, long nseq)
    {
  
      for (int i = iStart; i < width; i++)
      {
 -      Hashtable hci;
 +      Hashtable<String, Object> hci;
        if (i >= hconsensus.length || ((hci = hconsensus[i]) == null))
        {
          // happens if sequences calculated over were shorter than alignment
     * @param hconsensus
     * @return profile of the given column
     */
 -  public static int[] extractProfile(Hashtable hconsensus,
 +  public static int[] extractProfile(Hashtable<String, Object> hconsensus,
            boolean ignoreGapsInConsensusCalculation)
    {
      int[] rtnval = new int[STRUCTURE_PROFILE_LENGTH]; // 2*(5*5)+2
      System.arraycopy(rtnval, 0, result, 1, rtnval.length);
      return result;
    }
 -
 -  public static void main(String args[])
 -  {
 -    // Short test to see if checkBpType works
 -    ArrayList<String> test = new ArrayList<String>();
 -    test.add("A");
 -    test.add("c");
 -    test.add("g");
 -    test.add("T");
 -    test.add("U");
 -    for (String i : test)
 -    {
 -      for (String j : test)
 -      {
 -        System.out.println(i + "-" + j + ": "
 -                + Rna.isCanonicalOrWobblePair(i.charAt(0), j.charAt(0)));
 -      }
 -    }
 -  }
  }
@@@ -23,7 -23,6 +23,7 @@@ package jalview.api
  import jalview.analysis.Conservation;
  import jalview.analysis.TreeModel;
  import jalview.datamodel.AlignmentAnnotation;
 +import jalview.datamodel.AlignmentExportData;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.ColumnSelection;
@@@ -108,9 -107,9 +108,9 @@@ public interface AlignViewportI extend
     * 
     * @return
     */
 -  Hashtable[] getComplementConsensusHash();
 +  Hashtable<String, Object>[] getComplementConsensusHash();
  
 -  Hashtable[] getRnaStructureConsensusHash();
 +  Hashtable<String, Object>[] getRnaStructureConsensusHash();
  
    boolean isIgnoreGapsConsensus();
  
     * 
     * @param hconsensus
     */
 -  void setComplementConsensusHash(Hashtable[] hconsensus);
 +  void setComplementConsensusHash(Hashtable<String, Object>[] hconsensus);
  
    /**
     * 
     * 
     * @param hStrucConsensus
     */
 -  void setRnaStructureConsensusHash(Hashtable[] hStrucConsensus);
 +  void setRnaStructureConsensusHash(
 +          Hashtable<String, Object>[] hStrucConsensus);
  
    /**
     * Sets the colour scheme for the background alignment (as distinct from
     */
    void setFollowHighlight(boolean b);
  
+   /**
+    * configure the feature renderer with predefined feature settings
+    * 
+    * @param featureSettings
+    */
    public void applyFeaturesStyle(FeatureSettingsModelI featureSettings);
  
    /**
+    * Apply the given feature settings on top of existing feature settings.
+    */
+   public void mergeFeaturesStyle(FeatureSettingsModelI featureSettings);
+   /**
     * check if current selection group is defined on the view, or is simply a
     * temporary group.
     * 
    @Override
    void setProteinFontAsCdna(boolean b);
  
 -  public abstract TreeModel getCurrentTree();
 +  TreeModel getCurrentTree();
  
 -  public abstract void setCurrentTree(TreeModel tree);
 +  void setCurrentTree(TreeModel tree);
 +
 +  /**
 +   * Answers a data bean containing data for export as configured by the
 +   * supplied options
 +   * 
 +   * @param options
 +   * @return
 +   */
 +  AlignmentExportData getAlignExportData(AlignExportSettingsI options);
  
    /**
     * @param update
@@@ -106,6 -106,7 +106,7 @@@ import java.awt.event.KeyListener
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
  import java.io.IOException;
+ import java.io.UnsupportedEncodingException;
  import java.net.URL;
  import java.net.URLEncoder;
  import java.util.Arrays;
@@@ -416,7 -417,7 +417,7 @@@ public class AlignFrame extends Embmenu
          viewport.featureSettings.refreshTable();
        }
        alignPanel.paintAlignment(true, true);
 -      statusBar.setText(MessageManager
 +      setStatus(MessageManager
                .getString("label.successfully_added_features_alignment"));
      }
      return featuresFile;
  
      case KeyEvent.VK_F2:
        viewport.cursorMode = !viewport.cursorMode;
 -      statusBar.setText(MessageManager
 +      setStatus(MessageManager
                .formatMessage("label.keyboard_editing_mode", new String[]
                { (viewport.cursorMode ? "on" : "off") }));
        if (viewport.cursorMode)
      try
      {
        new URL(url);
-       url = URLEncoder.encode(url);
+       url = URLEncoder.encode(url, "UTF-8");
      }
      /*
       * When we finally deprecate 1.1 compatibility, we can start to use
      {
        url = viewport.applet.getCodeBase() + url;
      }
+     catch (UnsupportedEncodingException ex)
+     {
+       System.err.println(
+               "WARNING = IMPLEMENTATION ERROR - UNSUPPORTED ENCODING EXCEPTION FOR "
+                       + url);
+       ex.printStackTrace();
+     }
      return url;
    }
  
              seqs, 0, viewport.getAlignment().getWidth(),
              viewport.getAlignment()));
  
 -    viewport.getRanges().setEndSeq(viewport.getAlignment().getHeight());
 +    viewport.getRanges().setEndSeq(viewport.getAlignment().getHeight() - 1); // BH
 +                                                                             // 2019.04.18
      viewport.getAlignment().getWidth();
      viewport.firePropertyChange("alignment", null,
              viewport.getAlignment().getSequences());
                  column, al);
        }
  
 -      statusBar.setText(MessageManager
 +      setStatus(MessageManager
                .formatMessage("label.removed_columns", new String[]
                { Integer.valueOf(trimRegion.getSize()).toString() }));
        addHistoryItem(trimRegion);
  
      addHistoryItem(removeGapCols);
  
 -    statusBar.setText(MessageManager
 +    setStatus(MessageManager
              .formatMessage("label.removed_empty_columns", new String[]
              { Integer.valueOf(removeGapCols.getSize()).toString() }));
  
       */
      statusBar.setBackground(Color.white);
      statusBar.setFont(new java.awt.Font("Verdana", 0, 11));
 -    statusBar.setText(MessageManager.getString("label.status_bar"));
 +    setStatus(MessageManager.getString("label.status_bar"));
      this.add(statusBar, BorderLayout.SOUTH);
    }
  
      }
      else
      {
 -      new MCview.AppletPDBViewer(pdb, seqs, chains, alignPanel, protocol);
 +      new mc_view.AppletPDBViewer(pdb, seqs, chains, alignPanel, protocol);
      }
  
    }
@@@ -52,6 -52,8 +52,6 @@@ public class AlignViewport extends Alig
  
    public jalview.bin.JalviewLite applet;
  
 -  boolean MAC = false;
 -
    private AnnotationColumnChooser annotationColumnSelectionState;
  
    public AlignViewport(AlignmentI al, JalviewLite applet)
@@@ -71,7 -73,7 +71,7 @@@
        {
          try
          {
-           widthScale = new Float(param).floatValue();
+           widthScale = Float.valueOf(param).floatValue();
          } catch (Exception e)
          {
          }
@@@ -94,7 -96,7 +94,7 @@@
        {
          try
          {
-           heightScale = new Float(param).floatValue();
+           heightScale = Float.valueOf(param).floatValue();
          } catch (Exception e)
          {
          }
      }
      setFont(font, true);
  
 -    MAC = new jalview.util.Platform().isAMac();
 -
      if (applet != null)
      {
        setShowJVSuffix(
  
    public void resetSeqLimits(int height)
    {
 -    ranges.setEndSeq(height / getCharHeight());
 +    ranges.setEndSeq(height / getCharHeight() - 1); // BH 2019.04.18
    }
  
    boolean centreColumnLabels;
    }
  
    /**
-    * Applies the supplied feature settings descriptor to currently known
-    * features. This supports an 'initial configuration' of feature colouring
-    * based on a preset or user favourite. This may then be modified in the usual
-    * way using the Feature Settings dialogue.
+    * Applies the supplied feature settings descriptor to currently known features.
+    * This supports an 'initial configuration' of feature colouring based on a
+    * preset or user favourite. This may then be modified in the usual way using
+    * the Feature Settings dialogue. NOT IMPLEMENTED FOR APPLET
     * 
     * @param featureSettings
     */
      // TODO implement for applet
    }
  
+   /**
+    * Merges the supplied feature settings descriptor with existing feature styles.
+    * This supports an 'initial configuration' of feature colouring based on a
+    * preset or user favourite. This may then be modified in the usual way using
+    * the Feature Settings dialogue. NOT IMPLEMENTED FOR APPLET
+    * 
+    * @param featureSettings
+    */
+   @Override
+   public void mergeFeaturesStyle(FeatureSettingsModelI featureSettings)
+   {
+     // TODO Auto-generated method stub
+   }
  }
@@@ -99,14 -99,14 +99,14 @@@ public class AnnotationPanel extends Pa
  
    public static int GRAPH_HEIGHT = 40;
  
 -  boolean MAC = false;
 +//  boolean MAC = false;
  
    public final AnnotationRenderer renderer;
  
    public AnnotationPanel(AlignmentPanel ap)
    {
      new jalview.util.Platform();
 -    MAC = Platform.isAMac();
 +//    MAC = Platform.isAMac();
      this.ap = ap;
      av = ap.av;
      setLayout(null);
        }
      }
  
-     if ((evt.getModifiers()
-             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK
+     if ((evt.getModifiersEx()
+             & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK
              && activeRow != -1)
      {
        if (av.getColumnSelection() == null
@@@ -20,8 -20,6 +20,8 @@@
   */
  package jalview.appletgui;
  
 +import jalview.util.Platform;
 +
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.FlowLayout;
@@@ -51,8 -49,8 +51,8 @@@ import java.util.Map
   * @author Jim Procter and Andrew Waterhouse
   * 
   */
- @SuppressWarnings("serial")
- public class EmbmenuFrame extends Frame implements MouseListener
+ public class EmbmenuFrame extends Frame
+         implements MouseListener, AutoCloseable
  {
    protected static final Font FONT_ARIAL_PLAIN_11 = new Font("Arial",
            Font.PLAIN, 11);
@@@ -62,7 -60,7 +62,7 @@@
    /**
     * map from labels to popup menus for the embedded menubar
     */
-   protected Map<Label, PopupMenu> embeddedPopup = new HashMap<Label, PopupMenu>();
+   protected Map<Label, PopupMenu> embeddedPopup = new HashMap<>();
  
    /**
     * the embedded menu is built on this and should be added to the frame at the
@@@ -98,7 -96,7 +98,7 @@@
        return false;
      }
      // DEBUG Hint: can test embedded menus by inserting true here.
 -    if (new jalview.util.Platform().isAMac())
 +    if (Platform.isAMacAndNotJS())
      {
        // Build the embedded menu panel, allowing override with system font
        embeddedMenu = makeEmbeddedPopupMenu(topMenuBar, true, false);
      return embeddedMenu;
    }
  
+   @Override
    public void mousePressed(MouseEvent evt)
    {
      PopupMenu popup = null;
      return embeddedPopup.get(source);
    }
  
+   @Override
    public void mouseClicked(MouseEvent evt)
    {
    }
  
+   @Override
    public void mouseReleased(MouseEvent evt)
    {
    }
  
+   @Override
    public void mouseEntered(MouseEvent evt)
    {
    }
  
+   @Override
    public void mouseExited(MouseEvent evt)
    {
    }
    /**
     * calls destroyMenus()
     */
-   public void finalize() throws Throwable
+   @Override
+   public void close()
    {
      destroyMenus();
      embeddedPopup = null;
      embeddedMenu = null;
-     super.finalize();
 -    // no close for Frame
 -    // super.finalize();
    }
  }
@@@ -44,8 -44,6 +44,8 @@@ import java.awt.event.MouseListener
  import java.awt.event.MouseMotionListener;
  import java.beans.PropertyChangeEvent;
  
 +import javax.swing.SwingUtilities;
 +
  public class OverviewPanel extends Panel implements Runnable,
          MouseMotionListener, MouseListener, ViewportListenerI
  {
    @Override
    public void mouseClicked(MouseEvent evt)
    {
-     if ((evt.getModifiers()
-             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+     if ((evt.getModifiersEx()
+             & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
      {
        showPopupMenu(evt);
      }
    @Override
    public void mousePressed(MouseEvent evt)
    {
-     if ((evt.getModifiers()
-             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+     if ((evt.getModifiersEx()
+             & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
      {
 -      if (!Platform.isAMac())
 +      if (!Platform.isMac()) // BH was excluding JavaScript
        {
          showPopupMenu(evt);
        }
    @Override
    public void mouseDragged(MouseEvent evt)
    {
 -    if ((evt.getModifiersEx()
 -            & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
 +    if (Platform.isWinRightButton(evt)) 
      {
 -      if (!Platform.isAMac())
 -      {
 -        showPopupMenu(evt);
 -      }
 +      showPopupMenu(evt);
 +      return;
      }
 -    else
 -    {
 -      if (draggingBox)
 -      {
 -        // set the mouse position as a fixed point in the box
 -        // and drag relative to that position
 -        od.adjustViewportFromMouse(evt.getX(), evt.getY(),
 -                av.getAlignment().getHiddenSequences(),
 -                av.getAlignment().getHiddenColumns());
 -      }
 -      else
 -      {
 -        od.updateViewportFromMouse(evt.getX(), evt.getY(),
 -                av.getAlignment().getHiddenSequences(),
 -                av.getAlignment().getHiddenColumns());
 -      }
 -      ap.paintAlignment(false, false);
 +
 +    if (SwingUtilities.isRightMouseButton(evt))
 +    { 
 +      return;
      }
 +    
 +        if (draggingBox)
 +        {
 +          // set the mouse position as a fixed point in the box
 +          // and drag relative to that position
 +          od.adjustViewportFromMouse(evt.getX(), evt.getY(),
 +                  av.getAlignment().getHiddenSequences(),
 +                  av.getAlignment().getHiddenColumns());
 +        }
 +        else
 +        {
 +          od.updateViewportFromMouse(evt.getX(), evt.getY(),
 +                  av.getAlignment().getHiddenSequences(),
 +                  av.getAlignment().getHiddenColumns());
 +        }
 +        ap.paintAlignment(false, false);
    }
  
    /**
@@@ -42,7 -42,6 +42,7 @@@ import jalview.structure.VamsasSource
  import jalview.util.Comparison;
  import jalview.util.MappingUtils;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  import jalview.viewmodel.AlignmentViewport;
  
  import java.awt.BorderLayout;
@@@ -500,8 -499,9 +500,7 @@@ public class SeqPanel extends Panel imp
  
      // For now, ignore the mouseWheel font resizing on Macs
      // As the Button2_mask always seems to be true
-     
 -    if ((evt.getModifiersEx()
 -            & InputEvent.BUTTON2_DOWN_MASK) == InputEvent.BUTTON2_DOWN_MASK
 -            && !av.MAC)
 +    if (Platform.isWinMiddleButton(evt))
      {
        mouseWheelPressed = true;
        return;
      }
  
      // DETECT RIGHT MOUSE BUTTON IN AWT
-     if ((evt.getModifiers()
-             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+     if ((evt.getModifiersEx()
+             & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
      {
        List<SequenceFeature> allFeatures = findFeaturesAtColumn(sequence,
                sequence.findPosition(column + 1));
@@@ -28,7 -28,6 +28,7 @@@ import jalview.schemes.UserColourScheme
  import jalview.structure.StructureImportSettings;
  import jalview.urls.IdOrgSettings;
  import jalview.util.ColorUtils;
 +import jalview.util.Platform;
  import jalview.ws.sifts.SiftsSettings;
  
  import java.awt.Color;
@@@ -278,9 -277,7 +278,9 @@@ public class Cach
    /** Default file is ~/.jalview_properties */
    static String propertiesFile;
  
 -  private static boolean propsAreReadOnly = false;
 +  private static boolean propsAreReadOnly = Platform.isJS();
 +
 +  private final static String JS_PROPERTY_PREFIX = "jalview_";
  
    public static void initLogger()
    {
    public static void loadProperties(String propsFile)
    {
      propertiesFile = propsFile;
 -    if (propsFile == null)
 +    if (propsFile == null && !propsAreReadOnly)
      {
        propertiesFile = System.getProperty("user.home") + File.separatorChar
                + ".jalview_properties";
        propsAreReadOnly = true;
      }
  
 -    try
 +    if (propertiesFile == null)
 +    { // BH 2019
 +      Platform.readInfoProperties(JS_PROPERTY_PREFIX,
 +              applicationProperties);
 +    }
 +    else
      {
 -      InputStream fis;
        try
        {
 -        fis = new java.net.URL(propertiesFile).openStream();
 -        System.out.println(
 -                "Loading jalview properties from : " + propertiesFile);
 -        System.out.println(
 -                "Disabling Jalview writing to user's local properties file.");
 -        propsAreReadOnly = true;
 +        InputStream fis;
 +        try
 +        {
 +          fis = new java.net.URL(propertiesFile).openStream();
 +          System.out.println(
 +                  "Loading jalview properties from : " + propertiesFile);
 +          System.out.println(
 +                  "Disabling Jalview writing to user's local properties file.");
 +          propsAreReadOnly = true;
 +
 +        } catch (Exception ex)
 +        {
 +          fis = null;
 +        }
 +        if (fis == null)
 +        {
 +          fis = new FileInputStream(propertiesFile);
 +        }
 +        applicationProperties.clear();
 +        applicationProperties.load(fis);
 +
 +        // remove any old build properties
  
 +        deleteBuildProperties();
 +        fis.close();
        } catch (Exception ex)
        {
 -        fis = null;
 -      }
 -      if (fis == null)
 -      {
 -        fis = new FileInputStream(propertiesFile);
 +        System.out.println("Error reading properties file: " + ex);
        }
 -      applicationProperties.clear();
 -      applicationProperties.load(fis);
 -
 -      // remove any old build properties
 -
 -      deleteBuildProperties();
 -      fis.close();
 -    } catch (Exception ex)
 -    {
 -      System.out.println("Error reading properties file: " + ex);
      }
 -
      if (getDefault("USE_PROXY", false))
      {
        String proxyServer = getDefault("PROXY_SERVER", ""),
      }
  
      // LOAD THE AUTHORS FROM THE authors.props file
 +    String authorDetails = resolveResourceURLFor("authors.props");
 +
      try
      {
 -      String authorDetails = "jar:"
 -              .concat(Cache.class.getProtectionDomain().getCodeSource()
 -                      .getLocation().toString().concat("!/authors.props"));
 -
 -      java.net.URL localJarFileURL = new java.net.URL(authorDetails);
 -
 -      InputStream in = localJarFileURL.openStream();
 -      applicationProperties.load(in);
 -      in.close();
 +      if (authorDetails != null)
 +      {
 +        java.net.URL localJarFileURL = new java.net.URL(authorDetails);
 +        InputStream in = localJarFileURL.openStream();
 +        applicationProperties.load(in);
 +        in.close();
 +      }
      } catch (Exception ex)
      {
        System.out.println("Error reading author details: " + ex);
 -      applicationProperties.remove("AUTHORS");
 -      applicationProperties.remove("AUTHORFNAMES");
 -      applicationProperties.remove("YEAR");
 +      authorDetails = null;
 +    }
 +    if (authorDetails == null)
 +    {
 +        applicationProperties.remove("AUTHORS");
 +        applicationProperties.remove("AUTHORFNAMES");
 +        applicationProperties.remove("YEAR");
      }
  
-     // FIND THE VERSION NUMBER AND BUILD DATE FROM jalview.jar
-     // MUST FOLLOW READING OF LOCAL PROPERTIES FILE AS THE
-     // VERSION MAY HAVE CHANGED SINCE LAST USING JALVIEW
-     String buildDetails = resolveResourceURLFor("/.build_properties");
-     if (buildDetails != null)
-     {
-       try
-       {
-         java.net.URL localJarFileURL = new java.net.URL(buildDetails);
-         InputStream in = localJarFileURL.openStream();
-         applicationProperties.load(in);
-         in.close();
-       } catch (Exception ex)
-       {
-         System.out.println("Error reading build details: " + ex);
-         buildDetails = null;
-       }
-     }
-     if (buildDetails == null)
-     {
-         applicationProperties.remove("VERSION");
-     }
-     String jnlpVersion = System.getProperty("jalview.version");
-     String codeVersion = getProperty("VERSION");
-     String codeInstallation = getProperty("INSTALLATION");
-     if (codeVersion == null)
-     {
-       // THIS SHOULD ONLY BE THE CASE WHEN TESTING!!
-       codeVersion = "Test";
-       jnlpVersion = "Test";
-       codeInstallation = "";
-     }
-     else
-     {
-       codeInstallation = " (" + codeInstallation + ")";
-     }
-     new BuildDetails(codeVersion, null, codeInstallation);
+     loadBuildProperties(false);
  
      SiftsSettings
              .setMapWithSifts(Cache.getDefault("MAP_WITH_SIFTS", false));
              "http://www.jalview.org/services/identifiers"));
      IdOrgSettings.setDownloadLocation(ID_ORG_FILE);
  
-     System.out
-             .println("Jalview Version: " + codeVersion + codeInstallation);
      StructureImportSettings.setDefaultStructureFileFormat(jalview.bin.Cache
              .getDefault("PDB_DOWNLOAD_FORMAT", PDB_DOWNLOAD_FORMAT));
      StructureImportSettings
      // StructureImportSettings
      // .setDefaultPDBFileParser(jalview.bin.Cache.getDefault(
      // "DEFAULT_PDB_FILE_PARSER", DEFAULT_PDB_FILE_PARSER));
-     // jnlpVersion will be null if we're using InstallAnywhere
+     String jnlpVersion = System.getProperty("jalview.version");
+     // jnlpVersion will be null if a latest version check for the channel needs to
+     // be done
      // Dont do this check if running in headless mode
      if (jnlpVersion == null && getDefault("VERSION_CHECK", true)
              && (System.getProperty("java.awt.headless") == null || System
                      .getProperty("java.awt.headless").equals("false")))
      {
--
--      class VersionChecker extends Thread
++      new Thread()
        {
          @Override
          public void run()
  
            setProperty("LATEST_VERSION", remoteVersion);
          }
--      }
--
--      VersionChecker vc = new VersionChecker();
--      vc.start();
++      }.start();
      }
      else
      {
        }
      }
  
-     setProperty("VERSION", codeVersion);
      // LOAD USERDEFINED COLOURS
--    jalview.bin.Cache
--            .initUserColourSchemes(getProperty("USER_DEFINED_COLOURS"));
++    Cache.initUserColourSchemes(getProperty("USER_DEFINED_COLOURS"));
      jalview.io.PIRFile.useModellerOutput = Cache.getDefault("PIR_MODELLER",
              false);
    }
  
 +  /**
 +   * construct a resource URL for the given absolute resource pathname
 +   * 
 +   * @param resourcePath
 +   * @return
 +   */
 +  private static String resolveResourceURLFor(String resourcePath)
 +  {
 +    String url = null;
 +    if (Platform.isJS() || !Cache.class.getProtectionDomain()
 +            .getCodeSource().getLocation().toString().endsWith(".jar"))
 +    {
 +      try
 +      {
 +        url = Cache.class.getResource(resourcePath).toString();
 +      } catch (Exception ex)
 +      {
 +
 +      }
 +    }
 +    else
 +    {
 +      url = "jar:".concat(Cache.class.getProtectionDomain().getCodeSource()
 +              .getLocation().toString().concat("!" + resourcePath));
 +    }
 +    return url;
 +  }
 +
+   public static void loadBuildProperties(boolean reportVersion)
+   {
+     String codeInstallation = getProperty("INSTALLATION");
+     boolean printV = codeInstallation == null;
+     // FIND THE VERSION NUMBER AND BUILD DATE FROM jalview.jar
+     try
+     {
+       String buildDetails = "jar:".concat(Cache.class.getProtectionDomain()
+               .getCodeSource().getLocation().toString()
+               .concat("!/.build_properties"));
+       java.net.URL localJarFileURL = new java.net.URL(buildDetails);
+       InputStream in = localJarFileURL.openStream();
+       applicationProperties.load(in);
+       in.close();
+     } catch (Exception ex)
+     {
+       System.out.println("Error reading build details: " + ex);
+       applicationProperties.remove("VERSION");
+     }
+     String codeVersion = getProperty("VERSION");
+     codeInstallation = getProperty("INSTALLATION");
+     if (codeVersion == null)
+     {
+       // THIS SHOULD ONLY BE THE CASE WHEN TESTING!!
+       codeVersion = "Test";
+       codeInstallation = "";
+     }
+     else
+     {
+       codeInstallation = " (" + codeInstallation + ")";
+     }
+     setProperty("VERSION", codeVersion);
+     new BuildDetails(codeVersion, null, codeInstallation);
+     if (printV && reportVersion)
+     {
+       System.out
+             .println("Jalview Version: " + codeVersion + codeInstallation);
+     }
+   }
    private static void deleteBuildProperties()
    {
      applicationProperties.remove("LATEST_VERSION");
     */
    public static String getProperty(String key)
    {
 -    return applicationProperties.getProperty(key);
 +    String prop = applicationProperties.getProperty(key);
 +    if (prop == null && Platform.isJS())
 +    {
 +      prop = applicationProperties.getProperty(Platform.getUniqueAppletID()
 +              + "_" + JS_PROPERTY_PREFIX + key);
 +    }
 +    return prop;
    }
  
    /**
    }
  
    /**
 -   * These methods are used when checking if the saved preference is different
 -   * to the default setting
 +   * Answers the value of the given property, or the supplied default value if
 +   * the property is not set
     */
    public static String getDefault(String property, String def)
    {
 -    String string = getProperty(property);
 -    if (string != null)
 -    {
 -      return string;
 -    }
 -
 -    return def;
 +    String value = getProperty(property);
 +    return value == null ? def : value;
    }
  
    /**
      try
      {
        oldValue = applicationProperties.setProperty(key, obj);
-       if (!propsAreReadOnly)
+       if (propertiesFile != null && !propsAreReadOnly)
        {
          FileOutputStream out = new FileOutputStream(propertiesFile);
          applicationProperties.store(out, "---JalviewX Properties File---");
      }
    }
  
+   /**
+    * 
+    * @return Jalview version, build details and JVM platform version for console
+    */
+   public static String getVersionDetailsForConsole()
+   {
+     return "Jalview Version: "
+             + jalview.bin.Cache.getDefault("VERSION", "TEST")
+             + "\n" + "Jalview Installation: "
+             + jalview.bin.Cache.getDefault("INSTALLATION",
+                     "unknown")
+             + "\n" + "Build Date: "
+             + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown")
+             + "\n" + "Java version: "
+             + System.getProperty("java.version") + "\n"
+             + System.getProperty("os.arch") + " "
+             + System.getProperty("os.name") + " "
+             + System.getProperty("os.version")
+             + (jalview.bin.Cache.getDefault("VERSION", "TEST")
+                     .equals("DEVELOPMENT")
+                                                               ? "\nGetdown appdir: " + System.getProperty("getdownappdir") + "\nJava path: "
+                                     + System.getProperty(
+                                             "java.home")
+                                     + File.separator + "bin"
+                                     + File.separator + "java"
+                                                               : "");
+   }
+   /**
+    * 
+    * @return build details as reported in splashscreen
+    */
+   public static String getBuildDetailsForSplash()
+   {
+     // consider returning more human friendly info
+     // eg 'built from Source' or update channel
+     return jalview.bin.Cache.getDefault("INSTALLATION", "unknown");
+   }
  }
@@@ -60,13 -60,12 +60,15 @@@ import java.security.Policy
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Vector;
 +import java.util.logging.ConsoleHandler;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
  
  import javax.swing.LookAndFeel;
  import javax.swing.UIManager;
  
+ import com.threerings.getdown.util.LaunchUtil;
  import groovy.lang.Binding;
  import groovy.util.GroovyScriptEngine;
  
   */
  public class Jalview
  {
 -  /*
 -   * singleton instance of this class
 -   */
 +  static
 +  {
 +    Platform.getURLCommandArguments();
 +  }
 +
 +  // singleton instance of this class
 +
    private static Jalview instance;
  
    private Desktop desktop;
  
    static
    {
 -    // grab all the rights we can the JVM
 -    Policy.setPolicy(new Policy()
 +    if (!Platform.isJS())
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
      {
 -      @Override
 -      public PermissionCollection getPermissions(CodeSource codesource)
 -      {
 -        Permissions perms = new Permissions();
 -        perms.add(new AllPermission());
 -        return (perms);
 -      }
 -
 -      @Override
 -      public void refresh()
 -      {
 -      }
 -    });
 +      // grab all the rights we can for the JVM
 +          Policy.setPolicy(new Policy()
 +          {
 +            @Override
 +            public PermissionCollection getPermissions(CodeSource codesource)
 +            {
 +              Permissions perms = new Permissions();
 +              perms.add(new AllPermission());
 +              return (perms);
 +            }
 +      
 +            @Override
 +            public void refresh()
 +            {
 +            }
 +          });
 +    }
    }
  
    /**
    class FeatureFetcher
    {
      /*
-      * TODO: generalise to track all jalview events to orchestrate batch
-      * processing events.
+      * TODO: generalise to track all jalview events to orchestrate batch processing
+      * events.
       */
  
      private int queued = 0;
     * main class for Jalview application
     * 
     * @param args
-    *          open <em>filename</em>
+    *               open <em>filename</em>
     */
    public static void main(String[] args)
    {
 +//    setLogging(); // BH - for event debugging in JavaScript
      instance = new Jalview();
      instance.doMain(args);
 +}
 +
 +  private static void logClass(String name) 
 +  {   
 +        // BH - for event debugging in JavaScript
 +      ConsoleHandler consoleHandler = new ConsoleHandler();
 +      consoleHandler.setLevel(Level.ALL);
 +      Logger logger = Logger.getLogger(name);
 +      logger.setLevel(Level.ALL);
 +      logger.addHandler(consoleHandler);
 +  }
 +
 +  @SuppressWarnings("unused")
 +  private static void setLogging() 
 +  {
 +
 +    /**
 +     * @j2sIgnore
 +     * 
 +     */
 +    {
 +      System.out.println("not in js");
 +    }
 +
 +        // BH - for event debugging in JavaScript (Java mode only)
 +    if (!Platform.isJS())
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +      {
 +              Logger.getLogger("").setLevel(Level.ALL);
 +        logClass("java.awt.EventDispatchThread");
 +        logClass("java.awt.EventQueue");
 +        logClass("java.awt.Component");
 +        logClass("java.awt.focus.Component");
 +        logClass("java.awt.focus.DefaultKeyboardFocusManager"); 
 +      }       
 +
    }
 +  
 +
 +  
  
    /**
     * @param args
     */
    void doMain(String[] args)
    {
 -    System.setSecurityManager(null);
 +
 +    if (!Platform.isJS())
 +    {
 +      System.setSecurityManager(null);
 +    }
 +
      System.out
              .println("Java version: " + System.getProperty("java.version"));
      System.out.println(System.getProperty("os.arch") + " "
              + System.getProperty("os.name") + " "
              + System.getProperty("os.version"));
+     // report Jalview version
+     Cache.loadBuildProperties(true);
  
      ArgsParser aparser = new ArgsParser(args);
      boolean headless = false;
  
 -    if (aparser.contains("help") || aparser.contains("h"))
 -    {
 -      showUsage();
 -      System.exit(0);
 -    }
 -    if (aparser.contains("nodisplay") || aparser.contains("nogui")
 -            || aparser.contains("headless"))
 -    {
 -      System.setProperty("java.awt.headless", "true");
 -      headless = true;
 -    }
      String usrPropsFile = aparser.getValue("props");
      Cache.loadProperties(usrPropsFile); // must do this before
      if (usrPropsFile != null)
                "CMD [-props " + usrPropsFile + "] executed successfully!");
      }
  
 -    // anything else!
 -
 -    final String jabawsUrl = aparser.getValue("jabaws");
 -    if (jabawsUrl != null)
 +    if (!Platform.isJS())
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
      {
 -      try
 +      if (aparser.contains("help") || aparser.contains("h"))
        {
 -        Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
 -        System.out.println(
 -                "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
 -      } catch (MalformedURLException e)
 +        showUsage();
 +        System.exit(0);
 +      }
 +      if (aparser.contains("nodisplay") || aparser.contains("nogui")
 +              || aparser.contains("headless"))
        {
 -        System.err.println(
 -                "Invalid jabaws parameter: " + jabawsUrl + " ignored");
 +        System.setProperty("java.awt.headless", "true");
 +        headless = true;
 +      }
 +      // anything else!
 +
 +      final String jabawsUrl = aparser.getValue("jabaws");
 +      if (jabawsUrl != null)
 +      {
 +        try
 +        {
 +          Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
 +          System.out.println(
 +                  "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
 +        } catch (MalformedURLException e)
 +        {
 +          System.err.println(
 +                  "Invalid jabaws parameter: " + jabawsUrl + " ignored");
 +        }
        }
 -    }
  
 +    }
      String defs = aparser.getValue("setprop");
      while (defs != null)
      {
        else
        {
          System.out.println("Executing setprop argument: " + defs);
 -        // DISABLED FOR SECURITY REASONS
 -        // TODO: add a property to allow properties to be overriden by cli args
 -        // Cache.setProperty(defs.substring(0,p), defs.substring(p+1));
 +        if (Platform.isJS())
 +        {
 +          Cache.setProperty(defs.substring(0,p), defs.substring(p+1));
 +        }
        }
        defs = aparser.getValue("setprop");
      }
        System.err.println("Unexpected Look and Feel Exception");
        ex.printStackTrace();
      }
 -    if (Platform.isAMac())
 +    if (Platform.isAMacAndNotJS())
      {
  
        LookAndFeel lookAndFeel = ch.randelshofer.quaqua.QuaquaManager
                    "Failed to set QuaQua look and feel: " + e.toString());
          }
        }
-       if (lookAndFeel == null || !(lookAndFeel.getClass()
-               .isAssignableFrom(UIManager.getLookAndFeel().getClass()))
+       if (lookAndFeel == null
+               || !(lookAndFeel.getClass().isAssignableFrom(
+                       UIManager.getLookAndFeel().getClass()))
                || !UIManager.getLookAndFeel().getClass().toString()
                        .toLowerCase().contains("quaqua"))
        {
      }
  
      /*
-      * configure 'full' SO model if preferences say to, 
-      * else use the default (SO Lite)
+      * configure 'full' SO model if preferences say to, else use the default (SO
+      * Lite)
       */
-     if (Cache.getDefault("USE_FULL_SO", false))
+     if (Cache.getDefault("USE_FULL_SO", true))
      {
        SequenceOntologyFactory.setInstance(new SequenceOntology());
      }
  
      if (!headless)
      {
 -      desktop = new Desktop();
 +      desktop = new Desktop() 
 +//      {
 +// // BH testing
 +//              @Override
 +//              protected void processEvent(AWTEvent e) {
 +//                      System.out.println("Jalview.java " + e);
 +//                      super.processEvent(e);
 +//              }
 +//       }
 +      ;
        desktop.setInBatchMode(true); // indicate we are starting up
+       try
+       {
+         JalviewTaskbar.setTaskbar(this);
+       } catch (Exception e)
+       {
+         System.out.println("Cannot set Taskbar");
+         // e.printStackTrace();
+       } catch (Throwable t)
+       {
+         System.out.println("Cannot set Taskbar");
+         // t.printStackTrace();
+       }
        desktop.setVisible(true);
 -      desktop.startServiceDiscovery();
 -      if (!aparser.contains("nousagestats"))
 -      {
 -        startUsageStats(desktop);
 -      }
 -      else
 -      {
 -        System.err.println("CMD [-nousagestats] executed successfully!");
 -      }
  
 -      if (!aparser.contains("noquestionnaire"))
 +      if (!Platform.isJS())
 +      /**
 +       * Java only
 +       * 
 +       * @j2sIgnore
 +       */
        {
 -        String url = aparser.getValue("questionnaire");
 -        if (url != null)
 +        desktop.startServiceDiscovery();
 +        if (!aparser.contains("nousagestats"))
          {
 -          // Start the desktop questionnaire prompter with the specified
 -          // questionnaire
 -          Cache.log.debug("Starting questionnaire url at " + url);
 -          desktop.checkForQuestionnaire(url);
 -          System.out.println(
 -                  "CMD questionnaire[-" + url + "] executed successfully!");
 +          startUsageStats(desktop);
          }
          else
          {
 -          if (Cache.getProperty("NOQUESTIONNAIRES") == null)
 +          System.err.println("CMD [-nousagestats] executed successfully!");
 +        }
 +
 +        if (!aparser.contains("noquestionnaire"))
 +        {
 +          String url = aparser.getValue("questionnaire");
 +          if (url != null)
            {
              // Start the desktop questionnaire prompter with the specified
              // questionnaire
 -            // String defurl =
 -            // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
 -            // //
 -            String defurl = "http://www.jalview.org/cgi-bin/questionnaire.pl";
 -            Cache.log.debug(
 -                    "Starting questionnaire with default url: " + defurl);
 -            desktop.checkForQuestionnaire(defurl);
 +            Cache.log.debug("Starting questionnaire url at " + url);
 +            desktop.checkForQuestionnaire(url);
 +            System.out.println("CMD questionnaire[-" + url
 +                    + "] executed successfully!");
 +          }
 +          else
 +          {
 +            if (Cache.getProperty("NOQUESTIONNAIRES") == null)
 +            {
 +              // Start the desktop questionnaire prompter with the specified
 +              // questionnaire
 +              // String defurl =
 +              // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
 +              // //
 +              String defurl = "http://www.jalview.org/cgi-bin/questionnaire.pl";
 +              Cache.log.debug(
 +                      "Starting questionnaire with default url: " + defurl);
 +              desktop.checkForQuestionnaire(defurl);
 +            }
            }
          }
 -      }
 -      else
 -      {
 -        System.err.println("CMD [-noquestionnaire] executed successfully!");
 -      }
 +        else
 +        {
 +          System.err
 +                  .println("CMD [-noquestionnaire] executed successfully!");
 +        }
  
 -      if (!aparser.contains("nonews"))
 -      {
 -        desktop.checkForNews();
 -      }
 +        if (!aparser.contains("nonews"))
 +        {
 +          desktop.checkForNews();
 +        }
  
 -      BioJsHTMLOutput.updateBioJS();
 +        BioJsHTMLOutput.updateBioJS();
 +      }
      }
  
+     // Move any new getdown-launcher-new.jar into place over old
+     // getdown-launcher.jar
+     String appdirString = System.getProperty("getdownappdir");
+     if (appdirString != null && appdirString.length() > 0)
+     {
+       final File appdir = new File(appdirString);
+       new Thread()
+       {
+         @Override
+         public void run()
+         {
+           LaunchUtil.upgradeGetdown(
+                   new File(appdir, "getdown-launcher-old.jar"),
+                   new File(appdir, "getdown-launcher.jar"),
+                   new File(appdir, "getdown-launcher-new.jar"));
+         }
+       }.start();
+     }
      String file = null, data = null;
      FileFormatI format = null;
      DataSourceType protocol = null;
        System.out.println("No files to open!");
        System.exit(1);
      }
-     String vamsasImport = aparser.getValue("vdoc");
-     String vamsasSession = aparser.getValue("vsess");
-     if (vamsasImport != null || vamsasSession != null)
-     {
-       if (desktop == null || headless)
-       {
-         System.out.println(
-                 "Headless vamsas sessions not yet supported. Sorry.");
-         System.exit(1);
-       }
-       // if we have a file, start a new session and import it.
-       boolean inSession = false;
-       if (vamsasImport != null)
-       {
-         try
-         {
-           DataSourceType viprotocol = AppletFormatAdapter
-                   .checkProtocol(vamsasImport);
-           if (viprotocol == DataSourceType.FILE)
-           {
-             inSession = desktop.vamsasImport(new File(vamsasImport));
-           }
-           else if (viprotocol == DataSourceType.URL)
-           {
-             inSession = desktop.vamsasImport(new URL(vamsasImport));
-           }
-         } catch (Exception e)
-         {
-           System.err.println("Exeption when importing " + vamsasImport
-                   + " as a vamsas document.");
-           e.printStackTrace();
-         }
-         if (!inSession)
-         {
-           System.err.println("Failed to import " + vamsasImport
-                   + " as a vamsas document.");
-         }
-         else
-         {
-           System.out.println("Imported Successfully into new session "
-                   + desktop.getVamsasApplication().getCurrentSession());
-         }
-       }
-       if (vamsasSession != null)
-       {
-         if (vamsasImport != null)
-         {
-           // close the newly imported session and import the Jalview specific
-           // remnants into the new session later on.
-           desktop.vamsasStop_actionPerformed(null);
-         }
-         // now join the new session
-         try
-         {
-           if (desktop.joinVamsasSession(vamsasSession))
-           {
-             System.out.println(
-                     "Successfully joined vamsas session " + vamsasSession);
-           }
-           else
-           {
-             System.err.println("WARNING: Failed to join vamsas session "
-                     + vamsasSession);
-           }
-         } catch (Exception e)
-         {
-           System.err.println(
-                   "ERROR: Failed to join vamsas session " + vamsasSession);
-           e.printStackTrace();
-         }
-         if (vamsasImport != null)
-         {
-           // the Jalview specific remnants can now be imported into the new
-           // session at the user's leisure.
-           Cache.log.info(
-                   "Skipping Push for import of data into existing vamsas session."); // TODO:
-           // enable
-           // this
-           // when
-           // debugged
-           // desktop.getVamsasApplication().push_update();
-         }
-       }
-     }
      long progress = -1;
      // Finally, deal with the remaining input data.
      if (file != null)
        }
        System.out.println("CMD [-open " + file + "] executed successfully!");
  
 -      if (!file.startsWith("http://"))
 +      if (!Platform.isJS())
 +        /**
 +         * ignore in JavaScript -- can't just file existence - could load it?
 +         * 
 +         * @j2sIgnore
 +         */
        {
 -        if (!(new File(file)).exists())
 +        if (!file.startsWith("http://") && !file.startsWith("https://"))
 +        // BH 2019 added https check for Java
          {
 -          System.out.println("Can't find " + file);
 -          if (headless)
 +          if (!(new File(file)).exists())
            {
 -            System.exit(1);
 +            System.out.println("Can't find " + file);
 +            if (headless)
 +            {
 +              System.exit(1);
 +            }
            }
          }
        }
  
 -      protocol = AppletFormatAdapter.checkProtocol(file);
 +        protocol = AppletFormatAdapter.checkProtocol(file);
  
        try
        {
          {
            data.replaceAll("%20", " ");
  
-           ColourSchemeI cs = ColourSchemeProperty
-                   .getColourScheme(af.getViewport(),
-                           af.getViewport().getAlignment(), data);
+           ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
+                   af.getViewport(), af.getViewport().getAlignment(), data);
  
            if (cs != null)
            {
              continue;
            }
  
 -          if (af.saveAlignment(file, format))
 +          af.saveAlignment(file, format);
 +          if (af.isSaveAlignmentSuccessful())
            {
              System.out.println("Written alignment in " + format
                      + " format to " + file);
      // And the user
      // ////////////////////
  
 -    if (!headless && file == null
 -            && jalview.bin.Cache.getDefault("SHOW_STARTUP_FILE", true))
 +    if (!Platform.isJS() && !headless && file == null
-             && vamsasImport == null
-             && jalview.bin.Cache.getDefault("SHOW_STARTUP_FILE", true))
++            && Cache.getDefault("SHOW_STARTUP_FILE", true))
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
      {
--      file = jalview.bin.Cache.getDefault("STARTUP_FILE",
--              jalview.bin.Cache.getDefault("www.jalview.org",
++      file = Cache.getDefault("STARTUP_FILE",
++              Cache.getDefault("www.jalview.org",
                        "http://www.jalview.org")
                        + "/examples/exampleFile_2_7.jar");
        if (file.equals(
          // hardwire upgrade of the startup file
          file.replace("_2_3.jar", "_2_7.jar");
          // and remove the stale setting
--        jalview.bin.Cache.removeProperty("STARTUP_FILE");
++        Cache.removeProperty("STARTUP_FILE");
        }
  
        protocol = DataSourceType.FILE;
                      // passed in correctly)"
                      + "-jabaws URL\tSpecify URL for Jabaws services (e.g. for a local installation).\n"
                      + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
-                     // +
-                     // "-vdoc vamsas-document\tImport vamsas document into new
-                     // session or join existing session with same URN\n"
-                     // + "-vses vamsas-session\tJoin session with given URN\n"
                      + "-groovy FILE\tExecute groovy script in FILE, after all other arguments have been processed (if FILE is the text 'STDIN' then the file will be read from STDIN)\n"
                      + "\n~Read documentation in Application or visit http://www.jalview.org for description of Features and Annotations file~\n\n");
    }
     * Locate the given string as a file and pass it to the groovy interpreter.
     * 
     * @param groovyscript
-    *          the script to execute
+    *                         the script to execute
     * @param jalviewContext
-    *          the Jalview Desktop object passed in to the groovy binding as the
-    *          'Jalview' object.
+    *                         the Jalview Desktop object passed in to the groovy
+    *                         binding as the 'Jalview' object.
     */
    private void executeGroovyScript(String groovyscript, AlignFrame af)
    {
      }
      try
      {
-       Map<String, Object> vbinding = new HashMap<>();
+       Map<String, java.lang.Object> vbinding = new HashMap<>();
        vbinding.put("Jalview", this);
        if (af != null)
        {
    }
  
    /**
-    * Quit method delegates to Desktop.quit - unless running in headless mode
-    * when it just ends the JVM
+    * Quit method delegates to Desktop.quit - unless running in headless mode when
+    * it just ends the JVM
     */
    public void quit()
    {
@@@ -198,7 -198,7 +198,7 @@@ public class JalviewLite extends Apple
        int apos = -1;
        try
        {
-         apos = new Integer(position).intValue();
+         apos = Integer.valueOf(position).intValue();
          apos--;
        } catch (NumberFormatException ex)
        {
            int from = -1, to = -1;
            try
            {
-             from = new Integer(cl.substring(0, p)).intValue();
+             from = Integer.valueOf(cl.substring(0, p)).intValue();
              from--;
            } catch (NumberFormatException ex)
            {
            }
            try
            {
-             to = new Integer(cl.substring(p + 1)).intValue();
+             to = Integer.valueOf(cl.substring(p + 1)).intValue();
              to--;
            } catch (NumberFormatException ex)
            {
            int r = -1;
            try
            {
-             r = new Integer(cl).intValue();
+             r = Integer.valueOf(cl).intValue();
              r--;
            } catch (NumberFormatException ex)
            {
          try
          {
            StructureSelectionManager.getStructureSelectionManager(me)
-                   .mouseOverStructure(new Integer(pdbResNum).intValue(),
+                   .mouseOverStructure(Integer.valueOf(pdbResNum).intValue(),
                            chain, pdbfile);
            if (debug)
            {
        {
          try
          {
-           alf.scrollTo(new Integer(topRow).intValue(),
-                   new Integer(leftHandColumn).intValue());
+           alf.scrollTo(Integer.valueOf(topRow).intValue(),
+                   Integer.valueOf(leftHandColumn).intValue());
  
          } catch (Exception ex)
          {
        {
          try
          {
-           alf.scrollToRow(new Integer(topRow).intValue());
+           alf.scrollToRow(Integer.valueOf(topRow).intValue());
  
          } catch (Exception ex)
          {
        {
          try
          {
-           alf.scrollToColumn(new Integer(leftHandColumn).intValue());
+           alf.scrollToColumn(Integer.valueOf(leftHandColumn).intValue());
  
          } catch (Exception ex)
          {
    private boolean alignPdbStructures = false;
  
    /**
 -   * use an external structure viewer exclusively (no jmols or MCViews will be
 +   * use an external structure viewer exclusively (no jmols or mc_views will be
     * opened by JalviewLite itself)
     */
    public boolean useXtrnalSviewer = false;
            if (!jmolAvailable)
            {
              System.out.println(
 -                    "Jmol not available - Using MCview for structures");
 +                    "Jmol not available - Using mc_view for structures");
            }
          } catch (java.lang.ClassNotFoundException ex)
          {
          if (debug)
          {
            System.err.println(
 -                  "Skipping Jmol check. Will use MCView (probably)");
 +                  "Skipping Jmol check. Will use mc_view (probably)");
          }
        }
        checkedForJmol = true;
@@@ -619,8 -619,8 +619,8 @@@ public class EditCommand implements Com
           */
          if (command.alIndex[i] < command.al.getHeight())
          {
 -          List<SequenceI> sequences;
 -          synchronized (sequences = command.al.getSequences())
 +          List<SequenceI> sequences = command.al.getSequences();
 +          synchronized (sequences)
            {
              if (!(command.alIndex[i] < 0))
              {
              // old ds and edited ds are different, so
              // create the new dataset sequence
              SequenceI newds = new Sequence(oldds);
-             newds.setSequence(fullseq);
+             newds.setSequence(fullseq.toUpperCase());
  
              if (command.oldds == null)
              {
  
    public class Edit
    {
 -    private SequenceI[] oldds;
 +    SequenceI[] oldds;
  
      /**
       * start and end of sequence prior to edit
       */
 -    private Range[] oldStartEnd;
 +    Range[] oldStartEnd;
  
 -    private boolean fullAlignmentHeight = false;
 +    boolean fullAlignmentHeight = false;
  
 -    private Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
 +    Map<SequenceI, AlignmentAnnotation[]> deletedAnnotationRows;
  
 -    private Map<String, Annotation[]> deletedAnnotations;
 +    Map<String, Annotation[]> deletedAnnotations;
  
      /*
       * features deleted by the cut (re-add on Undo)
       * (including the original of any shortened features)
       */
 -    private Map<SequenceI, List<SequenceFeature>> deletedFeatures;
 +    Map<SequenceI, List<SequenceFeature>> deletedFeatures;
  
      /*
       * shortened features added by the cut (delete on Undo)
       */
 -    private Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
 +    Map<SequenceI, List<SequenceFeature>> truncatedFeatures;
  
 -    private AlignmentI al;
 +    AlignmentI al;
  
 -    final private Action command;
 +    final Action command;
  
      char[][] string;
  
      SequenceI[] seqs;
  
 -    private int[] alIndex;
 +    int[] alIndex;
  
 -    private int position;
 +    int position;
  
 -    private int number;
 +    int number;
  
 -    private char gapChar;
 +    char gapChar;
  
      /*
       * flag that identifies edits inserted to balance 
@@@ -166,9 -166,10 +166,10 @@@ public class AlignViewController implem
      // JBPNote this routine could also mark rows, not just columns.
      // need a decent query structure to allow all types of feature searches
      BitSet bs = new BitSet();
-     SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null
-             || extendCurrent) ? viewport.getAlignment()
-                     : viewport.getSelectionGroup();
+     boolean searchSelection = viewport.getSelectionGroup() != null
+             && !extendCurrent;
+     SequenceCollectionI sqcol = searchSelection ? viewport
+             .getSelectionGroup() : viewport.getAlignment();
  
      int nseq = findColumnsWithFeature(featureType, sqcol, bs);
  
      }
      else
      {
-       avcg.setStatus(MessageManager
-               .formatMessage("label.no_feature_of_type_found", new String[]
-               { featureType }));
+       String key = searchSelection ? "label.no_feature_found_selection"
+               : "label.no_feature_of_type_found";
+       avcg.setStatus(MessageManager.formatMessage(key,
+               new String[] { featureType }));
        if (!extendCurrent)
        {
          cs.clear();
    }
  
    @Override
 -  public boolean parseFeaturesFile(String file, DataSourceType protocol,
 +  public boolean parseFeaturesFile(Object file, DataSourceType protocol,
            boolean relaxedIdMatching)
    {
      boolean featuresAdded = false;
@@@ -47,7 -47,7 +47,7 @@@ import java.util.Vector
   * @author JimP
   * 
   */
- public class Alignment implements AlignmentI
+ public class Alignment implements AlignmentI, AutoCloseable
  {
    private Alignment dataset;
  
    {
      synchronized (sequences)
      {
 +    
        if (i > -1 && i < sequences.size())
        {
          return sequences.get(i);
    }
  
    @Override
-   public void finalize() throws Throwable
+   public void close()
    {
      if (getDataset() != null)
      {
-       getDataset().removeAlignmentRef();
+       try
+       {
+         getDataset().removeAlignmentRef();
+       } catch (Throwable e)
+       {
+         e.printStackTrace();
+       }
      }
  
      nullReferences();
-     super.finalize();
    }
  
    /**
      int i = 0;
      SequenceI sq = null;
      String sqname = null;
 +    int nseq = sequences.size();
      if (startAfter != null)
      {
        // try to find the sequence in the alignment
        boolean matched = false;
 -      while (i < sequences.size())
 +      while (i < nseq)
        {
          if (getSequenceAt(i++) == startAfter)
          {
          i = 0;
        }
      }
 -    while (i < sequences.size())
 +    while (i < nseq)
      {
        sq = getSequenceAt(i);
        sqname = sq.getName();
      int maxLength = -1;
  
      SequenceI current;
 -    for (int i = 0; i < sequences.size(); i++)
 +    int nseq = sequences.size();
 +    for (int i = 0; i < nseq; i++)
      {
        current = getSequenceAt(i);
        for (int j = current.getLength(); j > maxLength; j--)
      maxLength++;
  
      int cLength;
 -    for (int i = 0; i < sequences.size(); i++)
 +    for (int i = 0; i < nseq; i++)
      {
        current = getSequenceAt(i);
        cLength = current.getLength();
  package jalview.datamodel;
  
  import jalview.api.DBRefEntryI;
- import jalview.io.vamsas.Dbref;
 +import jalview.util.DBRefUtils;
 +import jalview.util.MapList;
  
 -import java.util.Arrays;
  import java.util.List;
  
  public class DBRefEntry implements DBRefEntryI
  {
-   /*
-    * the mapping to chromosome (genome) is held as an instance with
-    * source = speciesId
-    * version = assemblyId
-    * accessionId = "chromosome:" + chromosomeId
-    * map = mapping from sequence to reference assembly
-    */
-   public static final String CHROMOSOME = "chromosome";
-   private String source = "";
+   String source = "";
  
 -  String version = "";
 +  private String version = "";
 +  
 +  private String ucversion;
  
 -  String accessionId = "";
 +  private String accessionId = "";
 +  
 +  int sourceKey = Integer.MIN_VALUE;
  
 -  /**
 +  String canonicalSourceName;
 +
-   /**
++  /*
     * maps from associated sequence to the database sequence's coordinate system
     */
    Mapping map = null;
    {
  
    }
- /**
-  * 
-  * @param source may not be null
-  * @param version may be null
-  * @param accessionId may be null
-  */
++  /**
++   * 
++   * @param source
++   *                      may not be null
++   * @param version
++   *                      may be null
++   * @param accessionId
++   *                      may be null
++   */
    public DBRefEntry(String source, String version, String accessionId)
    {
      this(source, version, accessionId, null);
    /**
     * 
     * @param source
 -   *          canonical source (uppercase only)
 +   *          canonical source (turned to uppercase; cannot be null)
     * @param version
 -   *          (source dependent version string)
 +   *          (source dependent version string or null)
     * @param accessionId
 -   *          (source dependent accession number string)
 +   *          (source dependent accession number string or null)
     * @param map
     *          (mapping from local sequence numbering to source accession
 -   *          numbering)
 +   *          numbering or null)
     */
    public DBRefEntry(String source, String version, String accessionId,
            Mapping map)
    {
 +      
      this.source = source.toUpperCase();
 -    this.version = version;
 +    setVersion(version);
      this.accessionId = accessionId;
      this.map = map;
    }
  
 +  /**
 +   * Clone an entry, this time not allowing any null fields except map.
 +   * 
 +   */
    public DBRefEntry(DBRefEntryI entry)
    {
      this((entry.getSource() == null ? "" : new String(entry.getSource())),
    {
      // TODO should also override hashCode to ensure equal objects have equal
      // hashcodes
 -    if (o == null || !(o instanceof DBRefEntry))
 -    {
 -      return false;
 -    }
 -    DBRefEntry entry = (DBRefEntry) o;
 -    if (entry == this)
 -    {
 -      return true;
 -    }
 -    if (equalRef(entry) && ((map == null && entry.map == null)
 -            || (map != null && entry.map != null && map.equals(entry.map))))
 -    {
 -      return true;
 -    }
 -    return false;
 +        
 +        
 +//    if (o == null || !(o instanceof DBRefEntry))
 +//    {
 +//      return false;
 +//    }
 +//    DBRefEntry entry = (DBRefEntry) o;
 +//    if (entry == this)
 +//    {
 +//      return true;
 +//    }
 +    Mapping em;
 +    return (o != null && o instanceof DBRefEntry 
 +              && (o == this 
 +              || equalRef((DBRefEntry) o) 
 +                && (map == null) == ((em = ((DBRefEntry) o).map) == null) 
 +                && (map == null || map.equals(em))));
 +//            
 +//    {
 +//      return true;
 +//    }
 +//    return false;
    }
  
    /**
      {
        return true;
      }
 -    if (entry != null
 +    
 +    // BH 2019.01.25/2019.02.04  source cannot/should not be null. 
 +    // for example, StructureChooser has dbRef.getSource().equalsIgnoreCase...
 +    
 +    return (entry != null
              && (source != null && entry.getSource() != null
                      && source.equalsIgnoreCase(entry.getSource()))
              && (accessionId != null && entry.getAccessionId() != null
                      && accessionId.equalsIgnoreCase(entry.getAccessionId()))
              && (version != null && entry.getVersion() != null
 -                    && version.equalsIgnoreCase(entry.getVersion())))
 -    {
 -      return true;
 -    }
 -    return false;
 +                    && version.equalsIgnoreCase(entry.getVersion())));
    }
  
    @Override
      return source;
    }
  
 +  public int getSourceKey() 
 +  {
 +      return (sourceKey == Integer.MIN_VALUE ? (sourceKey =  DBRefSource.getSourceKey(getCanonicalSourceName())) : sourceKey);
 +  }
 +
 +  /**
 +   * can be null
 +   */
    @Override
    public String getVersion()
    {
      return version;
    }
  
 +  /**
 +   * can be null
 +   */
    @Override
    public String getAccessionId()
    {
    @Override
    public void setAccessionId(String accessionId)
    {
 -    this.accessionId = accessionId;
 +        this.accessionId = accessionId;
 +//    this.accessionId = (accessionId == null ? "" : accessionId).toUpperCase();
    }
  
 +  /**
 +   * CAUTION! allows setting source null or not uppercase!
 +   */
    @Override
    public void setSource(String source)
    {
 -    this.source = source;
 +        this.source = source;
 +        
 +//    this.source = (source == null ? "" : source).toUpperCase();
 +//    this.canonicalSourceName =      DBRefUtils.getCanonicalName(this.source);
 +//    this.sourceKey = DBRefSource.getSourceKey(this.canonicalSourceName);
    }
  
    @Override
    public void setVersion(String version)
    {
      this.version = version;
 +    this.ucversion = (version == null ? null : version.toUpperCase());
    }
  
    @Override
       */
      if (map != null)
      {
 -      if (map.getTo() != null)
 +      SequenceI mto = map.getTo();
 +      if (mto != null)
        {
          return false;
        }
 -      if (map.getMap().getFromRatio() != map.getMap().getToRatio()
 -              || map.getMap().getFromRatio() != 1)
 +      MapList ml = map.getMap();
 +      if (ml.getFromRatio() != ml.getToRatio()
 +              || ml.getFromRatio() != 1)
        {
          return false;
        }
        // check map is between identical single contiguous ranges
 -      List<int[]> fromRanges = map.getMap().getFromRanges();
 -      List<int[]> toRanges = map.getMap().getToRanges();
 -      if (fromRanges.size() != 1 || toRanges.size() != 1)
 +      List<int[]> fromRanges, toRanges;
 +      if ((fromRanges = ml.getFromRanges()).size() != 1 || (toRanges = ml.getToRanges()).size() != 1)
        {
          return false;
        }
        // no version string implies the reference has not been verified at all.
        return false;
      }
 -    // tricky - this test really needs to search the sequence's set of dbrefs to
 -    // see if there is a primary reference that derived this reference.
 -    String ucv = version.toUpperCase();
 -    for (String primsrc : Arrays.asList(DBRefSource.allSources()))
 -    {
 -      if (ucv.startsWith(primsrc.toUpperCase()))
 -      {
 -        // by convention, many secondary references inherit the primary
 -        // reference's
 -        // source string as a prefix for any version information from the
 -        // secondary reference.
 -        return false;
 -      }
 -    }
 -    return true;
 +    
 +    return DBRefSource.isPrimaryCandidate(ucversion);
 +  }
 +
 +  /**
-    * Mappings to chromosome are held with accessionId as "chromosome:id"
-    * 
-    * @return
-    */
-   public boolean isChromosome()
-   {
-     return accessionId != null && accessionId.startsWith(CHROMOSOME + ":");
-   }
-   /**
 +   * stores the upper-case canonical name of the source for use in
 +   * Sequence.getPrimaryDBRefs().
 +   * 
 +   * @author Bob Hanson
 +   * 
 +   * @return
 +   */
 +  public String getCanonicalSourceName() {
 +      return (canonicalSourceName == null ? (canonicalSourceName = DBRefUtils.getCanonicalName(this.source)) : canonicalSourceName);
    }
  }
@@@ -21,6 -21,7 +21,6 @@@
  package jalview.datamodel;
  
  import jalview.analysis.AlignSeq;
 -import jalview.api.DBRefEntryI;
  import jalview.datamodel.features.SequenceFeatures;
  import jalview.datamodel.features.SequenceFeaturesI;
  import jalview.util.Comparison;
@@@ -46,52 -47,25 +46,52 @@@ import fr.orsay.lri.varna.models.rna.RN
   */
  public class Sequence extends ASequence implements SequenceI
  {
 +
 +  /**
 +   * A subclass that gives us access to modCount, which tracks whether there
 +   * have been any changes. We use this to update
 +   * 
 +   * @author hansonr
 +   *
 +   * @param <T>
 +   */
 +  @SuppressWarnings("serial")
 +  public class DBModList<T> extends ArrayList<DBRefEntry>
 +  {
 +
 +    protected int getModCount()
 +    {
 +      return modCount;
 +    }
 +
 +  }
 +
    SequenceI datasetSequence;
  
 -  String name;
 +  private String name;
  
    private char[] sequence;
  
 -  String description;
 +  private String description;
 +
 +  private int start;
  
 -  int start;
 +  private int end;
  
 -  int end;
 +  private Vector<PDBEntry> pdbIds;
  
 -  Vector<PDBEntry> pdbIds;
 +  private String vamsasId;
  
 -  String vamsasId;
 +  private DBModList<DBRefEntry> dbrefs; // controlled access
  
 -  DBRefEntry[] dbrefs;
 +  /**
 +   * a flag to let us know that elements have changed in dbrefs
 +   * 
 +   * @author Bob Hanson
 +   */
 +  private int refModCount = 0;
  
 -  RNA rna;
 +  private RNA rna;
  
    /**
     * This annotation is displayed below the alignment but the positions are tied
@@@ -99,7 -73,7 +99,7 @@@
     *
     * TODO: change to List<>
     */
 -  Vector<AlignmentAnnotation> annotation;
 +  private Vector<AlignmentAnnotation> annotation;
  
    private SequenceFeaturesI sequenceFeatureStore;
  
      {
        setDatasetSequence(seq.getDatasetSequence());
      }
 -    
 +
      /*
       * only copy DBRefs and seqfeatures if we really are a dataset sequence
       */
      if (datasetSequence == null)
      {
 -      if (seq.getDBRefs() != null)
 +      List<DBRefEntry> dbr = seq.getDBRefs();
 +      if (dbr != null)
        {
 -        DBRefEntry[] dbr = seq.getDBRefs();
 -        for (int i = 0; i < dbr.length; i++)
 +        for (int i = 0, n = dbr.size(); i < n; i++)
          {
 -          addDBRef(new DBRefEntry(dbr[i]));
 +          addDBRef(new DBRefEntry(dbr.get(i)));
          }
        }
  
    {
      if (sf.getType() == null)
      {
 -      System.err.println("SequenceFeature type may not be null: "
 -              + sf.toString());
 +      System.err.println(
 +              "SequenceFeature type may not be null: " + sf.toString());
        return false;
      }
  
    public char[] getSequence()
    {
      // return sequence;
 -    return sequence == null ? null : Arrays.copyOf(sequence,
 -            sequence.length);
 +    return sequence == null ? null
 +            : Arrays.copyOf(sequence, sequence.length);
    }
  
    /*
    public void setGeneLoci(String speciesId, String assemblyId,
            String chromosomeId, MapList map)
    {
-     addDBRef(new DBRefEntry(speciesId, assemblyId,
-             DBRefEntry.CHROMOSOME + ":" + chromosomeId, new Mapping(map)));
+     addDBRef(new GeneLocus(speciesId, assemblyId, chromosomeId,
+             new Mapping(map)));
    }
  
    /**
    @Override
    public GeneLociI getGeneLoci()
    {
 -    DBRefEntry[] refs = getDBRefs();
 +    List<DBRefEntry> refs = getDBRefs();
      if (refs != null)
      {
        for (final DBRefEntry ref : refs)
        {
-         if (ref.isChromosome())
+         if (ref instanceof GeneLociI)
          {
-           return new GeneLociI()
-           {
-             @Override
-             public String getSpeciesId()
-             {
-               return ref.getSource();
-             }
-             @Override
-             public String getAssemblyId()
-             {
-               // DEV NOTE: DBRefEntry is reused here to hold chromosomal locus
-               // of a gene sequence.
-               // source=species, version=assemblyId, accession=chromosome, map =
-               // positions.
-               return ref.getVersion();
-             }
-             @Override
-             public String getChromosomeId()
-             {
-               // strip off "chromosome:" prefix to chrId
-               return ref.getAccessionId()
-                       .substring(DBRefEntry.CHROMOSOME.length() + 1);
-             }
-             @Override
-             public MapList getMap()
-             {
-               return ref.getMap().getMap();
-             }
-           };
+           return (GeneLociI) ref;
          }
        }
      }
      {
        return findPosition(column + 1, cursor);
      }
 -    
 +
      // TODO recode this more naturally i.e. count residues only
      // as they are found, not 'in anticipation'
  
    {
      ArrayList<int[]> map = new ArrayList<>();
      int lastj = -1, j = 0;
 -    int pos = start;
 +    // int pos = start;
      int seqlen = sequence.length;
      while ((j < seqlen))
      {
    {
      BitSet map = new BitSet();
      int lastj = -1, j = 0;
 -    int pos = start;
 +    // int pos = start;
      int seqlen = sequence.length;
      while ((j < seqlen))
      {
      vamsasId = id;
    }
  
 +  @Deprecated
    @Override
 -  public void setDBRefs(DBRefEntry[] dbref)
 +  public void setDBRefs(DBModList<DBRefEntry> newDBrefs)
    {
      if (dbrefs == null && datasetSequence != null
              && this != datasetSequence)
      {
 -      datasetSequence.setDBRefs(dbref);
 +      datasetSequence.setDBRefs(newDBrefs);
        return;
      }
 -    dbrefs = dbref;
 -    if (dbrefs != null)
 -    {
 -      DBRefUtils.ensurePrimaries(this);
 -    }
 +    dbrefs = newDBrefs;
 +    refModCount = 0;
    }
  
    @Override
 -  public DBRefEntry[] getDBRefs()
 +  public DBModList<DBRefEntry> getDBRefs()
    {
      if (dbrefs == null && datasetSequence != null
              && this != datasetSequence)
  
      if (dbrefs == null)
      {
 -      dbrefs = new DBRefEntry[0];
 +      dbrefs = new DBModList<>();
      }
  
 -    for (DBRefEntryI dbr : dbrefs)
 +    for (int ib = 0, nb = dbrefs.size(); ib < nb; ib++)
      {
 -      if (dbr.updateFrom(entry))
 +      if (dbrefs.get(ib).updateFrom(entry))
        {
          /*
           * found a dbref that either matched, or could be
        }
      }
  
 -    /*
 -     * extend the array to make room for one more
 -     */
 -    // TODO use an ArrayList instead
 -    int j = dbrefs.length;
 -    DBRefEntry[] temp = new DBRefEntry[j + 1];
 -    System.arraycopy(dbrefs, 0, temp, 0, j);
 -    temp[temp.length - 1] = entry;
 -
 -    dbrefs = temp;
 +    // /// BH OUCH!
 +    // /*
 +    // * extend the array to make room for one more
 +    // */
 +    // // TODO use an ArrayList instead
 +    // int j = dbrefs.length;
 +    // List<DBRefEntry> temp = new DBRefEntry[j + 1];
 +    // System.arraycopy(dbrefs, 0, temp, 0, j);
 +    // temp[temp.length - 1] = entry;
 +    //
 +    // dbrefs = temp;
  
 -    DBRefUtils.ensurePrimaries(this);
 +    dbrefs.add(entry);
    }
  
    @Override
  
    private int _seqhash = 0;
  
 +  private List<DBRefEntry> primaryRefs;
 +
    /**
     * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
     * true
        _isNa = Comparison.isNucleotide(this);
      }
      return !_isNa;
--  };
++  }
  
    /*
     * (non-Javadoc)
        // TODO: could merge DBRefs
        return datasetSequence.updatePDBIds();
      }
 -    if (dbrefs == null || dbrefs.length == 0)
 +    if (dbrefs == null || dbrefs.size() == 0)
      {
        return false;
      }
      boolean added = false;
 -    for (DBRefEntry dbr : dbrefs)
 +    for (int ib = 0, nb = dbrefs.size(); ib < nb; ib++)
      {
 +      DBRefEntry dbr = dbrefs.get(ib);
        if (DBRefSource.PDB.equals(dbr.getSource()))
        {
          /*
        List<SequenceFeature> sfs = entry.getSequenceFeatures();
        for (SequenceFeature feature : sfs)
        {
 -       SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
 -                : new SequenceFeature[] { new SequenceFeature(feature) };
 +        SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
 +                : new SequenceFeature[]
 +                { new SequenceFeature(feature) };
          if (sf != null)
          {
            for (int sfi = 0; sfi < sf.length; sfi++)
        }
      }
      // transfer database references
 -    DBRefEntry[] entryRefs = entry.getDBRefs();
 +    List<DBRefEntry> entryRefs = entry.getDBRefs();
      if (entryRefs != null)
      {
 -      for (int r = 0; r < entryRefs.length; r++)
 +      for (int r = 0, n = entryRefs.size(); r < n; r++)
        {
 -        DBRefEntry newref = new DBRefEntry(entryRefs[r]);
 +        DBRefEntry newref = new DBRefEntry(entryRefs.get(r));
          if (newref.getMap() != null && mp != null)
          {
            // remap ref using our local mapping
      return null;
    }
  
 +  private List<DBRefEntry> tmpList;
 +
    @Override
    public List<DBRefEntry> getPrimaryDBRefs()
    {
      {
        return datasetSequence.getPrimaryDBRefs();
      }
 -    if (dbrefs == null || dbrefs.length == 0)
 +    if (dbrefs == null || dbrefs.size() == 0)
      {
        return Collections.emptyList();
      }
      synchronized (dbrefs)
      {
 -      List<DBRefEntry> primaries = new ArrayList<>();
 -      DBRefEntry[] tmp = new DBRefEntry[1];
 -      for (DBRefEntry ref : dbrefs)
 +      if (refModCount == dbrefs.getModCount() && primaryRefs != null)
 +      {
 +        return primaryRefs; // no changes
 +      }
 +      refModCount = dbrefs.getModCount();
 +      List<DBRefEntry> primaries = (primaryRefs == null
 +              ? (primaryRefs = new ArrayList<>())
 +              : primaryRefs);
 +      primaries.clear();
 +      if (tmpList == null)
        {
 +        tmpList = new ArrayList<>();
 +        tmpList.add(null); // for replacement
 +      }
 +      for (int i = 0, n = dbrefs.size(); i < n; i++)
 +      {
 +        DBRefEntry ref = dbrefs.get(i);
          if (!ref.isPrimaryCandidate())
          {
            continue;
            }
          }
          // whilst it looks like it is a primary ref, we also sanity check type
 -        if (DBRefUtils.getCanonicalName(DBRefSource.PDB)
 -                .equals(DBRefUtils.getCanonicalName(ref.getSource())))
 +        if (DBRefSource.PDB_CANONICAL_NAME
 +                .equals(ref.getCanonicalSourceName()))
          {
            // PDB dbrefs imply there should be a PDBEntry associated
            // TODO: tighten PDB dbrefs
            // handle on the PDBEntry, and a real mapping between sequence and
            // extracted sequence from PDB file
            PDBEntry pdbentry = getPDBEntry(ref.getAccessionId());
 -          if (pdbentry != null && pdbentry.getFile() != null)
 +          if (pdbentry == null || pdbentry.getFile() == null)
            {
 -            primaries.add(ref);
 +            continue;
            }
 -          continue;
          }
 -        // check standard protein or dna sources
 -        tmp[0] = ref;
 -        DBRefEntry[] res = DBRefUtils.selectDbRefs(!isProtein(), tmp);
 -        if (res != null && res[0] == tmp[0])
 +        else
          {
 -          primaries.add(ref);
 -          continue;
 +          // check standard protein or dna sources
 +          tmpList.set(0, ref);
 +          List<DBRefEntry> res = DBRefUtils.selectDbRefs(!isProtein(),
 +                  tmpList);
 +          if (res == null || res.get(0) != tmpList.get(0))
 +          {
 +            continue;
 +          }
          }
 +        primaries.add(ref);
        }
 +
 +      // version must be not null, as otherwise it will not be a candidate,
 +      // above
 +      DBRefUtils.ensurePrimaries(this, primaries);
        return primaries;
      }
    }
@@@ -27,7 -27,6 +27,7 @@@ import jalview.datamodel.SequenceFeatur
  import jalview.datamodel.SequenceI;
  import jalview.io.gff.SequenceOntologyI;
  import jalview.util.JSONUtils;
 +import jalview.util.Platform;
  
  import java.io.BufferedReader;
  import java.io.IOException;
@@@ -36,8 -35,10 +36,8 @@@ import java.net.URL
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
 +import java.util.Map;
  
 -import org.json.simple.JSONArray;
 -import org.json.simple.JSONObject;
 -import org.json.simple.parser.JSONParser;
  import org.json.simple.parser.ParseException;
  
  /**
@@@ -91,14 -92,16 +91,14 @@@ class EnsemblFeatures extends EnsemblRe
    public AlignmentI getSequenceRecords(String query) throws IOException
    {
      // TODO: use a vararg String... for getSequenceRecords instead?
 +        
      List<String> queries = new ArrayList<>();
      queries.add(query);
 -    BufferedReader fp = getSequenceReader(queries);
 -    if (fp == null)
 -    {
 -      return null;
 -    }
 -
 -    SequenceI seq = parseFeaturesJson(fp);
 +    SequenceI seq = parseFeaturesJson(queries);
 +    if (seq == null)
 +      return null;
      return new Alignment(new SequenceI[] { seq });
 +
    }
  
    /**
     * @param br
     * @return
     */
 -  private SequenceI parseFeaturesJson(BufferedReader br)
 +  @SuppressWarnings("unchecked")
 +  private SequenceI parseFeaturesJson(List<String> queries)
    {
      SequenceI seq = new Sequence("Dummy", "");
 -
 -    JSONParser jp = new JSONParser();
      try
      {
 -      JSONArray responses = (JSONArray) jp.parse(br);
 -      Iterator rvals = responses.iterator();
 -      while (rvals.hasNext())
 +      Iterator<Object> rvals = (Iterator<Object>) getJSON(null, queries, -1, MODE_ITERATOR, null);
 +      if (rvals == null)
        {
 +        return null;
 +      }
 +      while (rvals.hasNext())
 +      {         
          try
          {
 -          JSONObject obj = (JSONObject) rvals.next();
 +          Map<String, Object> obj = (Map<String, Object>) rvals.next();
            String type = obj.get("feature_type").toString();
            int start = Integer.parseInt(obj.get("start").toString());
            int end = Integer.parseInt(obj.get("end").toString());
            String source = obj.get("source").toString();
            String strand = obj.get("strand").toString();
+           Object phase = obj.get("phase");
            String alleles = JSONUtils
 -                  .arrayToList((JSONArray) obj.get("alleles"));
 +                  .arrayToStringList((List<Object>) obj.get("alleles"));
            String clinSig = JSONUtils
 -                  .arrayToList(
 -                          (JSONArray) obj.get("clinical_significance"));
 +                  .arrayToStringList(
 +                          (List<Object>) obj.get("clinical_significance"));
  
            /*
             * convert 'variation' to 'sequence_variant', and 'cds' to 'CDS'
            {
              type = SequenceOntologyI.CDS;
            }
 -          
 +
            String desc = getFirstNotNull(obj, "alleles", "external_name",
                    JSON_ID);
            SequenceFeature sf = new SequenceFeature(type, desc, start, end,
                    source);
            sf.setStrand("1".equals(strand) ? "+" : "-");
+           if (phase != null)
+           {
+             sf.setPhase(phase.toString());
+           }
            setFeatureAttribute(sf, obj, "id");
            setFeatureAttribute(sf, obj, "Parent");
            setFeatureAttribute(sf, obj, "consequence_type");
            sf.setValue("clinical_significance", clinSig);
  
            seq.addSequenceFeature(sf);
 +          
          } catch (Throwable t)
          {
            // ignore - keep trying other features
        }
      } catch (ParseException | IOException e)
      {
 +      e.printStackTrace();
        // ignore
      }
  
     * @param keys
     * @return
     */
 -  protected String getFirstNotNull(JSONObject obj, String... keys)
 +  @SuppressWarnings("unchecked")
 +  protected String getFirstNotNull(Map<String, Object> obj, String... keys)
    {
 -    String desc = null;
 -
      for (String key : keys)
      {
        Object val = obj.get(key);
        if (val != null)
        {
 -        String s = val instanceof JSONArray
 -                ? JSONUtils.arrayToList((JSONArray) val)
 +        String s = val instanceof List<?>
 +                ? JSONUtils.arrayToStringList((List<Object>) val)
                  : val.toString();
          if (!s.isEmpty())
          {
          }
        }
      }
 -    return desc;
 +    return null;
    }
  
    /**
     * @param obj
     * @param key
     */
 -  protected void setFeatureAttribute(SequenceFeature sf, JSONObject obj,
 +  protected void setFeatureAttribute(SequenceFeature sf, Map<String, Object> obj,
            String key)
    {
      Object object = obj.get(key);
@@@ -23,7 -23,6 +23,6 @@@ package jalview.ext.ensembl
  import jalview.api.FeatureColourI;
  import jalview.api.FeatureSettingsModelI;
  import jalview.datamodel.AlignmentI;
- import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.GeneLociI;
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceFeature;
@@@ -34,7 -33,6 +33,7 @@@ import jalview.io.gff.SequenceOntologyI
  import jalview.schemes.FeatureColour;
  import jalview.schemes.FeatureSettingsAdapter;
  import jalview.util.MapList;
 +import jalview.util.Platform;
  
  import java.awt.Color;
  import java.io.UnsupportedEncodingException;
@@@ -63,6 -61,8 +62,8 @@@ public class EnsemblGene extends Ensemb
        EnsemblFeatureType.exon, EnsemblFeatureType.cds,
        EnsemblFeatureType.variation };
  
+   private static final String CHROMOSOME = "chromosome";
    /**
     * Default constructor (to use rest.ensembl.org)
     */
       * convert to a non-duplicated list of gene identifiers
       */
      List<String> geneIds = getGeneIds(query);
 -
      AlignmentI al = null;
      for (String geneId : geneIds)
      {
        {
          // ensure id has 'correct' case for the Ensembl identifier
          geneId = geneAlignment.getSequenceAt(0).getName();
 -
          findGeneLoci(geneAlignment.getSequenceAt(0), geneId);
 -
          getTranscripts(geneAlignment, geneId);
        }
        if (al == null)
      if (geneLoci != null)
      {
        seq.setGeneLoci(geneLoci.getSpeciesId(), geneLoci.getAssemblyId(),
-               geneLoci.getChromosomeId(), geneLoci.getMap());
+               geneLoci.getChromosomeId(), geneLoci.getMapping());
      }
      else
      {
        return false;
      }
      String[] tokens = description.split(":");
-     if (tokens.length == 6 && tokens[0].startsWith(DBRefEntry.CHROMOSOME))
+     if (tokens.length == 6 && tokens[0].startsWith(CHROMOSOME))
      {
        String ref = tokens[1];
        String chrom = tokens[2];
        return;
      }
  
-     MapList geneMapping = loci.getMap();
+     MapList geneMapping = loci.getMapping();
  
      List<int[]> exons = mapping.getFromRanges();
      List<int[]> transcriptLoci = new ArrayList<>();
    @Override
    public String getTestQuery()
    {
 -    return "ENSG00000157764"; // BRAF, 5 transcripts, reverse strand
 +    return Platform.isJS() ? "ENSG00000123569" : "ENSG00000157764";
 +    // ENSG00000123569 // H2BFWT histone, 2 transcripts, reverse strand
 +    // ENSG00000157764 // BRAF, 5 transcripts, reverse strand
      // ENSG00000090266 // NDUFB2, 15 transcripts, forward strand
      // ENSG00000101812 // H2BFM histone, 3 transcripts, forward strand
 -    // ENSG00000123569 // H2BFWT histone, 2 transcripts, reverse strand
    }
  
    /**
@@@ -23,16 -23,20 +23,18 @@@ package jalview.ext.ensembl
  import jalview.bin.Cache;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.GeneLociI;
+ import jalview.datamodel.GeneLocus;
+ import jalview.datamodel.Mapping;
  import jalview.util.MapList;
  
 -import java.io.BufferedReader;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.List;
 +import java.util.Map;
  
 -import org.json.simple.JSONObject;
 -import org.json.simple.parser.JSONParser;
  import org.json.simple.parser.ParseException;
  
  /**
@@@ -149,7 -153,7 +151,7 @@@ public class EnsemblLookup extends Ense
     * @param br
     * @return
     */
 -  protected String parseGeneId(JSONObject val)
 +  protected String parseGeneId(Map<String, Object> val)
    {
      if (val == null)
      {
    public String getSpecies(String identifier)
    {
      String species = null;
 -    JSONObject json = getResult(identifier, null);
 +    Map<String, Object> json = getResult(identifier, null);
      if (json != null)
      {
        Object o = json.get(SPECIES);
    }
  
    /**
 -   * Calls the /lookup/id rest service and returns the response as a JSONObject,
 +   * Calls the /lookup/id rest service and returns the response as a Map<String, Object>,
     * or null if any error
     * 
     * @param identifier
     *          (optional)
     * @return
     */
 -  protected JSONObject getResult(String identifier, String objectType)
 +  @SuppressWarnings("unchecked")
 +  protected Map<String, Object> getResult(String identifier, String objectType)
    {
      List<String> ids = Arrays.asList(new String[] { identifier });
  
 -    BufferedReader br = null;
      try
      {
 -      URL url = getUrl(identifier, objectType);
 -
 -      if (url != null)
 -      {
 -        br = getHttpResponse(url, ids);
 -      }
 -      return br == null ? null : (JSONObject) (new JSONParser().parse(br));
 -    } catch (IOException | ParseException e)
 +      return (Map<String, Object>) getJSON(getUrl(identifier, objectType), ids, -1, MODE_MAP, null);
 +    } 
 +    catch (IOException | ParseException e)
      {
        System.err.println("Error parsing " + identifier + " lookup response "
                + e.getMessage());
        return null;
 -    } finally
 -    {
 -      if (br != null)
 -      {
 -        try
 -        {
 -          br.close();
 -        } catch (IOException e)
 -        {
 -          // ignore
 -        }
 -      }
      }
    }
  
     * @param json
     * @return
     */
 -  GeneLociI parseGeneLoci(JSONObject json)
 +  GeneLociI parseGeneLoci(Map<String, Object> json)
    {
      if (json == null)
      {
            fromEnd });
        List<int[]> toRange = Collections.singletonList(new int[] { toStart,
            toEnd });
-       final MapList map = new MapList(fromRange, toRange, 1, 1);
-       return new GeneLociI()
-       {
-         @Override
-         public String getSpeciesId()
-         {
-           return species == null ? "" : species;
-         }
-         @Override
-         public String getAssemblyId()
-         {
-           return assembly;
-         }
-         @Override
-         public String getChromosomeId()
-         {
-           return chromosome;
-         }
-         @Override
-         public MapList getMap()
-         {
-           return map;
-         }
-       };
+       final Mapping map = new Mapping(
+               new MapList(fromRange, toRange, 1, 1));
+       return new GeneLocus(species == null ? "" : species, assembly,
+               chromosome, map);
      } catch (NullPointerException | NumberFormatException e)
      {
        Cache.log.error("Error looking up gene loci: " + e.getMessage());
@@@ -3,10 -3,11 +3,10 @@@ package jalview.ext.ensembl
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.DBRefSource;
  import jalview.datamodel.GeneLociI;
- import jalview.util.JSONUtils;
+ import jalview.datamodel.GeneLocus;
+ import jalview.datamodel.Mapping;
  import jalview.util.MapList;
  
--import java.io.BufferedReader;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
@@@ -14,10 -15,24 +14,22 @@@ import java.util.ArrayList
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.List;
 +import java.util.Map;
  
 -import org.json.simple.JSONArray;
 -import org.json.simple.JSONObject;
 -import org.json.simple.parser.JSONParser;
  import org.json.simple.parser.ParseException;
  
+ /**
+  * A client for the Ensembl REST service /map endpoint, to convert from
+  * coordinates of one genome assembly to another.
+  * <p>
+  * Note that species and assembly identifiers passed to this class must be valid
+  * in Ensembl. They are not case sensitive.
+  * 
+  * @author gmcarstairs
+  * @see https://rest.ensembl.org/documentation/info/assembly_map
+  * @see https://rest.ensembl.org/info/assembly/human?content-type=text/xml
+  * @see https://rest.ensembl.org/info/species?content-type=text/xml
+  */
  public class EnsemblMap extends EnsemblRestClient
  {
    private static final String MAPPED = "mapped";
            String fromRef, String toRef, int[] queryRange)
    {
      URL url = null;
 -    BufferedReader br = null;
 -
      try
      {
        url = getAssemblyMapUrl(species, chromosome, fromRef, toRef, queryRange[0],
                queryRange[1]);
 -      br = getHttpResponse(url, null);
 -      return (parseAssemblyMappingResponse(br));
 +      return (parseAssemblyMappingResponse(url));
      } catch (Throwable t)
      {
        System.out.println("Error calling " + url + ": " + t.getMessage());
        return null;
 -    } finally
 -    {
 -      if (br != null)
 -      {
 -        try
 -        {
 -          br.close();
 -        } catch (IOException e)
 -        {
 -          // ignore
 -        }
 -      }
      }
    }
  
     * @param br
     * @return
     */
 -  protected int[] parseAssemblyMappingResponse(BufferedReader br)
 +  @SuppressWarnings("unchecked")
 +  protected int[] parseAssemblyMappingResponse(URL url)
    {
      int[] result = null;
  
      try
      {
 -      JSONObject parsed = (JSONObject) jp.parse(br);
 -      JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
 -
 -      Iterator rvals = mappings.iterator();
 +      Iterator<Object> rvals = (Iterator<Object>) getJSON(url, null, -1, MODE_ITERATOR, MAPPINGS);
 +      if (rvals == null)
-         return null;
++      {
++        return null;
++      }
        while (rvals.hasNext())
        {
          // todo check for "mapped"
 -        JSONObject val = (JSONObject) rvals.next();
 -        JSONObject mapped = (JSONObject) val.get(MAPPED);
 +        Map<String, Object> val = (Map<String, Object>) rvals.next();
 +        Map<String, Object> mapped = (Map<String, Object>) val.get(MAPPED);
          int start = Integer.parseInt(mapped.get("start").toString());
          int end = Integer.parseInt(mapped.get("end").toString());
          String strand = mapped.get("strand").toString();
            int end, String cdsOrCdna)
    {
      URL url = null;
 -    BufferedReader br = null;
 -
      try
      {
        String domain = new EnsemblInfo().getDomain(division);
        if (domain != null)
        {
          url = getIdMapUrl(domain, accession, start, end, cdsOrCdna);
 -        br = getHttpResponse(url, null);
 -        if (br != null)
 -        {
 -          return (parseIdMappingResponse(br, accession, domain));
 -        }
 +        return (parseIdMappingResponse(url, accession, domain));
        }
        return null;
      } catch (Throwable t)
      {
        System.out.println("Error calling " + url + ": " + t.getMessage());
        return null;
 -    } finally
 -    {
 -      if (br != null)
 -      {
 -        try
 -        {
 -          br.close();
 -        } catch (IOException e)
 -        {
 -          // ignore
 -        }
 -      }
      }
    }
  
     * @param domain
     * @return
     */
 -  GeneLociI parseIdMappingResponse(BufferedReader br, String accession,
 +  @SuppressWarnings("unchecked")
 +GeneLociI parseIdMappingResponse(URL url, String accession,
            String domain)
    {
  
      try
      {
 -      JSONObject parsed = (JSONObject) jp.parse(br);
 -      JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
 -
 -      Iterator rvals = mappings.iterator();
 +      Iterator<Object> rvals = (Iterator<Object>) getJSON(url, null, -1, MODE_ITERATOR, MAPPINGS);
 +      if (rvals == null)
-         return null;
++      {
++        return null;
++      }
        String assembly = null;
        String chromosome = null;
        int fromEnd = 0;
  
        while (rvals.hasNext())
        {
 -        JSONObject val = (JSONObject) rvals.next();
 -        JSONObject original = (JSONObject) val.get("original");
 +        Map<String, Object> val = (Map<String, Object>) rvals.next();
 +        Map<String, Object> original = (Map<String, Object>) val.get("original");
          fromEnd = Integer.parseInt(original.get("end").toString());
  
 -        JSONObject mapped = (JSONObject) val.get(MAPPED);
 +        Map<String, Object> mapped = (Map<String, Object>) val.get(MAPPED);
          int start = Integer.parseInt(mapped.get("start").toString());
          int end = Integer.parseInt(mapped.get("end").toString());
          String ass = mapped.get("assembly_name").toString();
        final String chr = chromosome;
        List<int[]> fromRange = Collections.singletonList(new int[] { 1,
            fromEnd });
-       final MapList map = new MapList(fromRange, regions, 1, 1);
-       return new GeneLociI()
-       {
-         @Override
-         public String getSpeciesId()
-         {
-           return species == null ? "" : species;
-         }
-         @Override
-         public String getAssemblyId()
-         {
-           return as;
-         }
-         @Override
-         public String getChromosomeId()
-         {
-           return chr;
-         }
-         @Override
-         public MapList getMap()
-         {
-           return map;
-         }
-       };
+       Mapping mapping = new Mapping(new MapList(fromRange, regions, 1, 1));
+       return new GeneLocus(species == null ? "" : species, as, chr,
+               mapping);
      } catch (IOException | ParseException | NumberFormatException e)
      {
        // ignore
@@@ -44,7 -44,6 +44,6 @@@ import java.awt.event.ComponentEvent
  import java.awt.event.ComponentListener;
  import java.io.File;
  import java.net.URL;
- import java.security.AccessControlException;
  import java.util.ArrayList;
  import java.util.BitSet;
  import java.util.Hashtable;
@@@ -166,10 -165,7 +165,10 @@@ public abstract class JalviewJmolBindin
    {
      // remove listeners for all structures in viewer
      getSsm().removeStructureViewerListener(this, this.getStructureFiles());
 -    viewer.dispose();
 +    if (viewer != null)
 +    {
 +      viewer.dispose();
 +    }
      lastCommand = null;
      viewer = null;
      releaseUIResources();
      jmolHistory(false);
      if (lastCommand == null || !lastCommand.equals(command))
      {
 -      viewer.evalStringQuiet(command + "\n");
 +      jmolScript(command + "\n");
      }
      jmolHistory(true);
      lastCommand = command;
    }
  
    Thread colourby = null;
 +
    /**
     * Sends a set of colour commands to the structure viewer
     * 
        colourby.interrupt();
        colourby = null;
      }
 -    colourby = new Thread(new Runnable()
 +    Thread colourby = new Thread(new Runnable()
      {
        @Override
        public void run()
        }
      });
      colourby.start();
 +    this.colourby = colourby;
    }
  
    /**
     */
    private int _modelFileNameMap[];
  
-   // ////////////////////////////////
-   // /StructureListener
-   // @Override
-   public synchronized String[] getPdbFilex()
-   {
-     if (viewer == null)
-     {
-       return new String[0];
-     }
-     if (modelFileNames == null)
-     {
-       List<String> mset = new ArrayList<>();
-       _modelFileNameMap = new int[viewer.ms.mc];
-       String m = viewer.ms.getModelFileName(0);
-       if (m != null)
-       {
-         String filePath = m;
-         try
-         {
-           filePath = new File(m).getAbsolutePath();
-         } catch (AccessControlException x)
-         {
-           // usually not allowed to do this in applet
-           System.err.println(
-                   "jmolBinding: Using local file string from Jmol: " + m);
-         }
-         if (filePath.indexOf("/file:") != -1)
-         {
-           // applet path with docroot - discard as format won't match pdbfile
-           filePath = m;
-         }
-         mset.add(filePath);
-         _modelFileNameMap[0] = 0; // filename index for first model is always 0.
-       }
-       int j = 1;
-       for (int i = 1; i < viewer.ms.mc; i++)
-       {
-         m = viewer.ms.getModelFileName(i);
-         String filePath = m;
-         if (m != null)
-         {
-           try
-           {
-             filePath = new File(m).getAbsolutePath();
-           } catch (AccessControlException x)
-           {
-             // usually not allowed to do this in applet, so keep raw handle
-             // System.err.println("jmolBinding: Using local file string from
-             // Jmol: "+m);
-           }
-         }
-         /*
-          * add this model unless it is read from a structure file we have
-          * already seen (example: 2MJW is an NMR structure with 10 models)
-          */
-         if (!mset.contains(filePath))
-         {
-           mset.add(filePath);
-           _modelFileNameMap[j] = i; // record the model index for the filename
-           j++;
-         }
-       }
-       modelFileNames = mset.toArray(new String[mset.size()]);
-     }
-     return modelFileNames;
-   }
    @Override
    public synchronized String[] getStructureFiles()
    {
      {
        if (resetLastRes.length() > 0)
        {
 -        viewer.evalStringQuiet(resetLastRes.toString());
 +        jmolScript(resetLastRes.toString());
          resetLastRes.setLength(0);
        }
        for (AtomSpec atom : atoms)
  
      cmd.append("spacefill 200;select none");
  
 -    viewer.evalStringQuiet(cmd.toString());
 +    jmolScript(cmd.toString());
      jmolHistory(true);
  
    }
  
    private void jmolHistory(boolean enable)
    {
 -    viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
 +    jmolScript("History " + ((debug || enable) ? "on" : "off"));
    }
  
    public void loadInline(String string)
        try
        {
          // recover PDB filename for the model hovered over.
-         int mnumber = new Integer(mdlId).intValue() - 1;
+         int mnumber = Integer.valueOf(mdlId).intValue() - 1;
          if (_modelFileNameMap != null)
          {
            int _mp = _modelFileNameMap.length - 1;
    {
      /**
       * this implements the toggle label behaviour copied from the original
 -     * structure viewer, MCView
 +     * structure viewer, mc_view
       */
      if (strData != null)
      {
  
      if (!atomsPicked.contains(picked))
      {
 -      viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
 +      jmolScript("select " + picked + ";label %n %r:%c");
        atomsPicked.addElement(picked);
      }
      else
            // see JAL-623 - need method of matching pasted data up
            {
              pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
 -                    pdbfile, DataSourceType.PASTE,
 -                    getIProgressIndicator());
 +                    pdbfile, DataSourceType.PASTE, getIProgressIndicator());
              getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
              matches = true;
              foundEntry = true;
      // }
      if (!isLoadingFromArchive())
      {
 -      viewer.evalStringQuiet(
 +      jmolScript(
                "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
      }
      // register ourselves as a listener and notify the gui that it needs to
  
    public void showHelp()
    {
 -    showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
 +    showUrl("http://wiki.jmol.org"
 +    // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
 +            , "jmolHelp");
    }
  
    /**
     */
    public abstract void showConsole(boolean show);
  
 +  public static Viewer getJmolData(JmolParser jmolParser)
 +  {
 +    return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
 +            "-x -o -n", jmolParser);
 +  }
 +
    /**
 +   * 
 +   * 
 +   * 
     * @param renderPanel
     * @param jmolfileio
     *          - when true will initialise jmol's file IO system (should be false
     * @param consolePanel
     *          - panel to contain Jmol console
     * @param buttonsToShow
 -   *          - buttons to show on the console, in ordr
 +   *          - buttons to show on the console, in order
     */
    public void allocateViewer(Container renderPanel, boolean jmolfileio,
            String htmlName, URL documentBase, URL codeBase,
            String commandOptions, final Container consolePanel,
            String buttonsToShow)
    {
 +
 +    System.err.println("Allocating Jmol Viewer: " + commandOptions);
 +
      if (commandOptions == null)
      {
        commandOptions = "";
  
      viewer.setJmolStatusListener(this); // extends JmolCallbackListener
  
 -    console = createJmolConsole(consolePanel, buttonsToShow);
 +    try
 +    {
 +      console = createJmolConsole(consolePanel, buttonsToShow);
 +    } catch (Throwable e)
 +    {
 +      System.err.println("Could not create Jmol application console. "
 +              + e.getMessage());
 +      e.printStackTrace();
 +    }
      if (consolePanel != null)
      {
        consolePanel.addComponentListener(this);
    protected abstract JmolAppConsoleInterface createJmolConsole(
            Container consolePanel, String buttonsToShow);
  
 +  // BH 2018 -- Jmol console is not working due to problems with styled
 +  // documents.
 +
    protected org.jmol.api.JmolAppConsoleInterface console = null;
  
    @Override
    public void setBackgroundColour(java.awt.Color col)
    {
      jmolHistory(false);
 -    viewer.evalStringQuiet("background [" + col.getRed() + ","
 -            + col.getGreen() + "," + col.getBlue() + "];");
 +    jmolScript("background [" + col.getRed() + "," + col.getGreen() + ","
 +            + col.getBlue() + "];");
      jmolHistory(true);
    }
  
 +  private String jmolScript(String script)
 +  {
 +
 +    System.err.println(">>Jmol>> " + script);
 +
 +    String s = viewer.scriptWait(script);
 +
 +    System.err.println("<<Jmol<< " + s);
 +
 +    return s;
 +  }
 +
    @Override
    public int[] resizeInnerPanel(String data)
    {
    {
      showConsole(false);
    }
 +
  }
@@@ -20,7 -20,6 +20,7 @@@
   */
  package jalview.ext.paradise;
  
 +import jalview.util.JSONUtils;
  import jalview.util.MessageManager;
  import jalview.ws.HttpClientUtils;
  
@@@ -33,10 -32,11 +33,10 @@@ import java.net.URL
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
 +import java.util.Map;
  
  import org.apache.http.NameValuePair;
  import org.apache.http.message.BasicNameValuePair;
 -import org.json.simple.JSONArray;
 -import org.json.simple.JSONObject;
  import org.json.simple.parser.ContentHandler;
  import org.json.simple.parser.ParseException;
  
@@@ -137,13 -137,13 +137,13 @@@ public class Annotate3
    public static Iterator<Reader> getRNAMLForPDBFileAsString(String pdbfile)
            throws Exception
    {
-     List<NameValuePair> vals = new ArrayList<NameValuePair>();
+     List<NameValuePair> vals = new ArrayList<>();
      vals.add(new BasicNameValuePair("tool", "rnaview"));
      vals.add(new BasicNameValuePair("data", pdbfile));
      vals.add(new BasicNameValuePair("output", "rnaml"));
      // return processJsonResponseFor(HttpClientUtils.doHttpUrlPost(twoDtoolsURL,
      // vals));
-     ArrayList<Reader> readers = new ArrayList<Reader>();
+     ArrayList<Reader> readers = new ArrayList<>();
      final BufferedReader postResponse = HttpClientUtils
              .doHttpUrlPost(twoDtoolsURL, vals, 0, 0);
      readers.add(postResponse);
  
    }
  
 +  /**
 +   * @param respons
 +   * @return
 +   * @throws Exception
 +   */
    public static Iterator<Reader> processJsonResponseFor(Reader respons)
            throws Exception
    {
 -    org.json.simple.parser.JSONParser jp = new org.json.simple.parser.JSONParser();
 +        // BH 2019 never called?
      try
      {
 -      final JSONArray responses = (JSONArray) jp.parse(respons);
 -      final RvalsIterator rvals = new RvalsIterator(responses);
 -      return rvals;
 +      @SuppressWarnings("unchecked")
 +      final Iterator<Object> rvals = ((List<Object>) JSONUtils.parse(respons)).iterator();
-       return new Iterator<Reader>()
++      return new Iterator<>()
 +      {
 +        @Override
 +        public boolean hasNext()
 +        {
 +          return rvals.hasNext();
 +        }
 +
 +        @SuppressWarnings("unchecked")
 +              @Override
 +        public Reader next()
 +        {
 +          Map<String, Object> val = (Map<String, Object>) rvals.next();
 +
 +          Object sval = null;
 +          try
 +          {
 +            sval = val.get("2D");
 +          } catch (Exception x)
 +          {
 +            x.printStackTrace();
 +          }
-           ;
 +          if (sval == null)
 +          {
 +            System.err.println(
 +                    "DEVELOPER WARNING: Annotate3d didn't return a '2D' tag in its response. Consider checking output of server. Response was :"
 +                            + val.toString());
 +
 +            sval = "";
 +          }
 +          return new StringReader(sval.toString());
 +
 +        }
 +
 +        @Override
 +        public void remove()
 +        {
 +          throw new Error(
 +                  MessageManager.getString("error.not_implemented_remove"));
 +
 +        }
 +
 +        @Override
 +        protected Object clone() throws CloneNotSupportedException
 +        {
 +          throw new CloneNotSupportedException(
 +                  MessageManager.getString("error.not_implemented_clone"));
 +        }
 +
 +        @Override
 +        public boolean equals(Object obj)
 +        {
 +          return super.equals(obj);
 +        }
 +
 +        @Override
 +        protected void finalize() throws Throwable
 +        {
 +          while (rvals.hasNext())
 +          {
 +            rvals.next();
 +          }
 +          super.finalize();
 +        }
 +      };
      } catch (Exception foo)
      {
        throw new Exception(MessageManager.getString(
                "exception.couldnt_parse_responde_from_annotated3d_server"),
                foo);
      }
--
    }
  
    public static Iterator<Reader> getRNAMLForPDBId(String pdbid)
            throws Exception
    {
-     List<NameValuePair> vals = new ArrayList<NameValuePair>();
+     List<NameValuePair> vals = new ArrayList<>();
      vals.add(new BasicNameValuePair("tool", "rnaview"));
      vals.add(new BasicNameValuePair("pdbid", pdbid));
      vals.add(new BasicNameValuePair("output", "rnaml"));
              + pdbid + "&output=rnaml");
      // return processJsonResponseFor(new
      // InputStreamReader(geturl.openStream()));
-     ArrayList<Reader> readers = new ArrayList<Reader>();
+     ArrayList<Reader> readers = new ArrayList<>();
      readers.add(new InputStreamReader(geturl.openStream()));
      return readers.iterator();
    }
  
  }
 -
 -class RvalsIterator implements Iterator, AutoCloseable
 -{
 -  private Iterator rvals;
 -
 -  protected RvalsIterator(JSONArray responses)
 -  {
 -    this.rvals = responses.iterator();
 -  }
 -
 -  @Override
 -  public boolean hasNext()
 -  {
 -    return rvals.hasNext();
 -  }
 -
 -  @Override
 -  public Reader next()
 -  {
 -    JSONObject val = (JSONObject) rvals.next();
 -
 -    Object sval = null;
 -    try
 -    {
 -      sval = val.get("2D");
 -    } catch (Exception x)
 -    {
 -      x.printStackTrace();
 -    }
 -    ;
 -    if (sval == null)
 -    {
 -      System.err.println(
 -              "DEVELOPER WARNING: Annotate3d didn't return a '2D' tag in its response. Consider checking output of server. Response was :"
 -                      + val.toString());
 -
 -      sval = "";
 -    }
 -    return new StringReader(
 -            (sval instanceof JSONObject) ? ((JSONObject) sval).toString()
 -                    : sval.toString());
 -
 -  }
 -
 -  @Override
 -  public void remove()
 -  {
 -    throw new Error(
 -            MessageManager.getString("error.not_implemented_remove"));
 -
 -  }
 -
 -  @Override
 -  protected Object clone() throws CloneNotSupportedException
 -  {
 -    throw new CloneNotSupportedException(
 -            MessageManager.getString("error.not_implemented_clone"));
 -  }
 -
 -  @Override
 -  public boolean equals(Object obj)
 -  {
 -    return super.equals(obj);
 -  }
 -
 -  @Override
 -  public void close()
 -  {
 -    while (rvals.hasNext())
 -    {
 -      rvals.next();
 -    }
 -  }
 -}
@@@ -43,13 -43,13 +43,13 @@@ import java.util.Objects
   */
  public abstract class FTSRestClient implements FTSRestClientI
  {
-   protected Collection<FTSDataColumnI> dataColumns = new ArrayList<FTSDataColumnI>();
+   protected Collection<FTSDataColumnI> dataColumns = new ArrayList<>();
  
-   protected Collection<FTSDataColumnGroupI> dataColumnGroups = new ArrayList<FTSDataColumnGroupI>();
+   protected Collection<FTSDataColumnGroupI> dataColumnGroups = new ArrayList<>();
  
-   protected Collection<FTSDataColumnI> searchableDataColumns = new ArrayList<FTSDataColumnI>();
+   protected Collection<FTSDataColumnI> searchableDataColumns = new ArrayList<>();
  
-   protected Collection<FTSDataColumnI> defaulDisplayedDataColumns = new ArrayList<FTSDataColumnI>();
+   protected Collection<FTSDataColumnI> defaulDisplayedDataColumns = new ArrayList<>();
  
    protected FTSDataColumnI primaryKeyColumn;
  
@@@ -68,7 -68,7 +68,7 @@@
  
      InputStream in = getClass().getResourceAsStream(fileName);
  
 -    try (BufferedReader br = new BufferedReader(new InputStreamReader(in)))
 +    try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) 
      {
        String line;
        while ((line = br.readLine()) != null)
      throw new Exception("Couldn't find data column group with id : " + id);
    }
  
-   public String getMessageByHTTPStatusCode(int code, String service)
+   public static String getMessageByHTTPStatusCode(int code, String service)
    {
      String message = "";
      switch (code)
      {
      case 400:
-       message = MessageManager.getString("exception.bad_request");
+       message = "Bad request. There is a problem with your input.";
        break;
  
      case 410:
        message = MessageManager.formatMessage(
-               "exception.fts_rest_service_no_longer_available", service);
+               service + " rest services no longer available!");
        break;
      case 403:
      case 404:
-       message = MessageManager.getString("exception.resource_not_be_found");
+       message = "The requested resource could not be found";
        break;
      case 408:
      case 409:
      case 502:
      case 504:
      case 505:
-       message = MessageManager.formatMessage("exception.fts_server_error",
-               service);
+       message = "There seems to be an error from the " + service
+               + " server";
        break;
      case 503:
-       message = MessageManager.getString("exception.service_not_available");
+       message = "Service not available. The server is being updated, try again later.";
        break;
      default:
        break;
      }
-     return message;
+     return String.valueOf(code) + " " + message;
    }
  
    protected String getResourceFile(String fileName)
@@@ -27,31 -27,32 +27,31 @@@ import jalview.fts.api.FTSRestClientI
  import jalview.fts.core.FTSRestClient;
  import jalview.fts.core.FTSRestRequest;
  import jalview.fts.core.FTSRestResponse;
 +import jalview.util.JSONUtils;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  
 +import java.net.URI;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.List;
 +import java.util.Map;
  import java.util.Objects;
  
  import javax.ws.rs.core.MediaType;
  
 -import org.json.simple.JSONArray;
 -import org.json.simple.JSONObject;
 -import org.json.simple.parser.JSONParser;
  import org.json.simple.parser.ParseException;
  
  import com.sun.jersey.api.client.Client;
  import com.sun.jersey.api.client.ClientResponse;
  import com.sun.jersey.api.client.WebResource;
 -import com.sun.jersey.api.client.config.ClientConfig;
  import com.sun.jersey.api.client.config.DefaultClientConfig;
  
  /**
   * A rest client for querying the Search endpoint of the PDB API
   * 
   * @author tcnofoegbu
 - *
   */
  public class PDBFTSRestClient extends FTSRestClient
  {
     * @return the pdbResponse object for the given request
     * @throws Exception
     */
 +  @SuppressWarnings({ "unused", "unchecked" })
    @Override
    public FTSRestResponse executeRequest(FTSRestRequest pdbRestRequest)
            throws Exception
    {
      try
      {
 -      ClientConfig clientConfig = new DefaultClientConfig();
 -      Client client = Client.create(clientConfig);
 -
        String wantedFields = getDataColumnsFieldsAsCommaDelimitedString(
                pdbRestRequest.getWantedFields());
        int responseSize = (pdbRestRequest.getResponseSize() == 0)
                        : " AND status:REL");
  
        // Build request parameters for the REST Request
 -      WebResource webResource = null;
 +
 +      // BH 2018 the trick here is to coerce the classes in Javascript to be 
 +      // different from the ones in Java yet still allow this to be correct for Java
 +      Client client;
 +      Class<ClientResponse> clientResponseClass;
 +      if (Platform.isJS())
 +      {
 +        // JavaScript only -- coerce types to Java types for Java
 +        client = (Client) (Object) new jalview.javascript.web.Client();
 +        clientResponseClass = (Class<ClientResponse>) (Object) jalview.javascript.web.ClientResponse.class;
 +      }
 +      else
 +      /**
 +       * Java only
 +       * 
 +       * @j2sIgnore
 +       */
 +      {
 +        client = Client.create(new DefaultClientConfig());
 +        clientResponseClass = ClientResponse.class;
 +      }
 +
 +      WebResource webResource;
        if (pdbRestRequest.isFacet())
        {
          webResource = client.resource(PDB_SEARCH_ENDPOINT)
                  .queryParam("start", String.valueOf(offSet))
                  .queryParam("q", query).queryParam("sort", sortParam);
        }
 +
 +      URI uri = webResource.getURI();
 +
 +      System.out.println(uri);
 +
        // Execute the REST request
        ClientResponse clientResponse = webResource
 -              .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
 +              .accept(MediaType.APPLICATION_JSON).get(clientResponseClass );
 +
-       int status = clientResponse.getStatus();
 +      // Get the JSON string from the response object or directly from the
 +      // client (JavaScript)
-       Map<String, Object> jsonObj = (Platform.isJS() && status == 200
-               ? clientResponse.getEntity(Map.class)
-               : null);
-       String responseString = (jsonObj == null
-               ? clientResponse.getEntity(String.class)
-               : null);
++      Map<String, Object> jsonObj = null;
++      String responseString = null;
  
        // System.out.println("query >>>>>>> " + pdbRestRequest.toString());
  
        // Check the response status and report exception if one occurs
-       switch (status)
+       int responseStatus = clientResponse.getStatus();
 -      if (responseStatus != 200)
++      switch (responseStatus)
        {
 -        String errorMessage = "";
 -        if (responseStatus == 400)
 +      case 200:
++        if (Platform.isJS())
+         {
 -          errorMessage = parseJsonExceptionString(responseString);
 -          throw new Exception(errorMessage);
++          jsonObj = clientResponse.getEntity(Map.class);
+         }
+         else
+         {
 -          errorMessage = getMessageByHTTPStatusCode(
 -                  responseStatus, "PDB");
 -          throw new Exception(errorMessage);
++          responseString = clientResponse.getEntity(String.class);
+         }
 +        break;
 +      case 400:
 +        throw new Exception(parseJsonExceptionString(responseString));
 +      default:
-         throw new Exception(getMessageByHTTPStatusCode(status, "PDB"));
++        throw new Exception(
++                getMessageByHTTPStatusCode(responseStatus, "PDB"));
        }
  
--      // Make redundant objects eligible for garbage collection to conserve
--      // memory
--      clientResponse = null;
--      client = null;
--
        // Process the response and return the result to the caller.
 -      return parsePDBJsonResponse(responseString, pdbRestRequest);
 +      return parsePDBJsonResponse(responseString, jsonObj, pdbRestRequest);
      } catch (Exception e)
      {
        String exceptionMsg = e.getMessage();
     *          the JSON string containing error message from the server
     * @return the processed error message from the JSON string
     */
 -  public static String parseJsonExceptionString(String jsonErrorResponse)
 +  @SuppressWarnings("unchecked")
 +public static String parseJsonExceptionString(String jsonErrorResponse)
    {
      StringBuilder errorMessage = new StringBuilder(
              "\n============= PDB Rest Client RunTime error =============\n");
  
 +    
 +//    {
 +//      "responseHeader":{
 +//        "status":0,
 +//        "QTime":0,
 +//        "params":{
 +//          "q":"(text:q93xj9_soltu) AND molecule_sequence:['' TO *] AND status:REL",
 +//          "fl":"pdb_id,title,experimental_method,resolution",
 +//          "start":"0",
 +//          "sort":"overall_quality desc",
 +//          "rows":"500",
 +//          "wt":"json"}},
 +//      "response":{"numFound":1,"start":0,"docs":[
 +//          {
 +//            "experimental_method":["X-ray diffraction"],
 +//            "pdb_id":"4zhp",
 +//            "resolution":2.46,
 +//            "title":"The crystal structure of Potato ferredoxin I with 2Fe-2S cluster"}]
 +//      }}
 +//    
      try
      {
 -      JSONParser jsonParser = new JSONParser();
 -      JSONObject jsonObj = (JSONObject) jsonParser.parse(jsonErrorResponse);
 -      JSONObject errorResponse = (JSONObject) jsonObj.get("error");
 +      Map<String, Object> jsonObj = (Map<String, Object>) JSONUtils.parse(jsonErrorResponse);
 +      Map<String, Object> errorResponse = (Map<String, Object>) jsonObj.get("error");
  
 -      JSONObject responseHeader = (JSONObject) jsonObj
 +      Map<String, Object> responseHeader = (Map<String, Object>) jsonObj
                .get("responseHeader");
 -      JSONObject paramsObj = (JSONObject) responseHeader.get("params");
 +      Map<String, Object> paramsObj = (Map<String, Object>) responseHeader.get("params");
        String status = responseHeader.get("status").toString();
        String message = errorResponse.get("msg").toString();
        String query = paramsObj.get("q").toString();
     *          JSON string
     * @return
     */
    public static FTSRestResponse parsePDBJsonResponse(
            String pdbJsonResponseString, FTSRestRequest pdbRestRequest)
    {
 +    return parsePDBJsonResponse(pdbJsonResponseString,
 +            (Map<String, Object>) null, pdbRestRequest);
 +  }
 +
 +  @SuppressWarnings("unchecked")
 +  public static FTSRestResponse parsePDBJsonResponse(
 +          String pdbJsonResponseString, Map<String, Object> jsonObj,
 +          FTSRestRequest pdbRestRequest)
 +  {
      FTSRestResponse searchResult = new FTSRestResponse();
      List<FTSData> result = null;
      try
      {
 -      JSONParser jsonParser = new JSONParser();
 -      JSONObject jsonObj = (JSONObject) jsonParser
 -              .parse(pdbJsonResponseString);
 -
 -      JSONObject pdbResponse = (JSONObject) jsonObj.get("response");
 -      String queryTime = ((JSONObject) jsonObj.get("responseHeader"))
 +      if (jsonObj == null)
 +      {
 +        jsonObj = (Map<String, Object>) JSONUtils.parse(pdbJsonResponseString);
 +      }
 +      Map<String, Object> pdbResponse = (Map<String, Object>) jsonObj.get("response");
 +      String queryTime = ((Map<String, Object>) jsonObj.get("responseHeader"))
                .get("QTime").toString();
        int numFound = Integer
                .valueOf(pdbResponse.get("numFound").toString());
        if (numFound > 0)
        {
 -        result = new ArrayList<FTSData>();
 -        JSONArray docs = (JSONArray) pdbResponse.get("docs");
 -        for (Iterator<JSONObject> docIter = docs.iterator(); docIter
 +        result = new ArrayList<>();
 +        List<Object> docs = (List<Object>) pdbResponse.get("docs");
 +        for (Iterator<Object> docIter = docs.iterator(); docIter
                  .hasNext();)
          {
 -          JSONObject doc = docIter.next();
 +          Map<String, Object> doc = (Map<String, Object>) docIter.next();
            result.add(getFTSData(doc, pdbRestRequest));
          }
          searchResult.setNumberOfItemsFound(numFound);
      return searchResult;
    }
  
 -  public static FTSData getFTSData(JSONObject pdbJsonDoc,
 +  public static FTSData getFTSData(Map<String, Object> pdbJsonDoc,
            FTSRestRequest request)
    {
  
      if (allDefaultDisplayedStructureDataColumns == null
              || allDefaultDisplayedStructureDataColumns.isEmpty())
      {
 -      allDefaultDisplayedStructureDataColumns = new ArrayList<FTSDataColumnI>();
 +      allDefaultDisplayedStructureDataColumns = new ArrayList<>();
        allDefaultDisplayedStructureDataColumns
                .addAll(super.getAllDefaultDisplayedFTSDataColumns());
      }
      return allDefaultDisplayedStructureDataColumns;
    }
 +  
 +  
  }
index 0000000,05f5e08..c5a1b66
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,151 +1,151 @@@
+ package jalview.gui;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
+ import java.awt.Desktop;
+ import java.awt.desktop.AboutEvent;
+ import java.awt.desktop.AboutHandler;
+ import java.awt.desktop.PreferencesEvent;
+ import java.awt.desktop.PreferencesHandler;
+ import java.awt.desktop.QuitEvent;
+ import java.awt.desktop.QuitHandler;
+ import java.awt.desktop.QuitResponse;
+ import java.awt.desktop.QuitStrategy;
+ import javax.swing.JOptionPane;
+ public class APQHandlers
+ {
+   private static boolean setAPQHandlers = false;
+   public APQHandlers() {
+   }
+   protected static boolean setAPQHandlers(jalview.gui.Desktop jalviewDesktop)
+   {
+     // flagging this test to avoid unnecessary reflection
+     if (!setAPQHandlers)
+     {
+       // see if the Quit, About and Preferences handlers are available
+       Class desktopClass = Desktop.class;
+       Desktop hdesktop = Desktop.getDesktop();
+       try
+       {
+         Float specversion = Float.parseFloat(
+                 System.getProperty("java.specification.version"));
+         if (specversion >= 9)
+         {
 -          if (Platform.isAMac())
++          if (Platform.isAMacAndNotJS())
+           {
+             if (desktopClass.getDeclaredMethod("setAboutHandler",
+                     new Class[]
+                     { AboutHandler.class }) != null)
+             {
+               hdesktop.setAboutHandler(new AboutHandler()
+               {
+                 @Override
+                 public void handleAbout(AboutEvent e)
+                 {
+                   jalviewDesktop.aboutMenuItem_actionPerformed(null);
+                 }
+               });
+             }
+             if (desktopClass.getDeclaredMethod("setPreferencesHandler",
+                     new Class[]
+                     { PreferencesHandler.class }) != null)
+             {
+               hdesktop.setPreferencesHandler(
+                       new PreferencesHandler()
+               {
+                         @Override
+                         public void handlePreferences(
+                                 PreferencesEvent e)
+                         {
+                           jalviewDesktop.preferences_actionPerformed(null);
+                         }
+                       });
+             }
+             if (desktopClass.getDeclaredMethod("setQuitHandler",
+                     new Class[]
+                     { QuitHandler.class }) != null)
+             {
+               hdesktop.setQuitHandler(new QuitHandler()
+               {
+                 @Override
+                 public void handleQuitRequestWith(
+                         QuitEvent e, QuitResponse r)
+                 {
+                   boolean confirmQuit = jalview.bin.Cache
+                           .getDefault(
+                                   jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT,
+                                   true);
+                   int n;
+                   if (confirmQuit)
+                   {
+                     n = JOptionPane.showConfirmDialog(null,
+                             MessageManager.getString("label.quit_jalview"),
+                             MessageManager.getString("action.quit"),
+                             JOptionPane.OK_CANCEL_OPTION,
+                             JOptionPane.PLAIN_MESSAGE, null);
+                   }
+                   else
+                   {
+                     n = JOptionPane.OK_OPTION;
+                   }
+                   if (n == JOptionPane.OK_OPTION)
+                   {
+                     System.out.println("Shortcut Quit confirmed by user");
+                     jalviewDesktop.quit();
+                     r.performQuit(); // probably won't reach this line, but just
+                                      // in
+                                      // case
+                   }
+                   else
+                   {
+                     r.cancelQuit();
+                     System.out.println("Shortcut Quit cancelled by user");
+                   }
+                 }
+               });
+               hdesktop.setQuitStrategy(
+                       QuitStrategy.CLOSE_ALL_WINDOWS);
+             }
+           }
+           setAPQHandlers = true;
+         }
+         else
+         {
+           System.out.println(
+                   "Not going to try setting APQ Handlers as java.spec.version is "
+                           + specversion);
+         }
+       } catch (Exception e)
+       {
+         System.out.println(
+                 "Exception when looking for About, Preferences, Quit Handlers");
+         // e.printStackTrace();
+       } catch (Throwable t)
+       {
+         System.out.println(
+                 "Throwable when looking for About, Preferences, Quit Handlers");
+         // t.printStackTrace();
+       }
+     }
+     
+     return setAPQHandlers;
+   }
+ }
@@@ -27,12 -27,13 +27,13 @@@ import jalview.analysis.Dna
  import jalview.analysis.GeneticCodeI;
  import jalview.analysis.ParseProperties;
  import jalview.analysis.SequenceIdMatcher;
 -import jalview.api.AlignExportSettingI;
 +import jalview.api.AlignExportSettingsI;
  import jalview.api.AlignViewControllerGuiI;
  import jalview.api.AlignViewControllerI;
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
  import jalview.api.FeatureSettingsControllerI;
+ import jalview.api.FeatureSettingsModelI;
  import jalview.api.SplitContainerI;
  import jalview.api.ViewStyleI;
  import jalview.api.analysis.SimilarityParamsI;
@@@ -46,7 -47,6 +47,7 @@@ import jalview.commands.RemoveGapColCom
  import jalview.commands.RemoveGapsCommand;
  import jalview.commands.SlideSequencesCommand;
  import jalview.commands.TrimRegionCommand;
 +import jalview.datamodel.AlignExportSettingsAdapter;
  import jalview.datamodel.AlignedCodonFrame;
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentAnnotation;
@@@ -56,6 -56,7 +57,6 @@@ import jalview.datamodel.AlignmentOrder
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.HiddenColumns;
 -import jalview.datamodel.HiddenSequences;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SeqCigar;
  import jalview.datamodel.Sequence;
@@@ -85,14 -86,11 +86,14 @@@ import jalview.io.ScoreMatrixFile
  import jalview.io.TCoffeeScoreFile;
  import jalview.io.vcf.VCFLoader;
  import jalview.jbgui.GAlignFrame;
 +import jalview.project.Jalview2XML;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ColourSchemes;
  import jalview.schemes.ResidueColourScheme;
  import jalview.schemes.TCoffeeColourScheme;
 +import jalview.util.ImageMaker.TYPE;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  import jalview.viewmodel.AlignmentViewport;
  import jalview.viewmodel.ViewportRanges;
  import jalview.ws.DBRefFetcher;
@@@ -103,7 -101,6 +104,7 @@@ import jalview.ws.jws2.jabaws2.Jws2Inst
  import jalview.ws.seqfetcher.DbSourceProxy;
  
  import java.awt.BorderLayout;
 +import java.awt.Color;
  import java.awt.Component;
  import java.awt.Rectangle;
  import java.awt.Toolkit;
@@@ -142,26 -139,20 +143,26 @@@ import java.util.Vector
  
  import javax.swing.ButtonGroup;
  import javax.swing.JCheckBoxMenuItem;
 +import javax.swing.JComponent;
  import javax.swing.JEditorPane;
  import javax.swing.JInternalFrame;
 +import javax.swing.JLabel;
  import javax.swing.JLayeredPane;
  import javax.swing.JMenu;
  import javax.swing.JMenuItem;
 +import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.SwingUtilities;
  
 +import ext.vamsas.ServiceHandle;
 +
  /**
   * DOCUMENT ME!
   * 
   * @author $author$
   * @version $Revision$
   */
 +@SuppressWarnings("serial")
  public class AlignFrame extends GAlignFrame implements DropTargetListener,
          IProgressIndicator, AlignViewControllerGuiI, ColourChangeListener
  {
     */
    String fileName = null;
  
 +  File fileObject;
 +
    /**
     * Creates a new AlignFrame object with specific width and height.
     * 
     */
    void init()
    {
 +//      setBackground(Color.white); // BH 2019
 +                
      if (!Jalview.isHeadlessMode())
      {
        progressBar = new ProgressBar(this.statusPanel, this.statusBar);
      if (Desktop.desktop != null)
      {
        this.setDropTarget(new java.awt.dnd.DropTarget(this, this));
 -      addServiceListeners();
 +      if (!Platform.isJS())
 +      {
 +        addServiceListeners();
 +      }
        setGUINucleotide();
      }
  
    }
  
    /**
 +   * JavaScript will have this, maybe others. More dependable than a file name
 +   * and maintains a reference to the actual bytes loaded.
 +   * 
 +   * @param file
 +   */
 +  public void setFileObject(File file)
 +  {
 +    this.fileObject = file;
 +  }
 +
 +  /**
     * Add a KeyListener with handlers for various KeyPressed and KeyReleased
     * events
     */
          case KeyEvent.VK_BACK_SPACE:
            if (!viewport.cursorMode)
            {
 -            cut_actionPerformed(null);
 +            cut_actionPerformed();
            }
            else
            {
  
          case KeyEvent.VK_F2:
            viewport.cursorMode = !viewport.cursorMode;
 -          statusBar.setText(MessageManager
 +          setStatus(MessageManager
                    .formatMessage("label.keyboard_editing_mode", new String[]
                    { (viewport.cursorMode ? "on" : "off") }));
            if (viewport.cursorMode)
          Desktop.instance.removeJalviewPropertyChangeListener("services",
                  thisListener);
          closeMenuItem_actionPerformed(true);
 -      };
 +      }
      });
      // Finally, build the menu once to get current service state
      new Thread(new Runnable()
    }
  
    @Override
 -  public void fetchSequence_actionPerformed(ActionEvent e)
 +  public void fetchSequence_actionPerformed()
    {
 -    new jalview.gui.SequenceFetcher(this);
 +    new SequenceFetcher(this);
    }
  
    @Override
          Rectangle bounds = this.getBounds();
  
          FileLoader loader = new FileLoader();
 -        DataSourceType protocol = fileName.startsWith("http:")
 -                ? DataSourceType.URL
 -                : DataSourceType.FILE;
 -        AlignFrame newframe = loader.LoadFileWaitTillLoaded(fileName,
 -                protocol, currentFileFormat);
 +
 +        AlignFrame newframe = null;
 +
 +        if (fileObject == null)
 +        {
 +
 +          DataSourceType protocol = (fileName.startsWith("http:")
 +                  ? DataSourceType.URL
 +                  : DataSourceType.FILE);
 +          newframe = loader.LoadFileWaitTillLoaded(fileName, protocol,
 +                  currentFileFormat);
 +        }
 +        else
 +        {
 +          newframe = loader.LoadFileWaitTillLoaded(fileObject,
 +                  DataSourceType.FILE, currentFileFormat);
 +        }
  
          newframe.setBounds(bounds);
          if (featureSettings != null && featureSettings.isShowing())
      if (fileName == null || (currentFileFormat == null)
              || fileName.startsWith("http"))
      {
 -      saveAs_actionPerformed(null);
 +      saveAs_actionPerformed();
      }
      else
      {
    }
  
    /**
 -   * DOCUMENT ME!
 -   * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * Saves the alignment to a file with a name chosen by the user, if necessary
 +   * warning if a file would be overwritten
     */
    @Override
 -  public void saveAs_actionPerformed(ActionEvent e)
 +  public void saveAs_actionPerformed()
    {
      String format = currentFileFormat == null ? null
              : currentFileFormat.getName();
  
      int value = chooser.showSaveDialog(this);
  
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    if (value != JalviewFileChooser.APPROVE_OPTION)
      {
 +      return;
 +    }
 +    currentFileFormat = chooser.getSelectedFormat();
 +    // todo is this (2005) test now obsolete - value is never null?
 +    while (currentFileFormat == null)
 +    {
 +      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +              MessageManager
 +                      .getString("label.select_file_format_before_saving"),
 +              MessageManager.getString("label.file_format_not_specified"),
 +              JvOptionPane.WARNING_MESSAGE);
        currentFileFormat = chooser.getSelectedFormat();
 -      while (currentFileFormat == null)
 +      value = chooser.showSaveDialog(this);
 +      if (value != JalviewFileChooser.APPROVE_OPTION)
        {
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 -                MessageManager.getString(
 -                        "label.select_file_format_before_saving"),
 -                MessageManager.getString("label.file_format_not_specified"),
 -                JvOptionPane.WARNING_MESSAGE);
 -        currentFileFormat = chooser.getSelectedFormat();
 -        value = chooser.showSaveDialog(this);
 -        if (value != JalviewFileChooser.APPROVE_OPTION)
 -        {
 -          return;
 -        }
 +        return;
        }
 +    }
  
 -      fileName = chooser.getSelectedFile().getPath();
 +    fileName = chooser.getSelectedFile().getPath();
  
 -      Cache.setProperty("DEFAULT_FILE_FORMAT", currentFileFormat.getName());
 +    Cache.setProperty("DEFAULT_FILE_FORMAT", currentFileFormat.getName());
 +    Cache.setProperty("LAST_DIRECTORY", fileName);
 +    saveAlignment(fileName, currentFileFormat);
 +  }
  
 -      Cache.setProperty("LAST_DIRECTORY", fileName);
 -      saveAlignment(fileName, currentFileFormat);
 +  boolean lastSaveSuccessful = false;
 +
 +  FileFormatI lastFormatSaved;
 +
 +  String lastFilenameSaved;
 +
 +  /**
 +   * Raise a dialog or status message for the last call to saveAlignment.
 +   *
 +   * @return true if last call to saveAlignment(file, format) was successful.
 +   */
 +  public boolean isSaveAlignmentSuccessful()
 +  {
 +
 +    if (!lastSaveSuccessful)
 +    {
 +      JvOptionPane.showInternalMessageDialog(this, MessageManager
 +              .formatMessage("label.couldnt_save_file", new Object[]
 +              { lastFilenameSaved }),
 +              MessageManager.getString("label.error_saving_file"),
 +              JvOptionPane.WARNING_MESSAGE);
      }
 +    else
 +    {
 +
 +      setStatus(MessageManager.formatMessage(
 +              "label.successfully_saved_to_file_in_format", new Object[]
 +              { lastFilenameSaved, lastFormatSaved }));
 +
 +    }
 +    return lastSaveSuccessful;
    }
  
 -  public boolean saveAlignment(String file, FileFormatI format)
 +  /**
 +   * Saves the alignment to the specified file path, in the specified format,
 +   * which may be an alignment format, or Jalview project format. If the
 +   * alignment has hidden regions, or the format is one capable of including
 +   * non-sequence data (features, annotations, groups), then the user may be
 +   * prompted to specify what to include in the output.
 +   * 
 +   * @param file
 +   * @param format
 +   */
 +  public void saveAlignment(String file, FileFormatI format)
    {
 -    boolean success = true;
 +    lastSaveSuccessful = true;
 +    lastFilenameSaved = file;
 +    lastFormatSaved = format;
  
      if (FileFormat.Jalview.equals(format))
      {
        String shortName = title;
 -
 -      if (shortName.indexOf(java.io.File.separatorChar) > -1)
 +      if (shortName.indexOf(File.separatorChar) > -1)
        {
          shortName = shortName.substring(
 -                shortName.lastIndexOf(java.io.File.separatorChar) + 1);
 +                shortName.lastIndexOf(File.separatorChar) + 1);
        }
 -
 -      success = new jalview.project.Jalview2XML().saveAlignment(this, file,
 -              shortName);
 -
 +      lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file, shortName);
 +      
        statusBar.setText(MessageManager.formatMessage(
                "label.successfully_saved_to_file_in_format", new Object[]
                { fileName, format }));
 -
 +      
 +      return;
      }
 -    else
 +
 +    AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
 +    Runnable cancelAction = new Runnable()
      {
 -      AlignmentExportData exportData = getAlignmentForExport(format,
 -              viewport, null);
 -      if (exportData.getSettings().isCancelled())
 -      {
 -        return false;
 -      }
 -      FormatAdapter f = new FormatAdapter(alignPanel,
 -              exportData.getSettings());
 -      String output = f.formatSequences(format, exportData.getAlignment(), // class
 -                                                                           // cast
 -                                                                           // exceptions
 -                                                                           // will
 -              // occur in the distant future
 -              exportData.getOmitHidden(), exportData.getStartEndPostions(),
 -              f.getCacheSuffixDefault(format),
 -              viewport.getAlignment().getHiddenColumns());
 -
 -      if (output == null)
 +      @Override
 +      public void run()
        {
 -        success = false;
 +        lastSaveSuccessful = false;
        }
 -      else
 +    };
 +    Runnable outputAction = new Runnable()
 +    {
 +      @Override
 +      public void run()
        {
 -        // create backupfiles object and get new temp filename destination
 -        BackupFiles backupfiles = new BackupFiles(file);
 -
 -        try
 +        // todo defer this to inside formatSequences (or later)
 +        AlignmentExportData exportData = viewport
 +                .getAlignExportData(options);
 +        String output = new FormatAdapter(alignPanel, options)
 +                .formatSequences(format, exportData.getAlignment(),
 +                        exportData.getOmitHidden(),
 +                        exportData.getStartEndPostions(),
 +                        viewport.getAlignment().getHiddenColumns());
 +        if (output == null)
 +        {
 +          lastSaveSuccessful = false;
 +        }
 +        else
          {
 -          PrintWriter out = new PrintWriter(
 -                  new FileWriter(backupfiles.getTempFilePath()));
 +          // create backupfiles object and get new temp filename destination
 +          boolean doBackup = BackupFiles.getEnabled();
 +          BackupFiles backupfiles = doBackup ? new BackupFiles(file) : null;
 +          try
 +          {
 +            String tempFilePath = doBackup ? backupfiles.getTempFilePath() : file;
 +                      PrintWriter out = new PrintWriter(
 +                    new FileWriter(tempFilePath));
  
 -          out.print(output);
 -          out.close();
 -          this.setTitle(file);
 -          statusBar.setText(MessageManager.formatMessage(
 +            out.print(output);
 +            out.close();
 +            AlignFrame.this.setTitle(file);
 +            statusBar.setText(MessageManager.formatMessage(
                    "label.successfully_saved_to_file_in_format", new Object[]
                    { fileName, format.getName() }));
 -        } catch (Exception ex)
 -        {
 -          success = false;
 -          ex.printStackTrace();
 -        }
 -
 -        backupfiles.setWriteSuccess(success);
 -        // do the backup file roll and rename the temp file to actual file
 -        success = backupfiles.rollBackupsAndRenameTempFile();
 +            lastSaveSuccessful = true;
 +          } catch (Exception ex)
 +          {
 +            lastSaveSuccessful = false;
 +            ex.printStackTrace();
 +          }
  
 +          if (doBackup)
 +          {
 +            backupfiles.setWriteSuccess(lastSaveSuccessful);
 +            // do the backup file roll and rename the temp file to actual file
 +            lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
 +          }
 +        }
        }
 -    }
 -
 -    if (!success)
 -    {
 -      JvOptionPane.showInternalMessageDialog(this, MessageManager
 -              .formatMessage("label.couldnt_save_file", new Object[]
 -              { fileName }),
 -              MessageManager.getString("label.error_saving_file"),
 -              JvOptionPane.WARNING_MESSAGE);
 -    }
 -
 -    return success;
 -  }
 +    };
  
 -  private void warningMessage(String warning, String title)
 -  {
 -    if (new jalview.util.Platform().isHeadless())
 +    /*
 +     * show dialog with export options if applicable; else just do it
 +     */
 +    if (AlignExportOptions.isNeeded(viewport, format))
      {
 -      System.err.println("Warning: " + title + "\nWarning: " + warning);
 -
 +      AlignExportOptions choices = new AlignExportOptions(
 +              alignPanel.getAlignViewport(), format, options);
 +      choices.setResponseAction(0, outputAction);
 +      choices.setResponseAction(1, cancelAction);
 +      choices.showDialog();
      }
      else
      {
 -      JvOptionPane.showInternalMessageDialog(this, warning, title,
 -              JvOptionPane.WARNING_MESSAGE);
 +      outputAction.run();
      }
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Outputs the alignment to textbox in the requested format, if necessary
 +   * first prompting the user for whether to include hidden regions or
 +   * non-sequence data
     * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * @param fileFormatName
     */
    @Override
 -  protected void outputText_actionPerformed(ActionEvent e)
 +  protected void outputText_actionPerformed(String fileFormatName)
    {
      FileFormatI fileFormat = FileFormats.getInstance()
 -            .forName(e.getActionCommand());
 -    AlignmentExportData exportData = getAlignmentForExport(fileFormat,
 -            viewport, null);
 -    if (exportData.getSettings().isCancelled())
 +            .forName(fileFormatName);
 +    AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
 +    Runnable outputAction = new Runnable()
      {
 -      return;
 -    }
 -    CutAndPasteTransfer cap = new CutAndPasteTransfer();
 -    cap.setForInput(null);
 -    try
 -    {
 -      FileFormatI format = fileFormat;
 -      cap.setText(new FormatAdapter(alignPanel, exportData.getSettings())
 -              .formatSequences(format, exportData.getAlignment(),
 -                      exportData.getOmitHidden(),
 -                      exportData.getStartEndPostions(),
 -                      viewport.getAlignment().getHiddenColumns()));
 -      Desktop.addInternalFrame(cap, MessageManager
 -              .formatMessage("label.alignment_output_command", new Object[]
 -              { e.getActionCommand() }), 600, 500);
 -    } catch (OutOfMemoryError oom)
 -    {
 -      new OOMWarning("Outputting alignment as " + e.getActionCommand(),
 -              oom);
 -      cap.dispose();
 -    }
 -
 -  }
 -
 -  public static AlignmentExportData getAlignmentForExport(
 -          FileFormatI format, AlignViewportI viewport,
 -          AlignExportSettingI exportSettings)
 -  {
 -    AlignmentI alignmentToExport = null;
 -    AlignExportSettingI settings = exportSettings;
 -    String[] omitHidden = null;
 -
 -    HiddenSequences hiddenSeqs = viewport.getAlignment()
 -            .getHiddenSequences();
 -
 -    alignmentToExport = viewport.getAlignment();
 -
 -    boolean hasHiddenSeqs = hiddenSeqs.getSize() > 0;
 -    if (settings == null)
 -    {
 -      settings = new AlignExportSettings(hasHiddenSeqs,
 -              viewport.hasHiddenColumns(), format);
 -    }
 -    // settings.isExportAnnotations();
 -
 -    if (viewport.hasHiddenColumns() && !settings.isExportHiddenColumns())
 -    {
 -      omitHidden = viewport.getViewAsString(false,
 -              settings.isExportHiddenSequences());
 -    }
 +      @Override
 +      public void run()
 +      {
 +        // todo defer this to inside formatSequences (or later)
 +        AlignmentExportData exportData = viewport
 +                .getAlignExportData(options);
 +        CutAndPasteTransfer cap = new CutAndPasteTransfer();
 +        cap.setForInput(null);
 +        try
 +        {
 +          FileFormatI format = fileFormat;
 +          cap.setText(new FormatAdapter(alignPanel, options)
 +                  .formatSequences(format, exportData.getAlignment(),
 +                          exportData.getOmitHidden(),
 +                          exportData.getStartEndPostions(),
 +                          viewport.getAlignment().getHiddenColumns()));
 +          Desktop.addInternalFrame(cap, MessageManager.formatMessage(
 +                  "label.alignment_output_command", new Object[]
 +                  { fileFormat.getName() }), 600, 500);
 +        } catch (OutOfMemoryError oom)
 +        {
 +          new OOMWarning("Outputting alignment as " + fileFormat.getName(),
 +                  oom);
 +          cap.dispose();
 +        }
 +      }
 +    };
  
 -    int[] alignmentStartEnd = new int[2];
 -    if (hasHiddenSeqs && settings.isExportHiddenSequences())
 +    /*
 +     * show dialog with export options if applicable; else just do it
 +     */
 +    if (AlignExportOptions.isNeeded(viewport, fileFormat))
      {
 -      alignmentToExport = hiddenSeqs.getFullAlignment();
 +      AlignExportOptions choices = new AlignExportOptions(
 +              alignPanel.getAlignViewport(), fileFormat, options);
 +      choices.setResponseAction(0, outputAction);
 +      choices.showDialog();
      }
      else
      {
 -      alignmentToExport = viewport.getAlignment();
 +      outputAction.run();
      }
 -    alignmentStartEnd = viewport.getAlignment().getHiddenColumns()
 -            .getVisibleStartAndEndIndex(alignmentToExport.getWidth());
 -    AlignmentExportData ed = new AlignmentExportData(alignmentToExport,
 -            omitHidden, alignmentStartEnd, settings);
 -    return ed;
    }
  
    /**
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Creates a PNG image of the alignment and writes it to the given file. If
 +   * the file is null, the user is prompted to choose a file.
     * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * @param f
     */
    @Override
    public void createPNG(File f)
    {
 -    alignPanel.makePNG(f);
 +    alignPanel.makeAlignmentImage(TYPE.PNG, f);
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Creates an EPS image of the alignment and writes it to the given file. If
 +   * the file is null, the user is prompted to choose a file.
     * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * @param f
     */
    @Override
    public void createEPS(File f)
    {
 -    alignPanel.makeEPS(f);
 +    alignPanel.makeAlignmentImage(TYPE.EPS, f);
    }
  
 +  /**
 +   * Creates an SVG image of the alignment and writes it to the given file. If
 +   * the file is null, the user is prompted to choose a file.
 +   * 
 +   * @param f
 +   */
    @Override
    public void createSVG(File f)
    {
 -    alignPanel.makeSVG(f);
 +    alignPanel.makeAlignmentImage(TYPE.SVG, f);
    }
  
    @Override
    @Override
    public void associatedData_actionPerformed(ActionEvent e)
    {
 -    // Pick the tree file
 -    JalviewFileChooser chooser = new JalviewFileChooser(
 +    final JalviewFileChooser chooser = new JalviewFileChooser(
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(
              MessageManager.getString("label.load_jalview_annotations"));
      chooser.setToolTipText(
              MessageManager.getString("label.load_jalview_annotations"));
 -
 -    int value = chooser.showOpenDialog(null);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    chooser.setResponseHandler(0, new Runnable()
      {
 -      String choice = chooser.getSelectedFile().getPath();
 -      jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
 -      loadJalviewDataFile(choice, null, null, null);
 -    }
 +      @Override
 +      public void run()
 +      {
 +        String choice = chooser.getSelectedFile().getPath();
 +        jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
 +        loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
 +      }
 +    });
  
 +    chooser.showOpenDialog(this);
    }
  
    /**
     *          DOCUMENT ME!
     */
    @Override
 -  protected void copy_actionPerformed(ActionEvent e)
 +  protected void copy_actionPerformed()
    {
      if (viewport.getSelectionGroup() == null)
      {
  
      Desktop.jalviewClipboard = new Object[] { seqs,
          viewport.getAlignment().getDataset(), hiddenColumns };
 -    statusBar.setText(MessageManager.formatMessage(
 +    setStatus(MessageManager.formatMessage(
              "label.copied_sequences_to_clipboard", new Object[]
              { Integer.valueOf(seqs.length).toString() }));
    }
                  && Desktop.jalviewClipboard[1] != alignment.getDataset();
          // importDs==true instructs us to copy over new dataset sequences from
          // an existing alignment
 -        Vector newDs = (importDs) ? new Vector() : null; // used to create
 +        Vector<SequenceI> newDs = (importDs) ? new Vector<>() : null; // used to
 +                                                                      // create
          // minimum dataset set
  
          for (int i = 0; i < sequences.length; i++)
                      newGraphGroups.add(q, null);
                    }
                    newGraphGroups.set(newann.graphGroup,
-                           new Integer(++fgroup));
+                           Integer.valueOf(++fgroup));
                  }
                  newann.graphGroup = newGraphGroups.get(newann.graphGroup)
                          .intValue();
                      newGraphGroups.add(q, null);
                    }
                    newGraphGroups.set(newann.graphGroup,
-                           new Integer(++fgroup));
+                           Integer.valueOf(++fgroup));
                  }
                  newann.graphGroup = newGraphGroups.get(newann.graphGroup)
                          .intValue();
        {
  
          // propagate alignment changed.
 -        viewport.getRanges().setEndSeq(alignment.getHeight());
 +        viewport.getRanges().setEndSeq(alignment.getHeight() - 1);
          if (annotationAdded)
          {
            // Duplicate sequence annotation in all views.
    }
  
    /**
 -   * DOCUMENT ME!
 -   * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * Action Cut (delete and copy) the selected region
     */
    @Override
 -  protected void cut_actionPerformed(ActionEvent e)
 +  protected void cut_actionPerformed()
    {
 -    copy_actionPerformed(null);
 -    delete_actionPerformed(null);
 +    copy_actionPerformed();
 +    delete_actionPerformed();
    }
  
    /**
 -   * DOCUMENT ME!
 -   * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * Performs menu option to Delete the currently selected region
     */
    @Override
 -  protected void delete_actionPerformed(ActionEvent evt)
 +  protected void delete_actionPerformed()
    {
  
      SequenceGroup sg = viewport.getSelectionGroup();
        return;
      }
  
 +    Runnable okAction = new Runnable() 
 +    {
 +              @Override
 +              public void run() 
 +              {
 +                  SequenceI[] cut = sg.getSequences()
 +                          .toArray(new SequenceI[sg.getSize()]);
 +
 +                  addHistoryItem(new EditCommand(
 +                          MessageManager.getString("label.cut_sequences"), Action.CUT,
 +                          cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1,
 +                          viewport.getAlignment()));
 +
 +                  viewport.setSelectionGroup(null);
 +                  viewport.sendSelection();
 +                  viewport.getAlignment().deleteGroup(sg);
 +
 +                  viewport.firePropertyChange("alignment", null,
 +                          viewport.getAlignment().getSequences());
 +                  if (viewport.getAlignment().getHeight() < 1)
 +                  {
 +                    try
 +                    {
 +                      AlignFrame.this.setClosed(true);
 +                    } catch (Exception ex)
 +                    {
 +                    }
 +                  }
 +              }};
 +
      /*
 -     * If the cut affects all sequences, warn, remove highlighted columns
 +     * If the cut affects all sequences, prompt for confirmation
       */
 -    if (sg.getSize() == viewport.getAlignment().getHeight())
 -    {
 -      boolean isEntireAlignWidth = (((sg.getEndRes() - sg.getStartRes())
 -              + 1) == viewport.getAlignment().getWidth()) ? true : false;
 -      if (isEntireAlignWidth)
 -      {
 -        int confirm = JvOptionPane.showConfirmDialog(this,
 -                MessageManager.getString("warn.delete_all"), // $NON-NLS-1$
 -                MessageManager.getString("label.delete_all"), // $NON-NLS-1$
 -                JvOptionPane.OK_CANCEL_OPTION);
 -
 -        if (confirm == JvOptionPane.CANCEL_OPTION
 -                || confirm == JvOptionPane.CLOSED_OPTION)
 -        {
 -          return;
 -        }
 -      }
 -      viewport.getColumnSelection().removeElements(sg.getStartRes(),
 -              sg.getEndRes() + 1);
 -    }
 -    SequenceI[] cut = sg.getSequences()
 -            .toArray(new SequenceI[sg.getSize()]);
 -
 -    addHistoryItem(new EditCommand(
 -            MessageManager.getString("label.cut_sequences"), Action.CUT,
 -            cut, sg.getStartRes(), sg.getEndRes() - sg.getStartRes() + 1,
 -            viewport.getAlignment()));
 -
 -    viewport.setSelectionGroup(null);
 -    viewport.sendSelection();
 -    viewport.getAlignment().deleteGroup(sg);
 -
 -    viewport.firePropertyChange("alignment", null,
 -            viewport.getAlignment().getSequences());
 -    if (viewport.getAlignment().getHeight() < 1)
 -    {
 -      try
 -      {
 -        this.setClosed(true);
 -      } catch (Exception ex)
 -      {
 -      }
 -    }
 +    boolean wholeHeight = sg.getSize() == viewport.getAlignment().getHeight();
 +    boolean wholeWidth = (((sg.getEndRes() - sg.getStartRes())
 +            + 1) == viewport.getAlignment().getWidth()) ? true : false;
 +      if (wholeHeight && wholeWidth)
 +      {
 +          JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop);
 +              dialog.setResponseHandler(0, okAction); // 0 = OK_OPTION
 +          Object[] options = new Object[] { MessageManager.getString("action.ok"),
 +                  MessageManager.getString("action.cancel") };
 +              dialog.showDialog(MessageManager.getString("warn.delete_all"),
 +                  MessageManager.getString("label.delete_all"),
 +                  JvOptionPane.DEFAULT_OPTION, JvOptionPane.PLAIN_MESSAGE, null,
 +                  options, options[0]);
 +      } else 
 +      {
 +              okAction.run();
 +      }
    }
  
    /**
                  column, viewport.getAlignment());
        }
  
 -      statusBar.setText(MessageManager
 +      setStatus(MessageManager
                .formatMessage("label.removed_columns", new String[]
                { Integer.valueOf(trimRegion.getSize()).toString() }));
  
  
      addHistoryItem(removeGapCols);
  
 -    statusBar.setText(MessageManager
 +    setStatus(MessageManager
              .formatMessage("label.removed_empty_columns", new Object[]
              { Integer.valueOf(removeGapCols.getSize()).toString() }));
  
     * @param toggleSeqs
     * @param toggleCols
     */
 -  private void toggleHiddenRegions(boolean toggleSeqs, boolean toggleCols)
 +  protected void toggleHiddenRegions(boolean toggleSeqs, boolean toggleCols)
    {
  
      boolean hide = false;
    @Override
    public void alignmentProperties()
    {
 -    JEditorPane editPane = new JEditorPane("text/html", "");
 -    editPane.setEditable(false);
 +    JComponent pane;
      StringBuffer contents = new AlignmentProperties(viewport.getAlignment())
 +
              .formatAsHtml();
 -    editPane.setText(
 -            MessageManager.formatMessage("label.html_content", new Object[]
 -            { contents.toString() }));
 +    String content = MessageManager.formatMessage("label.html_content",
 +            new Object[]
 +            { contents.toString() });
 +    contents = null;
 +
 +    if (Platform.isJS())
 +    {
 +      JLabel textLabel = new JLabel();
 +      textLabel.setText(content);
 +      textLabel.setBackground(Color.WHITE);
 +      
 +      pane = new JPanel(new BorderLayout());
 +      ((JPanel) pane).setOpaque(true);
 +      pane.setBackground(Color.WHITE);
 +      ((JPanel) pane).add(textLabel, BorderLayout.NORTH);
 +    }
 +    else
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +    {
 +      JEditorPane editPane = new JEditorPane("text/html", "");
 +      editPane.setEditable(false);
 +      editPane.setText(content);
 +      pane = editPane;
 +    }
 +
      JInternalFrame frame = new JInternalFrame();
 -    frame.getContentPane().add(new JScrollPane(editPane));
 +
 +    frame.getContentPane().add(new JScrollPane(pane));
  
      Desktop.addInternalFrame(frame, MessageManager
              .formatMessage("label.alignment_properties", new Object[]
                {
                  overview.dispose();
                  alignPanel.setOverviewPanel(null);
 -              };
 +              }
              });
      if (getKeyListeners().length > 0)
      {
      {
        sortByAnnotScore.removeAll();
        // almost certainly a quicker way to do this - but we keep it simple
 -      Hashtable scoreSorts = new Hashtable();
 +      Hashtable<String, String> scoreSorts = new Hashtable<>();
        AlignmentAnnotation aann[];
        for (SequenceI sqa : viewport.getAlignment().getSequences())
        {
            }
          }
        }
 -      Enumeration labels = scoreSorts.keys();
 +      Enumeration<String> labels = scoreSorts.keys();
        while (labels.hasMoreElements())
        {
          addSortByAnnotScoreMenuItem(sortByAnnotScore,
 -                (String) labels.nextElement());
 +                labels.nextElement());
        }
        sortByAnnotScore.setVisible(scoreSorts.size() > 0);
        scoreSorts.clear();
      chooser.setToolTipText(
              MessageManager.getString("label.load_tree_file"));
  
 -    int value = chooser.showOpenDialog(null);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    chooser.setResponseHandler(0,new Runnable()
      {
 -      String filePath = chooser.getSelectedFile().getPath();
 -      Cache.setProperty("LAST_DIRECTORY", filePath);
 -      NewickFile fin = null;
 -      try
 -      {
 -        fin = new NewickFile(filePath, DataSourceType.FILE);
 -        viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
 -      } catch (Exception ex)
 -      {
 -        JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
 -                MessageManager.getString("label.problem_reading_tree_file"),
 -                JvOptionPane.WARNING_MESSAGE);
 -        ex.printStackTrace();
 -      }
 -      if (fin != null && fin.hasWarningMessage())
 +      @Override
 +      public void run()
        {
 -        JvOptionPane.showMessageDialog(Desktop.desktop,
 -                fin.getWarningMessage(),
 -                MessageManager
 -                        .getString("label.possible_problem_with_tree_file"),
 -                JvOptionPane.WARNING_MESSAGE);
 +        String filePath = chooser.getSelectedFile().getPath();
 +        Cache.setProperty("LAST_DIRECTORY", filePath);
 +        NewickFile fin = null;
 +        try
 +        {
 +          fin = new NewickFile(new FileParse(chooser.getSelectedFile(),
 +                  DataSourceType.FILE));
 +          viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
 +        } catch (Exception ex)
 +        {
 +          JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
 +                  MessageManager
 +                          .getString("label.problem_reading_tree_file"),
 +                  JvOptionPane.WARNING_MESSAGE);
 +          ex.printStackTrace();
 +        }
 +        if (fin != null && fin.hasWarningMessage())
 +        {
 +          JvOptionPane.showMessageDialog(Desktop.desktop,
 +                  fin.getWarningMessage(),
 +                  MessageManager.getString(
 +                          "label.possible_problem_with_tree_file"),
 +                  JvOptionPane.WARNING_MESSAGE);
 +        }
        }
 -    }
 +    });
 +    chooser.showOpenDialog(this);
    }
  
    public TreePanel showNewickTree(NewickFile nf, String treeTitle)
              // No MSAWS used any more:
              // Vector msaws = null; // (Vector)
              // Discoverer.services.get("MsaWS");
 -            Vector secstrpr = (Vector) Discoverer.services
 +            Vector<ServiceHandle> secstrpr = Discoverer.services
                      .get("SecStrPred");
              if (secstrpr != null)
              {
                // Add any secondary structure prediction services
                for (int i = 0, j = secstrpr.size(); i < j; i++)
                {
 -                final ext.vamsas.ServiceHandle sh = (ext.vamsas.ServiceHandle) secstrpr
 +                final ext.vamsas.ServiceHandle sh = secstrpr
                          .get(i);
                  jalview.ws.WSMenuEntryProviderI impl = jalview.ws.jws1.Discoverer
                          .getServiceClient(sh);
     * 
     * @param webService
     */
 -  private void build_urlServiceMenu(JMenu webService)
 +  protected void build_urlServiceMenu(JMenu webService)
    {
      // TODO: remove this code when 2.7 is released
      // DEBUG - alignmentView
     * Try to load a features file onto the alignment.
     * 
     * @param file
 -   *          contents or path to retrieve file
 +   *          contents or path to retrieve file or a File object
     * @param sourceType
     *          access mode of file (see jalview.io.AlignFile)
     * @return true if features file was parsed correctly.
     */
 -  public boolean parseFeaturesFile(String file, DataSourceType sourceType)
 +  public boolean parseFeaturesFile(Object file, DataSourceType sourceType)
    {
 +    // BH 2018
      return avc.parseFeaturesFile(file, sourceType,
              Cache.getDefault("RELAXEDSEQIDMATCHING", false));
  
      // Java's Transferable for native dnd
      evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
      Transferable t = evt.getTransferable();
 +
      final AlignFrame thisaf = this;
 -    final List<String> files = new ArrayList<>();
 +    final List<Object> files = new ArrayList<>();
      List<DataSourceType> protocols = new ArrayList<>();
  
      try
               * Object[] { String,SequenceI}
               */
              ArrayList<Object[]> filesmatched = new ArrayList<>();
 -            ArrayList<String> filesnotmatched = new ArrayList<>();
 +            ArrayList<Object> filesnotmatched = new ArrayList<>();
              for (int i = 0; i < files.size(); i++)
              {
 -              String file = files.get(i).toString();
 +              // BH 2018
 +              Object file = files.get(i);
 +              String fileName = file.toString();
                String pdbfn = "";
 -              DataSourceType protocol = FormatAdapter.checkProtocol(file);
 +              DataSourceType protocol = (file instanceof File
 +                      ? DataSourceType.FILE
 +                      : FormatAdapter.checkProtocol(fileName));
                if (protocol == DataSourceType.FILE)
                {
 -                File fl = new File(file);
 +                File fl;
 +                if (file instanceof File) {
 +                  fl = (File) file;
 +                  Platform.cacheFileData(fl);
 +                } else {
 +                  fl = new File(fileName);
 +                }
                  pdbfn = fl.getName();
                }
                else if (protocol == DataSourceType.URL)
                {
 -                URL url = new URL(file);
 +                URL url = new URL(fileName);
                  pdbfn = url.getFile();
                }
                if (pdbfn.length() > 0)
                  }
                  if (mtch != null)
                  {
 -                  FileFormatI type = null;
 +                  FileFormatI type;
                    try
                    {
                      type = new IdentifyFile().identify(file, protocol);
                    for (SequenceI toassoc : (SequenceI[]) fm[2])
                    {
                      PDBEntry pe = new AssociatePdbFileWithSeq()
 -                            .associatePdbWithSeq((String) fm[0],
 +                            .associatePdbWithSeq(fm[0].toString(),
                                      (DataSourceType) fm[1], toassoc, false,
                                      Desktop.instance);
                      if (pe != null)
                      {
                        System.err.println("Associated file : "
 -                              + ((String) fm[0]) + " with "
 +                              + (fm[0].toString()) + " with "
                                + toassoc.getDisplayId(true));
                        assocfiles++;
                      }
                   */
                  for (Object[] o : filesmatched)
                  {
 -                  filesnotmatched.add((String) o[0]);
 +                  filesnotmatched.add(o[0]);
                  }
                }
              }
                {
                  return;
                }
 -              for (String fn : filesnotmatched)
 +              for (Object fn : filesnotmatched)
                {
                  loadJalviewDataFile(fn, null, null, null);
                }
     * @param file
     *          either a filename or a URL string.
     */
 -  public void loadJalviewDataFile(String file, DataSourceType sourceType,
 +  public void loadJalviewDataFile(Object file, DataSourceType sourceType,
            FileFormatI format, SequenceI assocSeq)
    {
 +    // BH 2018 was String file
      try
      {
        if (sourceType == null)
                changeColour(
                        new TCoffeeColourScheme(viewport.getAlignment()));
                isAnnotation = true;
 -              statusBar.setText(MessageManager.getString(
 +              setStatus(MessageManager.getString(
                        "label.successfully_pasted_tcoffee_scores_to_alignment"));
              }
              else
                      new FileParse(file, sourceType));
              sm.parse();
              // todo: i18n this message
 -            statusBar.setText(MessageManager.formatMessage(
 +            setStatus(MessageManager.formatMessage(
                      "label.successfully_loaded_matrix",
                      sm.getMatrixName()));
            }
      if (e.isPopupTrigger())
      {
        String msg = MessageManager.getString("label.enter_view_name");
 -      String reply = JvOptionPane.showInternalInputDialog(this, msg, msg,
 -              JvOptionPane.QUESTION_MESSAGE);
 +      String ttl = tabbedPane.getTitleAt(tabbedPane.getSelectedIndex());
 +      String reply = JvOptionPane.showInputDialog(msg, ttl);
  
        if (reply != null)
        {
          trimrs.setSelected(trimrs.isSelected());
          Cache.setProperty(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES,
                  Boolean.valueOf(trimrs.isSelected()).toString());
 -      };
 +      }
      });
      rfetch.add(trimrs);
      JMenuItem fetchr = new JMenuItem(
                @Override
                public void finished()
                {
+                 for (FeatureSettingsModelI srcSettings : dbRefFetcher
+                         .getFeatureSettingsModels())
+                 {
+                   alignPanel.av.mergeFeaturesStyle(srcSettings);
+                 }
                  AlignFrame.this.setMenusForViewport();
                }
              });
  
      });
      rfetch.add(fetchr);
 -    final AlignFrame me = this;
      new Thread(new Runnable()
      {
        @Override
        public void run()
        {
          final jalview.ws.SequenceFetcher sf = jalview.gui.SequenceFetcher
 -                .getSequenceFetcherSingleton(me);
 +                .getSequenceFetcherSingleton();
          javax.swing.SwingUtilities.invokeLater(new Runnable()
          {
            @Override
            public void run()
            {
 -            String[] dbclasses = sf.getOrderedSupportedSources();
 -            // sf.getDbInstances(jalview.ws.dbsources.DasSequenceSource.class);
 -            // jalview.util.QuickSort.sort(otherdb, otherdb);
 +            String[] dbclasses = sf.getNonAlignmentSources();
              List<DbSourceProxy> otherdb;
              JMenu dfetch = new JMenu();
              JMenu ifetch = new JMenu();
                {
                  continue;
                }
 -              // List<DbSourceProxy> dbs=otherdb;
 -              // otherdb=new ArrayList<DbSourceProxy>();
 -              // for (DbSourceProxy db:dbs)
 -              // {
 -              // if (!db.isA(DBRefSource.ALIGNMENTDB)
 -              // }
                if (mname == null)
                {
                  mname = "From " + dbclass;
                                    @Override
                                    public void finished()
                                    {
+                                     FeatureSettingsModelI srcSettings = dassource[0]
+                                             .getFeatureColourScheme();
+                                     alignPanel.av.mergeFeaturesStyle(
+                                             srcSettings);
                                      AlignFrame.this.setMenusForViewport();
                                    }
                                  });
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager.getString("label.load_vcf_file"));
      chooser.setToolTipText(MessageManager.getString("label.load_vcf_file"));
 -
 -    int value = chooser.showOpenDialog(null);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    final AlignFrame us = this;
 +    chooser.setResponseHandler(0, new Runnable()
      {
 -      String choice = chooser.getSelectedFile().getPath();
 -      Cache.setProperty("LAST_DIRECTORY", choice);
 -      SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
 -      new VCFLoader(choice).loadVCF(seqs, this);
 -    }
 +      @Override
 +      public void run()
 +      {
 +        String choice = chooser.getSelectedFile().getPath();
 +        Cache.setProperty("LAST_DIRECTORY", choice);
 +        SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
 +        new VCFLoader(choice).loadVCF(seqs, us);
 +      }
 +    });
 +    chooser.showOpenDialog(null);
  
    }
 +
  }
  
  class PrintThread extends Thread
@@@ -47,6 -47,7 +47,7 @@@ import jalview.schemes.UserColourScheme
  import jalview.structure.SelectionSource;
  import jalview.structure.StructureSelectionManager;
  import jalview.structure.VamsasSource;
+ import jalview.util.ColorUtils;
  import jalview.util.MessageManager;
  import jalview.viewmodel.AlignmentViewport;
  import jalview.ws.params.AutoCalcSetting;
@@@ -56,6 -57,7 +57,7 @@@ import java.awt.Dimension
  import java.awt.Font;
  import java.awt.FontMetrics;
  import java.awt.Rectangle;
+ import java.util.ArrayList;
  import java.util.Hashtable;
  import java.util.Iterator;
  import java.util.List;
@@@ -708,20 -710,16 +710,20 @@@ public class AlignViewport extends Alig
      {
        if (AlignmentUtils.isMappable(toAdd, getAlignment()))
        {
 -        if (openLinkedAlignment(toAdd, title))
 -        {
 -          return;
 -        }
 +        openLinkedAlignment(toAdd, title);
 +        return;
        }
      }
 +    addDataToAlignment(toAdd);
 +  }
  
 -    /*
 -     * No mappings, or offer declined - add sequences to this alignment
 -     */
 +  /**
 +   * adds sequences to this alignment
 +   * 
 +   * @param toAdd
 +   */
 +  void addDataToAlignment(AlignmentI toAdd)
 +  {
      // TODO: JAL-407 regardless of above - identical sequences (based on ID and
      // provenance) should share the same dataset sequence
  
        }
      }
  
 -    ranges.setEndSeq(getAlignment().getHeight());
 +    ranges.setEndSeq(getAlignment().getHeight() - 1); // BH 2019.04.18
      firePropertyChange("alignment", null, getAlignment().getSequences());
    }
  
     * @param al
     * @param title
     */
 -  protected boolean openLinkedAlignment(AlignmentI al, String title)
 +  protected void openLinkedAlignment(AlignmentI al, String title)
    {
      String[] options = new String[] { MessageManager.getString("action.no"),
          MessageManager.getString("label.split_window"),
          MessageManager.getString("label.new_window"), };
      final String question = JvSwingUtils.wrapTooltip(true,
              MessageManager.getString("label.open_split_window?"));
 -    int response = JvOptionPane.showOptionDialog(Desktop.desktop, question,
 +    final AlignViewport us = this;
 +    
 +    /*
 +     * options No, Split Window, New Window correspond to
 +     * dialog responses 0, 1, 2 (even though JOptionPane shows them
 +     * in reverse order)
 +     */
 +    JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
 +            .setResponseHandler(0, new Runnable()
 +            {
 +              @Override
 +              public void run()
 +              {
 +                  addDataToAlignment(al);
 +              }
 +            }).setResponseHandler(1, new Runnable()
 +            {
 +              @Override
 +              public void run()
 +              {
 +                us.openLinkedAlignmentAs(al, title, true);
 +              }
 +            }).setResponseHandler(2, new Runnable()
 +            {
 +              @Override
 +              public void run()
 +              {
 +                us.openLinkedAlignmentAs(al, title, false);
 +              }
 +            });
 +      dialog.showDialog(question,
              MessageManager.getString("label.open_split_window"),
              JvOptionPane.DEFAULT_OPTION, JvOptionPane.PLAIN_MESSAGE, null,
              options, options[0]);
 +  }
  
 -    if (response != 1 && response != 2)
 +  protected void openLinkedAlignmentAs(AlignmentI al, String title,
 +          boolean newWindowOrSplitPane)
      {
 -      return false;
 -    }
 -    final boolean openSplitPane = (response == 1);
 -    final boolean openInNewWindow = (response == 2);
 -
      /*
       * Identify protein and dna alignments. Make a copy of this one if opening
       * in a new split pane.
       */
 -    AlignmentI thisAlignment = openSplitPane ? new Alignment(getAlignment())
 +    AlignmentI thisAlignment = newWindowOrSplitPane
 +            ? new Alignment(getAlignment())
              : getAlignment();
      AlignmentI protein = al.isNucleotide() ? thisAlignment : al;
      final AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
      // alignFrame.setFileName(file, format);
      // }
  
 -    if (openInNewWindow)
 +    if (!newWindowOrSplitPane)
      {
        Desktop.addInternalFrame(newAlignFrame, title,
                AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
      {
      }
  
 -    if (openSplitPane)
 +    if (newWindowOrSplitPane)
      {
        al.alignAs(thisAlignment);
        protein = openSplitFrame(newAlignFrame, thisAlignment);
      }
 -
 -    return true;
    }
  
    /**
    @Override
    public void applyFeaturesStyle(FeatureSettingsModelI featureSettings)
    {
+     transferFeaturesStyles(featureSettings, false);
+   }
+   /**
+    * Applies the supplied feature settings descriptor to currently known features.
+    * This supports an 'initial configuration' of feature colouring based on a
+    * preset or user favourite. This may then be modified in the usual way using
+    * the Feature Settings dialogue.
+    * 
+    * @param featureSettings
+    */
+   @Override
+   public void mergeFeaturesStyle(FeatureSettingsModelI featureSettings)
+   {
+     transferFeaturesStyles(featureSettings, true);
+   }
+   /**
+    * when mergeOnly is set, then group and feature visibility or feature colours
+    * are not modified for features and groups already known to the feature
+    * renderer. Feature ordering is always adjusted, and transparency is always set
+    * regardless.
+    * 
+    * @param featureSettings
+    * @param mergeOnly
+    */
+   private void transferFeaturesStyles(FeatureSettingsModelI featureSettings,
+           boolean mergeOnly)
+   {
      if (featureSettings == null)
      {
        return;
      }
+     
      FeatureRenderer fr = getAlignPanel().getSeqPanel().seqCanvas
              .getFeatureRenderer();
+     List<String> origRenderOrder = new ArrayList(),
+             origGroups = new ArrayList();
+     // preserve original render order - allows differentiation between user configured colours and autogenerated ones
+     origRenderOrder.addAll(fr.getRenderOrder());
+     origGroups.addAll(fr.getFeatureGroups());
      fr.findAllFeatures(true);
      List<String> renderOrder = fr.getRenderOrder();
      FeaturesDisplayedI displayed = fr.getFeaturesDisplayed();
-     displayed.clear();
+     if (!mergeOnly)
+     {
+       // only clear displayed features if we are mergeing
+       displayed.clear();
+     }
      // TODO this clears displayed.featuresRegistered - do we care?
+     //
+     // JAL-3330 - JBP - yes we do - calling applyFeatureStyle to a view where
+     // feature visibility has already been configured is not very friendly
      /*
       * set feature colour if specified by feature settings
       * set visibility of all features
      {
        FeatureColourI preferredColour = featureSettings
                .getFeatureColour(type);
-       if (preferredColour != null)
-       {
-         fr.setColour(type, preferredColour);
-       }
-       if (featureSettings.isFeatureDisplayed(type))
+       FeatureColourI origColour = fr.getFeatureStyle(type);
+       if (!mergeOnly || (!origRenderOrder.contains(type)
+               || origColour == null
+               || (!origColour.isGraduatedColour()
+                       && origColour.getColour() != null
+                       && origColour.getColour().equals(
+                               ColorUtils.createColourFromName(type)))))
        {
-         displayed.setVisible(type);
+         // if we are merging, only update if there wasn't already a colour defined for
+         // this type
+         if (preferredColour != null)
+         {
+           fr.setColour(type, preferredColour);
+         }
+         if (featureSettings.isFeatureDisplayed(type))
+         {
+           displayed.setVisible(type);
+         }
        }
      }
  
       */
      for (String group : fr.getFeatureGroups())
      {
-       fr.setGroupVisibility(group, featureSettings.isGroupDisplayed(group));
+       if (!mergeOnly || !origGroups.contains(group))
+       {
+         // when merging, display groups only if the aren't already marked as not visible
+         fr.setGroupVisibility(group,
+                 featureSettings.isGroupDisplayed(group));
+       }
      }
  
      /*
@@@ -24,11 -24,9 +24,11 @@@ import jalview.bin.Cache
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
 +import jalview.gui.ImageExporter.ImageWriterI;
  import jalview.gui.StructureViewer.ViewerType;
  import jalview.structures.models.AAStructureBindingModel;
  import jalview.util.BrowserLauncher;
 +import jalview.util.ImageMaker;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
  import jalview.ws.dbsources.Pdb;
@@@ -328,7 -326,7 +328,7 @@@ public class AppJmol extends StructureV
      for (String s : files)
      {
        fileList.append(SPACE).append(QUOTE)
-               .append(Platform.escapeString(s)).append(QUOTE);
+               .append(Platform.escapeBackslashes(s)).append(QUOTE);
      }
      String filesString = fileList.toString();
  
        } catch (Exception ex)
        {
          Cache.log.error("Couldn't open Jmol viewer!", ex);
 +        ex.printStackTrace();
 +        return;
        }
      }
      else
          new OOMWarning("When trying to add structures to the Jmol viewer!",
                  oomerror);
          Cache.log.debug("File locations are " + filesString);
 +        return;
        } catch (Exception ex)
        {
          Cache.log.error("Couldn't add files to Jmol viewer!", ex);
 +        ex.printStackTrace();
 +        return;
        }
      }
  
              addingStructures = true; // already files loaded.
              for (int c = 0; c < filesInViewer.length; c++)
              {
-               if (filesInViewer[c].equals(file))
+               if (Platform.pathEquals(filesInViewer[c], file))
                {
                  file = null;
                  break;
      return files;
    }
  
 -  @Override
 -  public void eps_actionPerformed(ActionEvent e)
 -  {
 -    makePDBImage(jalview.util.ImageMaker.TYPE.EPS);
 -  }
 -
 -  @Override
 -  public void png_actionPerformed(ActionEvent e)
 -  {
 -    makePDBImage(jalview.util.ImageMaker.TYPE.PNG);
 -  }
 -
 -  void makePDBImage(jalview.util.ImageMaker.TYPE type)
 +  /**
 +   * Outputs the Jmol viewer image as an image file, after prompting the user to
 +   * choose a file and (for EPS) choice of Text or Lineart character rendering
 +   * (unless a preference for this is set)
 +   * 
 +   * @param type
 +   */
 +  public void makePDBImage(ImageMaker.TYPE type)
    {
      int width = getWidth();
      int height = getHeight();
 -
 -    jalview.util.ImageMaker im;
 -
 -    if (type == jalview.util.ImageMaker.TYPE.PNG)
 +    ImageWriterI writer = new ImageWriterI()
      {
 -      im = new jalview.util.ImageMaker(this,
 -              jalview.util.ImageMaker.TYPE.PNG, "Make PNG image from view",
 -              width, height, null, null, null, 0, false);
 -    }
 -    else if (type == jalview.util.ImageMaker.TYPE.EPS)
 -    {
 -      im = new jalview.util.ImageMaker(this,
 -              jalview.util.ImageMaker.TYPE.EPS, "Make EPS file from view",
 -              width, height, null, this.getTitle(), null, 0, false);
 -    }
 -    else
 -    {
 -
 -      im = new jalview.util.ImageMaker(this,
 -              jalview.util.ImageMaker.TYPE.SVG, "Make SVG file from PCA",
 -              width, height, null, this.getTitle(), null, 0, false);
 -    }
 -
 -    if (im.getGraphics() != null)
 -    {
 -      jmb.viewer.renderScreenImage(im.getGraphics(), width, height);
 -      im.writeImage();
 -    }
 +      @Override
 +      public void exportImage(Graphics g) throws Exception
 +      {
 +        jmb.viewer.renderScreenImage(g, width, height);
 +      }
 +    };
 +    String view = MessageManager.getString("action.view").toLowerCase();
 +    ImageExporter exporter = new ImageExporter(writer,
 +            jmb.getIProgressIndicator(), type, getTitle());
 +    exporter.doExport(null, this, width, height, view);
    }
  
    @Override
    {
      try
      {
 -      BrowserLauncher
 -              .openURL("http://jmol.sourceforge.net/docs/JmolUserGuide/");
 +      BrowserLauncher // BH 2018
 +              .openURL("http://wiki.jmol.org");//http://jmol.sourceforge.net/docs/JmolUserGuide/");
      } catch (Exception ex)
      {
      }
@@@ -219,7 -219,7 +219,7 @@@ public class BlogReader extends JPane
  
        }
      };
 -  };
 +  }
  
    private JLabel lblChannels = new JLabel();
  
      } catch (Exception x)
      {
      }
 -    ;
      if (chan != null && chan.getItems() != null)
      {
        Cache.log.debug("Scanning news items: newsnew=" + newsnew
      public LaunchJvBrowserOnItem(JList listItems)
      {
        super("Open in Browser");
-       this.putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
+       this.putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_O));
        this.putValue(Action.LONG_DESCRIPTION, "Open in Browser");
        _listItems = listItems;
      }
      }
    }
  
 +  /**
 +   * @j2sIgnore
 +   * @param args
 +   */
    public static void main(String args[])
    {
      // this tests the detection of new news based on the last read date stored
          } catch (InterruptedException x)
          {
          }
 -        ;
        } while (me.isVisible());
  
        if (me.isNewsNew())
@@@ -32,7 -32,6 +32,7 @@@ import jalview.io.DataSourceType
  import jalview.io.StructureFile;
  import jalview.structures.models.AAStructureBindingModel;
  import jalview.util.BrowserLauncher;
 +import jalview.util.ImageMaker.TYPE;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
  import jalview.ws.dbsources.Pdb;
@@@ -481,7 -480,7 +481,7 @@@ public class ChimeraViewFrame extends S
          {
            filePDB.add(thePdbEntry);
            filePDBpos.add(Integer.valueOf(pi));
-           files.append(" \"" + Platform.escapeString(file) + "\"");
+           files.append(" \"" + Platform.escapeBackslashes(file) + "\"");
          }
        }
      } catch (OutOfMemoryError oomerror)
    }
  
    @Override
 -  public void eps_actionPerformed(ActionEvent e)
 +  public void makePDBImage(TYPE imageType)
    {
 -    throw new Error(MessageManager
 -            .getString("error.eps_generation_not_implemented"));
 -  }
 -
 -  @Override
 -  public void png_actionPerformed(ActionEvent e)
 -  {
 -    throw new Error(MessageManager
 -            .getString("error.png_generation_not_implemented"));
 +    throw new UnsupportedOperationException(
 +            "Image export for Chimera is not implemented");
    }
  
    @Override
@@@ -111,6 -111,13 +111,13 @@@ public class CrossRefAction implements 
        FeatureSettingsModelI featureColourScheme = new SequenceFetcher()
                .getFeatureColourScheme(source);
  
+       if (dna && AlignmentUtils.looksLikeEnsembl(alignment))
+       {
+         // override default featureColourScheme so products have Ensembl variant colours
+         featureColourScheme = new SequenceFetcher()
+                 .getFeatureColourScheme(DBRefSource.ENSEMBL);
+       }
        AlignmentI xrefsAlignment = makeCrossReferencesAlignment(dataset,
                xrefs);
        if (!dna)
  
        /*
         * apply 'database source' feature configuration
-        * if any was found
+        * if any - first to the new splitframe view about to be displayed
         */
-       // TODO is this the feature colouring for the original
-       // alignment or the fetched xrefs? either could be Ensembl
        newFrame.getViewport().applyFeaturesStyle(featureColourScheme);
        copyThis.getViewport().applyFeaturesStyle(featureColourScheme);
  
+       /*
+        * and for JAL-3330 also to original alignFrame view(s)
+        * this currently trashes any original settings.
+        */
+       for (AlignmentViewPanel origpanel: alignFrame.getAlignPanels()) {
+         origpanel.getAlignViewport()
+                 .mergeFeaturesStyle(featureColourScheme);
+       }
        SplitFrame sf = new SplitFrame(dna ? copyThis : newFrame,
                dna ? newFrame : copyThis);
        newFrame.setVisible(true);
        copyThis.setVisible(true);
        String linkedTitle = MessageManager
       */
      String[] divisionsArray = ensemblDivisions
              .toArray(new String[ensemblDivisions.size()]);
 -    DBRefEntry[] seqRefs = seq.getDBRefs();
 -    DBRefEntry[] directEnsemblRefs = DBRefUtils.selectRefs(seqRefs,
 +    List<DBRefEntry> seqRefs = seq.getDBRefs();
 +    List<DBRefEntry> directEnsemblRefs = DBRefUtils.selectRefs(seqRefs,
              divisionsArray);
      if (directEnsemblRefs != null)
      {
      {
        if (dbref.getMap() != null && dbref.getMap().getTo() != null)
        {
 -        DBRefEntry[] dbrefs = dbref.getMap().getTo().getDBRefs();
 -        DBRefEntry[] indirectEnsemblRefs = DBRefUtils.selectRefs(dbrefs,
 +        List<DBRefEntry> dbrefs = dbref.getMap().getTo().getDBRefs();
 +        List<DBRefEntry> indirectEnsemblRefs = DBRefUtils.selectRefs(dbrefs,
                  divisionsArray);
          if (indirectEnsemblRefs != null)
          {
              seq.getLength());
      if (geneLoci != null)
      {
-       MapList map = geneLoci.getMap();
+       MapList map = geneLoci.getMapping();
        int mappedFromLength = MappingUtils.getLength(map.getFromRanges());
        if (mappedFromLength == seq.getLength())
        {
          seq.setGeneLoci(geneLoci.getSpeciesId(), geneLoci.getAssemblyId(),
-                 geneLoci.getChromosomeId(), geneLoci.getMap());
+                 geneLoci.getChromosomeId(), map);
          retrievedLoci.put(dbref, geneLoci);
          return true;
        }
              seq.getLength());
      if (geneLoci != null)
      {
-       MapList map = geneLoci.getMap();
+       MapList map = geneLoci.getMapping();
        int mappedFromLength = MappingUtils.getLength(map.getFromRanges());
        if (mappedFromLength == seq.getLength())
        {
          seq.setGeneLoci(geneLoci.getSpeciesId(), geneLoci.getAssemblyId(),
-                 geneLoci.getChromosomeId(), geneLoci.getMap());
+                 geneLoci.getChromosomeId(), map);
          retrievedLoci.put(dbref, geneLoci);
          return true;
        }
   */
  package jalview.gui;
  
- import static jalview.util.UrlConstants.SEQUENCE_ID;
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
 +import jalview.gui.ImageExporter.ImageWriterI;
  import jalview.io.BackupFiles;
  import jalview.io.DataSourceType;
  import jalview.io.FileFormat;
@@@ -43,10 -40,9 +41,11 @@@ import jalview.jbgui.GStructureViewer
  import jalview.project.Jalview2XML;
  import jalview.structure.StructureSelectionManager;
  import jalview.urls.IdOrgSettings;
 -import jalview.util.ImageMaker;
 +import jalview.util.BrowserLauncher;
 +import jalview.util.ImageMaker.TYPE;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
++import jalview.util.ShortcutKeyMaskExWrapper;
  import jalview.util.UrlConstants;
  import jalview.viewmodel.AlignmentViewport;
  import jalview.ws.params.ParamManager;
@@@ -81,15 -77,16 +80,15 @@@ import java.awt.event.WindowAdapter
  import java.awt.event.WindowEvent;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
- import java.io.BufferedInputStream;
  import java.io.File;
- import java.io.FileOutputStream;
+ import java.io.FileWriter;
  import java.io.IOException;
  import java.net.URL;
  import java.util.ArrayList;
+ import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.List;
  import java.util.ListIterator;
 -import java.util.StringTokenizer;
  import java.util.Vector;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
@@@ -108,22 -105,18 +107,19 @@@ import javax.swing.JCheckBox
  import javax.swing.JComboBox;
  import javax.swing.JComponent;
  import javax.swing.JDesktopPane;
- import javax.swing.JFrame;
  import javax.swing.JInternalFrame;
  import javax.swing.JLabel;
  import javax.swing.JMenuItem;
  import javax.swing.JPanel;
  import javax.swing.JPopupMenu;
  import javax.swing.JProgressBar;
 +import javax.swing.JTextField;
  import javax.swing.KeyStroke;
  import javax.swing.SwingUtilities;
  import javax.swing.event.HyperlinkEvent;
  import javax.swing.event.HyperlinkEvent.EventType;
  import javax.swing.event.InternalFrameAdapter;
  import javax.swing.event.InternalFrameEvent;
- import javax.swing.event.MenuEvent;
- import javax.swing.event.MenuListener;
  
  import org.stackoverflowusers.file.WindowsShortcut;
  
@@@ -148,6 -141,10 +144,10 @@@ public class Desktop extends jalview.jb
  
    private static final String EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES";
  
+   protected static final String CONFIRM_KEYBOARD_QUIT = "CONFIRM_KEYBOARD_QUIT";
+   public static HashMap<String, FileWriter> savingFiles = new HashMap<>();
    private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
  
    /**
  
    public static MyDesktopPane desktop;
  
 +  public static MyDesktopPane getDesktop()
 +  {
 +    // BH 2018 could use currentThread() here as a reference to a
 +    // Hashtable<Thread, MyDesktopPane> in JavaScript
 +    return desktop;
 +  }
 +
    static int openFrameCount = 0;
  
    static final int xOffset = 30;
     */
    public Desktop()
    {
 +    super();
      /**
       * A note to implementors. It is ESSENTIAL that any activities that might
       * block are spawned off as threads rather than waited for during this
       * constructor.
       */
      instance = this;
-     if (!Platform.isJS())
+     doConfigureStructurePrefs();
 -    setTitle("Jalview " + jalview.bin.Cache.getProperty("VERSION"));
++    setTitle("Jalview " + Cache.getProperty("VERSION"));
+     /*
+     if (!Platform.isAMac())
      {
-       doVamsasClientCheck();
+       // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      }
+     else
+     {
+      this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+     }
+     */
  
-     doConfigureStructurePrefs();
-     setTitle("Jalview " + jalview.bin.Cache.getProperty("VERSION"));
-     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-     boolean selmemusage = jalview.bin.Cache.getDefault("SHOW_MEMUSAGE",
+     try
+     {
+       APQHandlers.setAPQHandlers(this);
+     } catch (Exception e)
+     {
+       System.out.println("Cannot set APQHandlers");
+       // e.printStackTrace();
+     } catch (Throwable t)
+     {
+       System.out.println("Cannot set APQHandlers");
+       // t.printStackTrace();
+     }
+     addWindowListener(new WindowAdapter()
+     {
+       @Override
+       public void windowClosing(WindowEvent ev)
+       {
+         quit();
+       }
+     });
 -    boolean selmemusage = jalview.bin.Cache.getDefault("SHOW_MEMUSAGE",
++    boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE",
              false);
-     boolean showjconsole = jalview.bin.Cache.getDefault("SHOW_JAVA_CONSOLE",
 -    boolean showjconsole = jalview.bin.Cache.getDefault("SHOW_JAVA_CONSOLE",
++    boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE",
              false);
      desktop = new MyDesktopPane(selmemusage);
 +
      showMemusage.setSelected(selmemusage);
      desktop.setBackground(Color.white);
      getContentPane().setLayout(new BorderLayout());
      // alternate config - have scrollbars - see notes in JAL-153
      // JScrollPane sp = new JScrollPane();
      // sp.getViewport().setView(desktop);
      // getContentPane().add(sp, BorderLayout.CENTER);
 +
 +    // BH 2018 - just an experiment to try unclipped JInternalFrames.
 +    if (Platform.isJS())
 +    {
 +      getRootPane().putClientProperty("swingjs.overflow.hidden", "false");
 +    }
 +
      getContentPane().add(desktop, BorderLayout.CENTER);
      desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
  
      // This line prevents Windows Look&Feel resizing all new windows to maximum
      // if previous window was maximised
      desktop.setDesktopManager(new MyDesktopManager(
 -            (Platform.isWindows() ? new DefaultDesktopManager()
 -                    : Platform.isAMac()
 +            (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
 +                    : Platform.isAMacAndNotJS()
                              ? new AquaInternalFrameManager(
                                      desktop.getDesktopManager())
                              : desktop.getDesktopManager())));
      else
      {
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 -      setBounds((screenSize.width - 900) / 2, (screenSize.height - 650) / 2,
 -              900, 650);
 +      int xPos = Math.max(5, (screenSize.width - 900) / 2);
 +      int yPos = Math.max(5, (screenSize.height - 650) / 2);
 +      setBounds(xPos, yPos, 900, 650);
      }
 -    jconsole = new Console(this, showjconsole);
 -    // add essential build information
 -    jconsole.setHeader(jalview.bin.Cache.getVersionDetailsForConsole());
  
 -    showConsole(showjconsole);
 +    if (!Platform.isJS())
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +    {
 +
 +      jconsole = new Console(this, showjconsole);
 +      // add essential build information
 +      jconsole.setHeader("Jalview Version: "
-               + jalview.bin.Cache.getProperty("VERSION") + "\n"
++              + Cache.getProperty("VERSION") + "\n"
 +              + "Jalview Installation: "
-               + jalview.bin.Cache.getDefault("INSTALLATION", "unknown")
++              + Cache.getDefault("INSTALLATION", "unknown")
 +              + "\n" + "Build Date: "
-               + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown") + "\n"
++              + Cache.getDefault("BUILD_DATE", "unknown") + "\n"
 +              + "Java version: " + System.getProperty("java.version") + "\n"
 +              + System.getProperty("os.arch") + " "
 +              + System.getProperty("os.name") + " "
 +              + System.getProperty("os.version"));
  
 -    showNews.setVisible(false);
 +      showConsole(showjconsole);
  
 -    experimentalFeatures.setSelected(showExperimental());
 +      showNews.setVisible(false);
  
 -    getIdentifiersOrgData();
 +      experimentalFeatures.setSelected(showExperimental());
  
 -    checkURLLinks();
 +      getIdentifiersOrgData();
 +
 +      checkURLLinks();
 +
 +      // Spawn a thread that shows the splashscreen
 +
 +      SwingUtilities.invokeLater(new Runnable()
 +      {
 +        @Override
 +        public void run()
 +        {
 +          new SplashScreen();
 +        }
 +      });
 +
 +      // Thread off a new instance of the file chooser - this reduces the time
 +      // it
 +      // takes to open it later on.
 +      new Thread(new Runnable()
 +      {
 +        @Override
 +        public void run()
 +        {
 +          Cache.log.debug("Filechooser init thread started.");
 +          String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
 +          JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
 +                  fileFormat);
 +          Cache.log.debug("Filechooser init thread finished.");
 +        }
 +      }).start();
 +      // Add the service change listener
 +      changeSupport.addJalviewPropertyChangeListener("services",
 +              new PropertyChangeListener()
 +              {
 +
 +                @Override
 +                public void propertyChange(PropertyChangeEvent evt)
 +                {
 +                  Cache.log.debug("Firing service changed event for "
 +                          + evt.getNewValue());
 +                  JalviewServicesChanged(evt);
 +                }
 +              });
 +    }
 +
 +    this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
  
      this.addWindowListener(new WindowAdapter()
      {
      });
      desktop.addMouseListener(ma);
  
 -    this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
 -    // Spawn a thread that shows the splashscreen
 -    SwingUtilities.invokeLater(new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        new SplashScreen();
 -      }
 -    });
 -
 -    // Thread off a new instance of the file chooser - this reduces the time it
 -    // takes to open it later on.
 -    new Thread(new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        Cache.log.debug("Filechooser init thread started.");
 -        String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
 -        JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"),
 -                fileFormat);
 -        Cache.log.debug("Filechooser init thread finished.");
 -      }
 -    }).start();
 -    // Add the service change listener
 -    changeSupport.addJalviewPropertyChangeListener("services",
 -            new PropertyChangeListener()
 -            {
 -
 -              @Override
 -              public void propertyChange(PropertyChangeEvent evt)
 -              {
 -                Cache.log.debug("Firing service changed event for "
 -                        + evt.getNewValue());
 -                JalviewServicesChanged(evt);
 -              }
 -
 -            });
    }
  
    /**
      // configure services
      StructureSelectionManager ssm = StructureSelectionManager
              .getStructureSelectionManager(this);
--    if (jalview.bin.Cache.getDefault(Preferences.ADD_SS_ANN, true))
++    if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
      {
--      ssm.setAddTempFacAnnot(jalview.bin.Cache
++      ssm.setAddTempFacAnnot(Cache
                .getDefault(Preferences.ADD_TEMPFACT_ANN, true));
--      ssm.setProcessSecondaryStructure(jalview.bin.Cache
++      ssm.setProcessSecondaryStructure(Cache
                .getDefault(Preferences.STRUCT_FROM_PDB, true));
        ssm.setSecStructServices(
--              jalview.bin.Cache.getDefault(Preferences.USE_RNAVIEW, true));
++              Cache.getDefault(Preferences.USE_RNAVIEW, true));
      }
      else
      {
        public void run()
        {
          Cache.log.debug("Starting news thread.");
 -
          jvnews = new BlogReader(me);
          showNews.setVisible(true);
          Cache.log.debug("Completed news thread.");
        public void run()
        {
          Cache.log.debug("Downloading data from identifiers.org");
--        UrlDownloadClient client = new UrlDownloadClient();
          try
          {
--          client.download(IdOrgSettings.getUrl(),
++          UrlDownloadClient.download(IdOrgSettings.getUrl(),
                    IdOrgSettings.getDownloadLocation());
          } catch (IOException e)
          {
          }
        }
      }).start();
--    ;
++    
    }
  
    @Override
  
    void showNews(boolean visible)
    {
 +    Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
 +    showNews.setSelected(visible);
 +    if (visible && !jvnews.isVisible())
      {
 -      Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
 -      showNews.setSelected(visible);
 -      if (visible && !jvnews.isVisible())
 +      new Thread(new Runnable()
        {
 -        new Thread(new Runnable()
 +        @Override
 +        public void run()
          {
 -          @Override
 -          public void run()
 -          {
 -            long now = System.currentTimeMillis();
 -            Desktop.instance.setProgressBar(
 -                    MessageManager.getString("status.refreshing_news"),
 -                    now);
 -            jvnews.refreshNews();
 -            Desktop.instance.setProgressBar(null, now);
 -            jvnews.showNews();
 -          }
 -        }).start();
 -      }
 +          long now = System.currentTimeMillis();
 +          Desktop.instance.setProgressBar(
 +                  MessageManager.getString("status.refreshing_news"), now);
 +          jvnews.refreshNews();
 +          Desktop.instance.setProgressBar(null, now);
 +          jvnews.showNews();
 +        }
 +      }).start();
      }
    }
  
    {
      // TODO: lock aspect ratio for scaling desktop Bug #0058199
      Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
--    String x = jalview.bin.Cache.getProperty(windowName + "SCREEN_X");
--    String y = jalview.bin.Cache.getProperty(windowName + "SCREEN_Y");
--    String width = jalview.bin.Cache
++    String x = Cache.getProperty(windowName + "SCREEN_X");
++    String y = Cache.getProperty(windowName + "SCREEN_Y");
++    String width = Cache
              .getProperty(windowName + "SCREEN_WIDTH");
--    String height = jalview.bin.Cache
++    String height = Cache
              .getProperty(windowName + "SCREEN_HEIGHT");
      if ((x != null) && (y != null) && (width != null) && (height != null))
      {
        int ix = Integer.parseInt(x), iy = Integer.parseInt(y),
                iw = Integer.parseInt(width), ih = Integer.parseInt(height);
--      if (jalview.bin.Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
++      if (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
        {
          // attempt #1 - try to cope with change in screen geometry - this
          // version doesn't preserve original jv aspect ratio.
          // take ratio of current screen size vs original screen size.
          double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(
--                jalview.bin.Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
++                Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
          double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(
--                jalview.bin.Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
++                Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
          // rescale the bounds depending upon the current screen geometry.
          ix = (int) (ix * sw);
          iw = (int) (iw * sw);
          ih = (int) (ih * sh);
          while (ix >= screenSize.width)
          {
--          jalview.bin.Cache.log.debug(
++          Cache.log.debug(
                    "Window geometry location recall error: shifting horizontal to within screenbounds.");
            ix -= screenSize.width;
          }
          while (iy >= screenSize.height)
          {
--          jalview.bin.Cache.log.debug(
++          Cache.log.debug(
                    "Window geometry location recall error: shifting vertical to within screenbounds.");
            iy -= screenSize.height;
          }
--        jalview.bin.Cache.log.debug(
++        Cache.log.debug(
                  "Got last known dimensions for " + windowName + ": x:" + ix
                          + " y:" + iy + " width:" + iw + " height:" + ih);
        }
      return null;
    }
  
-   private void doVamsasClientCheck()
-   {
-     if (Cache.vamsasJarsPresent())
-     {
-       setupVamsasDisconnectedGui();
-       VamsasMenu.setVisible(true);
-       final Desktop us = this;
-       VamsasMenu.addMenuListener(new MenuListener()
-       {
-         // this listener remembers when the menu was first selected, and
-         // doesn't rebuild the session list until it has been cleared and
-         // reselected again.
-         boolean refresh = true;
-         @Override
-         public void menuCanceled(MenuEvent e)
-         {
-           refresh = true;
-         }
-         @Override
-         public void menuDeselected(MenuEvent e)
-         {
-           refresh = true;
-         }
-         @Override
-         public void menuSelected(MenuEvent e)
-         {
-           if (refresh)
-           {
-             us.buildVamsasStMenu();
-             refresh = false;
-           }
-         }
-       });
-       vamsasStart.setVisible(true);
-     }
-   }
    void showPasteMenu(int x, int y)
    {
      JPopupMenu popup = new JPopupMenu();
      frame.setResizable(resizable);
      frame.setMaximizable(resizable);
      frame.setIconifiable(resizable);
 -    frame.setOpaque(false);
 +    frame.setOpaque(Platform.isJS());
  
      if (frame.getX() < 1 && frame.getY() < 1)
      {
            menuItem.removeActionListener(menuItem.getActionListeners()[0]);
          }
          windowMenu.remove(menuItem);
--      };
++      }
      });
  
      menuItem.addActionListener(new ActionListener()
            frame.setIcon(false);
          } catch (java.beans.PropertyVetoException ex)
          {
 -
 +          // System.err.println(ex.toString());
          }
        }
      });
    }
  
    /**
 -   * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close
 -   * the window
 +   * Add key bindings to a JInternalFrame so that Ctrl-W and Cmd-W will close the
 +   * window
     * 
     * @param frame
     */
      KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
              InputEvent.CTRL_DOWN_MASK);
      KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
 -            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
++            ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
  
      InputMap inputMap = frame
              .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
      // Java's Transferable for native dnd
      evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
      Transferable t = evt.getTransferable();
 -    List<String> files = new ArrayList<>();
 +    List<Object> files = new ArrayList<>();
      List<DataSourceType> protocols = new ArrayList<>();
  
      try
        {
          for (int i = 0; i < files.size(); i++)
          {
 -          String file = files.get(i).toString();
 +          // BH 2018 File or String
 +          Object file = files.get(i);
 +          String fileName = file.toString();
            DataSourceType protocol = (protocols == null)
                    ? DataSourceType.FILE
                    : protocols.get(i);
            FileFormatI format = null;
  
 -          if (file.endsWith(".jar"))
 +          if (fileName.endsWith(".jar"))
            {
              format = FileFormat.Jalview;
  
            {
              format = new IdentifyFile().identify(file, protocol);
            }
 -
 -          new FileLoader().LoadFile(file, protocol, format);
 +          if (file instanceof File)
 +          {
 +            Platform.cacheFileData((File) file);
 +          }
 +          new FileLoader().LoadFile(null, file, protocol, format);
  
          }
        } catch (Exception ex)
    {
      String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
      JalviewFileChooser chooser = JalviewFileChooser
-             .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat, true);
+             .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat, BackupFiles.getEnabled());
  
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(
              MessageManager.getString("label.open_local_file"));
      chooser.setToolTipText(MessageManager.getString("action.open"));
  
 -    int value = chooser.showOpenDialog(this);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    chooser.setResponseHandler(0, new Runnable()
      {
 -      String choice = chooser.getSelectedFile().getPath();
 -      Cache.setProperty("LAST_DIRECTORY",
 -              chooser.getSelectedFile().getParent());
 +      @Override
 +      public void run()
 +      {
 +        File selectedFile = chooser.getSelectedFile();
 +        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
  
 -      FileFormatI format = chooser.getSelectedFormat();
 +        FileFormatI format = chooser.getSelectedFormat();
  
 -      /*
 -       * Call IdentifyFile to verify the file contains what its extension implies.
 -       * Skip this step for dynamically added file formats, because
 -       * IdentifyFile does not know how to recognise them.
 -       */
 -      if (FileFormats.getInstance().isIdentifiable(format))
 -      {
 -        try
 -        {
 -          format = new IdentifyFile().identify(choice, DataSourceType.FILE);
 -        } catch (FileFormatException e)
 +        /*
 +         * Call IdentifyFile to verify the file contains what its extension implies.
 +         * Skip this step for dynamically added file formats, because
 +         * IdentifyFile does not know how to recognise them.
 +         */
 +        if (FileFormats.getInstance().isIdentifiable(format))
          {
 -          // format = null; //??
 +          try
 +          {
 +            format = new IdentifyFile().identify(selectedFile,
 +                    DataSourceType.FILE);
 +          } catch (FileFormatException e)
 +          {
 +            // format = null; //??
 +          }
          }
 -      }
  
 -      if (viewport != null)
 -      {
 -        new FileLoader().LoadFile(viewport, choice, DataSourceType.FILE,
 -                format);
 -      }
 -      else
 -      {
 -        new FileLoader().LoadFile(choice, DataSourceType.FILE, format);
 +        new FileLoader().LoadFile(viewport, selectedFile,
 +                DataSourceType.FILE, format);
        }
 -    }
 +    });
 +    chooser.showOpenDialog(this);
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Shows a dialog for input of a URL at which to retrieve alignment data
     * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * @param viewport
     */
    @Override
    public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
      // for viewing
      JLabel label = new JLabel(
              MessageManager.getString("label.input_file_url"));
  
      JPanel panel = new JPanel(new GridLayout(2, 1));
      panel.add(label);
 -    panel.add(history);
 -    history.setPreferredSize(new Dimension(400, 20));
 -    history.setEditable(true);
 -    history.addItem("http://www.");
 -
 -    String historyItems = jalview.bin.Cache.getProperty("RECENT_URL");
 -
 -    StringTokenizer st;
 -
 -    if (historyItems != null)
 -    {
 -      st = new StringTokenizer(historyItems, "\t");
 -
 -      while (st.hasMoreTokens())
 -      {
 -        history.addItem(st.nextElement());
 -      }
 -    }
 -
 -    int reply = JvOptionPane.showInternalConfirmDialog(desktop, panel,
 -            MessageManager.getString("label.input_alignment_from_url"),
 -            JvOptionPane.OK_CANCEL_OPTION);
  
 -    if (reply != JvOptionPane.OK_OPTION)
 +    /*
 +     * the URL to fetch is
 +     * Java: an editable combobox with history
 +     * JS: (pending JAL-3038) a plain text field
 +     */
 +    JComponent history;
 +    String urlBase = "http://www.";
 +    if (Platform.isJS())
      {
 -      return;
 +      history = new JTextField(urlBase, 35);
      }
 -
 -    String url = history.getSelectedItem().toString();
 -
 -    if (url.toLowerCase().endsWith(".jar"))
 +    else
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
      {
 -      if (viewport != null)
 -      {
 -        new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 -                FileFormat.Jalview);
 -      }
 -      else
 +      JComboBox<String> asCombo = new JComboBox<>();
 +      asCombo.setPreferredSize(new Dimension(400, 20));
 +      asCombo.setEditable(true);
 +      asCombo.addItem(urlBase);
 +      String historyItems = Cache.getProperty("RECENT_URL");
 +      if (historyItems != null)
        {
 -        new FileLoader().LoadFile(url, DataSourceType.URL,
 -                FileFormat.Jalview);
 +        for (String token : historyItems.split("\\t"))
 +        {
 +          asCombo.addItem(token);
 +        }
        }
 +      history = asCombo;
      }
 -    else
 +    panel.add(history);
 +
 +    Object[] options = new Object[] { MessageManager.getString("action.ok"),
 +        MessageManager.getString("action.cancel") };
 +    Runnable action = new Runnable()
      {
 -      FileFormatI format = null;
 -      try
 -      {
 -        format = new IdentifyFile().identify(url, DataSourceType.URL);
 -      } catch (FileFormatException e)
 +      @Override
 +      public void run()
        {
 -        // TODO revise error handling, distinguish between
 -        // URL not found and response not valid
 -      }
 +        @SuppressWarnings("unchecked")
 +        String url = (history instanceof JTextField
 +                ? ((JTextField) history).getText()
 +                : ((JComboBox<String>) history).getSelectedItem()
 +                        .toString());
  
 -      if (format == null)
 -      {
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 -                MessageManager.formatMessage("label.couldnt_locate",
 -                        new Object[]
 -                        { url }),
 -                MessageManager.getString("label.url_not_found"),
 -                JvOptionPane.WARNING_MESSAGE);
 +        if (url.toLowerCase().endsWith(".jar"))
 +        {
 +          if (viewport != null)
 +          {
 +            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 +                    FileFormat.Jalview);
 +          }
 +          else
 +          {
 +            new FileLoader().LoadFile(url, DataSourceType.URL,
 +                    FileFormat.Jalview);
 +          }
 +        }
 +        else
 +        {
 +          FileFormatI format = null;
 +          try
 +          {
 +            format = new IdentifyFile().identify(url, DataSourceType.URL);
 +          } catch (FileFormatException e)
 +          {
 +            // TODO revise error handling, distinguish between
 +            // URL not found and response not valid
 +          }
  
 -        return;
 -      }
 +          if (format == null)
 +          {
 +            String msg = MessageManager
 +                    .formatMessage("label.couldnt_locate", url);
 +            JvOptionPane.showInternalMessageDialog(Desktop.desktop, msg,
 +                    MessageManager.getString("label.url_not_found"),
 +                    JvOptionPane.WARNING_MESSAGE);
  
 -      if (viewport != null)
 -      {
 -        new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 -                format);
 -      }
 -      else
 -      {
 -        new FileLoader().LoadFile(url, DataSourceType.URL, format);
 +            return;
 +          }
 +
 +          if (viewport != null)
 +          {
 +            new FileLoader().LoadFile(viewport, url, DataSourceType.URL,
 +                    format);
 +          }
 +          else
 +          {
 +            new FileLoader().LoadFile(url, DataSourceType.URL, format);
 +          }
 +        }
        }
 -    }
 +    };
 +    String dialogOption = MessageManager
 +            .getString("label.input_alignment_from_url");
 +    JvOptionPane.newOptionDialog(desktop).setResponseHandler(0, action)
 +            .showInternalDialog(panel, dialogOption,
 +                    JvOptionPane.YES_NO_CANCEL_OPTION,
 +                    JvOptionPane.PLAIN_MESSAGE, null, options,
 +                    MessageManager.getString("action.ok"));
    }
  
    /**
    public void quit()
    {
      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
--    jalview.bin.Cache.setProperty("SCREENGEOMETRY_WIDTH",
++    Cache.setProperty("SCREENGEOMETRY_WIDTH",
              screen.width + "");
--    jalview.bin.Cache.setProperty("SCREENGEOMETRY_HEIGHT",
++    Cache.setProperty("SCREENGEOMETRY_HEIGHT",
              screen.height + "");
      storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
              getWidth(), getHeight()));
  
    private void storeLastKnownDimensions(String string, Rectangle jc)
    {
--    jalview.bin.Cache.log.debug("Storing last known dimensions for "
++    Cache.log.debug("Storing last known dimensions for "
              + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
              + " height:" + jc.height);
  
--    jalview.bin.Cache.setProperty(string + "SCREEN_X", jc.x + "");
--    jalview.bin.Cache.setProperty(string + "SCREEN_Y", jc.y + "");
--    jalview.bin.Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
--    jalview.bin.Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
++    Cache.setProperty(string + "SCREEN_X", jc.x + "");
++    Cache.setProperty(string + "SCREEN_Y", jc.y + "");
++    Cache.setProperty(string + "SCREEN_WIDTH", jc.width + "");
++    Cache.setProperty(string + "SCREEN_HEIGHT", jc.height + "");
    }
  
    /**
      if (shortv)
      {
        message.append("<h1><strong>Version: "
--              + jalview.bin.Cache.getProperty("VERSION")
++              + Cache.getProperty("VERSION")
                + "</strong></h1>");
-       message.append("<strong>Last Updated: <em>"
-               + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown")
-               + "</em></strong>");
+       message.append("<strong>Built: <em>"
 -              + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown")
 -              + "</em> from " + jalview.bin.Cache.getBuildDetailsForSplash()
++              + Cache.getDefault("BUILD_DATE", "unknown") + "</em> from "
++              + Cache.getBuildDetailsForSplash()
+               + "</strong>");
  
      }
      else
      {
  
        message.append("<strong>Version "
--              + jalview.bin.Cache.getProperty("VERSION")
++              + Cache.getProperty("VERSION")
                + "; last updated: "
--              + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown"));
++              + Cache.getDefault("BUILD_DATE", "unknown"));
      }
  
--    if (jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking")
++    if (Cache.getDefault("LATEST_VERSION", "Checking")
              .equals("Checking"))
      {
-       message.append("<br>...Checking latest version...</br>");
+       // JBP removed this message for 2.11: May be reinstated in future version
+       // message.append("<br>...Checking latest version...</br>");
      }
--    else if (!jalview.bin.Cache.getDefault("LATEST_VERSION", "Checking")
--            .equals(jalview.bin.Cache.getProperty("VERSION")))
++    else if (!Cache.getDefault("LATEST_VERSION", "Checking")
++            .equals(Cache.getProperty("VERSION")))
      {
        boolean red = false;
--      if (jalview.bin.Cache.getProperty("VERSION").toLowerCase()
++      if (Cache.getProperty("VERSION").toLowerCase()
                .indexOf("automated build") == -1)
        {
          red = true;
        }
  
        message.append("<br>!! Version "
--              + jalview.bin.Cache.getDefault("LATEST_VERSION",
++              + Cache.getDefault("LATEST_VERSION",
                        "..Checking..")
                + " is available for download from "
--              + jalview.bin.Cache.getDefault("www.jalview.org",
++              + Cache.getDefault("www.jalview.org",
                        "http://www.jalview.org")
                + " !!");
        if (red)
          message.append("</div>");
        }
      }
--    message.append("<br>Authors:  " + jalview.bin.Cache.getDefault(
++    message.append("<br>Authors:  " + Cache.getDefault(
              "AUTHORFNAMES",
              "The Jalview Authors (See AUTHORS file for current list)")
              + "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
    }
  
    /**
 -   * DOCUMENT ME!
 -   * 
 -   * @param e
 -   *          DOCUMENT ME!
 +   * Action on requesting Help documentation
     */
    @Override
 -  public void documentationMenuItem_actionPerformed(ActionEvent e)
 +  public void documentationMenuItem_actionPerformed()
    {
      try
      {
 -      Help.showHelpWindow();
 +      if (Platform.isJS())
 +      {
 +        BrowserLauncher.openURL("http://www.jalview.org/help.html");
 +      }
 +      else
 +      /**
 +       * Java only
 +       * 
 +       * @j2sIgnore
 +       */
 +      {
 +        Help.showHelpWindow();
 +      }
      } catch (Exception ex)
      {
 +      System.err.println("Error opening help: " + ex.getMessage());
      }
    }
  
      }
      Jalview.setCurrentAlignFrame(null);
      System.out.println("ALL CLOSED");
-     if (v_client != null)
-     {
-       // TODO clear binding to vamsas document objects on close_all
-     }
  
      /*
       * reset state of singleton objects as appropriate (clear down session state
    protected void garbageCollect_actionPerformed(ActionEvent e)
    {
      // We simply collect the garbage
--    jalview.bin.Cache.log.debug("Collecting garbage...");
++    Cache.log.debug("Collecting garbage...");
      System.gc();
--    jalview.bin.Cache.log.debug("Finished garbage collection.");
++    Cache.log.debug("Finished garbage collection.");
    }
  
    /*
     */
    void showConsole(boolean selected)
    {
 -    showConsole.setSelected(selected);
      // TODO: decide if we should update properties file
 -    Cache.setProperty("SHOW_JAVA_CONSOLE",
 -            Boolean.valueOf(selected).toString());
 -    jconsole.setVisible(selected);
 +    if (jconsole != null) // BH 2018
 +    {
 +      showConsole.setSelected(selected);
 +      Cache.setProperty("SHOW_JAVA_CONSOLE",
 +              Boolean.valueOf(selected).toString());
 +      jconsole.setVisible(selected);
 +    }
    }
  
    void reorderAssociatedWindows(boolean minimize, boolean close)
    }
  
    /**
 -   * Shows a file chooser dialog and writes out the current session as a Jalview
 -   * project file
 +   * Prompts the user to choose a file and then saves the Jalview state as a
 +   * Jalview project file
     */
    @Override
    public void saveState_actionPerformed()
            setProgressBar(MessageManager.formatMessage(
                    "label.saving_jalview_project", new Object[]
                    { chosenFile.getName() }), chosenFile.hashCode());
--          jalview.bin.Cache.setProperty("LAST_DIRECTORY",
++          Cache.setProperty("LAST_DIRECTORY",
                    chosenFile.getParent());
            // TODO catch and handle errors for savestate
            // TODO prevent user from messing with the Desktop whilst we're saving
            try
            {
 -            BackupFiles backupfiles = new BackupFiles(chosenFile);
 +              boolean doBackup = BackupFiles.getEnabled();
 +            BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
  
 -            new Jalview2XML().saveState(backupfiles.getTempFile());
 +            new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
  
 -            backupfiles.setWriteSuccess(true);
 -            backupfiles.rollBackupsAndRenameTempFile();
 +            if (doBackup)
 +            {
 +              backupfiles.setWriteSuccess(true);
 +              backupfiles.rollBackupsAndRenameTempFile();
 +            }
            } catch (OutOfMemoryError oom)
            {
              new OOMWarning("Whilst saving current state to "
            setProgressBar(null, chosenFile.hashCode());
          }
        }).start();
 -    }
 +      }
    }
  
    @Override
          "Jalview Project (old)" };
      JalviewFileChooser chooser = new JalviewFileChooser(
              Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
-             "Jalview Project", true, true); // last two booleans: allFiles,
+             "Jalview Project", true, BackupFiles.getEnabled()); // last two booleans: allFiles,
                                              // allowBackupFiles
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
 -
 -    int value = chooser.showOpenDialog(this);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    chooser.setResponseHandler(0, new Runnable()
      {
 -      final File selectedFile = chooser.getSelectedFile();
 -      setProjectFile(selectedFile);
 -      final String choice = selectedFile.getAbsolutePath();
 -      Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 -      new Thread(new Runnable()
 +      @Override
 +      public void run()
        {
 -        @Override
 -        public void run()
 +        File selectedFile = chooser.getSelectedFile();
 +        setProjectFile(selectedFile);
 +        String choice = selectedFile.getAbsolutePath();
 +        Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 +        new Thread(new Runnable()
          {
 -          setProgressBar(MessageManager.formatMessage(
 -                  "label.loading_jalview_project", new Object[]
 -                  { choice }), choice.hashCode());
 -          try
 -          {
 -            new Jalview2XML().loadJalviewAlign(choice);
 -          } catch (OutOfMemoryError oom)
 -          {
 -            new OOMWarning("Whilst loading project from " + choice, oom);
 -          } catch (Exception ex)
 +          @Override
 +          public void run()
            {
 -            Cache.log.error(
 -                    "Problems whilst loading project from " + choice, ex);
 -            JvOptionPane.showMessageDialog(Desktop.desktop,
 -                    MessageManager.formatMessage(
 -                            "label.error_whilst_loading_project_from",
 -                            new Object[]
 -                            { choice }),
 -                    MessageManager.getString("label.couldnt_load_project"),
 -                    JvOptionPane.WARNING_MESSAGE);
 +              try 
 +            {
 +              new Jalview2XML().loadJalviewAlign(choice);
 +            } catch (OutOfMemoryError oom)
 +              {
 +                new OOMWarning("Whilst loading project from " + choice, oom);
 +              } catch (Exception ex)
 +              {
 +                Cache.log.error(
 +                        "Problems whilst loading project from " + choice, ex);
 +                JvOptionPane.showMessageDialog(Desktop.desktop,
 +                        MessageManager.formatMessage(
 +                                "label.error_whilst_loading_project_from",
 +                              new Object[]
 +                                  { choice }),
 +                        MessageManager.getString("label.couldnt_load_project"),
 +                        JvOptionPane.WARNING_MESSAGE);
 +              }
            }
 -          setProgressBar(null, choice.hashCode());
 -        }
 -      }).start();
 -    }
 +        }).start();
 +      }
 +    });
 +    
 +    chooser.showOpenDialog(this);
    }
  
    @Override
  
    ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
  
 -  public void startLoading(final String fileName)
 +  public void startLoading(final Object fileName)
    {
      if (fileLoadingCount == 0)
      {
  
    /**
     * Gather expanded views (separate AlignFrame's) with the same sequence set
 -   * identifier back in to this frame as additional views, and close the
 -   * expanded views. Note the expanded frames may themselves have multiple
 -   * views. We take the lot.
 +   * identifier back in to this frame as additional views, and close the expanded
 +   * views. Note the expanded frames may themselves have multiple views. We take
 +   * the lot.
     * 
     * @param source
     */
          }
        }
      }
--
-   }
-   jalview.gui.VamsasApplication v_client = null;
-   @Override
-   public void vamsasImport_actionPerformed(ActionEvent e)
-   {
-     // TODO: JAL-3048 not needed for Jalview-JS
-     if (v_client == null)
-     {
-       // Load and try to start a session.
-       JalviewFileChooser chooser = new JalviewFileChooser(
-               jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
-       chooser.setFileView(new JalviewFileView());
-       chooser.setDialogTitle(
-               MessageManager.getString("label.open_saved_vamsas_session"));
-       chooser.setToolTipText(MessageManager.getString(
-               "label.select_vamsas_session_opened_as_new_vamsas_session"));
-       int value = chooser.showOpenDialog(this);
-       if (value == JalviewFileChooser.APPROVE_OPTION)
-       {
-         String fle = chooser.getSelectedFile().toString();
-         if (!vamsasImport(chooser.getSelectedFile()))
-         {
-           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-                   MessageManager.formatMessage(
-                           "label.couldnt_import_as_vamsas_session",
-                           new Object[]
-                           { fle }),
-                   MessageManager
-                           .getString("label.vamsas_document_import_failed"),
-                   JvOptionPane.ERROR_MESSAGE);
-         }
-       }
-     }
-     else
-     {
-       jalview.bin.Cache.log.error(
-               "Implementation error - load session from a running session is not supported.");
-     }
-   }
-   /**
-    * import file into a new vamsas session (uses jalview.gui.VamsasApplication)
-    * 
-    * @param file
-    * @return true if import was a success and a session was started.
-    */
-   public boolean vamsasImport(URL url)
-   {
-     // TODO: create progress bar
-     if (v_client != null)
-     {
-       jalview.bin.Cache.log.error(
-               "Implementation error - load session from a running session is not supported.");
-       return false;
-     }
-     try
-     {
-       // copy the URL content to a temporary local file
-       // TODO: be a bit cleverer here with nio (?!)
-       File file = File.createTempFile("vdocfromurl", ".vdj");
-       FileOutputStream fos = new FileOutputStream(file);
-       BufferedInputStream bis = new BufferedInputStream(url.openStream());
-       byte[] buffer = new byte[2048];
-       int ln;
-       while ((ln = bis.read(buffer)) > -1)
-       {
-         fos.write(buffer, 0, ln);
-       }
-       bis.close();
-       fos.close();
-       v_client = new jalview.gui.VamsasApplication(this, file,
-               url.toExternalForm());
-     } catch (Exception ex)
-     {
-       jalview.bin.Cache.log.error(
-               "Failed to create new vamsas session from contents of URL "
-                       + url,
-               ex);
-       return false;
-     }
-     setupVamsasConnectedGui();
-     v_client.initial_update(); // TODO: thread ?
-     return v_client.inSession();
-   }
-   /**
-    * import file into a new vamsas session (uses jalview.gui.VamsasApplication)
-    * 
-    * @param file
-    * @return true if import was a success and a session was started.
-    */
-   public boolean vamsasImport(File file)
-   {
-     if (v_client != null)
-     {
-       jalview.bin.Cache.log.error(
-               "Implementation error - load session from a running session is not supported.");
-       return false;
-     }
-     setProgressBar(MessageManager.formatMessage(
-             "status.importing_vamsas_session_from", new Object[]
-             { file.getName() }), file.hashCode());
-     try
-     {
-       v_client = new jalview.gui.VamsasApplication(this, file, null);
-     } catch (Exception ex)
-     {
-       setProgressBar(MessageManager.formatMessage(
-               "status.importing_vamsas_session_from", new Object[]
-               { file.getName() }), file.hashCode());
-       jalview.bin.Cache.log.error(
-               "New vamsas session from existing session file failed:", ex);
-       return false;
-     }
-     setupVamsasConnectedGui();
-     v_client.initial_update(); // TODO: thread ?
-     setProgressBar(MessageManager.formatMessage(
-             "status.importing_vamsas_session_from", new Object[]
-             { file.getName() }), file.hashCode());
-     return v_client.inSession();
-   }
-   public boolean joinVamsasSession(String mysesid)
-   {
-     if (v_client != null)
-     {
-       throw new Error(MessageManager
-               .getString("error.try_join_vamsas_session_another"));
-     }
-     if (mysesid == null)
-     {
-       throw new Error(
-               MessageManager.getString("error.invalid_vamsas_session_id"));
-     }
-     v_client = new VamsasApplication(this, mysesid);
-     setupVamsasConnectedGui();
-     v_client.initial_update();
-     return (v_client.inSession());
-   }
-   @Override
-   public void vamsasStart_actionPerformed(ActionEvent e)
-   {
-     if (v_client == null)
-     {
-       // Start a session.
-       // we just start a default session for moment.
-       /*
-        * JalviewFileChooser chooser = new JalviewFileChooser(jalview.bin.Cache.
-        * getProperty("LAST_DIRECTORY"));
-        * 
-        * chooser.setFileView(new JalviewFileView());
-        * chooser.setDialogTitle("Load Vamsas file");
-        * chooser.setToolTipText("Import");
-        * 
-        * int value = chooser.showOpenDialog(this);
-        * 
-        * if (value == JalviewFileChooser.APPROVE_OPTION) { v_client = new
-        * jalview.gui.VamsasApplication(this, chooser.getSelectedFile());
-        */
-       v_client = new VamsasApplication(this);
-       setupVamsasConnectedGui();
-       v_client.initial_update(); // TODO: thread ?
-     }
-     else
-     {
-       // store current data in session.
-       v_client.push_update(); // TODO: thread
-     }
-   }
-   protected void setupVamsasConnectedGui()
-   {
-     vamsasStart.setText(MessageManager.getString("label.session_update"));
-     vamsasSave.setVisible(true);
-     vamsasStop.setVisible(true);
-     vamsasImport.setVisible(false); // Document import to existing session is
-     // not possible for vamsas-client-1.0.
-   }
-   protected void setupVamsasDisconnectedGui()
-   {
-     vamsasSave.setVisible(false);
-     vamsasStop.setVisible(false);
-     vamsasImport.setVisible(true);
-     vamsasStart
-             .setText(MessageManager.getString("label.new_vamsas_session"));
-   }
-   @Override
-   public void vamsasStop_actionPerformed(ActionEvent e)
-   {
-     if (v_client != null)
-     {
-       v_client.end_session();
-       v_client = null;
-       setupVamsasDisconnectedGui();
-     }
-   }
-   protected void buildVamsasStMenu()
-   {
-     if (v_client == null)
-     {
-       String[] sess = null;
-       try
-       {
-         sess = VamsasApplication.getSessionList();
-       } catch (Exception e)
-       {
-         jalview.bin.Cache.log.warn("Problem getting current sessions list.",
-                 e);
-         sess = null;
-       }
-       if (sess != null)
-       {
-         jalview.bin.Cache.log.debug(
-                 "Got current sessions list: " + sess.length + " entries.");
-         VamsasStMenu.removeAll();
-         for (int i = 0; i < sess.length; i++)
-         {
-           JMenuItem sessit = new JMenuItem();
-           sessit.setText(sess[i]);
-           sessit.setToolTipText(MessageManager
-                   .formatMessage("label.connect_to_session", new Object[]
-                   { sess[i] }));
-           final Desktop dsktp = this;
-           final String mysesid = sess[i];
-           sessit.addActionListener(new ActionListener()
-           {
-             @Override
-             public void actionPerformed(ActionEvent e)
-             {
-               if (dsktp.v_client == null)
-               {
-                 Thread rthr = new Thread(new Runnable()
-                 {
-                   @Override
-                   public void run()
-                   {
-                     dsktp.v_client = new VamsasApplication(dsktp, mysesid);
-                     dsktp.setupVamsasConnectedGui();
-                     dsktp.v_client.initial_update();
-                   }
-                 });
-                 rthr.start();
-               }
-             };
-           });
-           VamsasStMenu.add(sessit);
-         }
-         // don't show an empty menu.
-         VamsasStMenu.setVisible(sess.length > 0);
-       }
-       else
-       {
-         jalview.bin.Cache.log.debug("No current vamsas sessions.");
-         VamsasStMenu.removeAll();
-         VamsasStMenu.setVisible(false);
-       }
-     }
-     else
-     {
-       // Not interested in the content. Just hide ourselves.
-       VamsasStMenu.setVisible(false);
-     }
-   }
-   @Override
-   public void vamsasSave_actionPerformed(ActionEvent e)
-   {
-     // TODO: JAL-3048 not needed for Jalview-JS
-     if (v_client != null)
-     {
-       // TODO: VAMSAS DOCUMENT EXTENSION is VDJ
-       JalviewFileChooser chooser = new JalviewFileChooser("vdj",
-               "Vamsas Document");
-       chooser.setFileView(new JalviewFileView());
-       chooser.setDialogTitle(MessageManager
-               .getString("label.save_vamsas_document_archive"));
-       int value = chooser.showSaveDialog(this);
-       if (value == JalviewFileChooser.APPROVE_OPTION)
-       {
-         java.io.File choice = chooser.getSelectedFile();
-         JPanel progpanel = addProgressPanel(MessageManager
-                 .formatMessage("label.saving_vamsas_doc", new Object[]
-                 { choice.getName() }));
-         Cache.setProperty("LAST_DIRECTORY", choice.getParent());
-         String warnmsg = null;
-         String warnttl = null;
-         try
-         {
-           v_client.vclient.storeDocument(choice);
-         } catch (Error ex)
-         {
-           warnttl = "Serious Problem saving Vamsas Document";
-           warnmsg = ex.toString();
-           jalview.bin.Cache.log
-                   .error("Error Whilst saving document to " + choice, ex);
-         } catch (Exception ex)
-         {
-           warnttl = "Problem saving Vamsas Document.";
-           warnmsg = ex.toString();
-           jalview.bin.Cache.log.warn(
-                   "Exception Whilst saving document to " + choice, ex);
-         }
-         removeProgressPanel(progpanel);
-         if (warnmsg != null)
-         {
-           JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-                   warnmsg, warnttl, JvOptionPane.ERROR_MESSAGE);
-         }
-       }
-     }
-   }
-   JPanel vamUpdate = null;
-   /**
-    * hide vamsas user gui bits when a vamsas document event is being handled.
-    * 
-    * @param b
-    *          true to hide gui, false to reveal gui
-    */
-   public void setVamsasUpdate(boolean b)
-   {
-     Cache.log.debug("Setting gui for Vamsas update "
-             + (b ? "in progress" : "finished"));
-     if (vamUpdate != null)
-     {
-       this.removeProgressPanel(vamUpdate);
-     }
-     if (b)
-     {
-       vamUpdate = this.addProgressPanel(
-               MessageManager.getString("label.updating_vamsas_session"));
-     }
-     vamsasStart.setVisible(!b);
-     vamsasStop.setVisible(!b);
-     vamsasSave.setVisible(!b);
    }
  
    public JInternalFrame[] getAllFrames()
            while (li.hasNext())
            {
              String link = li.next();
-             if (link.contains(SEQUENCE_ID)
+             if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
                      && !UrlConstants.isDefaultString(link))
              {
                check = true;
  
    /**
     * Proxy class for JDesktopPane which optionally displays the current memory
 -   * usage and highlights the desktop area with a red bar if free memory runs
 -   * low.
 +   * usage and highlights the desktop area with a red bar if free memory runs low.
     * 
     * @author AMW
     */
 -  public class MyDesktopPane extends JDesktopPane implements Runnable
 +  public class MyDesktopPane extends JDesktopPane
 +          implements Runnable
    {
 -
      private static final float ONE_MB = 1048576f;
  
      boolean showMemoryUsage = false;
        openGroovyConsole();
      } catch (Exception ex)
      {
--      jalview.bin.Cache.log.error("Groovy Shell Creation failed.", ex);
++      Cache.log.error("Groovy Shell Creation failed.", ex);
        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
  
                MessageManager.getString("label.couldnt_create_groovy_shell"),
    }
  
    /**
 -   * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this
 -   * binding when opened
 +   * Bind Ctrl/Cmd-Q to Quit - for reset as Groovy Console takes over this binding
 +   * when opened
     */
    protected void addQuitHandler()
    {
      getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
              .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
-                     Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
+                     jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
                      "Quit");
      getRootPane().getActionMap().put("Quit", new AbstractAction()
      {
    @Override
    public void setProgressBar(String message, long id)
    {
 +          Platform.timeCheck("Desktop " + message, Platform.TIME_MARK);     
 +
      if (progressBars == null)
      {
        progressBars = new Hashtable<>();
        progressBarHandlers = new Hashtable<>();
      }
  
-     if (progressBars.get(new Long(id)) != null)
+     if (progressBars.get(Long.valueOf(id)) != null)
      {
-       JPanel panel = progressBars.remove(new Long(id));
-       if (progressBarHandlers.contains(new Long(id)))
+       JPanel panel = progressBars.remove(Long.valueOf(id));
+       if (progressBarHandlers.contains(Long.valueOf(id)))
        {
-         progressBarHandlers.remove(new Long(id));
+         progressBarHandlers.remove(Long.valueOf(id));
        }
        removeProgressPanel(panel);
      }
      else
      {
-       progressBars.put(new Long(id), addProgressPanel(message));
+       progressBars.put(Long.valueOf(id), addProgressPanel(message));
      }
    }
  
            final IProgressIndicatorHandler handler)
    {
      if (progressBarHandlers == null
-             || !progressBars.containsKey(new Long(id)))
+             || !progressBars.containsKey(Long.valueOf(id)))
      {
        throw new Error(MessageManager.getString(
                "error.call_setprogressbar_before_registering_handler"));
      }
-     progressBarHandlers.put(new Long(id), handler);
-     final JPanel progressPanel = progressBars.get(new Long(id));
+     progressBarHandlers.put(Long.valueOf(id), handler);
+     final JPanel progressPanel = progressBars.get(Long.valueOf(id));
      if (handler.canCancel())
      {
        JButton cancel = new JButton(
    }
  
    /**
 -   * This will return the first AlignFrame holding the given viewport instance.
 -   * It will break if there are more than one AlignFrames viewing a particular
 -   * av.
 +   * This will return the first AlignFrame holding the given viewport instance. It
 +   * will break if there are more than one AlignFrames viewing a particular av.
     * 
     * @param viewport
     * @return alignFrame for viewport
  
    public VamsasApplication getVamsasApplication()
    {
-     return v_client;
+     // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
+     return null;
  
    }
  
            } catch (InterruptedException x)
            {
            }
--          ;
          }
          if (instance == null)
          {
      block.release();
    }
  
 +  /**
 +   * Outputs an image of the desktop to file in EPS format, after prompting the
 +   * user for choice of Text or Lineart character rendering (unless a preference
 +   * has been set). The file name is generated as
 +   * 
 +   * <pre>
 +   * Jalview_snapshot_nnnnn.eps where nnnnn is the current timestamp in milliseconds
 +   * </pre>
 +   */
    @Override
    protected void snapShotWindow_actionPerformed(ActionEvent e)
    {
 +    // currently the menu option to do this is not shown
      invalidate();
 -    File of;
 -    ImageMaker im = new jalview.util.ImageMaker(
 -            this, ImageMaker.TYPE.EPS, "View of Desktop", getWidth(),
 -            getHeight(), of = new File("Jalview_snapshot"
 -                    + System.currentTimeMillis() + ".eps"),
 -            "View of desktop", null, 0, false);
 -    try
 -    {
 -      paintAll(im.getGraphics());
 -      im.writeImage();
 -    } catch (Exception q)
 +
 +    int width = getWidth();
 +    int height = getHeight();
 +    File of = new File(
 +            "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
 +    ImageWriterI writer = new ImageWriterI()
      {
 -      Cache.log.error("Couldn't write snapshot to " + of.getAbsolutePath(),
 -              q);
 -      return;
 -    }
 -    Cache.log.info("Successfully written snapshot to file "
 -            + of.getAbsolutePath());
 +      @Override
 +      public void exportImage(Graphics g) throws Exception
 +      {
 +        paintAll(g);
 +        Cache.log.info("Successfully written snapshot to file "
 +                + of.getAbsolutePath());
 +      }
 +    };
 +    String title = "View of desktop";
 +    ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS,
 +            title);
 +    exporter.doExport(of, this, width, height, title);
    }
  
    /**
     * Explode the views in the given SplitFrame into separate SplitFrame windows.
 -   * This respects (remembers) any previous 'exploded geometry' i.e. the size
 -   * and location last time the view was expanded (if any). However it does not
 +   * This respects (remembers) any previous 'exploded geometry' i.e. the size and
 +   * location last time the view was expanded (if any). However it does not
     * remember the split pane divider location - this is set to match the
     * 'exploding' frame.
     * 
     *          - the payload from the drop event
     * @throws Exception
     */
 -  public static void transferFromDropTarget(List<String> files,
 +  public static void transferFromDropTarget(List<Object> files,
            List<DataSourceType> protocols, DropTargetDropEvent evt,
            Transferable t) throws Exception
    {
  
 +    // BH 2018 changed List<String> to List<Object> to allow for File from SwingJS
 +
 +    // DataFlavor[] flavors = t.getTransferDataFlavors();
 +    // for (int i = 0; i < flavors.length; i++) {
 +    // if (flavors[i].isFlavorJavaFileListType()) {
 +    // evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
 +    // List<File> list = (List<File>) t.getTransferData(flavors[i]);
 +    // for (int j = 0; j < list.size(); j++) {
 +    // File file = (File) list.get(j);
 +    // byte[] data = getDroppedFileBytes(file);
 +    // fileName.setText(file.getName() + " - " + data.length + " " +
 +    // evt.getLocation());
 +    // JTextArea target = (JTextArea) ((DropTarget) evt.getSource()).getComponent();
 +    // target.setText(new String(data));
 +    // }
 +    // dtde.dropComplete(true);
 +    // return;
 +    // }
 +    //
 +
      DataFlavor uriListFlavor = new DataFlavor(
              "text/uri-list;class=java.lang.String"), urlFlavour = null;
      try
          }
          else
          {
 -          if (Platform.isAMac())
 +          if (Platform.isAMacAndNotJS())
            {
              System.err.println(
                      "Please ignore plist error - occurs due to problem with java 8 on OSX");
            }
--          ;
          }
        } catch (Throwable ex)
        {
        for (Object file : (List) t
                .getTransferData(DataFlavor.javaFileListFlavor))
        {
 -        files.add(((File) file).toString());
 +        files.add(file);
          protocols.add(DataSourceType.FILE);
        }
      }
          }
        }
      }
 -    if (Platform.isWindows())
 -
 +    if (Platform.isWindowsAndNotJS())
      {
        Cache.log.debug("Scanning dropped content for Windows Link Files");
  
        // resolve any .lnk files in the file drop
        for (int f = 0; f < files.size(); f++)
        {
 -        String source = files.get(f).toLowerCase();
 +        String source = files.get(f).toString().toLowerCase();
          if (protocols.get(f).equals(DataSourceType.FILE)
                  && (source.endsWith(".lnk") || source.endsWith(".url")
                          || source.endsWith(".site")))
          {
            try
            {
 -            File lf = new File(files.get(f));
 +            Object obj = files.get(f);
 +            File lf = (obj instanceof File ? (File) obj
 +                    : new File((String) obj));
              // process link file to get a URL
              Cache.log.debug("Found potential link file: " + lf);
              WindowsShortcut wscfile = new WindowsShortcut(lf);
    }
  
    /**
 -   * Answers a (possibly empty) list of any structure viewer frames (currently
 -   * for either Jmol or Chimera) which are currently open. This may optionally
 -   * be restricted to viewers of a specified class, or viewers linked to a
 -   * specified alignment panel.
 +   * Answers a (possibly empty) list of any structure viewer frames (currently for
 +   * either Jmol or Chimera) which are currently open. This may optionally be
 +   * restricted to viewers of a specified class, or viewers linked to a specified
 +   * alignment panel.
     * 
     * @param apanel
     *          if not null, only return viewers linked to this panel
   */
  package jalview.gui;
  
- import java.awt.Color;
 -import jalview.api.FeatureColourI;
 -import jalview.datamodel.SearchResults;
 -import jalview.datamodel.SearchResultsI;
 -import jalview.datamodel.SequenceFeature;
 -import jalview.datamodel.SequenceI;
 -import jalview.io.FeaturesFile;
 -import jalview.schemes.FeatureColour;
 -import jalview.util.ColorUtils;
 -import jalview.util.MessageManager;
 -
 -import java.awt.BorderLayout;
 -import java.awt.Color;
 -import java.awt.Dimension;
 -import java.awt.Font;
 -import java.awt.GridLayout;
 -import java.awt.event.ActionEvent;
 -import java.awt.event.ActionListener;
 -import java.awt.event.ItemEvent;
 -import java.awt.event.ItemListener;
 -import java.awt.event.MouseAdapter;
 -import java.awt.event.MouseEvent;
 -import java.util.ArrayList;
 -import java.util.Arrays;
 -import java.util.Comparator;
 -import java.util.List;
 -
 -import javax.swing.JColorChooser;
 -import javax.swing.JComboBox;
 -import javax.swing.JLabel;
 -import javax.swing.JPanel;
 -import javax.swing.JScrollPane;
 -import javax.swing.JSpinner;
 -import javax.swing.JTextArea;
 -import javax.swing.JTextField;
 -import javax.swing.SwingConstants;
 -import javax.swing.event.DocumentEvent;
 -import javax.swing.event.DocumentListener;
 -
  /**
-- * DOCUMENT ME!
-- * 
-- * @author $author$
-- * @version $Revision$
++ * A class that manages drawing of sequence features for the Swing gui
   */
  public class FeatureRenderer
          extends jalview.renderer.seqfeatures.FeatureRenderer
  {
-   Color resBoxColour;
 -  /*
 -   * defaults for creating a new feature are the last created
 -   * feature type and group
 -   */
 -  static String lastFeatureAdded = "feature_1";
 -
 -  static String lastFeatureGroupAdded = "Jalview";
 -
 -  Color resBoxColour;
 -
    AlignmentPanel ap;
  
    /**
        transferSettings(alignPanel.getSeqPanel().seqCanvas.fr);
      }
    }
 -
 -  FeatureColourI oldcol, fcol;
 -
 -  int featureIndex = 0;
 -
 -  /**
 -   * Presents a dialog allowing the user to add new features, or amend or delete
 -   * existing features. Currently this can be on
 -   * <ul>
 -   * <li>double-click on a sequence - Amend/Delete features at position</li>
 -   * <li>Create sequence feature from pop-up menu on selected region</li>
 -   * <li>Create features for pattern matches from Find</li>
 -   * </ul>
 -   * If the supplied feature type is null, show (and update on confirm) the type
 -   * and group of the last new feature created (with initial defaults of
 -   * "feature_1" and "Jalview").
 -   * 
 -   * @param sequences
 -   *          the sequences features are to be created on (if creating
 -   *          features), or a single sequence (if amending features)
 -   * @param features
 -   *          the current features at the position (if amending), or template
 -   *          new feature(s) with start/end position set (if creating)
 -   * @param create
 -   *          true to create features, false to amend or delete
 -   * @param alignPanel
 -   * @return
 -   */
 -  protected boolean amendFeatures(final List<SequenceI> sequences,
 -          final List<SequenceFeature> features, boolean create,
 -          final AlignmentPanel alignPanel)
 -  {
 -    featureIndex = 0;
 -
 -    final JPanel mainPanel = new JPanel(new BorderLayout());
 -
 -    final JTextField name = new JTextField(25);
 -    name.getDocument().addDocumentListener(new DocumentListener()
 -    {
 -      @Override
 -      public void insertUpdate(DocumentEvent e)
 -      {
 -        warnIfTypeHidden(mainPanel, name.getText());
 -      }
 -
 -      @Override
 -      public void removeUpdate(DocumentEvent e)
 -      {
 -        warnIfTypeHidden(mainPanel, name.getText());
 -      }
 -
 -      @Override
 -      public void changedUpdate(DocumentEvent e)
 -      {
 -        warnIfTypeHidden(mainPanel, name.getText());
 -      }
 -    });
 -
 -    final JTextField group = new JTextField(25);
 -    group.getDocument().addDocumentListener(new DocumentListener()
 -    {
 -      @Override
 -      public void insertUpdate(DocumentEvent e)
 -      {
 -        warnIfGroupHidden(mainPanel, group.getText());
 -      }
 -
 -      @Override
 -      public void removeUpdate(DocumentEvent e)
 -      {
 -        warnIfGroupHidden(mainPanel, group.getText());
 -      }
 -
 -      @Override
 -      public void changedUpdate(DocumentEvent e)
 -      {
 -        warnIfGroupHidden(mainPanel, group.getText());
 -      }
 -    });
 -
 -    final JTextArea description = new JTextArea(3, 25);
 -    final JSpinner start = new JSpinner();
 -    final JSpinner end = new JSpinner();
 -    start.setPreferredSize(new Dimension(80, 20));
 -    end.setPreferredSize(new Dimension(80, 20));
 -    final JLabel colour = new JLabel();
 -    colour.setOpaque(true);
 -    // colour.setBorder(BorderFactory.createEtchedBorder());
 -    colour.setMaximumSize(new Dimension(30, 16));
 -    colour.addMouseListener(new MouseAdapter()
 -    {
 -      /*
 -       * open colour chooser on click in colour panel
 -       */
 -      @Override
 -      public void mousePressed(MouseEvent evt)
 -      {
 -        if (fcol.isSimpleColour())
 -        {
 -          Color col = JColorChooser.showDialog(Desktop.desktop,
 -                  MessageManager.getString("label.select_feature_colour"),
 -                  fcol.getColour());
 -          if (col != null)
 -          {
 -            fcol = new FeatureColour(col);
 -            updateColourButton(mainPanel, colour, fcol);
 -          }
 -        }
 -        else
 -        {
 -          /*
 -           * variable colour dialog - on OK, refetch the updated
 -           * feature colour and update this display
 -           */
 -          final String ft = features.get(featureIndex).getType();
 -          final String type = ft == null ? lastFeatureAdded : ft;
 -          FeatureTypeSettings fcc = new FeatureTypeSettings(
 -                  FeatureRenderer.this, type);
 -          fcc.setRequestFocusEnabled(true);
 -          fcc.requestFocus();
 -          fcc.addActionListener(new ActionListener()
 -          {
 -            @Override
 -            public void actionPerformed(ActionEvent e)
 -            {
 -              fcol = FeatureRenderer.this.getFeatureStyle(ft);
 -              setColour(type, fcol);
 -              updateColourButton(mainPanel, colour, fcol);
 -            }
 -          });
 -        }
 -      }
 -    });
 -    JPanel gridPanel = new JPanel(new GridLayout(3, 1));
 -
 -    if (!create && features.size() > 1)
 -    {
 -      /*
 -       * more than one feature at selected position - 
 -       * add a drop-down to choose the feature to amend
 -       * space pad text if necessary to make entries distinct
 -       */
 -      gridPanel = new JPanel(new GridLayout(4, 1));
 -      JPanel choosePanel = new JPanel();
 -      choosePanel.add(new JLabel(
 -              MessageManager.getString("label.select_feature") + ":"));
 -      final JComboBox<String> overlaps = new JComboBox<>();
 -      List<String> added = new ArrayList<>();
 -      for (SequenceFeature sf : features)
 -      {
 -        String text = String.format("%s/%d-%d (%s)", sf.getType(),
 -                sf.getBegin(), sf.getEnd(), sf.getFeatureGroup());
 -        while (added.contains(text))
 -        {
 -          text += " ";
 -        }
 -        overlaps.addItem(text);
 -        added.add(text);
 -      }
 -      choosePanel.add(overlaps);
 -
 -      overlaps.addItemListener(new ItemListener()
 -      {
 -        @Override
 -        public void itemStateChanged(ItemEvent e)
 -        {
 -          int index = overlaps.getSelectedIndex();
 -          if (index != -1)
 -          {
 -            featureIndex = index;
 -            SequenceFeature sf = features.get(index);
 -            name.setText(sf.getType());
 -            description.setText(sf.getDescription());
 -            group.setText(sf.getFeatureGroup());
 -            start.setValue(Integer.valueOf(sf.getBegin()));
 -            end.setValue(Integer.valueOf(sf.getEnd()));
 -
 -            SearchResultsI highlight = new SearchResults();
 -            highlight.addResult(sequences.get(0), sf.getBegin(),
 -                    sf.getEnd());
 -
 -            alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(
 -                    highlight, false);
 -          }
 -          FeatureColourI col = getFeatureStyle(name.getText());
 -          if (col == null)
 -          {
 -            col = new FeatureColour(
 -                    ColorUtils.createColourFromName(name.getText()));
 -          }
 -          oldcol = fcol = col;
 -          updateColourButton(mainPanel, colour, col);
 -        }
 -      });
 -
 -      gridPanel.add(choosePanel);
 -    }
 -
 -    JPanel namePanel = new JPanel();
 -    gridPanel.add(namePanel);
 -    namePanel.add(new JLabel(MessageManager.getString("label.name:"),
 -            JLabel.RIGHT));
 -    namePanel.add(name);
 -
 -    JPanel groupPanel = new JPanel();
 -    gridPanel.add(groupPanel);
 -    groupPanel.add(new JLabel(MessageManager.getString("label.group:"),
 -            JLabel.RIGHT));
 -    groupPanel.add(group);
 -
 -    JPanel colourPanel = new JPanel();
 -    gridPanel.add(colourPanel);
 -    colourPanel.add(new JLabel(MessageManager.getString("label.colour"),
 -            JLabel.RIGHT));
 -    colourPanel.add(colour);
 -    colour.setPreferredSize(new Dimension(150, 15));
 -    colour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 9));
 -    colour.setForeground(Color.black);
 -    colour.setHorizontalAlignment(SwingConstants.CENTER);
 -    colour.setVerticalAlignment(SwingConstants.CENTER);
 -    colour.setHorizontalTextPosition(SwingConstants.CENTER);
 -    colour.setVerticalTextPosition(SwingConstants.CENTER);
 -    mainPanel.add(gridPanel, BorderLayout.NORTH);
 -
 -    JPanel descriptionPanel = new JPanel();
 -    descriptionPanel.add(new JLabel(
 -            MessageManager.getString("label.description:"), JLabel.RIGHT));
 -    description.setFont(JvSwingUtils.getTextAreaFont());
 -    description.setLineWrap(true);
 -    descriptionPanel.add(new JScrollPane(description));
 -
 -    if (!create)
 -    {
 -      mainPanel.add(descriptionPanel, BorderLayout.SOUTH);
 -
 -      JPanel startEndPanel = new JPanel();
 -      startEndPanel.add(new JLabel(MessageManager.getString("label.start"),
 -              JLabel.RIGHT));
 -      startEndPanel.add(start);
 -      startEndPanel.add(new JLabel(MessageManager.getString("label.end"),
 -              JLabel.RIGHT));
 -      startEndPanel.add(end);
 -      mainPanel.add(startEndPanel, BorderLayout.CENTER);
 -    }
 -    else
 -    {
 -      mainPanel.add(descriptionPanel, BorderLayout.CENTER);
 -    }
 -
 -    /*
 -     * default feature type and group to that of the first feature supplied,
 -     * or to the last feature created if not supplied (null value) 
 -     */
 -    SequenceFeature firstFeature = features.get(0);
 -    boolean useLastDefaults = firstFeature.getType() == null;
 -    final String featureType = useLastDefaults ? lastFeatureAdded
 -            : firstFeature.getType();
 -    final String featureGroup = useLastDefaults ? lastFeatureGroupAdded
 -            : firstFeature.getFeatureGroup();
 -    name.setText(featureType);
 -    group.setText(featureGroup);
 -
 -    start.setValue(Integer.valueOf(firstFeature.getBegin()));
 -    end.setValue(Integer.valueOf(firstFeature.getEnd()));
 -    description.setText(firstFeature.getDescription());
 -    updateColourButton(mainPanel, colour,
 -            (oldcol = fcol = getFeatureStyle(featureType)));
 -    Object[] options;
 -    if (!create)
 -    {
 -      options = new Object[] { MessageManager.getString("label.amend"),
 -          MessageManager.getString("action.delete"),
 -          MessageManager.getString("action.cancel") };
 -    }
 -    else
 -    {
 -      options = new Object[] { MessageManager.getString("action.ok"),
 -          MessageManager.getString("action.cancel") };
 -    }
 -
 -    String title = create
 -            ? MessageManager.getString("label.create_new_sequence_features")
 -            : MessageManager.formatMessage("label.amend_delete_features",
 -                    new String[]
 -                    { sequences.get(0).getName() });
 -
 -    /*
 -     * show the dialog
 -     */
 -    int reply = JvOptionPane.showInternalOptionDialog(Desktop.desktop,
 -            mainPanel, title, JvOptionPane.YES_NO_CANCEL_OPTION,
 -            JvOptionPane.QUESTION_MESSAGE, null, options,
 -            MessageManager.getString("action.ok"));
 -
 -    FeaturesFile ffile = new FeaturesFile();
 -
 -    final String enteredType = name.getText().trim();
 -    final String enteredGroup = group.getText().trim();
 -    final String enteredDescription = description.getText().replaceAll("\n", " ");
 -
 -    if (reply == JvOptionPane.OK_OPTION && enteredType.length() > 0)
 -    {
 -      /*
 -       * update default values only if creating using default values
 -       */
 -      if (useLastDefaults)
 -      {
 -        lastFeatureAdded = enteredType;
 -        lastFeatureGroupAdded = enteredGroup;
 -        // TODO: determine if the null feature group is valid
 -        if (lastFeatureGroupAdded.length() < 1)
 -        {
 -          lastFeatureGroupAdded = null;
 -        }
 -      }
 -    }
 -
 -    if (!create)
 -    {
 -      SequenceFeature sf = features.get(featureIndex);
 -
 -      if (reply == JvOptionPane.NO_OPTION)
 -      {
 -        /*
 -         * NO_OPTION corresponds to the Delete button
 -         */
 -        sequences.get(0).getDatasetSequence().deleteFeature(sf);
 -        // update Feature Settings for removal of feature / group
 -        featuresAdded();
 -      }
 -      else if (reply == JvOptionPane.YES_OPTION)
 -      {
 -        /*
 -         * YES_OPTION corresponds to the Amend button
 -         * need to refresh Feature Settings if type, group or colour changed;
 -         * note we don't force the feature to be visible - the user has been
 -         * warned if a hidden feature type or group was entered
 -         */
 -        boolean refreshSettings = (!featureType.equals(enteredType) || !featureGroup
 -                .equals(enteredGroup));
 -        refreshSettings |= (fcol != oldcol);
 -        setColour(enteredType, fcol);
 -        int newBegin = sf.begin;
 -        int newEnd = sf.end;
 -        try
 -        {
 -          newBegin = ((Integer) start.getValue()).intValue();
 -          newEnd = ((Integer) end.getValue()).intValue();
 -        } catch (NumberFormatException ex)
 -        {
 -          // JSpinner doesn't accept invalid format data :-)
 -        }
 -
 -        /*
 -         * replace the feature by deleting it and adding a new one
 -         * (to ensure integrity of SequenceFeatures data store)
 -         */
 -        sequences.get(0).deleteFeature(sf);
 -        SequenceFeature newSf = new SequenceFeature(sf, enteredType,
 -                newBegin, newEnd, enteredGroup, sf.getScore());
 -        newSf.setDescription(enteredDescription);
 -        ffile.parseDescriptionHTML(newSf, false);
 -        // amend features dialog only updates one sequence at a time
 -        sequences.get(0).addSequenceFeature(newSf);
 -
 -        if (refreshSettings)
 -        {
 -          featuresAdded();
 -        }
 -      }
 -    }
 -    else
 -    // NEW FEATURES ADDED
 -    {
 -      if (reply == JvOptionPane.OK_OPTION && enteredType.length() > 0)
 -      {
 -        for (int i = 0; i < sequences.size(); i++)
 -        {
 -          SequenceFeature sf = features.get(i);
 -          SequenceFeature sf2 = new SequenceFeature(enteredType,
 -                  enteredDescription, sf.getBegin(), sf.getEnd(),
 -                  enteredGroup);
 -          ffile.parseDescriptionHTML(sf2, false);
 -          sequences.get(i).addSequenceFeature(sf2);
 -        }
 -
 -        setColour(enteredType, fcol);
 -
 -        featuresAdded();
 -
 -        alignPanel.paintAlignment(true, true);
 -
 -        return true;
 -      }
 -      else
 -      {
 -        return false;
 -      }
 -    }
 -
 -    alignPanel.paintAlignment(true, true);
 -
 -    return true;
 -  }
 -
 -  /**
 -   * Show a warning message if the entered type is one that is currently hidden
 -   * 
 -   * @param panel
 -   * @param type
 -   */
 -  protected void warnIfTypeHidden(JPanel panel, String type)
 -  {
 -    if (getRenderOrder().contains(type))
 -    {
 -      if (!showFeatureOfType(type))
 -      {
 -        String msg = MessageManager.formatMessage("label.warning_hidden",
 -                MessageManager.getString("label.feature_type"), type);
 -        JvOptionPane.showMessageDialog(panel, msg, "",
 -                JvOptionPane.OK_OPTION);
 -      }
 -    }
 -  }
 -
 -  /**
 -   * Show a warning message if the entered group is one that is currently hidden
 -   * 
 -   * @param panel
 -   * @param group
 -   */
 -  protected void warnIfGroupHidden(JPanel panel, String group)
 -  {
 -    if (featureGroups.containsKey(group) && !featureGroups.get(group))
 -    {
 -      String msg = MessageManager.formatMessage("label.warning_hidden",
 -              MessageManager.getString("label.group"), group);
 -      JvOptionPane.showMessageDialog(panel, msg, "",
 -              JvOptionPane.OK_OPTION);
 -    }
 -  }
 -
 -  /**
 -   * update the amend feature button dependent on the given style
 -   * 
 -   * @param bigPanel
 -   * @param col
 -   * @param col
 -   */
 -  protected void updateColourButton(JPanel bigPanel, JLabel colour,
 -          FeatureColourI col)
 -  {
 -    colour.removeAll();
 -    colour.setIcon(null);
 -    colour.setToolTipText(null);
 -    colour.setText("");
 -
 -    if (col.isSimpleColour())
 -    {
 -      colour.setBackground(col.getColour());
 -    }
 -    else
 -    {
 -      colour.setBackground(bigPanel.getBackground());
 -      colour.setForeground(Color.black);
 -      FeatureSettings.renderGraduatedColor(colour, col);
 -    }
 -  }
 -
 -  /**
 -   * 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);
 -  }
  }
@@@ -29,7 -29,6 +29,7 @@@ import jalview.datamodel.features.Featu
  import jalview.datamodel.features.FeatureMatcherSet;
  import jalview.datamodel.features.FeatureMatcherSetI;
  import jalview.gui.Help.HelpId;
 +import jalview.gui.JalviewColourChooser.ColourChooserListener;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
  import jalview.schemes.FeatureColour;
@@@ -81,7 -80,8 +81,7 @@@ import javax.swing.BorderFactory
  import javax.swing.Icon;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
 -import javax.swing.JColorChooser;
 -import javax.swing.JDialog;
 +import javax.swing.JCheckBoxMenuItem;
  import javax.swing.JInternalFrame;
  import javax.swing.JLabel;
  import javax.swing.JLayeredPane;
@@@ -93,7 -93,6 +93,7 @@@ import javax.swing.JSlider
  import javax.swing.JTable;
  import javax.swing.ListSelectionModel;
  import javax.swing.SwingConstants;
 +import javax.swing.ToolTipManager;
  import javax.swing.border.Border;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
@@@ -131,8 -130,7 +131,8 @@@ public class FeatureSettings extends JP
  
    private static final int MIN_HEIGHT = 400;
  
 -  private final static String BASE_TOOLTIP = MessageManager.getString("label.click_to_edit");
 +  private final static String BASE_TOOLTIP = MessageManager
 +          .getString("label.click_to_edit");
  
    final FeatureRenderer fr;
  
     */
    Object[][] originalData;
  
 -  private float originalTransparency;
 +  float originalTransparency;
  
 -  private Map<String, FeatureMatcherSetI> originalFilters;
 +  Map<String, FeatureMatcherSetI> originalFilters;
  
    final JInternalFrame frame;
  
    /*
     * true when Feature Settings are updating from feature renderer
     */
 -  private boolean handlingUpdate = false;
 +  boolean handlingUpdate = false;
  
    /*
     * holds {featureCount, totalExtent} for each feature type
          default:
            break;
          }
 -        
 +
          return tip;
        }
  
      tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
      tableHeader.setReorderingAllowed(false);
      table.setFont(new Font("Verdana", Font.PLAIN, 12));
 -
 -    table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
 +    ToolTipManager.sharedInstance().registerComponent(table);
 +    table.setDefaultEditor(FeatureColour.class, new ColorEditor());
      table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
  
 -    table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
 +    table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
      table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
  
      TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
 -            new ColorRenderer(), new ColorEditor(this));
 +            new ColorRenderer(), new ColorEditor());
      table.addColumn(colourColumn);
  
      TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
 -            new FilterRenderer(), new FilterEditor(this));
 +            new FilterRenderer(), new FilterEditor());
      table.addColumn(filterColumn);
  
      table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
          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)
          {
          {
            String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
            Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
 -          popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
 -                  evt.getY());
 +          showPopupMenu(selectedRow, type, colour, evt.getPoint());
          }
        }
      });
  
      frame = new JInternalFrame();
      frame.setContentPane(this);
 -    if (Platform.isAMac())
 -    {
 -      Desktop.addInternalFrame(frame,
 -              MessageManager.getString("label.sequence_feature_settings"),
 -              600, 480);
 -    }
 -    else
 -    {
 -      Desktop.addInternalFrame(frame,
 -              MessageManager.getString("label.sequence_feature_settings"),
 -              600, 450);
 -    }
 +    Desktop.addInternalFrame(frame,
 +            MessageManager.getString("label.sequence_feature_settings"),
 +            600, Platform.isAMacAndNotJS() ? 480 : 450);
      frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
  
      frame.addInternalFrameListener(
      inConstruction = false;
    }
  
 -  protected void popupSort(final int rowSelected, final String type,
 -          final Object typeCol, final Map<String, float[][]> minmax, int x,
 -          int y)
 +  /**
 +   * Constructs and shows a popup menu of possible actions on the selected row
 +   * and feature type
 +   * 
 +   * @param rowSelected
 +   * @param type
 +   * @param typeCol
 +   * @param pt
 +   */
 +  protected void showPopupMenu(final int rowSelected, final String type,
 +          final Object typeCol, final Point pt)
    {
      JPopupMenu men = new JPopupMenu(MessageManager
              .formatMessage("label.settings_for_param", new String[]
              { type }));
 +    final FeatureColourI featureColour = (FeatureColourI) typeCol;
 +
 +    /*
 +     * menu option to select (or deselect) variable colour
 +     */
 +    final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
 +            MessageManager.getString("label.variable_colour"));
 +    variableColourCB.setSelected(!featureColour.isSimpleColour());
 +    men.add(variableColourCB);
 +
 +    /*
 +     * checkbox action listener doubles up as listener to OK
 +     * from the variable colour / filters dialog
 +     */
 +    variableColourCB.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        if (e.getSource() == variableColourCB)
 +        {
 +          men.setVisible(true); // BH 2018 for JavaScript because this is a
 +                                // checkbox
 +          men.setVisible(false); // BH 2018 for JavaScript because this is a
 +                                 // checkbox
 +          if (featureColour.isSimpleColour())
 +          {
 +            /*
 +             * toggle simple colour to variable colour - show dialog
 +             */
 +            FeatureTypeSettings fc = new FeatureTypeSettings(fr, type);
 +            fc.addActionListener(this);
 +          }
 +          else
 +          {
 +            /*
 +             * toggle variable to simple colour - show colour chooser
 +             */
 +            String title = MessageManager
 +                    .formatMessage("label.select_colour_for", type);
 +            ColourChooserListener listener = new ColourChooserListener()
 +            {
 +              @Override
 +              public void colourSelected(Color c)
 +              {
 +                table.setValueAt(new FeatureColour(c), rowSelected,
 +                        COLOUR_COLUMN);
 +                table.validate();
 +                updateFeatureRenderer(
 +                        ((FeatureTableModel) table.getModel()).getData(),
 +                        false);
 +              }
 +            };
 +            JalviewColourChooser.showColourChooser(FeatureSettings.this,
 +                    title, featureColour.getMaxColour(), listener);
 +          }
 +        }
 +        else
 +        {
 +          if (e.getSource() instanceof FeatureTypeSettings)
 +          {
 +            /*
 +             * update after OK in feature colour dialog; the updated
 +             * colour will have already been set in the FeatureRenderer
 +             */
 +            FeatureColourI fci = fr.getFeatureColours().get(type);
 +            table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
 +            // BH 2018 setting a table value does not invalidate it.
 +            // System.out.println("FeatureSettings is valied" +
 +            // table.validate();
 +          }
 +        }
 +      }
 +    });
 +
 +    men.addSeparator();
 +
      JMenuItem scr = new JMenuItem(
              MessageManager.getString("label.sort_by_score"));
      men.add(scr);
 -    final FeatureSettings me = this;
      scr.addActionListener(new ActionListener()
      {
  
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        me.af.avc
 -                .sortAlignmentByFeatureScore(Arrays.asList(new String[]
 +        af.avc.sortAlignmentByFeatureScore(Arrays.asList(new String[]
                  { type }));
        }
 -
      });
      JMenuItem dens = new JMenuItem(
              MessageManager.getString("label.sort_by_density"));
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        me.af.avc
 -                .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
 +        af.avc.sortAlignmentByFeatureDensity(Arrays.asList(new String[]
                  { type }));
        }
 -
      });
      men.add(dens);
  
      men.add(clearCols);
      men.add(hideCols);
      men.add(hideOtherCols);
 -    men.show(table, x, y);
 +    men.show(table, pt.x, pt.y);
    }
  
    @Override
          data[dataIndex][FILTER_COLUMN] = featureFilter == null
                  ? new FeatureMatcherSet()
                  : featureFilter;
-         data[dataIndex][SHOW_COLUMN] = new Boolean(
+         data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(
                  af.getViewport().getFeaturesDisplayed().isVisible(type));
          dataIndex++;
          displayableTypes.remove(type);
        data[dataIndex][FILTER_COLUMN] = featureFilter == null
                ? new FeatureMatcherSet()
                : featureFilter;
-       data[dataIndex][SHOW_COLUMN] = new Boolean(true);
+       data[dataIndex][SHOW_COLUMN] = Boolean.valueOf(true);
        dataIndex++;
        displayableTypes.remove(type);
      }
    }
  
    /**
 -   * Updates 'originalData' (used for restore on Cancel) if we detect that changes
 -   * have been made outwith this dialog
 +   * Updates 'originalData' (used for restore on Cancel) if we detect that
 +   * changes have been made outwith this dialog
     * <ul>
     * <li>a new feature type added (and made visible)</li>
     * <li>a feature colour changed (in the Amend Features dialog)</li>
  
    /**
     * Remove from the groups panel any checkboxes for groups that are not in the
 -   * foundGroups set. This enables removing a group from the display when the last
 -   * feature in that group is deleted.
 +   * foundGroups set. This enables removing a group from the display when the
 +   * last feature in that group is deleted.
     * 
     * @param foundGroups
     */
      chooser.setDialogTitle(
              MessageManager.getString("label.load_feature_colours"));
      chooser.setToolTipText(MessageManager.getString("action.load"));
 -
 -    int value = chooser.showOpenDialog(this);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    chooser.setResponseHandler(0, new Runnable()
      {
 -      File file = chooser.getSelectedFile();
 -      load(file);
 -    }
 +      @Override
 +      public void run()
 +      {
 +        File file = chooser.getSelectedFile();
 +        load(file);
 +      }
 +    });
 +    chooser.showOpenDialog(this);
    }
  
    /**
        if (table != null)
        {
          resetTable(null);
 -        Object[][] data = ((FeatureTableModel) table.getModel())
 -                .getData();
 +        Object[][] data = ((FeatureTableModel) table.getModel()).getData();
          ensureOrder(data);
          updateFeatureRenderer(data, false);
          table.repaint();
      chooser.setDialogTitle(
              MessageManager.getString("label.save_feature_colours"));
      chooser.setToolTipText(MessageManager.getString("action.save"));
 -
 -    int value = chooser.showSaveDialog(this);
 -
 -    if (value == JalviewFileChooser.APPROVE_OPTION)
 +    int option = chooser.showSaveDialog(this);
 +    if (option == JalviewFileChooser.APPROVE_OPTION)
      {
 -      save(chooser.getSelectedFile());
 +      File file = chooser.getSelectedFile();
 +      save(file);
      }
    }
  
      ucs.setSchemeName("Sequence Features");
      try
      {
 -      PrintWriter out = new PrintWriter(new OutputStreamWriter(
 -              new FileOutputStream(file), "UTF-8"));
 +      PrintWriter out = new PrintWriter(
 +              new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
  
        /*
         * sort feature types by colour order, from 0 (highest)
          FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
          if (filter != null && !filter.isEmpty())
          {
 -          Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
 +          Iterator<FeatureMatcherI> iterator = filter.getMatchers()
 +                  .iterator();
            FeatureMatcherI firstMatcher = iterator.next();
            jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
 -                  .marshalFilter(firstMatcher, iterator,
 -                  filter.isAnded());
 +                  .marshalFilter(firstMatcher, iterator, filter.isAnded());
            Filter filterModel = new Filter();
            filterModel.setFeatureType(featureType);
            filterModel.setMatcherSet(ms);
        else
        {
          width[i] /= max; // normalize
 -        fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
 +        fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
 +                                                                // later
        }
        if (i > 0)
        {
    }
  
    /**
 -   * Update the priority order of features; only repaint if this changed the order
 -   * of visible features
 +   * Update the priority order of features; only repaint if this changed the
 +   * order of visible features
     * 
     * @param data
     * @param visibleNew
     */
 -  private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
 +  void updateFeatureRenderer(Object[][] data, boolean visibleNew)
    {
      FeatureSettingsBean[] rowData = getTableAsBeans(data);
  
    {
      boolean thr = false;
      StringBuilder tx = new StringBuilder();
 -  
 +
      if (gcol.isColourByAttribute())
      {
        tx.append(FeatureMatcher
      renderGraduatedColor(comp, gcol, w, h);
    }
  
 +  @SuppressWarnings("serial")
    class ColorEditor extends AbstractCellEditor
            implements TableCellEditor, ActionListener
    {
 -    FeatureSettings me;
 -
      FeatureColourI currentColor;
  
      FeatureTypeSettings chooser;
  
      JButton button;
  
 -    JColorChooser colorChooser;
 -
 -    JDialog dialog;
 -
      protected static final String EDIT = "edit";
  
      int rowSelected = 0;
  
 -    public ColorEditor(FeatureSettings me)
 +    public ColorEditor()
      {
 -      this.me = me;
        // Set up the editor (from the table's point of view),
        // which is a button.
        // This button brings up the color chooser dialog,
        button.setActionCommand(EDIT);
        button.addActionListener(this);
        button.setBorderPainted(false);
      }
  
      /**
 -     * Handles events from the editor button and from the dialog's OK button.
 +     * Handles events from the editor button, and from the colour/filters
 +     * dialog's OK button
       */
      @Override
      public void actionPerformed(ActionEvent e)
      {
 -      // todo test e.getSource() instead here
 -      if (EDIT.equals(e.getActionCommand()))
 +      if (button == e.getSource())
        {
 -        // The user has clicked the cell, so
 -        // bring up the dialog.
          if (currentColor.isSimpleColour())
          {
 -          // bring up simple color chooser
 -          button.setBackground(currentColor.getColour());
 -          colorChooser.setColor(currentColor.getColour());
 -          dialog.setVisible(true);
 +          /*
 +           * simple colour chooser
 +           */
 +          String ttl = MessageManager
 +                  .formatMessage("label.select_colour_for", type);
 +          ColourChooserListener listener = new ColourChooserListener()
 +          {
 +            @Override
 +            public void colourSelected(Color c)
 +            {
 +              currentColor = new FeatureColour(c);
 +              table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
 +              fireEditingStopped();
 +            }
 +
 +            @Override
 +            public void cancel()
 +            {
 +              fireEditingStopped();
 +            }
 +          };
 +          JalviewColourChooser.showColourChooser(button, ttl,
 +                  currentColor.getColour(), listener);
          }
          else
          {
 -          // bring up graduated chooser.
 -          chooser = new FeatureTypeSettings(me.fr, type);
 +          /*
 +           * variable colour and filters dialog
 +           */
 +          chooser = new FeatureTypeSettings(fr, type);
 +          if (!Platform.isJS())
            /**
 -           * @j2sNative
 +           * Java only
 +           * 
 +           * @j2sIgnore
             */
            {
              chooser.setRequestFocusEnabled(true);
              chooser.requestFocus();
            }
            chooser.addActionListener(this);
 -          // Make the renderer reappear.
            fireEditingStopped();
          }
        }
        else
        {
 -        if (currentColor.isSimpleColour())
 -        {
 -          /*
 -           * read off colour picked in colour chooser after OK pressed
 -           */
 -          currentColor = new FeatureColour(colorChooser.getColor());
 -          me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
 -        }
 -        else
 +        /*
 +         * after OK in variable colour dialog, any changes to colour 
 +         * (or filters!) are already set in FeatureRenderer, so just
 +         * update table data without triggering updateFeatureRenderer
 +         */
 +        currentColor = fr.getFeatureColours().get(type);
 +        FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
 +        if (currentFilter == null)
          {
 -          /*
 -           * after OK in variable colour dialog, any changes to colour 
 -           * (or filters!) are already set in FeatureRenderer, so just
 -           * update table data without triggering updateFeatureRenderer
 -           */
 -          currentColor = fr.getFeatureColours().get(type);
 -          FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
 -          if (currentFilter == null)
 -          {
 -            currentFilter = new FeatureMatcherSet();
 -          }
 -          Object[] data = ((FeatureTableModel) table.getModel())
 -                  .getData()[rowSelected];
 -          data[COLOUR_COLUMN] = currentColor;
 -          data[FILTER_COLUMN] = currentFilter;
 +          currentFilter = new FeatureMatcherSet();
          }
 +        Object[] data = ((FeatureTableModel) table.getModel())
 +                .getData()[rowSelected];
 +        data[COLOUR_COLUMN] = currentColor;
 +        data[FILTER_COLUMN] = currentFilter;
          fireEditingStopped();
 -        me.table.validate();
 +        // SwingJS needs an explicit repaint() here,
 +        // rather than relying upon no validation having
 +        // occurred since the stopEditing call was made.
 +        // Its laying out has not been stopped by the modal frame
 +        table.validate();
 +        table.repaint();
        }
      }
  
 +    /**
 +     * Override allows access to this method from anonymous inner classes
 +     */
 +    @Override
 +    protected void fireEditingStopped()
 +    {
 +      super.fireEditingStopped();
 +    }
 +
      // Implement the one CellEditor method that AbstractCellEditor doesn't.
      @Override
      public Object getCellEditorValue()
  
      // Implement the one method defined by TableCellEditor.
      @Override
 -    public Component getTableCellEditorComponent(JTable theTable, Object value,
 -            boolean isSelected, int row, int column)
 +    public Component getTableCellEditorComponent(JTable theTable,
 +            Object value, boolean isSelected, int row, int column)
      {
        currentColor = (FeatureColourI) value;
        this.rowSelected = row;
 -      type = me.table.getValueAt(row, TYPE_COLUMN).toString();
 +      type = table.getValueAt(row, TYPE_COLUMN).toString();
        button.setOpaque(true);
 -      button.setBackground(me.getBackground());
 +      button.setBackground(FeatureSettings.this.getBackground());
        if (!currentColor.isSimpleColour())
        {
          JLabel btn = new JLabel();
  
    /**
     * The cell editor for the Filter column. It displays the text of any filters
 -   * for the feature type in that row (in full as a tooltip, possible abbreviated
 -   * as display text). On click in the cell, opens the Feature Display Settings
 -   * dialog at the Filters tab.
 +   * for the feature type in that row (in full as a tooltip, possible
 +   * abbreviated as display text). On click in the cell, opens the Feature
 +   * Display Settings dialog at the Filters tab.
     */
 +  @SuppressWarnings("serial")
    class FilterEditor extends AbstractCellEditor
            implements TableCellEditor, ActionListener
    {
 -    FeatureSettings me;
  
      FeatureMatcherSetI currentFilter;
  
  
      int rowSelected = 0;
  
 -    public FilterEditor(FeatureSettings me)
 +    public FilterEditor()
      {
 -      this.me = me;
        button = new JButton();
        button.setActionCommand(EDIT);
        button.addActionListener(this);
      {
        if (button == e.getSource())
        {
 -        FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
 +        FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
          chooser.addActionListener(this);
          chooser.setRequestFocusEnabled(true);
          chooser.requestFocus();
           * update table data without triggering updateFeatureRenderer
           */
          FeatureColourI currentColor = fr.getFeatureColours().get(type);
 -        currentFilter = me.fr.getFeatureFilter(type);
 +        currentFilter = fr.getFeatureFilter(type);
          if (currentFilter == null)
          {
            currentFilter = new FeatureMatcherSet();
          }
 +
          Object[] data = ((FeatureTableModel) table.getModel())
                  .getData()[rowSelected];
          data[COLOUR_COLUMN] = currentColor;
          data[FILTER_COLUMN] = currentFilter;
          fireEditingStopped();
 -        me.table.validate();
 +        // SwingJS needs an explicit repaint() here,
 +        // rather than relying upon no validation having
 +        // occurred since the stopEditing call was made.
 +        // Its laying out has not been stopped by the modal frame
 +        table.validate();
 +        table.repaint();
        }
      }
  
      }
  
      @Override
 -    public Component getTableCellEditorComponent(JTable theTable, Object value,
 -            boolean isSelected, int row, int column)
 +    public Component getTableCellEditorComponent(JTable theTable,
 +            Object value, boolean isSelected, int row, int column)
      {
        currentFilter = (FeatureMatcherSetI) value;
        this.rowSelected = row;
 -      type = me.table.getValueAt(row, TYPE_COLUMN).toString();
 +      type = table.getValueAt(row, TYPE_COLUMN).toString();
        button.setOpaque(true);
 -      button.setBackground(me.getBackground());
 +      button.setBackground(FeatureSettings.this.getBackground());
        button.setText(currentFilter.toString());
        button.setIcon(null);
        return button;
@@@ -1984,8 -1897,7 +1984,8 @@@ class FeatureIcon implements Ico
          g.fillRect(s1, 0, e1 - s1, height);
        }
        g.setColor(gcol.getMaxColour());
 -      g.fillRect(0, e1, width - e1, height);
 +      // g.fillRect(0, e1, width - e1, height); // BH 2018
 +      g.fillRect(e1, 0, width - e1, height);
      }
    }
  }
@@@ -22,6 -22,7 +22,7 @@@ package jalview.gui
  
  import jalview.api.AlignmentViewPanel;
  import jalview.api.FeatureColourI;
+ import jalview.bin.Cache;
  import jalview.datamodel.GraphLine;
  import jalview.datamodel.features.FeatureAttributes;
  import jalview.datamodel.features.FeatureAttributes.Datatype;
@@@ -29,7 -30,6 +30,7 @@@ import jalview.datamodel.features.Featu
  import jalview.datamodel.features.FeatureMatcherI;
  import jalview.datamodel.features.FeatureMatcherSet;
  import jalview.datamodel.features.FeatureMatcherSetI;
 +import jalview.gui.JalviewColourChooser.ColourChooserListener;
  import jalview.schemes.FeatureColour;
  import jalview.util.ColorUtils;
  import jalview.util.MessageManager;
@@@ -57,6 -57,7 +58,6 @@@ import javax.swing.BoxLayout
  import javax.swing.ButtonGroup;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
 -import javax.swing.JColorChooser;
  import javax.swing.JComboBox;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
@@@ -111,9 -112,9 +112,9 @@@ public class FeatureTypeSettings extend
    /*
     * the view panel to update when settings change
     */
 -  private final AlignmentViewPanel ap;
 +  final AlignmentViewPanel ap;
  
 -  private final String featureType;
 +  final String featureType;
  
    /*
     * the colour and filters to reset to on Cancel
     * set flag to true when setting values programmatically,
     * to avoid invocation of action handlers
     */
 -  private boolean adjusting = false;
 +  boolean adjusting = false;
  
    /*
     * minimum of the value range for graduated colour
    /*
     * scale factor for conversion between absolute min-max and slider
     */
 -  private float scaleFactor;
 +  float scaleFactor;
  
    /*
     * radio button group, to select what to colour by:
     * simple colour, by category (text), or graduated
     */
 -  private JRadioButton simpleColour = new JRadioButton();
 +  JRadioButton simpleColour = new JRadioButton();
  
 -  private JRadioButton byCategory = new JRadioButton();
 +  JRadioButton byCategory = new JRadioButton();
  
 -  private JRadioButton graduatedColour = new JRadioButton();
 +  JRadioButton graduatedColour = new JRadioButton();
  
 -  /**
 -   * colours and filters are shown in tabbed view or single content pane
 -   */
 -  JPanel coloursPanel, filtersPanel;
 +  JPanel coloursPanel;
 +  
 +  JPanel filtersPanel;
  
    JPanel singleColour = new JPanel();
  
 -  private JPanel minColour = new JPanel();
 +  JPanel minColour = new JPanel();
  
 -  private JPanel maxColour = new JPanel();
 +  JPanel maxColour = new JPanel();
  
-   private JComboBox<String> threshold = new JComboBox<>();
+   private JComboBox<Object> threshold = new JComboBox<>();
  
 -  private JSlider slider = new JSlider();
 +  JSlider slider = new JSlider();
  
 -  private JTextField thresholdValue = new JTextField(20);
 +  JTextField thresholdValue = new JTextField(20);
  
    private JCheckBox thresholdIsMin = new JCheckBox();
  
    /*
     * choice of option for 'colour for no value'
     */
-   private JComboBox<String> noValueCombo;
+   private JComboBox<Object> noValueCombo;
  
    /*
     * choice of what to colour by text (Label or attribute)
     */
-   private JComboBox<String> colourByTextCombo;
+   private JComboBox<Object> colourByTextCombo;
  
    /*
     * choice of what to colour by range (Score or attribute)
     */
-   private JComboBox<String> colourByRangeCombo;
+   private JComboBox<Object> colourByRangeCombo;
  
    private JRadioButton andFilters;
  
    /*
     * filters for the currently selected feature type
     */
 -  private List<FeatureMatcherI> filters;
 +  List<FeatureMatcherI> filters;
  
    private JPanel chooseFiltersPanel;
  
        return;
      }
      
 -    updateColoursTab();
 +    updateColoursPanel();
      
 -    updateFiltersTab();
 +    updateFiltersPanel();
      
      adjusting = false;
      
    }
  
    /**
 -   * Configures the widgets on the Colours tab according to the current feature
 +   * Configures the widgets on the Colours panel according to the current feature
     * colour scheme
     */
 -  private void updateColoursTab()
 +  private void updateColoursPanel()
    {
      FeatureColourI fc = fr.getFeatureColours().get(featureType);
  
      };
  
      /*
 -     * first panel/tab: colour options
 +     * first panel: colour options
       */
      JPanel coloursPanel = initialiseColoursPanel();
      this.add(coloursPanel, BorderLayout.NORTH);
  
      /*
 -     * second panel/tab: filter options
 +     * second panel: filter options
       */
      JPanel filtersPanel = initialiseFiltersPanel();
      this.add(filtersPanel, BorderLayout.CENTER);
      graduatedColour = new JRadioButton(
              MessageManager.getString("label.by_range_of") + COLON);
      graduatedColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20));
 +    graduatedColour.setOpaque(false);
      graduatedColour.addItemListener(new ItemListener()
      {
        @Override
        {
          if (minColour.isEnabled())
          {
 -          showColourChooser(minColour, "label.select_colour_minimum_value");
 +          String ttl = MessageManager.getString("label.select_colour_minimum_value");
 +          showColourChooser(minColour, ttl);
          }
        }
      });
        {
          if (maxColour.isEnabled())
          {
 -          showColourChooser(maxColour, "label.select_colour_maximum_value");
 +          String ttl = MessageManager.getString("label.select_colour_maximum_value");
 +          showColourChooser(maxColour, ttl);
          }
        }
      });
      simpleColour = new JRadioButton(
              MessageManager.getString("label.simple_colour"));
      simpleColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20));
 +    simpleColour.setOpaque(false);
      simpleColour.addItemListener(new ItemListener()
      {
        @Override
        {
          if (simpleColour.isSelected())
          {
 -          showColourChooser(singleColour, "label.select_colour");
 +          String ttl = MessageManager.formatMessage("label.select_colour_for",  featureType);
 +          showColourChooser(singleColour, ttl);
          }
        }
      });
      byCategory = new JRadioButton(
              MessageManager.getString("label.by_text_of") + COLON);
      byCategory.setPreferredSize(new Dimension(RADIO_WIDTH, 20));
 +    byCategory.setOpaque(false);
      byCategory.addItemListener(new ItemListener()
      {
        @Override
      return colourByPanel;
    }
  
 -  private void showColourChooser(JPanel colourPanel, String key)
 +  /**
 +   * Shows a colour chooser dialog, and if a selection is made, updates the
 +   * colour of the given panel
 +   * 
 +   * @param colourPanel
 +   *          the panel whose background colour is being picked
 +   * @param title
 +   */
 +  void showColourChooser(JPanel colourPanel, String title)
    {
 -    Color col = JColorChooser.showDialog(this,
 -            MessageManager.getString(key), colourPanel.getBackground());
 -    if (col != null)
 +    ColourChooserListener listener = new ColourChooserListener()
      {
 -      colourPanel.setBackground(col);
 -      colourPanel.setForeground(col);
 -    }
 -    colourPanel.repaint();
 -    colourChanged(true);
 +      @Override
 +      public void colourSelected(Color col)
 +      {
 +        colourPanel.setBackground(col);
 +        colourPanel.setForeground(col);
 +        colourPanel.repaint();
 +        colourChanged(true);
 +      }
 +    };
 +      JalviewColourChooser.showColourChooser(this, title, 
 +        colourPanel.getBackground(), listener);
    }
  
    /**
      fr.setColour(featureType, acg);
      ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
  
 -    updateColoursTab();
 +    updateColoursPanel();
    }
  
    /**
      }
      float minValue = min;
      float maxValue = max;
 -    final int thresholdOption = threshold.getSelectedIndex();
 +    int thresholdOption = threshold.getSelectedIndex();
      if (thresholdIsMin.isSelected()
              && thresholdOption == ABOVE_THRESHOLD_OPTION)
      {
     * @param withRange
     * @param withText
     */
-   protected JComboBox<String> populateAttributesDropdown(
+   protected JComboBox<Object> populateAttributesDropdown(
            List<String[]> attNames, boolean withRange, boolean withText)
    {
      List<String> displayAtts = new ArrayList<>();
        tooltips.add(desc == null ? "" : desc);
      }
  
-     JComboBox<String> attCombo = JvSwingUtils
-             .buildComboWithTooltips(displayAtts, tooltips);
+     // now convert String List to Object List for buildComboWithTooltips
+     List<Object> displayAttsObjects = new ArrayList<>(displayAtts);
+     JComboBox<Object> attCombo = JvSwingUtils
+             .buildComboWithTooltips(displayAttsObjects, tooltips);
+     
      return attCombo;
    }
  
      andOrPanel.setBackground(Color.white);
      andFilters = new JRadioButton(MessageManager.getString("label.and"));
      orFilters = new JRadioButton(MessageManager.getString("label.or"));
 +    andFilters.setOpaque(false);
 +    orFilters.setOpaque(false);
      ActionListener actionListener = new ActionListener()
      {
        @Override
     * for adding a condition. This should be called after a filter has been
     * removed, added or amended.
     */
 -  private void updateFiltersTab()
 +  private void updateFiltersPanel()
    {
      /*
       * clear the panel and list of filter conditions
        {
          orFilters.setSelected(true);
        }
 -      featureFilters.getMatchers().forEach(matcher -> filters.add(matcher));
 +      // avoid use of lambda expression to keep SwingJS happy
 +      // featureFilters.getMatchers().forEach(item -> filters.add(item));
 +      for (FeatureMatcherI matcher : featureFilters.getMatchers())
 +      {
 +        filters.add(matcher);
 +      }
      }
  
      /*
       * drop-down choice of attribute, with description as a tooltip 
       * if we can obtain it
       */
-     JComboBox<String> attCombo = populateAttributesDropdown(attNames, true,
-             true);
+     final JComboBox<Object> attCombo = populateAttributesDropdown(attNames,
+             true, true);
      String filterBy = setSelectedAttribute(attCombo, filter);
  
      JComboBox<Condition> condCombo = new JComboBox<>();
      if (!patternField.isEnabled()
              || (pattern != null && pattern.trim().length() > 0))
      {
--      JButton removeCondition = new JButton("\u2717"); // Dingbats cursive x
++      JButton removeCondition = new JButton("\u2717");
++      // Dingbats cursive x
 +      removeCondition.setBorder(new EmptyBorder(0, 0, 0, 0));
 +      removeCondition.setBackground(Color.WHITE);
 +      removeCondition.setPreferredSize(new Dimension(23, 17));
-       removeCondition
-               .setToolTipText(MessageManager.getString("label.delete_row"));
+       removeCondition.setToolTipText(
+               MessageManager.getString("label.delete_condition"));
 -      removeCondition.setBorder(new EmptyBorder(0, 0, 0, 0));
        removeCondition.addActionListener(new ActionListener()
        {
          @Override
     * @param attCombo
     * @param filter
     */
-   private String setSelectedAttribute(JComboBox<String> attCombo,
+   private String setSelectedAttribute(JComboBox<Object> attCombo,
            FeatureMatcherI filter)
    {
      String item = null;
     * @param condCombo
     * @param patternField
     */
 -  private void populateConditions(String attName, Condition cond,
 +  void populateConditions(String attName, Condition cond,
            JComboBox<Condition> condCombo, JTextField patternField)
    {
      Datatype type = FeatureAttributes.getInstance().getDatatype(featureType,
     * @param valueField
     * @param filterIndex
     */
-   protected boolean updateFilter(JComboBox<String> attCombo,
+   protected boolean updateFilter(JComboBox<Object> attCombo,
            JComboBox<Condition> condCombo, JTextField valueField,
            int filterIndex)
    {
-     String attName = (String) attCombo.getSelectedItem();
+     String attName;
+     try
+     {
+       attName = (String) attCombo.getSelectedItem();
+     } catch (Exception e)
+     {
+       Cache.log.error("Problem casting Combo box entry to String");
+       attName = attCombo.getSelectedItem().toString();
+     }
      Condition cond = (Condition) condCombo.getSelectedItem();
      String pattern = valueField.getText().trim();
  
      fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined);
      ap.paintAlignment(true, true);
  
 -    updateFiltersTab();
 +    updateFiltersPanel();
    }
  }
@@@ -96,19 -96,8 +96,19 @@@ public final class JvSwingUtil
      }
  
      return (enclose ? "<html>" : "")
 -            + "<style> p.ttip {width: 350; text-align: justify; word-wrap: break-word;}</style><p class=\"ttip\">"
 -            + ttext + "</p>" + ((enclose ? "</html>" : ""));
 +            
 +     // BH 2018
 +            
 +            + "<style> div.ttip {width:350px;white-space:pre-wrap;padding:2px;overflow-wrap:break-word;}</style><div class=\"ttip\">"
 +            
 +//            + "<style> p.ttip {width:350px;margin:-14px 0px -14px 0px;padding:2px;overflow-wrap:break-word;}"
 +//            + "</style><p class=\"ttip\">"
 +                        
 +            + ttext
 +            
 +            + " </div>"
 +//            + "</p>"
 +            + ((enclose ? "</html>" : ""));
  
    }
  
     * @param entries
     * @param tooltips
     */
-   public static JComboBox<String> buildComboWithTooltips(
-           List<String> entries, List<String> tooltips)
+   public static JComboBox<Object> buildComboWithTooltips(
+           List<Object> entries, List<String> tooltips)
    {
-     JComboBox<String> combo = new JComboBox<>();
+     JComboBox<Object> combo = new JComboBox<>();
      final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer();
      combo.setRenderer(renderer);
-     for (String attName : entries)
+     for (Object attName : entries)
      {
        combo.addItem(attName);
      }
@@@ -30,8 -30,6 +30,8 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.SequenceI;
 +import jalview.gui.ImageExporter.ImageWriterI;
 +import jalview.gui.JalviewColourChooser.ColourChooserListener;
  import jalview.jbgui.GPCAPanel;
  import jalview.math.RotatableMatrix.Axis;
  import jalview.util.ImageMaker;
@@@ -51,6 -49,7 +51,6 @@@ import java.awt.print.PrinterException
  import java.awt.print.PrinterJob;
  
  import javax.swing.ButtonGroup;
 -import javax.swing.JColorChooser;
  import javax.swing.JMenuItem;
  import javax.swing.JRadioButtonMenuItem;
  import javax.swing.event.InternalFrameAdapter;
@@@ -131,6 -130,8 +131,6 @@@ public class PCAPanel extends GPCAPane
  
      addKeyListener(getRotatableCanvas());
      validate();
 -
 -    this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
    }
  
    /**
    @Override
    protected void bgcolour_actionPerformed()
    {
 -    Color col = JColorChooser.showDialog(this,
 -            MessageManager.getString("label.select_background_colour"),
 -            getRotatableCanvas().getBgColour());
 -
 -    if (col != null)
 +    String ttl = MessageManager.getString("label.select_background_colour");
 +    ColourChooserListener listener = new ColourChooserListener()
      {
 -      getRotatableCanvas().setBgColour(col);
 -    }
 -    getRotatableCanvas().repaint();
 +      @Override
 +      public void colourSelected(Color c)
 +      {
 +        rc.setBgColour(c);
 +        rc.repaint();
 +      }
 +    };
 +    JalviewColourChooser.showColourChooser(this, ttl, rc.getBgColour(),
 +            listener);
    }
  
    /**
                MessageManager.formatMessage("label.calc_title", "PCA",
                        getPcaModel().getScoreModelName()),
                475, 450);
 +      this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
      }
      working = false;
    }
      }
    }
  
 -  /**
 -   * Handler for 'Save as EPS' option
 -   */
 -  @Override
 -  protected void eps_actionPerformed()
 -  {
 -    makePCAImage(ImageMaker.TYPE.EPS);
 -  }
 -
 -  /**
 -   * Handler for 'Save as PNG' option
 -   */
 -  @Override
 -  protected void png_actionPerformed()
 -  {
 -    makePCAImage(ImageMaker.TYPE.PNG);
 -  }
 -
 -  void makePCAImage(ImageMaker.TYPE type)
 +  public void makePCAImage(ImageMaker.TYPE type)
    {
      int width = getRotatableCanvas().getWidth();
      int height = getRotatableCanvas().getHeight();
 -
 -    ImageMaker im;
 -
 -    switch (type)
 +    ImageWriterI writer = new ImageWriterI()
      {
 -    case PNG:
 -      im = new ImageMaker(this, ImageMaker.TYPE.PNG,
 -              "Make PNG image from PCA", width, height, null, null, null, 0,
 -              false);
 -      break;
 -    case EPS:
 -      im = new ImageMaker(this, ImageMaker.TYPE.EPS,
 -              "Make EPS file from PCA", width, height, null,
 -              this.getTitle(), null, 0, false);
 -      break;
 -    default:
 -      im = new ImageMaker(this, ImageMaker.TYPE.SVG,
 -              "Make SVG file from PCA", width, height, null,
 -              this.getTitle(), null, 0, false);
 -    }
 -
 -    if (im.getGraphics() != null)
 -    {
 -      getRotatableCanvas().drawBackground(im.getGraphics());
 -      getRotatableCanvas().drawScene(im.getGraphics());
 -      if (getRotatableCanvas().drawAxes)
 +      @Override
 +      public void exportImage(Graphics g) throws Exception
        {
 -        getRotatableCanvas().drawAxes(im.getGraphics());
 +      RotatableCanvas canvas = getRotatableCanvas();
 +      canvas.drawBackground(g);
 +      canvas.drawScene(g);
 +        if (canvas.drawAxes)
 +        {
 +          canvas.drawAxes(g);
 +        }
        }
 -      im.writeImage();
 -    }
 +    };
 +    String pca = MessageManager.getString("label.pca");
 +    ImageExporter exporter = new ImageExporter(writer, null, type, pca);
 +    exporter.doExport(null, this, width, height, pca);
    }
  
    @Override
      // }
      //
      // JPanel progressPanel;
-     // Long lId = new Long(id);
+     // Long lId = Long.valueOf(id);
      // GridLayout layout = (GridLayout) statusPanel.getLayout();
      // if (progressBars.get(lId) != null)
      // {
-     // progressPanel = (JPanel) progressBars.get(new Long(id));
+     // progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
      // statusPanel.remove(progressPanel);
      // progressBars.remove(lId);
      // progressPanel = null;
            final IProgressIndicatorHandler handler)
    {
      progressBar.registerHandler(id, handler);
-     // if (progressBarHandlers == null || !progressBars.contains(new Long(id)))
+     // if (progressBarHandlers == null || !progressBars.contains(Long.valueOf(id)))
      // {
      // throw new
      // Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
      // }
-     // progressBarHandlers.put(new Long(id), handler);
-     // final JPanel progressPanel = (JPanel) progressBars.get(new Long(id));
+     // progressBarHandlers.put(Long.valueOf(id), handler);
+     // final JPanel progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
      // if (handler.canCancel())
      // {
      // JButton cancel = new JButton(
@@@ -25,6 -25,7 +25,7 @@@ import jalview.bin.Cache
  import jalview.gui.Help.HelpId;
  import jalview.gui.StructureViewer.ViewerType;
  import jalview.io.BackupFiles;
+ import jalview.io.BackupFilesPresetEntry;
  import jalview.io.FileFormatI;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
@@@ -55,7 -56,7 +56,7 @@@ import java.util.ArrayList
  import java.util.List;
  
  import javax.help.HelpSetException;
 -import javax.swing.JColorChooser;
 +import javax.swing.JComboBox;
  import javax.swing.JFileChooser;
  import javax.swing.JInternalFrame;
  import javax.swing.JPanel;
@@@ -189,18 -190,11 +190,18 @@@ public class Preferences extends GPrefe
      super();
      frame = new JInternalFrame();
      frame.setContentPane(this);
 -    wsPrefs = new WsPreferences();
 -    wsTab.add(wsPrefs, BorderLayout.CENTER);
 +    if (!Platform.isJS())
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +    {
 +      wsPrefs = new WsPreferences();
 +      wsTab.add(wsPrefs, BorderLayout.CENTER);
 +    }
      int width = 500, height = 450;
 -    new jalview.util.Platform();
 -    if (Platform.isAMac())
 +    if (Platform.isAMacAndNotJS())
      {
        width = 570;
        height = 480;
              new RowSorter.SortKey(m.getNameColumn(), SortOrder.ASCENDING));
  
      sorter.setSortKeys(sortKeys);
 -    sorter.sort();
 +    // BH 2018 setSortKeys will do the sort
 +    // sorter.sort();
  
      // set up filtering
      ActionListener onReset;
      /*
       * Set Output tab defaults
       */
 -    epsRendering.addItem(promptEachTimeOpt);
 -    epsRendering.addItem(lineArtOpt);
 -    epsRendering.addItem(textOpt);
 -    String defaultEPS = Cache.getDefault("EPS_RENDERING",
 -            "Prompt each time");
 -    if (defaultEPS.equalsIgnoreCase("Text"))
 -    {
 -      epsRendering.setSelectedItem(textOpt);
 -    }
 -    else if (defaultEPS.equalsIgnoreCase("Lineart"))
 -    {
 -      epsRendering.setSelectedItem(lineArtOpt);
 -    }
 -    else
 -    {
 -      epsRendering.setSelectedItem(promptEachTimeOpt);
 -    }
 +    setupOutputCombo(epsRendering, "EPS_RENDERING");
 +    setupOutputCombo(htmlRendering, "HTML_RENDERING");
 +    setupOutputCombo(svgRendering, "SVG_RENDERING");
      autoIdWidth.setSelected(Cache.getDefault("FIGURE_AUTOIDWIDTH", false));
      userIdWidth.setEnabled(!autoIdWidth.isSelected());
      userIdWidthlabel.setEnabled(!autoIdWidth.isSelected());
    }
  
    /**
 +   * A helper method that sets the items and initial selection in a character
 +   * rendering option list (Prompt each time/Lineart/Text)
 +   * 
 +   * @param comboBox
 +   * @param propertyKey
 +   */
 +  protected void setupOutputCombo(JComboBox<Object> comboBox,
 +          String propertyKey)
 +  {
 +    comboBox.addItem(promptEachTimeOpt);
 +    comboBox.addItem(lineArtOpt);
 +    comboBox.addItem(textOpt);
 +    
 +    /*
 +     * JalviewJS doesn't support Lineart so force it to Text
 +     */
 +    String defaultOption = Platform.isJS() ? "Text"
 +            : Cache.getDefault(propertyKey, "Prompt each time");
 +    if (defaultOption.equalsIgnoreCase("Text"))
 +    {
 +      comboBox.setSelectedItem(textOpt);
 +    }
 +    else if (defaultOption.equalsIgnoreCase("Lineart"))
 +    {
 +      comboBox.setSelectedItem(lineArtOpt);
 +    }
 +    else
 +    {
 +      comboBox.setSelectedItem(promptEachTimeOpt);
 +    }
 +  }
 +
 +  /**
     * Save user selections on the Preferences tabs to the Cache and write out to
     * file.
     * 
       */
      Cache.applicationProperties.setProperty("EPS_RENDERING",
              ((OptionsParam) epsRendering.getSelectedItem()).getCode());
 +    Cache.applicationProperties.setProperty("HTML_RENDERING",
 +            ((OptionsParam) htmlRendering.getSelectedItem()).getCode());
 +    Cache.applicationProperties.setProperty("SVG_RENDERING",
 +            ((OptionsParam) svgRendering.getSelectedItem()).getCode());
  
      /*
       * Save Connections settings
      Cache.applicationProperties.setProperty("PAD_GAPS",
              Boolean.toString(padGaps.isSelected()));
  
 -    wsPrefs.updateAndRefreshWsMenuConfig(false);
 +    if (!Platform.isJS())
 +    {
 +      wsPrefs.updateAndRefreshWsMenuConfig(false);
 +    }
  
      /*
       * Save Backups settings
       */
-     Cache.applicationProperties.setProperty(BackupFiles.CONFIRM_DELETE_OLD,
-             Boolean.toString(backupfilesConfirmDelete.isSelected()));
      Cache.applicationProperties.setProperty(BackupFiles.ENABLED,
              Boolean.toString(enableBackupFiles.isSelected()));
-     Cache.applicationProperties.setProperty(BackupFiles.NO_MAX,
-             Boolean.toString(backupfilesKeepAll.isSelected()));
-     Cache.applicationProperties.setProperty(BackupFiles.REVERSE_ORDER,
-             Boolean.toString(suffixReverse.isSelected()));
-     Cache.applicationProperties.setProperty(BackupFiles.SUFFIX,
-             suffixTemplate.getText());
-     Cache.applicationProperties.setProperty(BackupFiles.ROLL_MAX,
-             Integer.toString(getSpinnerInt(backupfilesRollMaxSpinner, 4)));
-     Cache.applicationProperties.setProperty(BackupFiles.SUFFIX_DIGITS,
-             Integer.toString(getSpinnerInt(suffixDigitsSpinner, 3)));
-     Cache.applicationProperties.setProperty(BackupFiles.NS+"_PRESET",
-             Integer.toString(getComboIntStringKey(backupfilesPresetsCombo)));
+     int preset = getComboIntStringKey(backupfilesPresetsCombo);
+     Cache.applicationProperties.setProperty(BackupFiles.NS + "_PRESET", Integer.toString(preset));
+     if (preset == BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM)
+     {
+       BackupFilesPresetEntry customBFPE = getBackupfilesCurrentEntry();
+       BackupFilesPresetEntry.backupfilesPresetEntriesValues.put(
+               BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM, customBFPE);
+       Cache.applicationProperties
+               .setProperty(BackupFilesPresetEntry.CUSTOMCONFIG,
+                       customBFPE.toString());
+     }
+     BackupFilesPresetEntry savedBFPE = BackupFilesPresetEntry.backupfilesPresetEntriesValues
+             .get(preset);
+     Cache.applicationProperties.setProperty(
+             BackupFilesPresetEntry.SAVEDCONFIG, savedBFPE.toString());
  
      Cache.saveProperties();
      Desktop.instance.doConfigureStructurePrefs();
    @Override
    public void startupFileTextfield_mouseClicked()
    {
 +    // TODO: JAL-3048 not needed for Jalview-JS
      String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
      JalviewFileChooser chooser = JalviewFileChooser
              .forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat);
    {
      try
      {
 -      wsPrefs.updateWsMenuConfig(true);
 -      wsPrefs.refreshWs_actionPerformed(e);
 +      if (!Platform.isJS())
 +      {
 +        wsPrefs.updateWsMenuConfig(true);
 +        wsPrefs.refreshWs_actionPerformed(e);
 +      }
        frame.setClosed(true);
      } catch (Exception ex)
      {
    @Override
    public void defaultBrowser_mouseClicked(MouseEvent e)
    {
 -    JFileChooser chooser = new JFileChooser(".");
 -    chooser.setDialogTitle(
 -            MessageManager.getString("label.select_default_browser"));
 +    // TODO: JAL-3048 not needed for j2s
 +    if (!Platform.isJS()) // BH 2019
 +    /**
 +     * Java only
 +     * 
 +     * @j2sIgnore
 +     */
 +    {
 +      JFileChooser chooser = new JFileChooser(".");
 +      chooser.setDialogTitle(
 +              MessageManager.getString("label.select_default_browser"));
  
 -    int value = chooser.showOpenDialog(this);
 +      int value = chooser.showOpenDialog(this);
  
 -    if (value == JFileChooser.APPROVE_OPTION)
 -    {
 -      defaultBrowser.setText(chooser.getSelectedFile().getAbsolutePath());
 +      if (value == JFileChooser.APPROVE_OPTION)
 +      {
 +        defaultBrowser.setText(chooser.getSelectedFile().getAbsolutePath());
 +      }
      }
 -
    }
  
    /*
    @Override
    public void minColour_actionPerformed(JPanel panel)
    {
 -    Color col = JColorChooser.showDialog(this,
 +    JalviewColourChooser.showColourChooser(this,
              MessageManager.getString("label.select_colour_minimum_value"),
 -            minColour.getBackground());
 -    if (col != null)
 -    {
 -      panel.setBackground(col);
 -    }
 -    panel.repaint();
 +            panel);
    }
  
    @Override
    public void maxColour_actionPerformed(JPanel panel)
    {
 -    Color col = JColorChooser.showDialog(this,
 +    JalviewColourChooser.showColourChooser(this,
              MessageManager.getString("label.select_colour_maximum_value"),
 -            maxColour.getBackground());
 -    if (col != null)
 -    {
 -      panel.setBackground(col);
 -    }
 -    panel.repaint();
 +            panel);
    }
  
    @Override
    {
      if (!useLegacyGap.isSelected())
      {
 -      Color col = JColorChooser.showDialog(this,
 +      JalviewColourChooser.showColourChooser(this,
                MessageManager.getString("label.select_gap_colour"),
 -              gapColour.getBackground());
 -      if (col != null)
 -      {
 -        gap.setBackground(col);
 -      }
 -      gap.repaint();
 +              gap);
      }
    }
  
    @Override
    public void hiddenColour_actionPerformed(JPanel hidden)
    {
 -    Color col = JColorChooser.showDialog(this,
 +    JalviewColourChooser.showColourChooser(this,
              MessageManager.getString("label.select_hidden_colour"),
 -            hiddenColour.getBackground());
 -    if (col != null)
 -    {
 -      hidden.setBackground(col);
 -    }
 -    hidden.repaint();
 +            hidden);
    }
  
    @Override
        }
      } catch (NumberFormatException x)
      {
 +      userIdWidth.setText("");
        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                MessageManager
                        .getString("warn.user_defined_width_requirements"),
                MessageManager.getString("label.invalid_id_column_width"),
                JvOptionPane.WARNING_MESSAGE);
 -      userIdWidth.setText("");
      }
    }
  
@@@ -29,7 -29,6 +29,6 @@@ import jalview.util.Platform
  import jalview.viewmodel.AlignmentViewport;
  
  import java.awt.Component;
- import java.awt.Toolkit;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.KeyAdapter;
@@@ -43,6 -42,8 +42,8 @@@ import java.util.Map.Entry
  import javax.swing.AbstractAction;
  import javax.swing.InputMap;
  import javax.swing.JComponent;
+ import javax.swing.JDesktopPane;
+ import javax.swing.JInternalFrame;
  import javax.swing.JMenuItem;
  import javax.swing.KeyStroke;
  import javax.swing.event.InternalFrameAdapter;
@@@ -97,9 -98,9 +98,9 @@@ public class SplitFrame extends GSplitF
       * estimate width and height of SplitFrame; this.getInsets() doesn't seem to
       * give the full additional size (a few pixels short)
       */
 -    int widthFudge = Platform.isAMac() ? MAC_INSETS_WIDTH
 +    int widthFudge = Platform.isAMacAndNotJS() ? MAC_INSETS_WIDTH
              : WINDOWS_INSETS_WIDTH;
 -    int heightFudge = Platform.isAMac() ? MAC_INSETS_HEIGHT
 +    int heightFudge = Platform.isAMacAndNotJS() ? MAC_INSETS_HEIGHT
              : WINDOWS_INSETS_HEIGHT;
      int width = ((AlignFrame) getTopFrame()).getWidth() + widthFudge;
      int height = ((AlignFrame) getTopFrame()).getHeight()
      /*
       * estimate ratio of (topFrameContent / bottomFrameContent)
       */
 -    int insets = Platform.isAMac() ? MAC_INSETS_HEIGHT
 +    int insets = Platform.isAMacAndNotJS() ? MAC_INSETS_HEIGHT
              : WINDOWS_INSETS_HEIGHT;
      // allow 3 'rows' for scale, scrollbar, status bar
      int topHeight = insets + (3 + topCount) * topCharHeight
       * Ctrl-W / Cmd-W - close view or window
       */
      KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      action = new AbstractAction()
      {
        @Override
       * Ctrl-T / Cmd-T open new view
       */
      KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      AbstractAction action = new AbstractAction()
      {
        @Override
       * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
       */
      KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      AbstractAction action = new AbstractAction()
      {
        @Override
      };
      overrideKeyBinding(key_cmdF, action);
    }
+   /**
+    * Override to do nothing if triggered from one of the child frames
+    */
+   @Override
+   public void setSelected(boolean selected) throws PropertyVetoException
+   {
+     JDesktopPane desktopPane = getDesktopPane();
+     JInternalFrame fr = desktopPane == null ? null
+             : desktopPane.getSelectedFrame();
+     if (fr == getTopFrame() || fr == getBottomFrame())
+     {
+       /* 
+        * patch for JAL-3288 (deselecting top/bottom frame closes popup menu); 
+        * it may be possible to remove this method in future
+        * if the underlying Java behaviour changes
+        */
+       if (selected)
+       {
+         moveToFront();
+       }
+       return;
+     }
+     super.setSelected(selected);
+   }
  }
@@@ -83,7 -83,7 +83,7 @@@ public class StructureChooser extends G
  
    private FTSRestRequest lastPdbRequest;
  
 -  private FTSRestClientI pdbRestCleint;
 +  private FTSRestClientI pdbRestClient;
  
    private String selectedPdbFileName;
  
    void fetchStructuresMetaData()
    {
      long startTime = System.currentTimeMillis();
 -    pdbRestCleint = PDBFTSRestClient.getInstance();
 +    pdbRestClient = PDBFTSRestClient.getInstance();
      Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
              .getStructureSummaryFields();
  
        FTSRestResponse resultList;
        try
        {
 -        resultList = pdbRestCleint.executeRequest(pdbRequest);
 +        resultList = pdbRestClient.executeRequest(pdbRequest);
        } catch (Exception e)
        {
          e.printStackTrace();
      boolean isUniProtRefsFound = false;
      StringBuilder queryBuilder = new StringBuilder();
      Set<String> seqRefs = new LinkedHashSet<>();
+     
+     /*
+      * note PDBs as DBRefEntry so they are not duplicated in query
+      */
+     Set<String> pdbids = new HashSet<>();
  
      if (seq.getAllPDBEntries() != null
              && queryBuilder.length() < MAX_QLENGTH)
        {
          if (isValidSeqName(entry.getId()))
          {
-           queryBuilder.append("pdb_id:").append(entry.getId().toLowerCase())
-                   .append(" OR ");
+           String id = entry.getId().toLowerCase();
+           queryBuilder.append("pdb_id:").append(id).append(" OR ");
            isPDBRefsFound = true;
+           pdbids.add(id);
          }
        }
      }
  
 -    if (seq.getDBRefs() != null && seq.getDBRefs().length != 0)
 +    List<DBRefEntry> refs = seq.getDBRefs();
 +    if (refs != null && refs.size() != 0)
      {
 -      for (DBRefEntry dbRef : seq.getDBRefs())
 +      for (int ib = 0, nb = refs.size(); ib < nb; ib++)
        {
 +        DBRefEntry dbRef = refs.get(ib);
          if (isValidSeqName(getDBRefId(dbRef))
                  && queryBuilder.length() < MAX_QLENGTH)
          {
            else if (dbRef.getSource().equalsIgnoreCase(DBRefSource.PDB))
            {
  
-             queryBuilder.append("pdb_id:")
-                     .append(getDBRefId(dbRef).toLowerCase()).append(" OR ");
-             isPDBRefsFound = true;
+             String id = getDBRefId(dbRef).toLowerCase();
+             if (!pdbids.contains(id))
+             {
+               queryBuilder.append("pdb_id:").append(id).append(" OR ");
+               isPDBRefsFound = true;
+               pdbids.add(id);
+             }
            }
            else
            {
        public void run()
        {
          long startTime = System.currentTimeMillis();
 -        pdbRestCleint = PDBFTSRestClient.getInstance();
 +        pdbRestClient = PDBFTSRestClient.getInstance();
          lbl_loading.setVisible(true);
          Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
                  .getStructureSummaryFields();
            FTSRestResponse resultList;
            try
            {
 -            resultList = pdbRestCleint.executeRequest(pdbRequest);
 +            resultList = pdbRestClient.executeRequest(pdbRequest);
            } catch (Exception e)
            {
              e.printStackTrace();
    @Override
    protected void pdbFromFile_actionPerformed()
    {
 +    // TODO: JAL-3048 not needed for Jalview-JS until JSmol dep and StructureChooser
 +    // works
      jalview.io.JalviewFileChooser chooser = new jalview.io.JalviewFileChooser(
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
      chooser.setFileView(new jalview.io.JalviewFileView());
              && !discoveredStructuresSet.isEmpty();
    }
  
 +  protected int PDB_ID_MIN = 3;// or: (Jalview.isJS() ? 3 : 1); // Bob proposes this. 
 +  // Doing a search for "1" or "1c" is valuable?
 +  // Those work but are enormously slow.
 +
    @Override
    protected void txt_search_ActionPerformed()
    {
 +    String text = txt_search.getText().trim();
 +      if (text.length() >= PDB_ID_MIN) 
      new Thread()
      {
 -      @Override
 +
 +      @Override
        public void run()
        {
          errorWarning.setLength(0);
          isValidPBDEntry = false;
 -        if (txt_search.getText().length() > 0)
 +        if (text.length() > 0)
          {
 -          String searchTerm = txt_search.getText().toLowerCase();
 +          String searchTerm = text.toLowerCase();
            searchTerm = searchTerm.split(":")[0];
            // System.out.println(">>>>> search term : " + searchTerm);
            List<FTSDataColumnI> wantedFields = new ArrayList<>();
            pdbRequest.setWantedFields(wantedFields);
            pdbRequest.setSearchTerm(searchTerm + ")");
            pdbRequest.setAssociatedSequence(selectedSequence);
 -          pdbRestCleint = PDBFTSRestClient.getInstance();
 -          wantedFields.add(pdbRestCleint.getPrimaryKeyColumn());
 +          pdbRestClient = PDBFTSRestClient.getInstance();
 +          wantedFields.add(pdbRestClient.getPrimaryKeyColumn());
            FTSRestResponse resultList;
            try
            {
 -            resultList = pdbRestCleint.executeRequest(pdbRequest);
 +            resultList = pdbRestClient.executeRequest(pdbRequest);
            } catch (Exception e)
            {
              errorWarning.append(e.getMessage());
@@@ -26,12 -26,13 +26,13 @@@ import jalview.ws.WSClientI
  
  import java.awt.BorderLayout;
  import java.awt.Color;
- import java.awt.Font;
+ import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.Graphics2D;
  import java.awt.GridLayout;
  import java.awt.Image;
  import java.awt.MediaTracker;
+ import java.awt.RenderingHints;
  import java.awt.event.ActionEvent;
  import java.awt.image.BufferedImage;
  import java.util.Vector;
@@@ -45,6 -46,6 +46,8 @@@ import javax.swing.JTabbedPane
  import javax.swing.JTextArea;
  import javax.swing.event.HyperlinkEvent;
  import javax.swing.event.HyperlinkListener;
++import javax.swing.event.InternalFrameAdapter;
++import javax.swing.event.InternalFrameEvent;
  import javax.swing.text.html.HTMLEditorKit;
  import javax.swing.text.html.StyleSheet;
  
@@@ -81,7 -82,7 +84,7 @@@ public class WebserviceInfo extends GWe
  
    Image image;
  
-   int angle = 0;
+   float angle = 0f;
  
    String title = "";
  
    {
      super.setVisible(aFlag);
      frame.setVisible(aFlag);
--  };
++  }
  
    JTabbedPane subjobs = null;
  
      setInfoText(info);
  
      java.net.URL url = getClass()
-             .getResource("/images/Jalview_Logo_small.png");
+             .getResource("/images/Jalview_Logo_small_with_border.png");
      image = java.awt.Toolkit.getDefaultToolkit().createImage(url);
  
      MediaTracker mt = new MediaTracker(this);
      }
  
      AnimatedPanel ap = new AnimatedPanel();
-     titlePanel.add(ap, BorderLayout.CENTER);
+     ap.setPreferredSize(new Dimension(60, 60));
+     titlePanel.add(ap, BorderLayout.WEST);
+     titlePanel.add(titleText, BorderLayout.CENTER);
+     setStatus(currentStatus);
  
      Thread thread = new Thread(ap);
      thread.start();
      final WebserviceInfo thisinfo = this;
      frame.addInternalFrameListener(
--            new javax.swing.event.InternalFrameAdapter()
++            new InternalFrameAdapter()
              {
                @Override
-               public void internalFrameClosed(
-                       javax.swing.event.InternalFrameEvent evt)
 -                      public void internalFrameClosed(
 -                      javax.swing.event.InternalFrameEvent evt)
++              public void internalFrameClosed(InternalFrameEvent evt)
                {
                  // System.out.println("Shutting down webservice client");
                  WSClientI service = thisinfo.getthisService();
                  {
                    service.cancelJob();
                  }
--              };
++              }
              });
      frame.validate();
  
    public void setStatus(int status)
    {
      currentStatus = status;
+     String message = null;
+     switch (currentStatus)
+     {
+     case STATE_QUEUING:
+       message = MessageManager.getString("label.state_queueing");
+       break;
+     case STATE_RUNNING:
+       message = MessageManager.getString("label.state_running");
+       break;
+     case STATE_STOPPED_OK:
+       message = MessageManager.getString("label.state_completed");
+       break;
+     case STATE_CANCELLED_OK:
+       message = MessageManager.getString("label.state_job_cancelled");
+       break;
+     case STATE_STOPPED_ERROR:
+       message = MessageManager.getString("label.state_job_error");
+       break;
+     case STATE_STOPPED_SERVERERROR:
+       message = MessageManager.getString("label.server_error_try_later");
+       break;
+     }
+     titleText.setText(title + (message == null ? "" : " - " + message));
+     titleText.repaint();
    }
  
    /**
     *          DOCUMENT ME!
     */
    @Override
 -protected void cancel_actionPerformed(ActionEvent e)
 +  protected void cancel_actionPerformed(ActionEvent e)
    {
      if (!serviceIsCancellable)
      {
      javax.swing.SwingUtilities.invokeLater(new Runnable()
      {
        @Override
 -      public void run()
 +      public void run()
        {
          JvOptionPane.showInternalMessageDialog(Desktop.desktop, message,
                  title, JvOptionPane.WARNING_MESSAGE);
      {
        startTime = System.currentTimeMillis();
  
+       float invSpeed = 15f;
+       float factor = 1f;
        while (currentStatus < STATE_STOPPED_OK)
        {
+         if (currentStatus == STATE_QUEUING)
+         {
+           invSpeed = 25f;
+           factor = 1f;
+         }
+         else if (currentStatus == STATE_RUNNING)
+         {
+           invSpeed = 10f;
+           factor = (float) (0.5 + 1.5
+                   * (0.5 - (0.5 * Math.sin(3.14159 / 180 * (angle + 45)))));
+         }
          try
          {
            Thread.sleep(50);
  
-           int units = (int) ((System.currentTimeMillis() - startTime)
-                   / 10f);
-           angle += units;
+           float delta = (System.currentTimeMillis() - startTime) / invSpeed;
+           angle += delta * factor;
            angle %= 360;
            startTime = System.currentTimeMillis();
  
            if (currentStatus >= STATE_STOPPED_OK)
            {
+             park();
              angle = 0;
            }
  
        cancel.setEnabled(false);
      }
  
+     public void park()
+     {
+       startTime = System.currentTimeMillis();
+       while (angle < 360)
+       {
+         float invSpeed = 5f;
+         float factor = 1f;
+         try
+         {
+           Thread.sleep(25);
+           float delta = (System.currentTimeMillis() - startTime) / invSpeed;
+           angle += delta * factor;
+           startTime = System.currentTimeMillis();
+           if (angle >= 360)
+           {
+             angle = 360;
+           }
+           repaint();
+         } catch (Exception ex)
+         {
+         }
+       }
+     }
      void drawPanel()
      {
        if (offscreen == null || offscreen.getWidth(this) != getWidth()
                || offscreen.getHeight(this) != getHeight())
        {
          offscreen = new BufferedImage(getWidth(), getHeight(),
-                 BufferedImage.TYPE_INT_ARGB);
+                 BufferedImage.TYPE_INT_RGB);
        }
  
        Graphics2D g = (Graphics2D) offscreen.getGraphics();
  
+       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+               RenderingHints.VALUE_ANTIALIAS_ON);
+       g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+               RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+       g.setRenderingHint(RenderingHints.KEY_RENDERING,
+               RenderingHints.VALUE_RENDER_QUALITY);
        g.setColor(Color.white);
        g.fillRect(0, 0, getWidth(), getHeight());
  
        if (image != null)
        {
          int x = image.getWidth(this) / 2, y = image.getHeight(this) / 2;
-         g.rotate(Math.toRadians(angle), 10 + x, 10 + y);
-         g.drawImage(image, 10, 10, this);
-         g.rotate(-Math.toRadians(angle), 10 + x, 10 + y);
+         g.rotate(3.14159 / 180 * (angle), x, y);
+         g.drawImage(image, 0, 0, this);
+         g.rotate(-3.14159 / 180 * (angle), x, y);
        }
      }
  
      @Override
-     public void paintComponent(Graphics g1)
+       public void paintComponent(Graphics g1)
      {
        drawPanel();
  
    }
  
    @Override
-   public void hyperlinkUpdate(HyperlinkEvent e)
+ public void hyperlinkUpdate(HyperlinkEvent e)
    {
      Desktop.hyperlinkUpdate(e);
    }
@@@ -274,7 -274,7 +274,7 @@@ public class AnnotationFil
            if (row.graphGroup > -1)
            {
              graphGroupSeen.set(row.graphGroup);
-             Integer key = new Integer(row.graphGroup);
+             Integer key = Integer.valueOf(row.graphGroup);
              if (graphGroup.containsKey(key))
              {
                graphGroup.put(key, graphGroup.get(key) + "\t" + row.label);
  
    String refSeqId = null;
  
 -  public boolean annotateAlignmentView(AlignViewportI viewport, String file,
 +  public boolean annotateAlignmentView(AlignViewportI viewport, Object file,
            DataSourceType protocol)
    {
      ColumnSelection colSel = viewport.getColumnSelection();
    }
  
    public boolean readAnnotationFile(AlignmentI al, HiddenColumns hidden,
 -          String file, DataSourceType sourceType)
 +          Object file, DataSourceType sourceType)
    {
      BufferedReader in = null;
      try
      {
        if (sourceType == DataSourceType.FILE)
        {
 -        in = new BufferedReader(new FileReader(file));
 +        in = FileLoader.getBufferedReader(file);
        }
        else if (sourceType == DataSourceType.URL)
        {
 -        URL url = new URL(file);
 +        URL url = new URL(file.toString());
          in = new BufferedReader(new InputStreamReader(url.openStream()));
        }
        else if (sourceType == DataSourceType.PASTE)
        {
 -        in = new BufferedReader(new StringReader(file));
 +        in = new BufferedReader(new StringReader(file.toString()));
        }
        else if (sourceType == DataSourceType.CLASSLOADER)
        {
                        autoAnnotsKey(aa[aai], aa[aai].sequenceRef,
                                (aa[aai].groupRef == null ? null
                                        : aa[aai].groupRef.getName())),
-                       new Integer(1));
+                       Integer.valueOf(1));
              }
            }
          }
          {
            displayChar = token;
            // foo
-           value = new Float(token).floatValue();
+           value = Float.valueOf(token).floatValue();
            parsedValue = true;
            continue;
          } catch (NumberFormatException ex)
@@@ -4,7 -4,6 +4,7 @@@ import jalview.bin.Cache
  import jalview.gui.Desktop;
  import jalview.gui.JvOptionPane;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  
  import java.io.File;
  import java.io.IOException;
@@@ -18,12 -17,8 +18,8 @@@ import java.util.TreeMap
   * BackupFiles used for manipulating (naming rolling/deleting) backup/version files when an alignment or project file is saved.
   * User configurable options are:
   * BACKUPFILES_ENABLED - boolean flag as to whether to use this mechanism or act as before, including overwriting files as saved.
-  * BACKUPFILES_SUFFIX - a template to insert after the file extension.  Use '%n' to be replaced by a 0-led SUFFIX_DIGITS long integer.
-  * BACKUPFILES_NO_MAX - flag to turn off setting a maximum number of backup files to keep.
-  * BACKUPFILES_ROLL_MAX - the maximum number of backupfiles to keep for any one alignment or project file.
-  * BACKUPFILES_SUFFIX_DIGITS - the number of digits to insert replace %n with (e.g. BACKUPFILES_SUFFIX_DIGITS = 3 would make "001", "002", etc)
-  * BACKUPFILES_REVERSE_ORDER - if true then "logfile" style numbering and file rolling will occur. If false then ever-increasing version numbering will occur, but old files will still be deleted if there are more than ROLL_MAX backup files. 
-  * BACKUPFILES_CONFIRM_DELETE_OLD - if true then prompt/confirm with the user when deleting older backup/version files.
+  * The rest of the options are now saved as BACKUPFILES_PRESET, BACKUPFILES_SAVED and BACKUPFILES_CUSTOM
+  * (see BackupFilesPresetEntry)
   */
  
  public class BackupFiles
  
    public static final String ENABLED = NS + "_ENABLED";
  
-   public static final String SUFFIX = NS + "_SUFFIX";
-   public static final String NO_MAX = NS + "_NO_MAX";
-   public static final String ROLL_MAX = NS + "_ROLL_MAX";
-   public static final String SUFFIX_DIGITS = NS + "_SUFFIX_DIGITS";
    public static final String NUM_PLACEHOLDER = "%n";
  
-   public static final String REVERSE_ORDER = NS + "_REVERSE_ORDER";
-   public static final String CONFIRM_DELETE_OLD = NS
-           + "_CONFIRM_DELETE_OLD";
    private static final String DEFAULT_TEMP_FILE = "jalview_temp_file_" + NS;
  
    private static final String TEMP_FILE_EXT = ".tmp";
    // REVERSE_ORDER
    public BackupFiles(File file)
    {
-     this(file, ".bak" + NUM_PLACEHOLDER, false, 3, 3, false);
-   }
-   public BackupFiles(File file, String defaultSuffix, boolean defaultNoMax,
-           int defaultMax, int defaultDigits, boolean defaultReverseOrder)
-   {
      classInit();
      this.file = file;
-     this.suffix = Cache.getDefault(SUFFIX, defaultSuffix);
-     this.noMax = Cache.getDefault(NO_MAX, defaultNoMax);
-     this.max = Cache.getDefault(ROLL_MAX, defaultMax);
-     this.digits = Cache.getDefault(SUFFIX_DIGITS, defaultDigits);
-     this.reverseOrder = Cache.getDefault(REVERSE_ORDER,
-             defaultReverseOrder);
+     BackupFilesPresetEntry bfpe = BackupFilesPresetEntry.getSavedBackupEntry();
+     this.suffix = bfpe.suffix;
+     this.noMax = bfpe.keepAll;
+     this.max = bfpe.rollMax;
+     this.digits = bfpe.digits;
+     this.reverseOrder = bfpe.reverse;
  
      // create a temp file to save new data in
      File temp = null;
  
    public static void classInit()
    {
 -    setEnabled(Cache.getDefault(ENABLED, true));
 +    setEnabled(Cache.getDefault(ENABLED, !Platform.isJS()));
-     setConfirmDelete(Cache.getDefault(CONFIRM_DELETE_OLD, true));
+     BackupFilesPresetEntry bfpe = BackupFilesPresetEntry
+             .getSavedBackupEntry();
+     setConfirmDelete(bfpe.confirmDelete);
    }
  
    public static void setEnabled(boolean flag)
@@@ -105,11 -105,11 +105,11 @@@ public class FeaturesFile extends Align
    /**
     * Constructor which does not parse the file immediately
     * 
 -   * @param file
 +   * @param file File or String filename
     * @param paste
     * @throws IOException
     */
 -  public FeaturesFile(String file, DataSourceType paste)
 +  public FeaturesFile(Object file, DataSourceType paste)
            throws IOException
    {
      super(false, file, paste);
     * @param type
     * @throws IOException
     */
 -  public FeaturesFile(boolean parseImmediately, String file,
 +  public FeaturesFile(boolean parseImmediately, Object file,
            DataSourceType type) throws IOException
    {
      super(parseImmediately, file, type);
        float score = Float.NaN;
        try
        {
-         score = new Float(gffColumns[6]).floatValue();
+         score = Float.valueOf(gffColumns[6]).floatValue();
        } catch (NumberFormatException ex)
        {
          sf = new SequenceFeature(ft, desc, startPos, endPos, featureGroup);
@@@ -39,16 -39,10 +39,16 @@@ import jalview.project.Jalview2XML
  import jalview.schemes.ColourSchemeI;
  import jalview.structure.StructureSelectionManager;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  import jalview.ws.utils.UrlDownloadClient;
  
 +import java.io.BufferedReader;
 +import java.io.ByteArrayInputStream;
  import java.io.File;
 +import java.io.FileNotFoundException;
 +import java.io.FileReader;
  import java.io.IOException;
 +import java.io.InputStreamReader;
  import java.util.StringTokenizer;
  import java.util.Vector;
  
@@@ -77,8 -71,6 +77,8 @@@ public class FileLoader implements Runn
  
    boolean raiseGUI = true;
  
 +  private File selectedFile;
 +
    /**
     * default constructor always raised errors in GUI dialog boxes
     */
      this.raiseGUI = raiseGUI;
    }
  
 -  public void LoadFile(AlignViewport viewport, String file,
 +  public void LoadFile(AlignViewport viewport, Object file,
            DataSourceType protocol, FileFormatI format)
    {
      this.viewport = viewport;
 -    LoadFile(file, protocol, format);
 +    if (file instanceof File) {
 +      this.selectedFile = (File) file;
 +      file = selectedFile.getPath();
 +    }
 +    LoadFile(file.toString(), protocol, format);
    }
  
    public void LoadFile(String file, DataSourceType protocol,
    }
  
    /**
 +   * Load alignment from (file, protocol) of type format and wait till loaded
 +   * 
 +   * @param file
 +   * @param sourceType
 +   * @param format
 +   * @return alignFrame constructed from file contents
 +   */
 +  public AlignFrame LoadFileWaitTillLoaded(File file,
 +          DataSourceType sourceType, FileFormatI format)
 +  {
 +    this.selectedFile = file;
 +    this.file = file.getPath();
 +    this.protocol = sourceType;
 +    this.format = format;
 +    return _LoadFileWaitTillLoaded();
 +  }
 +
 +  /**
     * Load alignment from FileParse source of type format and wait till loaded
     * 
     * @param source
    }
  
    /**
 -   * start thread and wait until finished, then return the alignFrame that's
 -   * (hopefully) been read.
 +   * runs the 'run' method (in this thread), then return the alignFrame that's
 +   * (hopefully) been read
     * 
     * @return
     */
    protected AlignFrame _LoadFileWaitTillLoaded()
    {
 -    Thread loader = new Thread(this);
 -    loader.start();
 -
 -    while (loader.isAlive())
 -    {
 -      try
 -      {
 -        Thread.sleep(500);
 -      } catch (Exception ex)
 -      {
 -        System.out.println(
 -                "Exception caught while waiting for FileLoader thread");
 -        ex.printStackTrace();
 -      }
 -    }
 +    this.run();
      return alignFrame;
    }
  
    public void updateRecentlyOpened()
    {
 -    Vector recent = new Vector();
 +    Vector<String> recent = new Vector<>();
      if (protocol == DataSourceType.PASTE)
      {
        // do nothing if the file was pasted in as text... there is no filename to
      String type = protocol == DataSourceType.FILE ? "RECENT_FILE"
              : "RECENT_URL";
  
 -    String historyItems = jalview.bin.Cache.getProperty(type);
 +    String historyItems = Cache.getProperty(type);
  
      StringTokenizer st;
  
  
        while (st.hasMoreTokens())
        {
 -        recent.addElement(st.nextElement().toString().trim());
 +        recent.addElement(st.nextToken().trim());
        }
      }
  
            format = new IdentifyFile().identify(source, false);
            // identify stream and rewind rather than close
          }
 +        else if (selectedFile != null) {
 +          format = new IdentifyFile().identify(selectedFile, protocol);
 +        }
          else
          {
            format = new IdentifyFile().identify(file, protocol);
                    "IMPLEMENTATION ERROR: Cannot read consecutive Jalview XML projects from a stream.");
            // We read the data anyway - it might make sense.
          }
 -        alignFrame = new Jalview2XML(raiseGUI).loadJalviewAlign(file);
 +        // BH 2018 switch to File object here instead of filename
 +        alignFrame = new Jalview2XML(raiseGUI).loadJalviewAlign(selectedFile == null ? file : selectedFile);
        }
        else
        {
                        file.lastIndexOf("."));
                String tempStructureFileStr = createNamedJvTempFile(
                        urlLeafName, structExt);
 -              UrlDownloadClient.download(file, tempStructureFileStr);
 -              al = fa.readFile(tempStructureFileStr, DataSourceType.FILE,
 +              
 +              // BH - switching to File object here so as to hold
 +              // ._bytes array directly
 +              File tempFile = new File(tempStructureFileStr);
 +              UrlDownloadClient.download(file, tempFile);
 +              
 +              al = fa.readFile(tempFile, DataSourceType.FILE,
                        format);
                source = fa.getAlignFile();
              }
              else
              {
 -              al = fa.readFile(file, protocol, format);
 +              if (selectedFile == null) {
 +                al = fa.readFile(file, protocol, format);
 +                
 +              } else {
 +                al = fa.readFile(selectedFile, protocol, format);
 +                             }
                source = fa.getAlignFile(); // keep reference for later if
 +              
                                            // necessary.
              }
            }
              if (!(protocol == DataSourceType.PASTE))
              {
                alignFrame.setFileName(file, format);
 +              alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
              }
              if (proxyColourScheme != null)
              {
      return tempStructFile.toString();
    }
  
 +  /**
 +   * 
 +   * @param file a File, or a String which is a name of a file
 +   * @return
 +   * @throws FileNotFoundException 
 +   */
 +  public static BufferedReader getBufferedReader(Object file) throws FileNotFoundException {
 +    if (file instanceof String)
 +    {
 +      return new BufferedReader(new FileReader((String) file));
 +    }
 +    byte[] bytes = Platform.getFileBytes((File) file);
 +    if (bytes != null)
 +    {
 +      return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
 +    }
 +    return  new BufferedReader(new FileReader((File) file));
 +  }
 +
  }
@@@ -68,8 -68,6 +68,8 @@@ public class JPredFile extends AlignFil
    /**
     * Creates a new JPredFile object.
     * 
 +   * BH allows File or String
 +   * 
     * @param inFile
     *          DOCUMENT ME!
     * @param sourceType
@@@ -78,7 -76,7 +78,7 @@@
     * @throws IOException
     *           DOCUMENT ME!
     */
 -  public JPredFile(String inFile, DataSourceType sourceType)
 +  public JPredFile(Object inFile, DataSourceType sourceType)
            throws IOException
    {
      super(inFile, sourceType);
            {
              ascore = symbols.nextToken();
  
-             Float score = new Float(ascore);
+             Float score = Float.valueOf(ascore);
              scores.addElement(score);
            }
  
  
            seq_entries.addElement(newseq.toString());
            ids.addElement(id);
-           Symscores.put(id, new Integer(ids.size() - 1));
+           Symscores.put(id, Integer.valueOf(ids.size() - 1));
          }
        }
      }
    }
  
    /**
 -   * DOCUMENT ME!
     * 
     * @param args
 -   *          DOCUMENT ME!
 +   * @j2sIgnore
     */
    public static void main(String[] args)
    {
   */
  package jalview.io;
  
 +import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.SequenceI;
  
 +import java.util.List;
 +
 +import com.stevesoft.pat.Regex;
 +
  public class ModellerDescription
  {
    /**
@@@ -90,7 -85,7 +90,7 @@@
  
      resCode(int v)
      {
-       val = new Integer(v);
+       val = Integer.valueOf(v);
        field = val.toString();
      }
    };
@@@ -98,7 -93,7 +98,7 @@@
    private resCode validResidueCode(String field)
    {
      Integer val = null;
 -    com.stevesoft.pat.Regex r = new com.stevesoft.pat.Regex(
 +    Regex r = new Regex(
              "\\s*((([-0-9]+).?)|FIRST|LAST|@)");
  
      if (!r.search(field))
        if (seq.getDatasetSequence() != null
                && seq.getDatasetSequence().getDBRefs() != null)
        {
 -        jalview.datamodel.DBRefEntry[] dbr = seq.getDatasetSequence()
 -                .getDBRefs();
 -        int i, j;
 -        for (i = 0, j = dbr.length; i < j; i++)
 +        List<DBRefEntry> dbr = seq.getDatasetSequence().getDBRefs();
 +        for (int i = 0, ni = dbr.size(); i < ni; i++)
          {
 -          if (dbr[i] != null)
 +              DBRefEntry dbri = dbr.get(i);
 +          if (dbri != null)
            {
              // JBPNote PDB dbRefEntry needs properties to propagate onto
              // ModellerField
              // JBPNote Need to get info from the user about whether the sequence
              // is the one being modelled, or if it is a template.
 -            if (dbr[i].getSource()
 +            if (dbri.getSource()
                      .equals(jalview.datamodel.DBRefSource.PDB))
              {
 -              fields.put(Fields[LOCALID], dbr[i].getAccessionId());
 +              fields.put(Fields[LOCALID], dbri.getAccessionId());
                t = 2;
                break;
              }
@@@ -35,8 -35,6 +35,8 @@@ import java.io.FileReader
  import java.io.IOException;
  import java.util.StringTokenizer;
  
 +import com.stevesoft.pat.Regex;
 +
  /**
   * Parse a new hanpshire style tree Caveats: NHX files are NOT supported and the
   * tree distances and topology are unreliable when they are parsed. TODO: on
@@@ -89,13 -87,13 +89,13 @@@ public class NewickFile extends FilePar
  
    boolean printRootInfo = true;
  
 -  private com.stevesoft.pat.Regex[] NodeSafeName = new com.stevesoft.pat.Regex[] {
 -      new com.stevesoft.pat.Regex().perlCode("m/[\\[,:'()]/"), // test for
 +  private Regex[] NodeSafeName = new Regex[] {
 +      new Regex().perlCode("m/[\\[,:'()]/"), // test for
        // requiring
        // quotes
 -      new com.stevesoft.pat.Regex().perlCode("s/'/''/"), // escaping quote
 +      new Regex().perlCode("s/'/''/"), // escaping quote
        // characters
 -      new com.stevesoft.pat.Regex().perlCode("s/\\/w/_/") // unqoted whitespace
 +      new Regex().perlCode("s/\\/w/_/") // unqoted whitespace
        // transformation
    };
  
      boolean ascending = false; // flag indicating that we are leaving the
      // current node
  
 -    com.stevesoft.pat.Regex majorsyms = new com.stevesoft.pat.Regex(
 +    Regex majorsyms = new Regex(
              "[(\\['),;]");
  
      int nextcp = 0;
  
            continue;
          }
 -
 -        ;
          d++;
  
          if (c.right() == null)
        // Deal with quoted fields
        case '\'':
  
 -        com.stevesoft.pat.Regex qnodename = new com.stevesoft.pat.Regex(
 +        Regex qnodename = new Regex(
                  "'([^']|'')+'");
  
          if (qnodename.searchFrom(nf, fcp))
            nodename = new String(
                    qnodename.stringMatched().substring(1, nl - 1));
            // unpack any escaped colons
 -          com.stevesoft.pat.Regex xpandquotes = com.stevesoft.pat.Regex
 +          Regex xpandquotes = Regex
                    .perlCode("s/''/'/");
            String widernodename = xpandquotes.replaceAll(nodename);
            nodename = widernodename;
             * '"+nf.substring(cp,fcp)+"'"); }
             */
            // verify termination.
 -          com.stevesoft.pat.Regex comment = new com.stevesoft.pat.Regex(
 +          Regex comment = new Regex(
                    "]");
            if (comment.searchFrom(nf, fcp))
            {
              Error = ErrorStringrange(Error, "Unterminated comment", 3, fcp,
                      nf);
            }
 -
 -          ;
          }
          // Parse simpler field strings
          String fstring = nf.substring(ncp, fcp);
                    + fstring.substring(cend + 1);
  
          }
 -        com.stevesoft.pat.Regex uqnodename = new com.stevesoft.pat.Regex(
 +        Regex uqnodename = new Regex(
                  "\\b([^' :;\\](),]+)");
 -        com.stevesoft.pat.Regex nbootstrap = new com.stevesoft.pat.Regex(
 +        Regex nbootstrap = new Regex(
                  "\\s*([0-9+]+)\\s*:");
 -        com.stevesoft.pat.Regex ndist = new com.stevesoft.pat.Regex(
 +        Regex ndist = new Regex(
                  ":([-0-9Ee.+]+)");
  
          if (!parsednodename && uqnodename.search(fstring)
            {
              try
              {
-               bootstrap = (new Integer(nbootstrap.stringMatched(1)))
+               bootstrap = (Integer.valueOf(nbootstrap.stringMatched(1)))
                        .intValue();
                HasBootstrap = true;
              } catch (Exception e)
          {
            try
            {
-             distance = (new Float(ndist.stringMatched(1))).floatValue();
+             distance = (Float.valueOf(ndist.stringMatched(1))).floatValue();
              HasDistances = true;
              nodehasdistance = true;
            } catch (Exception e)
              if (code.toLowerCase().equals("b"))
              {
                int v = -1;
-               Float iv = new Float(value);
+               Float iv = Float.valueOf(value);
                v = iv.intValue(); // jalview only does integer bootstraps
                // currently
                c.setBootstrap(v);
      }
    }
  
 -  // Test
 +  /**
 +   * 
 +   * @param args
 +   * @j2sIgnore
 +   */
    public static void main(String[] args)
    {
      try
        trf.parse();
        System.out.println("Original file :\n");
  
 -      com.stevesoft.pat.Regex nonl = new com.stevesoft.pat.Regex("\n+", "");
 +      Regex nonl = new Regex("\n+", "");
        System.out.println(nonl.replaceAll(newickfile.toString()) + "\n");
  
        System.out.println("Parsed file.\n");
@@@ -23,6 -23,7 +23,7 @@@ package jalview.io
  import jalview.api.FeatureColourI;
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.DBRefSource;
+ import jalview.datamodel.GeneLociI;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.util.MessageManager;
@@@ -52,7 -53,9 +53,7 @@@ public class SequenceAnnotationRepor
  
    private static final int MAX_SOURCES = 40;
  
 -  private static final String[][] PRIMARY_SOURCES = new String[][] {
 -      DBRefSource.CODINGDBS, DBRefSource.DNACODINGDBS,
 -      DBRefSource.PROTEINDBS };
 + // public static final String[][] PRIMARY_SOURCES  moved to DBRefSource.java
  
    final String linkImageURL;
  
      @Override
      public int compare(DBRefEntry ref1, DBRefEntry ref2)
      {
-       if (ref1.isChromosome())
+       if (ref1 instanceof GeneLociI)
        {
          return -1;
        }
-       if (ref2.isChromosome())
+       if (ref2 instanceof GeneLociI)
        {
          return 1;
        }
        String s1 = ref1.getSource();
        String s2 = ref2.getSource();
 -      boolean s1Primary = isPrimarySource(s1);
 -      boolean s2Primary = isPrimarySource(s2);
 +      boolean s1Primary = DBRefSource.isPrimarySource(s1);
 +      boolean s2Primary = DBRefSource.isPrimarySource(s2);
        if (s1Primary && !s2Primary)
        {
          return -1;
        return comp;
      }
  
 -    private boolean isPrimarySource(String source)
 -    {
 -      for (String[] primary : PRIMARY_SOURCES)
 -      {
 -        for (String s : primary)
 -        {
 -          if (source.equals(s))
 -          {
 -            return true;
 -          }
 -        }
 -      }
 -      return false;
 -    }
 +//    private boolean isPrimarySource(String source)
 +//    {
 +//      for (String[] primary : DBRefSource.PRIMARY_SOURCES)
 +//      {
 +//        for (String s : primary)
 +//        {
 +//          if (source.equals(s))
 +//          {
 +//            return true;
 +//          }
 +//        }
 +//      }
 +//      return false;
 +//    }
    };
  
    public SequenceAnnotationReport(String linkURL)
    protected int appendDbRefs(final StringBuilder sb, SequenceI ds,
            boolean summary)
    {
 -    DBRefEntry[] dbrefs = ds.getDBRefs();
 +    List<DBRefEntry> dbrefs = ds.getDBRefs();
      if (dbrefs == null)
      {
        return 0;
      }
  
      // note this sorts the refs held on the sequence!
 -    Arrays.sort(dbrefs, comparator);
 +    dbrefs.sort(comparator);
      boolean ellipsis = false;
      String source = null;
      String lastSource = null;
@@@ -271,14 -271,14 +271,14 @@@ public class Tree extends DatastoreIte
     * @return vector of alignment sequences in order of SeqCigar array (but
     *         missing unfound seqcigars)
     */
 -  private Vector findAlignmentSequences(AlignmentI jal,
 +  private Vector<SequenceI> findAlignmentSequences(AlignmentI jal,
            SeqCigar[] sequences)
    {
      SeqCigar[] tseqs = new SeqCigar[sequences.length];
      System.arraycopy(sequences, 0, tseqs, 0, sequences.length);
 -    Vector alsq = new Vector();
 -    List<SequenceI> jalsqs;
 -    synchronized (jalsqs = jal.getSequences())
 +    Vector<SequenceI> alsq = new Vector<>();
 +    List<SequenceI> jalsqs = jal.getSequences();
 +    synchronized (jalsqs)
      {
        for (SequenceI asq : jalsqs)
        {
      Integer nindx = (Integer) nodespecs.get(nname);
      if (nindx == null)
      {
-       nindx = new Integer(1);
+       nindx = Integer.valueOf(1);
      }
      nname = nindx.toString() + " " + nname;
      return nname;
      String oval = nodespec.substring(0, nodespec.indexOf(' '));
      try
      {
-       occurence = new Integer(oval).intValue();
+       occurence = Integer.valueOf(oval).intValue();
      } catch (Exception e)
      {
        System.err.println("Invalid nodespec '" + nodespec + "'");
@@@ -51,6 -51,8 +51,8 @@@ import htsjdk.variant.vcf.VCFInfoHeader
   */
  public class VCFLoader
  {
+   private static final String DEFAULT_SPECIES = "homo_sapiens";
    /**
     * A class to model the mapping from sequence to VCF coordinates. Cases include
     * <ul>
@@@ -82,7 -84,7 +84,7 @@@
  
    /*
     * Lookup keys, and default values, for Preference entries that describe
-    * patterns for VCF and VEP fields to capture 
+    * patterns for VCF and VEP fields to capture
     */
    private static final String VEP_FIELDS_PREF = "VEP_FIELDS";
  
    private static final String DEFAULT_VEP_FIELDS = ".*";// "Allele,Consequence,IMPACT,SWISSPROT,SIFT,PolyPhen,CLIN_SIG";
  
    /*
+    * Lookup keys, and default values, for Preference entries that give
+    * mappings from tokens in the 'reference' header to species or assembly
+    */
+   private static final String VCF_ASSEMBLY = "VCF_ASSEMBLY";
+   private static final String DEFAULT_VCF_ASSEMBLY = "assembly19=GRCh37,hs37=GRCh37,grch37=GRCh37,grch38=GRCh38";
+   private static final String VCF_SPECIES = "VCF_SPECIES"; // default is human
+   private static final String DEFAULT_REFERENCE = "grch37"; // fallback default is human GRCh37
+   /*
     * keys to fields of VEP CSQ consequence data
     * see https://www.ensembl.org/info/docs/tools/vep/vep_formats.html
     */
    private static final String PIPE_REGEX = "\\|";
  
    /*
-    * key for Allele Frequency output by VEP
-    * see http://www.ensembl.org/info/docs/tools/vep/vep_formats.html
-    */
-   private static final String ALLELE_FREQUENCY_KEY = "AF";
-   /*
     * delimiter that separates multiple consequence data blocks
     */
    private static final String COMMA = ",";
    private VCFHeader header;
  
    /*
+    * species (as a valid Ensembl term) the VCF is for 
+    */
+   private String vcfSpecies;
+   /*
+    * genome assembly version (as a valid Ensembl identifier) the VCF is for 
+    */
+   private String vcfAssembly;
+   /*
     * a Dictionary of contigs (if present) referenced in the VCF file
     */
    private SAMSequenceDictionary dictionary;
     */
    public SequenceI loadVCFContig(String contig)
    {
-     String ref = header.getOtherHeaderLine(VCFHeader.REFERENCE_KEY)
-             .getValue();
+     VCFHeaderLine headerLine = header.getOtherHeaderLine(VCFHeader.REFERENCE_KEY);
+     if (headerLine == null)
+     {
+       Cache.log.error("VCF reference header not found");
+       return null;
+     }
+     String ref = headerLine.getValue();
      if (ref.startsWith("file://"))
      {
        ref = ref.substring(7);
      }
+     setSpeciesAndAssembly(ref);
  
      SequenceI seq = null;
      File dbFile = new File(ref);
      {
        HtsContigDb db = new HtsContigDb("", dbFile);
        seq = db.getSequenceProxy(contig);
-       loadSequenceVCF(seq, ref);
+       loadSequenceVCF(seq);
        db.close();
      }
      else
      {
-       System.err.println("VCF reference not found: " + ref);
+       Cache.log.error("VCF reference not found: " + ref);
      }
  
      return seq;
      {
        VCFHeaderLine ref = header
                .getOtherHeaderLine(VCFHeader.REFERENCE_KEY);
-       String vcfAssembly = ref.getValue();
+       String reference = ref == null ? null : ref.getValue();
+       setSpeciesAndAssembly(reference);
  
        int varCount = 0;
        int seqCount = 0;
         */
        for (SequenceI seq : seqs)
        {
-         int added = loadSequenceVCF(seq, vcfAssembly);
+         int added = loadSequenceVCF(seq);
          if (added > 0)
          {
            seqCount++;
    }
  
    /**
+    * Attempts to determine and save the species and genome assembly version to
+    * which the VCF data applies. This may be done by parsing the {@code reference}
+    * header line, configured in a property file, or (potentially) confirmed
+    * interactively by the user.
+    * <p>
+    * The saved values should be identifiers valid for Ensembl's REST service
+    * {@code map} endpoint, so they can be used (if necessary) to retrieve the
+    * mapping between VCF coordinates and sequence coordinates.
+    * 
+    * @param reference
+    * @see https://rest.ensembl.org/documentation/info/assembly_map
+    * @see https://rest.ensembl.org/info/assembly/human?content-type=text/xml
+    * @see https://rest.ensembl.org/info/species?content-type=text/xml
+    */
+   protected void setSpeciesAndAssembly(String reference)
+   {
+     if (reference == null)
+     {
+       Cache.log.error("No VCF ##reference found, defaulting to "
+               + DEFAULT_REFERENCE + ":" + DEFAULT_SPECIES);
+       reference = DEFAULT_REFERENCE; // default to GRCh37 if not specified
+     }
+     reference = reference.toLowerCase();
+     /*
+      * for a non-human species, or other assembly identifier,
+      * specify as a Jalview property file entry e.g.
+      * VCF_ASSEMBLY = hs37=GRCh37,assembly19=GRCh37
+      * VCF_SPECIES = c_elegans=celegans
+      * to map a token in the reference header to a value
+      */
+     String prop = Cache.getDefault(VCF_ASSEMBLY, DEFAULT_VCF_ASSEMBLY);
+     for (String token : prop.split(","))
+     {
+       String[] tokens = token.split("=");
+       if (tokens.length == 2)
+       {
+         if (reference.contains(tokens[0].trim().toLowerCase()))
+         {
+           vcfAssembly = tokens[1].trim();
+           break;
+         }
+       }
+     }
+     vcfSpecies = DEFAULT_SPECIES;
+     prop = Cache.getProperty(VCF_SPECIES);
+     if (prop != null)
+     {
+       for (String token : prop.split(","))
+       {
+         String[] tokens = token.split("=");
+         if (tokens.length == 2)
+         {
+           if (reference.contains(tokens[0].trim().toLowerCase()))
+           {
+             vcfSpecies = tokens[1].trim();
+             break;
+           }
+         }
+       }
+     }
+   }
+   /**
     * Opens the VCF file and parses header data
     * 
     * @param filePath
     */
    protected void transferAddedFeatures(SequenceI seq)
    {
 -    DBRefEntry[] dbrefs = seq.getDBRefs();
 +    List<DBRefEntry> dbrefs = seq.getDBRefs();
      if (dbrefs == null)
      {
        return;
     * and returns the number of variant features added
     * 
     * @param seq
-    * @param vcfAssembly
     * @return
     */
-   protected int loadSequenceVCF(SequenceI seq, String vcfAssembly)
+   protected int loadSequenceVCF(SequenceI seq)
    {
-     VCFMap vcfMap = getVcfMap(seq, vcfAssembly);
+     VCFMap vcfMap = getVcfMap(seq);
      if (vcfMap == null)
      {
        return 0;
     * Answers a map from sequence coordinates to VCF chromosome ranges
     * 
     * @param seq
-    * @param vcfAssembly
     * @return
     */
-   private VCFMap getVcfMap(SequenceI seq, String vcfAssembly)
+   private VCFMap getVcfMap(SequenceI seq)
    {
      /*
       * simplest case: sequence has id and length matching a VCF contig
      String species = seqCoords.getSpeciesId();
      String chromosome = seqCoords.getChromosomeId();
      String seqRef = seqCoords.getAssemblyId();
-     MapList map = seqCoords.getMap();
+     MapList map = seqCoords.getMapping();
  
-     if (!vcfSpeciesMatchesSequence(vcfAssembly, species))
+     // note this requires the configured species to match that
+     // returned with the Ensembl sequence; todo: support aliases?
+     if (!vcfSpecies.equalsIgnoreCase(species))
      {
+       Cache.log.warn("No VCF loaded to " + seq.getName()
+               + " as species not matched");
        return null;
      }
  
-     if (vcfAssemblyMatchesSequence(vcfAssembly, seqRef))
+     if (seqRef.equalsIgnoreCase(vcfAssembly))
      {
        return new VCFMap(chromosome, map);
      }
  
-     if (!"GRCh38".equalsIgnoreCase(seqRef) // Ensembl
-             || !vcfAssembly.contains("Homo_sapiens_assembly19")) // gnomAD
-     {
-       return null;
-     }
      /*
-      * map chromosomal coordinates from sequence to VCF if the VCF
-      * data has a different reference assembly to the sequence
+      * VCF data has a different reference assembly to the sequence:
+      * query Ensembl to map chromosomal coordinates from sequence to VCF
       */
-     // TODO generalise for cases other than GRCh38 -> GRCh37 !
-     // - or get the user to choose in a dialog
      List<int[]> toVcfRanges = new ArrayList<>();
      List<int[]> fromSequenceRanges = new ArrayList<>();
-     String toRef = "GRCh37";
  
      for (int[] range : map.getToRanges())
      {
        }
  
        int[] newRange = mapReferenceRange(range, chromosome, "human", seqRef,
-               toRef);
+               vcfAssembly);
        if (newRange == null)
        {
          Cache.log.error(
                  String.format("Failed to map %s:%s:%s:%d:%d to %s", species,
-                         chromosome, seqRef, range[0], range[1], toRef));
+                         chromosome, seqRef, range[0], range[1],
+                         vcfAssembly));
          continue;
        }
        else
    }
  
    /**
-    * Answers true if we determine that the VCF data uses the same reference
-    * assembly as the sequence, else false
-    * 
-    * @param vcfAssembly
-    * @param seqRef
-    * @return
-    */
-   private boolean vcfAssemblyMatchesSequence(String vcfAssembly,
-           String seqRef)
-   {
-     // TODO improve on this stub, which handles gnomAD and
-     // hopes for the best for other cases
-     if ("GRCh38".equalsIgnoreCase(seqRef) // Ensembl
-             && vcfAssembly.contains("Homo_sapiens_assembly19")) // gnomAD
-     {
-       return false;
-     }
-     return true;
-   }
-   /**
-    * Answers true if the species inferred from the VCF reference identifier
-    * matches that for the sequence
-    * 
-    * @param vcfAssembly
-    * @param speciesId
-    * @return
-    */
-   boolean vcfSpeciesMatchesSequence(String vcfAssembly, String speciesId)
-   {
-     // PROBLEM 1
-     // there are many aliases for species - how to equate one with another?
-     // PROBLEM 2
-     // VCF ##reference header is an unstructured URI - how to extract species?
-     // perhaps check if ref includes any (Ensembl) alias of speciesId??
-     // TODO ask the user to confirm this??
-     if (vcfAssembly.contains("Homo_sapiens") // gnomAD exome data example
-             && "HOMO_SAPIENS".equals(speciesId)) // Ensembl species id
-     {
-       return true;
-     }
-     if (vcfAssembly.contains("c_elegans") // VEP VCF response example
-             && "CAENORHABDITIS_ELEGANS".equals(speciesId)) // Ensembl
-     {
-       return true;
-     }
-     // this is not a sustainable solution...
-     return false;
-   }
-   /**
     * Queries the VCF reader for any variants that overlap the mapped chromosome
     * ranges of the sequence, and adds as variant features. Returns the number of
     * overlapping variants found.
@@@ -35,7 -35,6 +35,6 @@@ import jalview.util.Platform
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.GridLayout;
- import java.awt.Toolkit;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.FocusAdapter;
@@@ -62,18 -61,15 +61,18 @@@ import javax.swing.event.ChangeEvent
  import javax.swing.event.MenuEvent;
  import javax.swing.event.MenuListener;
  
 +@SuppressWarnings("serial")
  public class GAlignFrame extends JInternalFrame
  {
    protected JMenuBar alignFrameMenuBar = new JMenuBar();
  
    protected JMenuItem closeMenuItem = new JMenuItem();
  
 -  protected JMenu webService = new JMenu();
 +  public JMenu webService = new JMenu();// BH 2019 was protected, but not
 +                                        // sufficient for AlignFrame thread run
  
 -  protected JMenuItem webServiceNoServices;
 +  public JMenuItem webServiceNoServices;// BH 2019 was protected, but not
 +                                        // sufficient for AlignFrame thread run
  
    protected JCheckBoxMenuItem viewBoxesMenuItem = new JCheckBoxMenuItem();
  
@@@ -81,9 -77,7 +80,9 @@@
  
    protected JMenu sortByAnnotScore = new JMenu();
  
 -  protected JLabel statusBar = new JLabel();
 +  public JLabel statusBar = new JLabel(); // BH 2019 was protected, but not
 +                                          // sufficient for
 +                                          // AlignFrame.printWriter
  
    protected JMenu outputTextboxMenu = new JMenu();
  
  
    protected JCheckBoxMenuItem applyAutoAnnotationSettings = new JCheckBoxMenuItem();
  
 +  protected JMenuItem openFeatureSettings;
 +
    private SequenceAnnotationOrder annotationSortOrder;
  
    private boolean showAutoCalculatedAbove = false;
    {
      try
      {
 +
 +      // for Web-page embedding using id=align-frame-div
 +      setName("jalview-alignment");
 +
        jbInit();
        setJMenuBar(alignFrameMenuBar);
  
            @Override
            public void actionPerformed(ActionEvent e)
            {
 -            outputText_actionPerformed(e);
 +            outputText_actionPerformed(e.getActionCommand());
            }
          });
  
        System.err.println(e.toString());
      }
  
 -    if (!Platform.isAMac())
 +    if (Platform.allowMnemonics()) // was "not mac and not JS"
      {
        closeMenuItem.setMnemonic('C');
        outputTextboxMenu.setMnemonic('T');
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        saveAs_actionPerformed(e);
 +        saveAs_actionPerformed();
        }
      };
  
      // FIXME getDefaultToolkit throws an exception in Headless mode
      KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                     | KeyEvent.SHIFT_MASK,
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                     | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
              false);
      addMenuActionAndAccelerator(keyStroke, saveAs, al);
  
      closeMenuItem.setText(MessageManager.getString("action.close"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem selectAllSequenceMenuItem = new JMenuItem(
              MessageManager.getString("action.select_all"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_A,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem invertSequenceMenuItem = new JMenuItem(
              MessageManager.getString("action.invert_sequence_selection"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem remove2LeftMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_left"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_L,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem remove2RightMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_right"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_R,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem removeGappedColumnMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_empty_columns"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem removeAllGapsMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_all_gaps"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_E,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                     | KeyEvent.SHIFT_MASK,
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                     | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
              false);
      al = new ActionListener()
      {
      JMenuItem removeRedundancyMenuItem = new JMenuItem(
              MessageManager.getString("action.remove_redundancy"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_D,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      undoMenuItem.setEnabled(false);
      undoMenuItem.setText(MessageManager.getString("action.undo"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      redoMenuItem.setEnabled(false);
      redoMenuItem.setText(MessageManager.getString("action.redo"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Y,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem printMenuItem = new JMenuItem(
              MessageManager.getString("action.print"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_P,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem findMenuItem = new JMenuItem(
              MessageManager.getString("action.find"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      findMenuItem.setToolTipText(JvSwingUtils.wrapTooltip(true,
              MessageManager.getString("label.find_tip")));
      al = new ActionListener()
      JMenuItem deleteGroups = new JMenuItem(
              MessageManager.getString("action.undefine_groups"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_U,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem createGroup = new JMenuItem(
              MessageManager.getString("action.create_group"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem unGroup = new JMenuItem(
              MessageManager.getString("action.remove_group"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_G,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                     | KeyEvent.SHIFT_MASK,
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                     | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
              false);
      al = new ActionListener()
      {
  
      copy.setText(MessageManager.getString("action.copy"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_C,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
  
      al = new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        copy_actionPerformed(e);
 +        copy_actionPerformed();
        }
      };
      addMenuActionAndAccelerator(keyStroke, copy, al);
  
      cut.setText(MessageManager.getString("action.cut"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        cut_actionPerformed(e);
 +        cut_actionPerformed();
        }
      };
      addMenuActionAndAccelerator(keyStroke, cut, al);
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        delete_actionPerformed(e);
 +        delete_actionPerformed();
        }
      });
  
      JMenuItem pasteNew = new JMenuItem(
              MessageManager.getString("label.to_new_alignment"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                     | KeyEvent.SHIFT_MASK,
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                     | jalview.util.ShortcutKeyMaskExWrapper.SHIFT_DOWN_MASK,
              false);
      al = new ActionListener()
      {
      JMenuItem pasteThis = new JMenuItem(
              MessageManager.getString("label.to_this_alignment"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_V,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      /*
       * Translate as cDNA with sub-menu of translation tables
       */
 -    showTranslation.setText(MessageManager
 -            .getString("label.translate_cDNA"));
 +    showTranslation
 +            .setText(MessageManager.getString("label.translate_cDNA"));
      boolean first = true;
      for (final GeneticCodeI table : GeneticCodes.getInstance()
              .getCodeTables())
        }
      });
  
 -    JMenuItem openFeatureSettings = new JMenuItem(
 +    openFeatureSettings = new JMenuItem(
              MessageManager.getString("action.feature_settings"));
      openFeatureSettings.addActionListener(new ActionListener()
      {
          featureSettings_actionPerformed(e);
        }
      });
 +
 +    /*
 +     * add sub-menu of database we can fetch from
 +     */
      JMenuItem fetchSequence = new JMenuItem(
              MessageManager.getString("label.fetch_sequences"));
      fetchSequence.addActionListener(new ActionListener()
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        fetchSequence_actionPerformed(e);
 +        fetchSequence_actionPerformed();
        }
      });
  
          associatedData_actionPerformed(e);
        }
      });
 -    loadVcf = new JMenuItem(MessageManager.getString("label.load_vcf_file"));
 +    loadVcf = new JMenuItem(
 +            MessageManager.getString("label.load_vcf_file"));
      loadVcf.setToolTipText(MessageManager.getString("label.load_vcf"));
      loadVcf.addActionListener(new ActionListener()
      {
      JMenuItem invertColSel = new JMenuItem(
              MessageManager.getString("action.invert_column_selection"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
-                     | KeyEvent.ALT_MASK,
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
+                     | jalview.util.ShortcutKeyMaskExWrapper.ALT_DOWN_MASK,
              false);
      al = new ActionListener()
      {
  
      JMenuItem save = new JMenuItem(MessageManager.getString("action.save"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      JMenuItem newView = new JMenuItem(
              MessageManager.getString("action.new_view"));
      keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_T,
-             Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
+             jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
      al = new ActionListener()
      {
        @Override
      alignFrameMenuBar.add(formatMenu);
      alignFrameMenuBar.add(colourMenu);
      alignFrameMenuBar.add(calculateMenu);
 -    alignFrameMenuBar.add(webService);
 +    if (!Platform.isJS())
 +    {
 +      alignFrameMenuBar.add(webService);
 +    }
  
      fileMenu.add(fetchSequence);
      fileMenu.add(addSequenceMenu);
      fileMenu.add(exportAnnotations);
      fileMenu.add(loadTreeMenuItem);
      fileMenu.add(associatedData);
 -    fileMenu.add(loadVcf);
 +    if (!Platform.isJS())
 +    {
 +      fileMenu.add(loadVcf);
 +    }
      fileMenu.addSeparator();
      fileMenu.add(closeMenuItem);
  
      calculateMenu.addSeparator();
      calculateMenu.add(expandAlignment);
      calculateMenu.add(extractScores);
 -    calculateMenu.addSeparator();
 -    calculateMenu.add(runGroovy);
 +    if (!Platform.isJS())
 +    {
 +      calculateMenu.addSeparator();
 +      calculateMenu.add(runGroovy);
 +    }
  
      webServiceNoServices = new JMenuItem(
              MessageManager.getString("label.no_services"));
      webService.add(webServiceNoServices);
 -    exportImageMenu.add(htmlMenuItem);
 +    if (!Platform.isJS())
 +    {
 +      exportImageMenu.add(htmlMenuItem);
 +    }
      exportImageMenu.add(epsFile);
      exportImageMenu.add(createPNG);
 -    exportImageMenu.add(createBioJS);
 -    exportImageMenu.add(createSVG);
 +    if (!Platform.isJS())
 +    {
 +      exportImageMenu.add(createBioJS);
 +      exportImageMenu.add(createSVG);
 +    }
      addSequenceMenu.add(addFromFile);
      addSequenceMenu.add(addFromText);
      addSequenceMenu.add(addFromURL);
    {
    }
  
 -  protected void outputText_actionPerformed(ActionEvent e)
 +  protected void outputText_actionPerformed(String formatName)
    {
    }
  
    {
    }
  
 -  protected void copy_actionPerformed(ActionEvent e)
 +  protected void copy_actionPerformed()
    {
    }
  
 -  protected void cut_actionPerformed(ActionEvent e)
 +  protected void cut_actionPerformed()
    {
    }
  
 -  protected void delete_actionPerformed(ActionEvent e)
 +  protected void delete_actionPerformed()
    {
    }
  
    {
    }
  
 -  protected void saveAs_actionPerformed(ActionEvent e)
 +  protected void saveAs_actionPerformed()
    {
    }
  
  
    }
  
 -  public void fetchSequence_actionPerformed(ActionEvent e)
 +  public void fetchSequence_actionPerformed()
    {
  
    }
@@@ -26,7 -26,6 +26,6 @@@ import jalview.util.MessageManager
  import jalview.util.Platform;
  
  import java.awt.FlowLayout;
- import java.awt.Toolkit;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  
@@@ -42,10 -41,8 +41,10 @@@ import javax.swing.JMenuItem
   * @author $author$
   * @version $Revision$
   */
 +@SuppressWarnings("serial")
  public class GDesktop extends JFrame
  {
 +
    protected static JMenu windowMenu = new JMenu();
  
    JMenuBar desktopMenubar = new JMenuBar();
  
    JMenu HelpMenu = new JMenu();
  
-   protected JMenu VamsasMenu = new JMenu();
-   protected JMenu VamsasStMenu = new JMenu();
    JMenuItem inputLocalFileMenuItem = new JMenuItem();
  
    JMenuItem inputURLMenuItem = new JMenuItem();
  
    JMenu inputMenu = new JMenu();
  
-   protected JMenuItem vamsasStart = new JMenuItem();
-   protected JMenuItem vamsasImport = new JMenuItem();
-   protected JMenuItem vamsasSave = new JMenuItem();
    JMenuItem inputSequence = new JMenuItem();
  
-   protected JMenuItem vamsasStop = new JMenuItem();
    JMenuItem closeAll = new JMenuItem();
  
    JMenuItem raiseRelated = new JMenuItem();
     */
    public GDesktop()
    {
 +    super();
      try
      {
        jbInit();
        e.printStackTrace();
      }
  
 -    if (!Platform.isAMac())
 +    if (Platform.allowMnemonics()) 
      {
 +      //BH was !Platform.isAMacAndNotJS()) i.e. "JS or not Mac"
 +      // but here we want just not a Mac, period, right?
        FileMenu.setMnemonic('F');
        inputLocalFileMenuItem.setMnemonic('L');
-       VamsasMenu.setMnemonic('V'); 
        inputURLMenuItem.setMnemonic('U');
        inputTextboxMenuItem.setMnemonic('C');
        quit.setMnemonic('Q');
     */
    private void jbInit() throws Exception
    {
 -
 +    setName("jalview-desktop");
      FileMenu.setText(MessageManager.getString("action.file"));
      HelpMenu.setText(MessageManager.getString("action.help"));
-     VamsasMenu.setText("Vamsas");
-     VamsasMenu.setToolTipText(MessageManager
-             .getString("label.share_data_vamsas_applications"));
-     VamsasStMenu.setText(MessageManager.getString("label.connect_to"));
-     VamsasStMenu.setToolTipText(
-             MessageManager.getString("label.join_existing_vamsas_session"));
      inputLocalFileMenuItem
              .setText(MessageManager.getString("label.load_tree_from_file"));
      inputLocalFileMenuItem.setAccelerator(
              javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O,
-                     Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(),
+                     jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(),
                      false));
      inputLocalFileMenuItem
              .addActionListener(new java.awt.event.ActionListener()
                @Override
                public void actionPerformed(ActionEvent e)
                {
 -                documentationMenuItem_actionPerformed(e);
 +                documentationMenuItem_actionPerformed();
                }
              });
      this.getContentPane().setLayout(flowLayout1);
        }
      });
      inputMenu.setText(MessageManager.getString("label.input_alignment"));
-     vamsasStart
-             .setText(MessageManager.getString("label.new_vamsas_session"));
-     vamsasStart.setVisible(false);
-     vamsasStart.addActionListener(new ActionListener()
-     {
-       @Override
-       public void actionPerformed(ActionEvent e)
-       {
-         vamsasStart_actionPerformed(e);
-       }
-     });
-     vamsasImport.setText(
-             MessageManager.getString("action.load_vamsas_session"));
-     vamsasImport.setVisible(false);
-     vamsasImport.addActionListener(new ActionListener()
-     {
-       @Override
-       public void actionPerformed(ActionEvent e)
-       {
-         vamsasImport_actionPerformed(e);
-       }
-     });
-     vamsasSave.setText(
-             MessageManager.getString("action.save_vamsas_session"));
-     vamsasSave.setVisible(false);
-     vamsasSave.addActionListener(new ActionListener()
-     {
-       @Override
-       public void actionPerformed(ActionEvent e)
-       {
-         vamsasSave_actionPerformed(e);
-       }
-     });
      inputSequence
              .setText(MessageManager.getString("action.fetch_sequences"));
      inputSequence.addActionListener(new ActionListener()
          inputSequence_actionPerformed(e);
        }
      });
-     vamsasStop
-             .setText(MessageManager.getString("label.stop_vamsas_session"));
-     vamsasStop.setVisible(false);
-     vamsasStop.addActionListener(new ActionListener()
-     {
-       @Override
-       public void actionPerformed(ActionEvent e)
-       {
-         vamsasStop_actionPerformed(e);
-       }
-     });
      closeAll.setText(MessageManager.getString("action.close_all"));
      closeAll.addActionListener(new ActionListener()
      {
        }
      });
  
+     Float specversion = Float.parseFloat(System.getProperty("java.specification.version"));
+     
      desktopMenubar.add(FileMenu);
      desktopMenubar.add(toolsMenu);
-     VamsasMenu.setVisible(false);
-     desktopMenubar.add(VamsasMenu);
      desktopMenubar.add(HelpMenu);
      desktopMenubar.add(windowMenu);
      FileMenu.add(inputMenu);
      FileMenu.add(inputSequence);
      FileMenu.addSeparator();
-     FileMenu.add(saveState);
+     //FileMenu.add(saveState);
      FileMenu.add(saveAsState);
      FileMenu.add(loadState);
      FileMenu.addSeparator();
      FileMenu.add(quit);
      HelpMenu.add(aboutMenuItem);
      HelpMenu.add(documentationMenuItem);
-     VamsasMenu.add(VamsasStMenu);
-     VamsasStMenu.setVisible(false);
-     VamsasMenu.add(vamsasStart);
-     VamsasMenu.add(vamsasImport);
-     VamsasMenu.add(vamsasSave);
-     VamsasMenu.add(vamsasStop);
-     toolsMenu.add(preferences);
-     if (!Platform.isJS())
 -    if (!Platform.isAMac() || specversion < 11)
++    if (!Platform.isAMacAndNotJS() || specversion < 11)
      {
-       toolsMenu.add(showMemusage);
-       toolsMenu.add(showConsole);
+       toolsMenu.add(preferences);
      }
 -    toolsMenu.add(showMemusage);
 -    toolsMenu.add(showConsole);
 -    toolsMenu.add(showNews);
 -    toolsMenu.add(garbageCollect);
 -    toolsMenu.add(groovyShell);
 +    if (!Platform.isJS())
 +    {
++      toolsMenu.add(showMemusage);
++      toolsMenu.add(showConsole);
 +      toolsMenu.add(showNews);
 +      toolsMenu.add(garbageCollect);
 +      toolsMenu.add(groovyShell);
 +    }
      toolsMenu.add(experimentalFeatures);
      // toolsMenu.add(snapShotWindow);
      inputMenu.add(inputLocalFileMenuItem);
     */
    protected void quit()
    {
+     //System.out.println("********** GDesktop.quit()");
    }
  
    /**
    {
    }
  
 -  /**
 -   * DOCUMENT ME!
 -   * 
 -   * @param e
 -   *          DOCUMENT ME!
 -   */
 -  protected void documentationMenuItem_actionPerformed(ActionEvent e)
 +  protected void documentationMenuItem_actionPerformed()
    {
    }
  
@@@ -31,8 -31,9 +31,10 @@@ import jalview.gui.JvSwingUtils
  import jalview.gui.StructureViewer.ViewerType;
  import jalview.io.BackupFilenameParts;
  import jalview.io.BackupFiles;
+ import jalview.io.BackupFilesPresetEntry;
+ import jalview.io.IntKeyStringValueEntry;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
@@@ -52,8 -53,8 +54,8 @@@ import java.awt.event.KeyEvent
  import java.awt.event.KeyListener;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
- import java.util.HashMap;
- import java.util.Map;
+ import java.util.Arrays;
+ import java.util.List;
  
  import javax.swing.AbstractCellEditor;
  import javax.swing.BorderFactory;
@@@ -64,7 -65,6 +66,6 @@@ import javax.swing.JCheckBox
  import javax.swing.JComboBox;
  import javax.swing.JFileChooser;
  import javax.swing.JLabel;
- import javax.swing.JList;
  import javax.swing.JPanel;
  import javax.swing.JRadioButton;
  import javax.swing.JScrollPane;
@@@ -249,10 -249,6 +250,10 @@@ public class GPreferences extends JPane
     */
    protected JComboBox<Object> epsRendering = new JComboBox<>();
  
 +  protected JComboBox<Object> htmlRendering = new JComboBox<>();
 +
 +  protected JComboBox<Object> svgRendering = new JComboBox<>();
 +
    protected JLabel userIdWidthlabel = new JLabel();
  
    protected JCheckBox autoIdWidth = new JCheckBox();
  
    protected JPanel presetsPanel = new JPanel();
  
+   protected JLabel presetsComboLabel = new JLabel();
+   protected JCheckBox customiseCheckbox = new JCheckBox();
    protected JButton revertButton = new JButton();
  
-   protected JComboBox<IntKeyStringValueEntry> backupfilesPresetsCombo = new JComboBox<>();
+   protected JComboBox<Object> backupfilesPresetsCombo = new JComboBox<>();
+   private int backupfilesPresetsComboLastSelected = 0;
  
    protected JPanel suffixPanel = new JPanel();
  
      tabbedPane.add(initConnectionsTab(),
              MessageManager.getString("label.connections"));
  
 -    tabbedPane.add(initBackupsTab(),
 -            MessageManager.getString("label.backups"));
 +      if (!Platform.isJS()) 
 +      {
 +        tabbedPane.add(initBackupsTab(), 
 +                      MessageManager.getString("label.backups"));
 +      }
  
      tabbedPane.add(initLinksTab(),
              MessageManager.getString("label.urllinks"));
      /*
       * See WsPreferences for the real work of configuring this tab.
       */
 -    wsTab.setLayout(new BorderLayout());
 -    tabbedPane.add(wsTab, MessageManager.getString("label.web_services"));
 +    if (!Platform.isJS())
 +    {
 +      wsTab.setLayout(new BorderLayout());
 +      tabbedPane.add(wsTab, MessageManager.getString("label.web_services"));
 +    }
  
      /*
       * Handler to validate a tab before leaving it - currently only for
    }
  
    /**
 -   * Initialises the Output tabbed panel.
 +   * Initialises the Output tab
     * 
     * @return
     */
    {
      JPanel outputTab = new JPanel();
      outputTab.setLayout(null);
 -    JLabel epsLabel = new JLabel();
 +
 +    JLabel epsLabel = new JLabel(
 +            MessageManager.formatMessage("label.rendering_style", "EPS"));
      epsLabel.setFont(LABEL_FONT);
      epsLabel.setHorizontalAlignment(SwingConstants.RIGHT);
 -    epsLabel.setText(MessageManager.getString("label.eps_rendering_style"));
 -    epsLabel.setBounds(new Rectangle(9, 31, 140, 24));
 +    epsLabel.setBounds(new Rectangle(9, 31, 160, 24));
      epsRendering.setFont(LABEL_FONT);
 -    epsRendering.setBounds(new Rectangle(154, 34, 187, 21));
 +    epsRendering.setBounds(new Rectangle(174, 34, 187, 21));
 +    JLabel htmlLabel = new JLabel(
 +            MessageManager.formatMessage("label.rendering_style", "HTML"));
 +    htmlLabel.setFont(LABEL_FONT);
 +    htmlLabel.setHorizontalAlignment(SwingConstants.RIGHT);
 +    htmlLabel.setBounds(new Rectangle(9, 55, 160, 24));
 +    htmlRendering.setFont(LABEL_FONT);
 +    htmlRendering.setBounds(new Rectangle(174, 58, 187, 21));
 +    JLabel svgLabel = new JLabel(
 +            MessageManager.formatMessage("label.rendering_style", "SVG"));
 +    svgLabel.setFont(LABEL_FONT);
 +    svgLabel.setHorizontalAlignment(SwingConstants.RIGHT);
 +    svgLabel.setBounds(new Rectangle(9, 79, 160, 24));
 +    svgRendering.setFont(LABEL_FONT);
 +    svgRendering.setBounds(new Rectangle(174, 82, 187, 21));
 +
      JLabel jLabel1 = new JLabel();
      jLabel1.setFont(LABEL_FONT);
      jLabel1.setHorizontalAlignment(SwingConstants.CENTER);
      jLabel1.setText(MessageManager.getString("label.append_start_end"));
      jLabel1.setFont(LABEL_FONT);
 +
      fastajv.setFont(LABEL_FONT);
      fastajv.setHorizontalAlignment(SwingConstants.LEFT);
      clustaljv.setText(MessageManager.getString("label.clustal") + "     ");
      TitledBorder titledBorder2 = new TitledBorder(
              MessageManager.getString("label.file_output"));
      jPanel11.setBorder(titledBorder2);
 -    jPanel11.setBounds(new Rectangle(30, 72, 196, 182));
 +    jPanel11.setBounds(new Rectangle(30, 120, 196, 182));
      GridLayout gridLayout3 = new GridLayout();
      jPanel11.setLayout(gridLayout3);
      gridLayout3.setRows(8);
              MessageManager.getString("label.automatically_set_id_width"));
      autoIdWidth.setToolTipText(JvSwingUtils.wrapTooltip(true, MessageManager
              .getString("label.adjusts_width_generated_eps_png")));
 -    autoIdWidth.setBounds(new Rectangle(228, 96, 188, 23));
 +    autoIdWidth.setBounds(new Rectangle(228, 144, 320, 23));
      autoIdWidth.addActionListener(new ActionListener()
      {
  
      userIdWidthlabel.setToolTipText(
              JvSwingUtils.wrapTooltip(true, MessageManager.getString(
                      "label.manually_specify_width_left_column")));
 -    userIdWidthlabel.setBounds(new Rectangle(236, 120, 168, 23));
 +    userIdWidthlabel.setBounds(new Rectangle(236, 168, 320, 23));
      userIdWidth.setFont(JvSwingUtils.getTextAreaFont());
      userIdWidth.setText("");
 -    userIdWidth.setBounds(new Rectangle(232, 144, 84, 23));
 +    userIdWidth.setBounds(new Rectangle(232, 192, 84, 23));
      userIdWidth.addActionListener(new ActionListener()
      {
  
      modellerOutput.setFont(LABEL_FONT);
      modellerOutput
              .setText(MessageManager.getString("label.use_modeller_output"));
 -    modellerOutput.setBounds(new Rectangle(228, 226, 168, 23));
 +    modellerOutput.setBounds(new Rectangle(228, 274, 320, 23));
      embbedBioJSON.setFont(LABEL_FONT);
      embbedBioJSON.setText(MessageManager.getString("label.embbed_biojson"));
 -    embbedBioJSON.setBounds(new Rectangle(228, 200, 250, 23));
 +    embbedBioJSON.setBounds(new Rectangle(228, 248, 250, 23));
  
      jPanel11.add(jLabel1);
      jPanel11.add(blcjv);
      outputTab.add(userIdWidth);
      outputTab.add(userIdWidthlabel);
      outputTab.add(modellerOutput);
 -    outputTab.add(embbedBioJSON);
 -    outputTab.add(epsLabel);
 -    outputTab.add(epsRendering);
 +    if (!Platform.isJS())
 +    {
 +      /*
 +       * JalviewJS doesn't support Lineart option or SVG output
 +       */
 +      outputTab.add(embbedBioJSON);
 +      outputTab.add(epsLabel);
 +      outputTab.add(epsRendering);
 +      outputTab.add(htmlLabel);
 +      outputTab.add(htmlRendering);
 +      outputTab.add(svgLabel);
 +      outputTab.add(svgRendering);
 +    }
      outputTab.add(jPanel11);
      return outputTab;
    }
                      GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
                      new Insets(0, 2, 5, 5), 70, 1));
  
+     versioncheck.setVisible(false);
      // Add padding so the panel doesn't look ridiculous
      JPanel spacePanel = new JPanel();
      connectTab.add(spacePanel,
      docFieldPref.setBounds(new Rectangle(10, ypos, 450, 120));
      structureTab.add(docFieldPref);
  
 +    /*
 +     * hide Chimera options in JalviewJS
 +     */
 +    if (Platform.isJS()) 
 +    {
 +      pathLabel.setVisible(false);
 +      chimeraPath.setVisible(false);
 +      viewerLabel.setVisible(false);
 +      structViewer.setVisible(false);
 +    }
 +    
      return structureTab;
    }
  
      visualTab.add(fontNameCB);
      visualTab.add(fontSizeCB);
      visualTab.add(fontStyleCB);
 +    
 +    if (Platform.isJS())
 +    {
 +      startupCheckbox.setVisible(false);
 +      startupFileTextfield.setVisible(false);
 +    }
 +    
      return visualTab;
    }
  
  
    protected void loadLastSavedBackupsOptions()
    {
+     BackupFilesPresetEntry savedPreset = BackupFilesPresetEntry
+             .getSavedBackupEntry();
      enableBackupFiles
 -            .setSelected(Cache.getDefault(BackupFiles.ENABLED, true));
 +            .setSelected(Cache.getDefault(BackupFiles.ENABLED, !Platform.isJS()));
+     BackupFilesPresetEntry backupfilesCustomEntry = BackupFilesPresetEntry
+             .createBackupFilesPresetEntry(Cache
+                     .getDefault(BackupFilesPresetEntry.CUSTOMCONFIG, null));
+     if (backupfilesCustomEntry == null)
+     {
+       backupfilesCustomEntry = BackupFilesPresetEntry.backupfilesPresetEntriesValues
+               .get(BackupFilesPresetEntry.BACKUPFILESSCHEMEDEFAULT);
+     }
+     BackupFilesPresetEntry.backupfilesPresetEntriesValues.put(
+             BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM,
+             backupfilesCustomEntry);
      setComboIntStringKey(backupfilesPresetsCombo,
-             Cache.getDefault(BackupFiles.NS + "_PRESET", 1));
-     suffixTemplate.setText(Cache.getDefault(BackupFiles.SUFFIX,
-             ".bak" + BackupFiles.NUM_PLACEHOLDER));
-     suffixDigitsSpinner
-             .setValue(Cache.getDefault(BackupFiles.SUFFIX_DIGITS, 3));
-     suffixReverse.setSelected(
-             Cache.getDefault(BackupFiles.REVERSE_ORDER, false));
-     backupfilesKeepAll
-             .setSelected(Cache.getDefault(BackupFiles.NO_MAX, false));
-     backupfilesRollMaxSpinner
-             .setValue(Cache.getDefault(BackupFiles.ROLL_MAX, 3));
-     backupfilesConfirmDelete.setSelected(
-             Cache.getDefault(BackupFiles.CONFIRM_DELETE_OLD, true));
+             Cache.getDefault(BackupFiles.NS + "_PRESET",
+                     BackupFilesPresetEntry.BACKUPFILESSCHEMEDEFAULT));
+     backupsSetOptions(savedPreset);
  
      backupsOptionsSetEnabled();
      updateBackupFilesExampleLabel();
  
    private boolean warnAboutSuffixReverseChange()
    {
-     boolean savedSuffixReverse = Cache.getDefault(BackupFiles.REVERSE_ORDER,
-             false);
-     int savedSuffixDigits = Cache.getDefault(BackupFiles.SUFFIX_DIGITS, 3);
-     String savedSuffixTemplate = Cache.getDefault(BackupFiles.SUFFIX,
-             ".bak" + BackupFiles.NUM_PLACEHOLDER);
+     BackupFilesPresetEntry bfpe = BackupFilesPresetEntry
+             .getSavedBackupEntry();
+     boolean savedSuffixReverse = bfpe.reverse;
+     int savedSuffixDigits = bfpe.digits;
+     String savedSuffixTemplate = bfpe.suffix;
  
      boolean nowSuffixReverse = suffixReverse.isSelected();
      int nowSuffixDigits = getSpinnerInt(suffixDigitsSpinner, 3);
      gbc.gridy++; // row 1
      backupsTab.add(presetsPanel, gbc);
  
-     // gbc.anchor = GridBagConstraints.NORTHWEST;
      // now using whole row
      gbc.gridwidth = 2;
      gbc.gridheight = 1;
      return backupsTab;
    }
  
-   protected static final int BACKUPFILESSCHEMECUSTOMISE = 0;
-   private static final IntKeyStringValueEntry[] backupfilesPresetEntries = {
-       new IntKeyStringValueEntry(1,
-               MessageManager.getString("label.default")),
-       new IntKeyStringValueEntry(2,
-               MessageManager.getString("label.single_file")),
-       new IntKeyStringValueEntry(3,
-               MessageManager.getString("label.keep_all_versions")),
-       new IntKeyStringValueEntry(4,
-               MessageManager.getString("label.rolled_backups")),
-       // ...
-       // IMPORTANT, keep "Custom" entry with key 0 (even though it appears last)
-       new IntKeyStringValueEntry(BACKUPFILESSCHEMECUSTOMISE,
-               MessageManager.getString("label.customise")) };
-   private static final Map<Integer, BackupFilesPresetEntry> backupfilesPresetEntriesValues = new HashMap<Integer, BackupFilesPresetEntry>()
-   {
-     /**
-      * 
-      */
-     private static final long serialVersionUID = 125L;
-     {
-       put(1, new BackupFilesPresetEntry(
-               ".bak" + BackupFiles.NUM_PLACEHOLDER, 3, false, false, 3,
-               false));
-       put(2, new BackupFilesPresetEntry("~", 1, false, false, 1, false));
-       put(3, new BackupFilesPresetEntry(".v" + BackupFiles.NUM_PLACEHOLDER,
-               3, false, true, 10, true));
-       put(4, new BackupFilesPresetEntry(
-               "_bak." + BackupFiles.NUM_PLACEHOLDER, 1, true, false, 9,
-               false));
-     }
-   };
    private JPanel initBackupsTabPresetsPanel()
    {
  
      String title = MessageManager.getString("label.schemes");
-     // TitledBorder tb = new TitledBorder(new EmptyBorder(0, 0, 0, 0), title);
-     // TitledBorder tb = new TitledBorder(title);
-     // tb.setTitleFont(LABEL_FONT);
-     // presetsPanel.setBorder(tb);
  
      presetsPanel.setLayout(new GridBagLayout());
  
      // "Scheme: "
      gbc.gridx = 0;
      gbc.gridy = 0;
-     presetsPanel.add(new JLabel(title + ":"), gbc);
  
-     for (int i = 0; i < backupfilesPresetEntries.length; i++)
+     presetsComboLabel = new JLabel(title + ":");
+     presetsPanel.add(presetsComboLabel, gbc);
+     List<Object> entries = Arrays
+             .asList((Object[]) BackupFilesPresetEntry.backupfilesPresetEntries);
+     List<String> tooltips = Arrays.asList(
+             BackupFilesPresetEntry.backupfilesPresetEntryDescriptions);
+     backupfilesPresetsCombo = JvSwingUtils.buildComboWithTooltips(entries,
+             tooltips);
+     /*
+     for (int i = 0; i < BackupFilesPresetEntry.backupfilesPresetEntries.length; i++)
      {
-       backupfilesPresetsCombo.addItem(backupfilesPresetEntries[i]);
+       backupfilesPresetsCombo
+               .addItem(BackupFilesPresetEntry.backupfilesPresetEntries[i]);
      }
+     */
  
-     // put "Previously saved scheme" item in italics (it's not really
-     // selectable, as such -- it deselects itself when selected) and
-     // "Customise" in bold
-     backupfilesPresetsCombo
-             .setRenderer(new BackupFilesPresetsComboBoxRenderer());
      backupfilesPresetsCombo.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         backupsTabUpdatePresets();
+         int key = getComboIntStringKey(backupfilesPresetsCombo);
+         if (!customiseCheckbox.isSelected())
+         {
+           backupfilesPresetsComboLastSelected = key;
+         }
+         if (key == BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM)
+         {
+           if (customiseCheckbox.isSelected())
+           {
+             // got here by clicking on customiseCheckbox so don't change the values
+             backupfilesCustomOptionsSetEnabled();
+           }
+           else
+           {
+             backupsTabUpdatePresets();
+             backupfilesCustomOptionsSetEnabled();
+           }
+         }
+         else
+         {
+           customiseCheckbox.setSelected(false);
+           backupsTabUpdatePresets();
+           backupfilesCustomOptionsSetEnabled();
+         }
        }
      });
  
      presetsPanel.add(backupfilesPresetsCombo, gbc);
  
      revertButton.setText(MessageManager.getString("label.cancel_changes"));
+     revertButton.setToolTipText(
+             MessageManager.getString("label.cancel_changes_description"));
      revertButton.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         loadLastSavedBackupsOptions();
+         backupsSetOptions(
+                 BackupFilesPresetEntry.backupfilesPresetEntriesValues.get(
+                         BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM));
+         backupfilesCustomOptionsSetEnabled();
        }
  
      });
      revertButton.setFont(LABEL_FONT);
  
+     customiseCheckbox.setFont(LABEL_FONT);
+     customiseCheckbox.setText(MessageManager.getString("label.customise"));
+     customiseCheckbox.addActionListener(new ActionListener()
+     {
+       @Override
+       public void actionPerformed(ActionEvent e)
+       {
+         int currently = getComboIntStringKey(backupfilesPresetsCombo);
+         if (customiseCheckbox.isSelected())
+         {
+           backupfilesPresetsComboLastSelected = currently;
+           setComboIntStringKey(backupfilesPresetsCombo,
+                   BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM);
+         }
+         else
+         {
+           setComboIntStringKey(backupfilesPresetsCombo,
+                   backupfilesPresetsComboLastSelected);
+         }
+         backupfilesCustomOptionsSetEnabled();
+       }
+     });
+     customiseCheckbox.setToolTipText(
+             MessageManager.getString("label.customise_description"));
+     // customise checkbox
+     gbc.gridx = 0;
+     gbc.gridy++;
+     presetsPanel.add(customiseCheckbox, gbc);
      // "Cancel changes" button (aligned with combo box above)
      gbc.gridx = 1;
-     gbc.gridy++;
      presetsPanel.add(revertButton, gbc);
  
      return presetsPanel;
    private JPanel initBackupsTabFilenameExamplesPanel()
    {
      String title = MessageManager
-             .getString("label.summary_of_backups_scheme");
+             .getString("label.scheme_examples");
      TitledBorder tb = new TitledBorder(title);
      exampleFilesPanel.setBorder(tb);
      exampleFilesPanel.setLayout(new GridBagLayout());
    {
      IntKeyStringValueEntry entry = (IntKeyStringValueEntry) backupfilesPresetsCombo
              .getSelectedItem();
-     int key = entry.getKey();
-     String value = entry.getValue();
+     int key = entry.k;
+     String value = entry.v;
  
-     // BACKUPFILESSCHEMECUSTOMISE (==0) reserved for "Custom"
-     if (key != BACKUPFILESSCHEMECUSTOMISE)
+     if (BackupFilesPresetEntry.backupfilesPresetEntriesValues
+             .containsKey(key))
      {
-       if (backupfilesPresetEntriesValues.containsKey(key))
-       {
-         backupsSetOptions(backupfilesPresetEntriesValues.get(key));
-       }
-       else
-       {
-         System.out.println("Preset '" + value + "' not implemented");
-       }
+       backupsSetOptions(
+               BackupFilesPresetEntry.backupfilesPresetEntriesValues
+                       .get(key));
+     }
+     else
+     {
+       Cache.log.error(
+               "Preset '" + value + "' [key:" + key + "] not implemented");
      }
  
-     backupfilesCustomOptionsSetEnabled();
+     // Custom options will now be enabled when the customiseCheckbox is checked
+     // (performed above)
+     // backupfilesCustomOptionsSetEnabled();
      updateBackupFilesExampleLabel();
    }
  
-   protected int getComboIntStringKey(JComboBox<IntKeyStringValueEntry> c)
+   protected int getComboIntStringKey(
+           JComboBox<Object> backupfilesPresetsCombo2)
    {
-     IntKeyStringValueEntry e = (IntKeyStringValueEntry) c.getSelectedItem();
-     return e != null ? e.getKey() : 0;
+     IntKeyStringValueEntry e;
+     try
+     {
+       e = (IntKeyStringValueEntry) backupfilesPresetsCombo2
+               .getSelectedItem();
+     } catch (Exception ex)
+     {
+       Cache.log.error(
+               "Problem casting Combo entry to IntKeyStringValueEntry.");
+       e = null;
+     }
+     return e != null ? e.k : 0;
    }
  
-   protected void setComboIntStringKey(JComboBox<IntKeyStringValueEntry> c,
+   protected void setComboIntStringKey(
+           JComboBox<Object> backupfilesPresetsCombo2,
            int key)
    {
-     for (int i = 0; i < c.getItemCount(); i++)
+     for (int i = 0; i < backupfilesPresetsCombo2.getItemCount(); i++)
      {
-       IntKeyStringValueEntry e = c.getItemAt(i);
-       if (e.getKey() == key)
+       IntKeyStringValueEntry e;
+       try
+       {
+         e = (IntKeyStringValueEntry) backupfilesPresetsCombo2.getItemAt(i);
+       } catch (Exception ex)
+       {
+         Cache.log.error(
+                 "Problem casting Combo entry to IntKeyStringValueEntry. Skipping item. ");
+         continue;
+       }
+       if (e.k == key)
        {
-         c.setSelectedIndex(i);
+         backupfilesPresetsCombo2.setSelectedIndex(i);
          break;
        }
      }
-     backupsTabUpdatePresets();
+     // backupsTabUpdatePresets();
    }
  
    private JPanel initBackupsTabSuffixPanel()
        {
          updateBackupFilesExampleLabel();
          backupfilesCustomOptionsSetEnabled();
+         backupfilesRevertButtonSetEnabled(true);
        }
  
      });
-     KeyListener kl = new KeyListener()
+     suffixTemplate.addKeyListener(new KeyListener()
      {
        @Override
        public void keyReleased(KeyEvent e)
        {
          updateBackupFilesExampleLabel();
          backupfilesCustomOptionsSetEnabled();
+         backupfilesRevertButtonSetEnabled(true);
        }
  
        @Override
          }
        }
  
-     };
-     suffixTemplate.addKeyListener(kl);
+     });
  
      // digits spinner
      suffixDigitsLabel
              .setText(MessageManager.getString("label.index_digits"));
      suffixDigitsLabel.setHorizontalAlignment(SwingConstants.LEFT);
      suffixDigitsLabel.setFont(LABEL_FONT);
-     int defaultmin = 1;
-     int defaultmax = 6;
      ChangeListener c = new ChangeListener()
      {
        @Override
        public void stateChanged(ChangeEvent e)
        {
+         backupfilesRevertButtonSetEnabled(true);
          updateBackupFilesExampleLabel();
        }
  
      };
-     setIntegerSpinner(suffixDigitsSpinner, defaultmin, defaultmax, 3, c);
+     setIntegerSpinner(suffixDigitsSpinner, BackupFilesPresetEntry.DIGITSMIN,
+             BackupFilesPresetEntry.DIGITSMAX, 3, c);
  
      suffixReverse.setLabels(MessageManager.getString("label.reverse_roll"),
              MessageManager.getString("label.increment_index"));
          }
          if (okay)
          {
+           backupfilesRevertButtonSetEnabled(true);
            updateBackupFilesExampleLabel();
          }
          else
          {
-           boolean savedSuffixReverse = Cache
-                   .getDefault(BackupFiles.REVERSE_ORDER, false);
+           boolean savedSuffixReverse = BackupFilesPresetEntry
+                   .getSavedBackupEntry().reverse;
            suffixReverse.setSelected(savedSuffixReverse);
          }
        }
        @Override
        public void actionPerformed(ActionEvent e)
        {
+         backupfilesRevertButtonSetEnabled(true);
          updateBackupFilesExampleLabel();
        }
      });
        @Override
        public void actionPerformed(ActionEvent e)
        {
+         backupfilesRevertButtonSetEnabled(true);
          keepRollMaxOptionsEnabled();
          updateBackupFilesExampleLabel();
        }
        @Override
        public void stateChanged(ChangeEvent e)
        {
+         backupfilesRevertButtonSetEnabled(true);
          updateBackupFilesExampleLabel();
        }
  
      };
-     setIntegerSpinner(backupfilesRollMaxSpinner, 1, 999, 4, true, c);
+     setIntegerSpinner(backupfilesRollMaxSpinner,
+             BackupFilesPresetEntry.ROLLMAXMIN,
+             BackupFilesPresetEntry.ROLLMAXMAX, 4, true, c);
  
      backupfilesConfirmDelete.setLabels(
              MessageManager.getString("label.always_ask"),
              MessageManager.getString("label.auto_delete"));
+     backupfilesConfirmDelete.addActionListener(new ActionListener()
+     {
+       @Override
+       public void actionPerformed(ActionEvent e)
+       {
+         backupfilesRevertButtonSetEnabled(true);
+       }
+     });
      // update the enabled section
      keepRollMaxOptionsEnabled();
  
      kgbc.gridwidth = GridBagConstraints.REMAINDER;
      kgbc.fill = GridBagConstraints.HORIZONTAL;
      kgbc.weightx = 1.0;
-     /*
-     keepfilesPanel.add(backupfilesConfirmDelete.getTrueButton(), kgbc);
-     
-     // fourth row (indented)
-     kgbc.gridy = 3;
-     keepfilesPanel.add(backupfilesConfirmDelete.getFalseButton(), kgbc);
-     */
  
      JPanel jp = new JPanel();
      jp.setLayout(new FlowLayout());
      int uppersurround = 0;
      StringBuilder exampleSB = new StringBuilder();
      boolean firstLine = true;
+     int lineNumber = 0;
      if (reverse)
      {
  
          if (index == min + lowersurround && index < max - uppersurround - 1)
          {
            exampleSB.append("\n...");
+           lineNumber++;
          }
          else if (index > min + lowersurround && index < max - uppersurround)
          {
            else
            {
              exampleSB.append("\n");
+             lineNumber++;
            }
            exampleSB.append(BackupFilenameParts.getBackupFilename(index,
                    base, suffix, digits));
          if (index == min + lowersurround && index < max - uppersurround - 1)
          {
            exampleSB.append("\n...");
+           lineNumber++;
          }
          else if (index > min + lowersurround && index < max - uppersurround)
          {
            else
            {
              exampleSB.append("\n");
+             lineNumber++;
            }
            exampleSB.append(BackupFilenameParts.getBackupFilename(index,
                    base, suffix, digits));
  
      }
  
+     // add some extra empty lines to pad out the example files box. ugh, please tell
+     // me how to do this better
+     int remainingLines = lowersurround + uppersurround + 1 - lineNumber;
+     if (remainingLines > 0)
+     {
+       for (int i = 0; i < remainingLines; i++)
+       {
+         exampleSB.append("\n ");
+         lineNumber++;
+       }
+     }
      backupfilesExampleLabel.setText(exampleSB.toString());
    }
  
          i = Integer.parseInt((String) s.getValue());
        } catch (Exception e)
        {
-         System.out.println(
+         Cache.log.error(
                  "Exception casting the initial value of s.getValue()");
        }
      }
        i = (Integer) s.getValue();
      } catch (Exception e)
      {
-       System.out.println("Failed casting (Integer) JSpinner s.getValue()");
+       Cache.log.error("Failed casting (Integer) JSpinner s.getValue()");
      }
      return i;
    }
    private void backupfilesKeepAllSetEnabled(boolean tryEnabled)
    {
      boolean enabled = tryEnabled && enableBackupFiles.isSelected()
-             && getComboIntStringKey(backupfilesPresetsCombo) == 0
+             && customiseCheckbox.isSelected()
              && suffixTemplate.getText()
                      .indexOf(BackupFiles.NUM_PLACEHOLDER) > -1;
      keepfilesPanel.setEnabled(enabled);
    private void backupfilesSuffixTemplateSetEnabled(boolean tryEnabled)
    {
      boolean enabled = tryEnabled && enableBackupFiles.isSelected()
-             && getComboIntStringKey(backupfilesPresetsCombo) == 0;
+             && customiseCheckbox.isSelected();
      suffixPanel.setEnabled(enabled);
      suffixTemplateLabel.setEnabled(enabled);
      suffixTemplate.setEnabled(enabled);
      backupfilesSuffixTemplateDigitsSetEnabled();
    }
  
+   private void backupfilesRevertButtonSetEnabled(boolean tryEnabled)
+   {
+     boolean enabled = tryEnabled && enableBackupFiles.isSelected()
+             && customiseCheckbox.isSelected() && backupfilesCustomChanged();
+     revertButton.setEnabled(enabled);
+   }
+   private boolean backupfilesCustomChanged()
+   {
+     BackupFilesPresetEntry custom = BackupFilesPresetEntry.backupfilesPresetEntriesValues
+             .get(BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM);
+     BackupFilesPresetEntry current = getBackupfilesCurrentEntry();
+     return !custom.equals(current);
+   }
+   protected BackupFilesPresetEntry getBackupfilesCurrentEntry()
+   {
+     String suffix = suffixTemplate.getText();
+     int digits = getSpinnerInt(suffixDigitsSpinner, 3);
+     boolean reverse = suffixReverse.isSelected();
+     boolean keepAll = backupfilesKeepAll.isSelected();
+     int rollMax = getSpinnerInt(backupfilesRollMaxSpinner, 3);
+     boolean confirmDelete = backupfilesConfirmDelete.isSelected();
+     BackupFilesPresetEntry bfpe = new BackupFilesPresetEntry(suffix, digits,
+             reverse, keepAll, rollMax, confirmDelete);
+     return bfpe;
+   }
    protected void backupfilesCustomOptionsSetEnabled()
    {
-     int scheme = getComboIntStringKey(backupfilesPresetsCombo);
-     boolean enabled = scheme == 0 && enableBackupFiles.isSelected();
+     boolean enabled = customiseCheckbox.isSelected();
  
+     backupfilesRevertButtonSetEnabled(enabled);
      backupfilesSuffixTemplateSetEnabled(enabled);
      backupfilesKeepAllSetEnabled(enabled);
    }
    {
      boolean enabled = enableBackupFiles.isSelected();
      presetsPanel.setEnabled(enabled);
+     presetsComboLabel.setEnabled(enabled);
      backupfilesPresetsCombo.setEnabled(enabled);
+     customiseCheckbox.setEnabled(enabled);
+     revertButton.setEnabled(enabled);
    }
  
    protected void backupsOptionsSetEnabled()
    }
  }
  
- class IntKeyStringValueEntry
- {
-   int k;
-   String v;
-   public IntKeyStringValueEntry(int k, String v)
-   {
-     this.k = k;
-     this.v = v;
-   }
-   @Override
-   public String toString()
-   {
-     return this.getValue();
-   }
-   public int getKey()
-   {
-     return k;
-   }
-   public String getValue()
-   {
-     return v;
-   }
- }
- class BackupFilesPresetEntry
- {
-   String suffix;
-   int digits;
-   boolean reverse;
-   boolean keepAll;
-   int rollMax;
-   boolean confirmDelete;
-   public BackupFilesPresetEntry(String suffix, int digits, boolean reverse,
-           boolean keepAll, int rollMax, boolean confirmDelete)
-   {
-     this.suffix = suffix;
-     this.digits = digits;
-     this.reverse = reverse;
-     this.keepAll = keepAll;
-     this.rollMax = rollMax;
-     this.confirmDelete = confirmDelete;
-   }
- }
- class BackupFilesPresetsComboBoxRenderer extends DefaultListCellRenderer
- {
-   /**
-    * 
-    */
-   private static final long serialVersionUID = 88L;
-   @Override
-   public Component getListCellRendererComponent(JList list, Object value,
-           int index, boolean isSelected, boolean cellHasFocus)
-   {
-     super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
-     
-     try {
-       IntKeyStringValueEntry e = (IntKeyStringValueEntry) value;
-       if (e.getKey() == GPreferences.BACKUPFILESSCHEMECUSTOMISE)
-       {
-         // "Customise" item
-         this.setFont(this.getFont().deriveFont(Font.BOLD));
-       }
-     } catch (Exception e) {
-       return this;
-     }
-     return this;
-   }
- }
@@@ -39,6 -39,7 +39,7 @@@ import jalview.datamodel.Alignment
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.DBRefEntry;
+ import jalview.datamodel.GeneLocus;
  import jalview.datamodel.GraphLine;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.Point;
@@@ -153,7 -154,6 +154,7 @@@ import java.awt.Color
  import java.awt.Font;
  import java.awt.Rectangle;
  import java.io.BufferedReader;
 +import java.io.ByteArrayInputStream;
  import java.io.DataInputStream;
  import java.io.DataOutputStream;
  import java.io.File;
@@@ -210,15 -210,6 +211,15 @@@ import javax.xml.stream.XMLStreamReader
   */
  public class Jalview2XML
  {
 +
 +  // BH 2018 we add the .jvp binary extension to J2S so that
 +  // it will declare that binary when we do the file save from the browser
 +
 +  static
 +  {
 +    Platform.addJ2SBinaryType(".jvp?");
 +  }
 +
    private static final String VIEWER_PREFIX = "viewer_";
  
    private static final String RNA_PREFIX = "rna_";
        public boolean isResolvable()
        {
          return super.isResolvable() && mp.getTo() != null;
 -      };
 +      }
  
        @Override
        boolean resolve()
        } catch (Exception foo)
        {
        }
 -      ;
        jout.close();
      } catch (Exception ex)
      {
      try
      {
        // create backupfiles object and get new temp filename destination
 -      BackupFiles backupfiles = new BackupFiles(jarFile);
 -      FileOutputStream fos = new FileOutputStream(
 -              backupfiles.getTempFilePath());
 +      boolean doBackup = BackupFiles.getEnabled();
 +      BackupFiles backupfiles = doBackup ? new BackupFiles(jarFile) : null;
 +      FileOutputStream fos = new FileOutputStream(doBackup ? 
 +              backupfiles.getTempFilePath() : jarFile);
  
        JarOutputStream jout = new JarOutputStream(fos);
        List<AlignFrame> frames = new ArrayList<>();
        } catch (Exception foo)
        {
        }
        jout.close();
        boolean success = true;
  
 -      backupfiles.setWriteSuccess(success);
 -      success = backupfiles.rollBackupsAndRenameTempFile();
 +      if (doBackup)
 +      {
 +        backupfiles.setWriteSuccess(success);
 +        success = backupfiles.rollBackupsAndRenameTempFile();
 +      }
  
        return success;
      } catch (Exception ex)
        // using save and then load
        try
        {
 +      fileName = fileName.replace('\\', '/');
          System.out.println("Writing jar entry " + fileName);
          JarEntry entry = new JarEntry(fileName);
          jout.putNextEntry(entry);
    {
      if (jout != null)
      {
 +      jarEntryName = jarEntryName.replace('\\','/');
        System.out.println("Writing jar entry " + jarEntryName);
        jout.putNextEntry(new JarEntry(jarEntryName));
        DataOutputStream dout = new DataOutputStream(jout);
      vamsasSeq.setName(jds.getName());
      vamsasSeq.setSequence(jds.getSequenceAsString());
      vamsasSeq.setDescription(jds.getDescription());
 -    jalview.datamodel.DBRefEntry[] dbrefs = null;
 +    List<DBRefEntry> dbrefs = null;
      if (jds.getDatasetSequence() != null)
      {
        vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
          parentseq = jds;
        }
      }
+     /*
+      * save any dbrefs; special subclass GeneLocus is flagged as 'locus'
+      */
      if (dbrefs != null)
      {
 -      for (int d = 0; d < dbrefs.length; d++)
 +      for (int d = 0, nd = dbrefs.size(); d < nd; d++)
        {
          DBRef dbref = new DBRef();
 -        DBRefEntry dbRefEntry = dbrefs[d];
 -        dbref.setSource(dbRefEntry.getSource());
 -        dbref.setVersion(dbRefEntry.getVersion());
 -        dbref.setAccessionId(dbRefEntry.getAccessionId());
 -        if (dbRefEntry instanceof GeneLocus)
 +        DBRefEntry ref = dbrefs.get(d);
 +        dbref.setSource(ref.getSource());
 +        dbref.setVersion(ref.getVersion());
 +        dbref.setAccessionId(ref.getAccessionId());
++        if (ref instanceof GeneLocus)
+         {
+           dbref.setLocus(true);
+         }
 -        if (dbRefEntry.hasMap())
 +        if (ref.hasMap())
          {
 -          Mapping mp = createVamsasMapping(dbRefEntry.getMap(), parentseq,
 +          Mapping mp = createVamsasMapping(ref.getMap(), parentseq,
                    jds, recurse);
            dbref.setMapping(mp);
          }
-         // vamsasSeq.addDBRef(dbref);
          vamsasSeq.getDBRef().add(dbref);
        }
      }
     * @param file
     *          - HTTP URL or filename
     */
 -  public AlignFrame loadJalviewAlign(final String file)
 +  public AlignFrame loadJalviewAlign(final Object file)
    {
  
      jalview.gui.AlignFrame af = null;
            public void run()
            {
              setLoadingFinishedForNewStructureViewers();
 -          };
 +          }
          });
        } catch (Exception x)
        {
      return af;
    }
  
 -  private jarInputStreamProvider createjarInputStreamProvider(
 -          final String file) throws MalformedURLException
 -  {
 -    URL url = null;
 -    errorMessage = null;
 -    uniqueSetSuffix = null;
 -    seqRefIds = null;
 -    viewportsAdded.clear();
 -    frefedSequence = null;
 -
 -    if (file.startsWith("http://"))
 -    {
 -      url = new URL(file);
 -    }
 -    final URL _url = url;
 -    return new jarInputStreamProvider()
 -    {
 -
 -      @Override
 -      public JarInputStream getJarInputStream() throws IOException
 -      {
 -        if (_url != null)
 -        {
 -          return new JarInputStream(_url.openStream());
 -        }
 -        else
 -        {
 -          return new JarInputStream(new FileInputStream(file));
 -        }
 -      }
 -
 -      @Override
 -      public String getFilename()
 -      {
 -        return file;
 -      }
 -    };
 -  }
 +      @SuppressWarnings("unused")
 +      private jarInputStreamProvider createjarInputStreamProvider(final Object ofile) throws MalformedURLException {
 +
 +              // BH 2018 allow for bytes already attached to File object
 +              try {
 +                      String file = (ofile instanceof File ? ((File) ofile).getCanonicalPath() : ofile.toString());
 +      byte[] bytes = Platform.isJS() ? Platform.getFileBytes((File) ofile)
 +              : null;
 +                      URL url = null;
 +                      errorMessage = null;
 +                      uniqueSetSuffix = null;
 +                      seqRefIds = null;
 +                      viewportsAdded.clear();
 +                      frefedSequence = null;
 +
 +                      if (file.startsWith("http://")) {
 +                              url = new URL(file);
 +                      }
 +                      final URL _url = url;
 +                      return new jarInputStreamProvider() {
 +
 +                              @Override
 +                              public JarInputStream getJarInputStream() throws IOException {
 +                                      if (bytes != null) {
 +//                                            System.out.println("Jalview2XML: opening byte jarInputStream for bytes.length=" + bytes.length);
 +                                              return new JarInputStream(new ByteArrayInputStream(bytes));
 +                                      }
 +                                      if (_url != null) {
 +//                                            System.out.println("Jalview2XML: opening url jarInputStream for " + _url);
 +                                              return new JarInputStream(_url.openStream());
 +                                      } else {
 +//                                            System.out.println("Jalview2XML: opening file jarInputStream for " + file);
 +                                              return new JarInputStream(new FileInputStream(file));
 +                                      }
 +                              }
 +
 +                              @Override
 +                              public String getFilename() {
 +                                      return file;
 +                              }
 +                      };
 +              } catch (IOException e) {
 +                      e.printStackTrace();
 +                      return null;
 +              }
 +      }
  
    /**
     * Recover jalview session from a jalview project archive. Caller may
  
          if (jarentry != null && jarentry.getName().endsWith(".xml"))
          {
 -          InputStreamReader in = new InputStreamReader(jin, UTF_8);
 -          // JalviewModel object = new JalviewModel();
 -
            JAXBContext jc = JAXBContext
                    .newInstance("jalview.xml.binding.jalview");
            XMLStreamReader streamReader = XMLInputFactory.newInstance()
                    .unmarshal(streamReader, JalviewModel.class);
            JalviewModel object = jbe.getValue();
  
 -          /*
 -          Unmarshaller unmar = new Unmarshaller(object);
 -          unmar.setValidation(false);
 -          object = (JalviewModel) unmar.unmarshal(in);
 -          */
            if (true) // !skipViewport(object))
            {
              _af = loadFromObject(object, file, true, jprovider);
            String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
            filedat = oldFiles.get(new File(reformatedOldFilename));
          }
-         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
+         newFileLoc.append(Platform.escapeBackslashes(filedat.getFilePath()));
          pdbfilenames.add(filedat.getFilePath());
          pdbids.add(filedat.getPdbId());
          seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
    {
      AlignFrame af = null;
      af = new AlignFrame(al, safeInt(view.getWidth()),
 -            safeInt(view.getHeight()), uniqueSeqSetId, viewId);
 +            safeInt(view.getHeight()), uniqueSeqSetId, viewId) 
 +//    {
 +//            
 +//            @Override
 +//            protected void processKeyEvent(java.awt.event.KeyEvent e) {
 +//                    System.out.println("Jalview2XML   AF " + e);
 +//                    super.processKeyEvent(e);
 +//                    
 +//            }
 +//            
 +//    }
 +    ;
  
      af.setFileName(file, FileFormat.Jalview);
  
          }
          else
          {
-           featureOrder.put(featureType, new Float(
+           featureOrder.put(featureType, Float.valueOf(
                    fs / jm.getFeatureSettings().getSetting().size()));
          }
          if (safeBoolean(setting.isDisplay()))
        for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
        {
          Group grp = jm.getFeatureSettings().getGroup().get(gs);
-         fgtable.put(grp.getName(), new Boolean(grp.isDisplay()));
+         fgtable.put(grp.getName(), Boolean.valueOf(grp.isDisplay()));
        }
        // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
        // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
      return datasetId;
    }
  
+   /**
+    * Add any saved DBRefEntry's to the sequence. An entry flagged as 'locus' is
+    * constructed as a special subclass GeneLocus.
+    * 
+    * @param datasetSequence
+    * @param sequence
+    */
    private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
    {
      for (int d = 0; d < sequence.getDBRef().size(); d++)
      {
        DBRef dr = sequence.getDBRef().get(d);
-       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
-               dr.getSource(), dr.getVersion(), dr.getAccessionId());
+       DBRefEntry entry;
+       if (dr.isLocus())
+       {
+         entry = new GeneLocus(dr.getSource(), dr.getVersion(),
+                 dr.getAccessionId());
+       }
+       else
+       {
+         entry = new DBRefEntry(dr.getSource(), dr.getVersion(),
+                 dr.getAccessionId());
+       }
        if (dr.getMapping() != null)
        {
          entry.setMap(addMapping(dr.getMapping()));
@@@ -41,6 -41,7 +41,7 @@@ import jalview.io.DataSourceType
  import jalview.io.StructureFile;
  import jalview.util.MappingUtils;
  import jalview.util.MessageManager;
+ import jalview.util.Platform;
  import jalview.ws.sifts.SiftsClient;
  import jalview.ws.sifts.SiftsException;
  import jalview.ws.sifts.SiftsSettings;
@@@ -56,9 -57,9 +57,9 @@@ import java.util.List
  import java.util.Map;
  import java.util.Vector;
  
 -import MCview.Atom;
 -import MCview.PDBChain;
 -import MCview.PDBfile;
 +import mc_view.Atom;
 +import mc_view.PDBChain;
 +import mc_view.PDBfile;
  
  public class StructureSelectionManager
  {
      StringBuilder sb = new StringBuilder(64);
      for (StructureMapping sm : mappings)
      {
-       if (sm.pdbfile.equals(pdbfile) && seqs.contains(sm.sequence))
+       if (Platform.pathEquals(sm.pdbfile, pdbfile)
+               && seqs.contains(sm.sequence))
        {
          sb.append(sm.mappingDetails);
          sb.append(NEWLINE);
          instances.remove(jalviewLite);
          try
          {
-           mnger.finalize();
+           /* bsoares 2019-03-20 finalize deprecated, no apparent external
+            * resources to close
+            */
+           // mnger.finalize();
          } catch (Throwable x)
          {
          }
   */
  package jalview.util;
  
 -import jalview.bin.Jalview;
 -import jalview.gui.EPSOptions;
 -import jalview.gui.IProgressIndicator;
 -import jalview.gui.SVGOptions;
  import jalview.io.JalviewFileChooser;
  
 -import java.awt.Component;
  import java.awt.Graphics;
  import java.awt.Graphics2D;
  import java.awt.RenderingHints;
  import java.awt.image.BufferedImage;
  import java.io.File;
  import java.io.FileOutputStream;
 +import java.io.IOException;
  
  import javax.imageio.ImageIO;
  
@@@ -50,8 -54,14 +50,8 @@@ public class ImageMake
  
    public static final String PNG_DESCRIPTION = "Portable  network graphics";
  
 -  public static final String HTML_EXTENSION = "html";
 -
 -  public static final String HTML_DESCRIPTION = "Hypertext Markup Language";
 -
    EpsGraphics2D pg;
  
 -  SVGGraphics2D g2;
 -
    Graphics graphics;
  
    FileOutputStream out;
  
    TYPE type;
  
 -  private IProgressIndicator pIndicator;
 -
 -  private long pSessionId;
 -
 -  private boolean headless;
 -
    public enum TYPE
    {
      EPS("EPS", MessageManager.getString("label.eps_file"), EPS_EXTENSION,
              EPS_DESCRIPTION),
 -    PNG("PNG", MessageManager.getString("label.png_image"),
 -            PNG_EXTENSION, PNG_DESCRIPTION),
 +    PNG("PNG", MessageManager.getString("label.png_image"), PNG_EXTENSION,
 +            PNG_DESCRIPTION),
      SVG("SVG", "SVG", SVG_EXTENSION, SVG_DESCRIPTION);
  
 -    private String name;
 +    public final String name;
  
 -    private String label;
 +    public final String label;
  
 -    private String extension;
 +    public final String extension;
  
 -    private String description;
 +    public final String description;
  
      TYPE(String name, String label, String ext, String desc)
      {
  
    }
  
 -  public ImageMaker(Component parent, TYPE type, String title, int width,
 -          int height, File file, String fileTitle,
 -          IProgressIndicator pIndicator, long pSessionId, boolean headless)
 +  /**
 +   * Constructor configures the graphics context ready for writing to
 +   * 
 +   * @param imageType
 +   * @param width
 +   * @param height
 +   * @param file
 +   * @param fileTitle
 +   * @param useLineart
 +   * @throws IOException
 +   */
 +  public ImageMaker(TYPE imageType, int width, int height,
 +          File file, String fileTitle, boolean useLineart)
 +          throws IOException
    {
 -    this.pIndicator = pIndicator;
 -    this.type = type;
 -    this.pSessionId = pSessionId;
 -    this.headless = headless;
 -    if (file == null)
 -    {
 -      setProgressMessage(MessageManager.formatMessage(
 -              "status.waiting_for_user_to_select_output_file", type.name));
 -      JalviewFileChooser chooser;
 -      chooser = type.getFileChooser();
 -      chooser.setFileView(new jalview.io.JalviewFileView());
 -      chooser.setDialogTitle(title);
 -      chooser.setToolTipText(MessageManager.getString("action.save"));
 -      int value = chooser.showSaveDialog(parent);
 -
 -      if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
 -      {
 -        jalview.bin.Cache.setProperty("LAST_DIRECTORY",
 -                chooser.getSelectedFile().getParent());
 -        file = chooser.getSelectedFile();
 -      }
 -      else
 -      {
 -        setProgressMessage(MessageManager.formatMessage(
 -                "status.cancelled_image_export_operation", type.name));
 -      }
 -    }
 -
 -    if (file != null)
 -    {
 -      try
 -      {
 -        out = new FileOutputStream(file);
 -        setProgressMessage(null);
 -        setProgressMessage(MessageManager.formatMessage(
 -                "status.exporting_alignment_as_x_file", type.getName()));
 -        if (type == TYPE.SVG)
 -        {
 -          setupSVG(width, height, fileTitle);
 -        }
 -        else if (type == TYPE.EPS)
 -        {
 -          setupEPS(width, height, fileTitle);
 -        }
 -        else if (type == TYPE.PNG)
 -        {
 -          setupPNG(width, height);
 -        }
 -
 -      } catch (Exception ex)
 -      {
 -        System.out.println("Error creating " + type.getName() + " file.");
 -
 -        setProgressMessage(MessageManager
 -                .formatMessage("info.error_creating_file", type.getName()));
 -      }
 +    this.type = imageType;
 +
 +    out = new FileOutputStream(file);
-     if (imageType == TYPE.SVG)
++    switch (imageType)
 +    {
++    case SVG:
 +      setupSVG(width, height, useLineart);
-     }
-     else if (imageType == TYPE.EPS)
-     {
++      break;
++    case EPS:
 +      setupEPS(width, height, fileTitle, useLineart);
-     }
-     else if (imageType == TYPE.PNG)
-     {
++      break;
++    case PNG:
 +      setupPNG(width, height);
++      break;
++    default:
      }
    }
  
      return graphics;
    }
  
 +  /**
 +   * For SVG or PNG, writes the generated graphics data to the file output
 +   * stream. For EPS, flushes the output graphics (which is written to file as
 +   * it is generated).
 +   */
    public void writeImage()
    {
      try
      }
    }
  
 -  void setupEPS(int width, int height, String title)
 -  {
 -    boolean accurateText = true;
 -
 -    String renderStyle = jalview.bin.Cache.getDefault("EPS_RENDERING",
 -            "Prompt each time");
 -
 -    // If we need to prompt, and if the GUI is visible then
 -    // Prompt for EPS rendering style
 -    if (renderStyle.equalsIgnoreCase("Prompt each time")
 -            && !(System.getProperty("java.awt.headless") != null && System
 -                    .getProperty("java.awt.headless").equals("true")))
 -    {
 -      EPSOptions eps = new EPSOptions();
 -      renderStyle = eps.getValue();
 -
 -      if (renderStyle == null || eps.cancelled)
 -      {
 -        setProgressMessage(MessageManager.formatMessage(
 -                "status.cancelled_image_export_operation", "EPS"));
 -        return;
 -      }
 -    }
 -
 -    if (renderStyle.equalsIgnoreCase("text"))
 -    {
 -      accurateText = false;
 -    }
 -
 -    try
 -    {
 -      pg = new EpsGraphics2D(title, out, 0, 0, width, height);
 -      Graphics2D ig2 = pg;
 -      ig2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 -              RenderingHints.VALUE_ANTIALIAS_ON);
 -
 -      pg.setAccurateTextMode(accurateText);
 -
 -      graphics = pg;
 -      setProgressMessage(MessageManager
 -              .formatMessage("status.export_complete", type.getName()));
 -    } catch (Exception ex)
 -    {
 -    }
 -  }
 -
 -  void setupPNG(int width, int height)
 +  /**
 +   * Sets up a graphics object for the PNG image to be written on
 +   * 
 +   * @param width
 +   * @param height
 +   */
 +  protected void setupPNG(int width, int height)
    {
      bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
      graphics = bi.getGraphics();
      Graphics2D ig2 = (Graphics2D) graphics;
      ig2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
              RenderingHints.VALUE_ANTIALIAS_ON);
 -    setProgressMessage(MessageManager
 -            .formatMessage("status.export_complete", type.getName()));
 -
    }
  
 -  void setupSVG(int width, int height, String title)
 +  /**
 +   * A helper method to configure the SVG output graphics, with choice of Text
 +   * or Lineart character rendering
 +   * 
 +   * @param width
 +   * @param height
 +   * @param useLineart
 +   *          true for Lineart character rendering, false for Text
 +   */
 +  protected void setupSVG(int width, int height, boolean useLineart)
    {
 -
 -    g2 = new SVGGraphics2D(width, height);
 -    Graphics2D ig2 = g2;
 -
 -    String renderStyle = jalview.bin.Cache.getDefault("SVG_RENDERING",
 -            "Prompt each time");
 -
 -    // If we need to prompt, and if the GUI is visible then
 -    // Prompt for EPS rendering style
 -    if (renderStyle.equalsIgnoreCase("Prompt each time")
 -            && !(System.getProperty("java.awt.headless") != null && System
 -                    .getProperty("java.awt.headless").equals("true")))
 +    SVGGraphics2D g2 = new SVGGraphics2D(width, height);
 +    if (useLineart)
      {
 -      SVGOptions svgOption = new SVGOptions();
 -      renderStyle = svgOption.getValue();
 -
 -      if (renderStyle == null || svgOption.cancelled)
 -      {
 -        setProgressMessage(MessageManager.formatMessage(
 -                "status.cancelled_image_export_operation", "SVG"));
 -        return;
 -      }
 -    }
 -
 -    if (renderStyle.equalsIgnoreCase("Lineart"))
 -    {
 -      ig2.setRenderingHint(SVGHints.KEY_DRAW_STRING_TYPE,
 +      g2.setRenderingHint(SVGHints.KEY_DRAW_STRING_TYPE,
                SVGHints.VALUE_DRAW_STRING_TYPE_VECTOR);
      }
      graphics = g2;
    }
  
 -  static JalviewFileChooser getPNGChooser()
 -  {
 -    if (Jalview.isHeadlessMode())
 -    {
 -      return null;
 -    }
 -    return new JalviewFileChooser(PNG_EXTENSION, PNG_DESCRIPTION);
 -  }
 -
 -  static JalviewFileChooser getEPSChooser()
 +  /**
 +   * A helper method that sets up the EPS graphics output with user choice of
 +   * Text or Lineart character rendering
 +   * 
 +   * @param width
 +   * @param height
 +   * @param title
 +   * @param useLineart
 +   *          true for Lineart character rendering, false for Text
 +   * @throws IOException
 +   */
 +  protected void setupEPS(int width, int height, String title,
 +          boolean useLineart) throws IOException
    {
 -    if (Jalview.isHeadlessMode())
 -    {
 -      return null;
 -    }
 -    return new JalviewFileChooser(EPS_EXTENSION, EPS_DESCRIPTION);
 -  }
 -
 -  private void setProgressMessage(String message)
 -  {
 -    if (pIndicator != null && !headless)
 -    {
 -      pIndicator.setProgressBar(message, pSessionId);
 -    }
 -  }
 -
 -  static JalviewFileChooser getSVGChooser()
 -  {
 -    if (Jalview.isHeadlessMode())
 -    {
 -      return null;
 -    }
 -    return new JalviewFileChooser(SVG_EXTENSION, SVG_DESCRIPTION);
 +    pg = new EpsGraphics2D(title, out, 0, 0, width, height);
 +    Graphics2D ig2 = pg;
 +    ig2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 +            RenderingHints.VALUE_ANTIALIAS_ON);
 +    pg.setAccurateTextMode(useLineart);
 +    graphics = pg;
    }
  }
   */
  package jalview.util;
  
 +import jalview.javascript.json.JSON;
 +
 +import java.awt.Toolkit;
  import java.awt.event.MouseEvent;
 +import java.io.BufferedReader;
 +import java.io.File;
 +import java.io.FileOutputStream;
 +import java.io.FileReader;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.InputStreamReader;
 +import java.io.Reader;
 +import java.net.URL;
 +import java.util.Properties;
 +
 +import javax.swing.SwingUtilities;
 +
 +import org.json.simple.parser.JSONParser;
 +import org.json.simple.parser.ParseException;
  
  /**
   * System platform information used by Applet and Application
   */
  public class Platform
  {
 -  private static Boolean isAMac = null, isWindows = null;
 +
 +  private static boolean isJS = /** @j2sNative true || */
 +          false;
 +
 +  private static Boolean isNoJSMac = null, isNoJSWin = null, isMac = null,
 +          isWin = null;
  
    private static Boolean isHeadless = null;
  
    /**
 -   * sorry folks - Macs really are different
 +   * added to group mouse events into Windows and nonWindows (mac, unix, linux)
     * 
 -   * @return true if we do things in a special way.
 +   * @return
     */
 -  public static boolean isAMac()
 +  public static boolean isMac()
    {
 -    if (isAMac == null)
 -    {
 -      isAMac = System.getProperty("os.name").indexOf("Mac") > -1;
 -    }
 +    return (isMac == null
 +            ? (isMac = (System.getProperty("os.name").indexOf("Mac") >= 0))
 +            : isMac);
 +  }
 +
 +  /**
 +   * added to group mouse events into Windows and nonWindows (mac, unix, linux)
 +   * 
 +   * @return
 +   */
 +  public static boolean isWin()
 +  {
 +    return (isWin == null
 +            ? (isWin = (System.getProperty("os.name").indexOf("Win") >= 0))
 +            : isWin);
 +  }
  
 -    return isAMac.booleanValue();
 +  /**
 +   * 
 +   * @return true if HTML5 JavaScript
 +   */
 +  public static boolean isJS()
 +  {
 +    return isJS;
 +  }
  
 +  /**
 +   * sorry folks - Macs really are different
 +   * 
 +   * BH: disabled for SwingJS -- will need to check key-press issues
 +   * 
 +   * @return true if we do things in a special way.
 +   */
 +  public static boolean isAMacAndNotJS()
 +  {
 +    return (isNoJSMac == null ? (isNoJSMac = !isJS && isMac()) : isNoJSMac);
    }
  
    /**
     * 
     * @return true if we have to cope with another platform variation
     */
 -  public static boolean isWindows()
 +  public static boolean isWindowsAndNotJS()
    {
 -    if (isWindows == null)
 -    {
 -      isWindows = System.getProperty("os.name").indexOf("Win") > -1;
 -    }
 -    return isWindows.booleanValue();
 +    return (isNoJSWin == null ? (isNoJSWin = !isJS && isWin()) : isNoJSWin);
    }
  
    /**
    }
  
    /**
-    * escape a string according to the local platform's escape character
+    * Answers the input with every backslash replaced with a double backslash (an
+    * 'escaped' single backslash)
     * 
-    * @param file
-    * @return escaped file
+    * @param s
+    * @return
     */
-   public static String escapeString(String file)
+   public static String escapeBackslashes(String s)
    {
-     StringBuffer f = new StringBuffer();
-     int p = 0, lastp = 0;
-     while ((p = file.indexOf('\\', lastp)) > -1)
-     {
-       f.append(file.subSequence(lastp, p));
-       f.append("\\\\");
-       lastp = p + 1;
-     }
-     f.append(file.substring(lastp));
-     return f.toString();
+     return s == null ? null : s.replace("\\", "\\\\");
    }
  
    /**
     * Answers true if the mouse event has Meta-down (Command key on Mac) or
     * Ctrl-down (on other o/s). Note this answers _false_ if the Ctrl key is
 -   * pressed instead of the Meta/Cmd key on Mac. To test for Ctrl-click on Mac,
 -   * you can use e.isPopupTrigger().
 +   * pressed instead of the Meta/Cmd key on Mac. To test for Ctrl-pressed on
 +   * Mac, you can use e.isPopupTrigger().
     * 
     * @param e
     * @return
     */
    public static boolean isControlDown(MouseEvent e)
    {
 -    boolean aMac = isAMac();
 -    return isControlDown(e, aMac);
 +    return isControlDown(e, isMac());
    }
  
    /**
     */
    protected static boolean isControlDown(MouseEvent e, boolean aMac)
    {
 -    if (aMac)
 +    if (!aMac)
      {
 -      /*
 -       * answer false for right mouse button
 -       */
 -      if (e.isPopupTrigger())
 +      return e.isControlDown();
++
++      // Jalview 2.11 code below: above is as amended for JalviewJS
++      // /*
++      // * answer false for right mouse button
++      // */
++      // if (e.isPopupTrigger())
++      // {
++      // return false;
++      // }
++      // return
++      // (jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx() //
++      // .getMenuShortcutKeyMaskEx()
++      // & jalview.util.ShortcutKeyMaskExWrapper
++      // .getModifiersEx(e)) != 0; // getModifiers()) != 0;
 +    }
 +    // answer false for right mouse button
 +    // shortcut key will be META for a Mac
 +    return !e.isPopupTrigger()
 +            && (Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
 +                    & e.getModifiers()) != 0;
 +    // could we use e.isMetaDown() here?
 +  }
 +
 +  // BH: I don't know about that previous method. Here is what SwingJS uses.
 +  // Notice the distinction in mouse events. (BUTTON3_MASK == META)
 +  //
 +  // private static boolean isPopupTrigger(int id, int mods, boolean isWin) {
 +  // boolean rt = ((mods & InputEvent.BUTTON3_MASK) != 0);
 +  // if (isWin) {
 +  // if (id != MouseEvent.MOUSE_RELEASED)
 +  // return false;
 +  ////
 +  //// // Oddly, Windows returns InputEvent.META_DOWN_MASK on release, though
 +  //// // BUTTON3_DOWN_MASK for pressed. So here we just accept both.
 +  ////
 +  //// actually, we can use XXX_MASK, not XXX_DOWN_MASK and avoid this issue,
 +  // because
 +  //// J2S adds the appropriate extended (0x3FC0) and simple (0x3F) modifiers.
 +  ////
 +  // return rt;
 +  // } else {
 +  // // mac, linux, unix
 +  // if (id != MouseEvent.MOUSE_PRESSED)
 +  // return false;
 +  // boolean lt = ((mods & InputEvent.BUTTON1_MASK) != 0);
 +  // boolean ctrl = ((mods & InputEvent.CTRL_MASK) != 0);
 +  // return rt || (ctrl && lt);
 +  // }
 +  // }
 +  //
 +
 +  /**
 +   * Windows (not Mac, Linux, or Unix) and right button to test for the
 +   * right-mouse pressed event in Windows that would have opened a menu or a
 +   * Mac.
 +   * 
 +   * @param e
 +   * @return
 +   */
 +  public static boolean isWinRightButton(MouseEvent e)
 +  {
 +    // was !isAMac(), but that is true also for Linux and Unix and JS,
 +
 +    return isWin() && SwingUtilities.isRightMouseButton(e);
 +  }
 +
 +  /**
 +   * Windows (not Mac, Linux, or Unix) and middle button -- for mouse wheeling
 +   * without pressing the button.
 +   * 
 +   * @param e
 +   * @return
 +   */
 +  public static boolean isWinMiddleButton(MouseEvent e)
 +  {
 +    // was !isAMac(), but that is true also for Linux and Unix and JS
 +    return isWin() && SwingUtilities.isMiddleMouseButton(e);
 +  }
 +
 +  public static boolean allowMnemonics()
 +  {
 +    return !isMac();
 +  }
 +
 +  public final static int TIME_RESET = 0;
 +
 +  public final static int TIME_MARK = 1;
 +
 +  public static final int TIME_SET = 2;
 +
 +  public static final int TIME_GET = 3;
 +
 +  public static long time, mark, set, duration;
 +
 +  public static void timeCheck(String msg, int mode)
 +  {
 +    long t = System.currentTimeMillis();
 +    switch (mode)
 +    {
 +    case TIME_RESET:
 +      time = mark = t;
 +      if (msg != null)
 +      {
 +        System.err.println("Platform: timer reset\t\t\t" + msg);
 +      }
 +      break;
 +    case TIME_MARK:
 +      if (set > 0)
 +      {
 +        duration += (t - set);
 +      }
 +      else
        {
 -        return false;
 +        if (time == 0)
 +        {
 +          time = mark = t;
 +        }
 +        if (msg != null)
 +        {
 +          System.err.println("Platform: timer mark\t" + ((t - time) / 1000f)
 +                  + "\t" + ((t - mark) / 1000f) + "\t" + msg);
 +        }
 +        mark = t;
        }
 -      return (jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx() // .getMenuShortcutKeyMaskEx()
 -              & jalview.util.ShortcutKeyMaskExWrapper
 -                      .getModifiersEx(e)) != 0; // getModifiers()) != 0;
 +      break;
 +    case TIME_SET:
 +      set = t;
 +      break;
 +    case TIME_GET:
 +      if (msg != null)
 +      {
 +        System.err.println("Platform: timer dur\t" + ((t - time) / 1000f)
 +                + "\t" + ((duration) / 1000f) + "\t" + msg);
 +      }
 +      set = 0;
 +      break;
 +    }
 +  }
 +
 +  public static void cacheFileData(String path, Object data)
 +  {
 +    if (!isJS() || data == null)
 +    {
 +      return;
 +    }
 +    /**
 +     * @j2sNative
 +     * 
 +     *            swingjs.JSUtil.cacheFileData$S$O(path, data);
 +     * 
 +     */
 +  }
 +
 +  public static void cacheFileData(File file)
 +  {
 +    byte[] data;
 +    if (!isJS() || (data = Platform.getFileBytes(file)) == null)
 +    {
 +      return;
 +    }
 +    cacheFileData(file.toString(), data);
 +  }
 +
 +  public static byte[] getFileBytes(File f)
 +  {
 +    return /** @j2sNative f && f._bytes || */
 +    null;
 +  }
 +
 +  public static byte[] getFileAsBytes(String fileStr)
 +  {
 +    byte[] bytes = null;
 +    // BH 2018 hack for no support for access-origin
 +    /**
 +     * @j2sNative bytes = swingjs.JSUtil.getFileAsBytes$O(fileStr)
 +     */
 +    cacheFileData(fileStr, bytes);
 +    return bytes;
 +  }
 +
 +  @SuppressWarnings("unused")
 +  public static String getFileAsString(String url)
 +  {
 +    String ret = null;
 +    /**
 +     * @j2sNative
 +     * 
 +     *            ret = swingjs.JSUtil.getFileAsString$S(url);
 +     * 
 +     * 
 +     */
 +    cacheFileData(url, ret);
 +    return ret;
 +  }
 +
 +  public static boolean setFileBytes(File f, String urlstring)
 +  {
 +    if (!isJS())
 +    {
 +      return false;
      }
 -    return e.isControlDown();
 +    @SuppressWarnings("unused")
 +    byte[] bytes = getFileAsBytes(urlstring);
 +    /**
 +     * @j2sNative f._bytes = bytes;
 +     */
 +    return true;
 +  }
 +
 +  public static void addJ2SBinaryType(String ext)
 +  {
 +    /**
 +     * @j2sNative
 +     * 
 +     *            J2S._binaryTypes.push("." + ext + "?");
 +     * 
 +     */
 +  }
 +
 +  /**
 +   * Encode the URI using JavaScript encodeURIComponent
 +   * 
 +   * @param value
 +   * @return encoded value
 +   */
 +  public static String encodeURI(String value)
 +  {
 +    /**
 +     * @j2sNative value = encodeURIComponent(value);
 +     */
 +    return value;
 +  }
 +
 +  /**
 +   * Open the URL using a simple window call if this is JavaScript
 +   * 
 +   * @param url
 +   * @return true if window has been opened
 +   */
 +  public static boolean openURL(String url)
 +  {
 +    if (!isJS())
 +    {
 +      return false;
 +    }
 +    /**
 +     * @j2sNative
 +     * 
 +     * 
 +     *            window.open(url);
 +     */
 +    return true;
 +  }
 +
 +  public static String getUniqueAppletID()
 +  {
 +    @SuppressWarnings("unused")
 +    ThreadGroup g = Thread.currentThread().getThreadGroup();
 +    /**
 +     * @j2sNative return g.html5Applet._uniqueId;
 +     *
 +     */
 +    return null;
 +
 +  }
 +
 +  /**
 +   * Read the Info block for this applet.
 +   * 
 +   * @param prefix
 +   *          "jalview_"
 +   * @param p
 +   * @return unique id for this applet
 +   */
 +  public static void readInfoProperties(String prefix, Properties p)
 +  {
 +    if (!isJS())
 +    {
 +      return;
 +    }
 +    @SuppressWarnings("unused")
 +    ThreadGroup g = Thread.currentThread().getThreadGroup();
 +    String id = getUniqueAppletID();
 +    String key = "", value = "";
 +    /**
 +     * @j2sNative var info = g.html5Applet.__Info || {}; for (var key in info) {
 +     *            if (key.indexOf(prefix) == 0) { value = "" + info[key];
 +     */
 +
 +    System.out.println(
 +            "Platform id=" + id + " reading Info." + key + " = " + value);
 +    p.put(id + "_" + key, value);
 +
 +    /**
 +     * @j2sNative
 +     * 
 +     * 
 +     *            } }
 +     */
 +  }
 +
 +  public static void setAjaxJSON(URL url)
 +  {
 +    if (isJS())
 +    {
 +      JSON.setAjax(url);
 +    }
 +  }
 +
 +  public static Object parseJSON(InputStream response)
 +          throws IOException, ParseException
 +  {
 +    if (isJS())
 +    {
 +      return JSON.parse(response);
 +    }
 +
 +    BufferedReader br = null;
 +    try
 +    {
 +      br = new BufferedReader(new InputStreamReader(response, "UTF-8"));
 +      return new JSONParser().parse(br);
 +    } finally
 +    {
 +      if (br != null)
 +      {
 +        try
 +        {
 +          br.close();
 +        } catch (IOException e)
 +        {
 +          // ignore
 +        }
 +      }
 +    }
 +  }
 +
 +  public static Object parseJSON(String json) throws ParseException
 +  {
 +    return (isJS() ? JSON.parse(json)
 +            : new JSONParser().parse(json));
 +  }
 +
 +  public static Object parseJSON(Reader r)
 +          throws IOException, ParseException
 +  {
 +    if (r == null)
 +    {
 +      return null;
 +    }
 +
 +    if (!isJS())
 +    {
 +      return new JSONParser().parse(r);
 +    }
 +    // Using a file reader is not currently supported in SwingJS JavaScript
 +
 +    if (r instanceof FileReader)
 +    {
 +      throw new IOException(
 +              "StringJS does not support FileReader parsing for JSON -- but it could...");
 +    }
 +    return JSON.parse(r);
 +
 +  }
 +
 +  /**
 +   * Dump the input stream to an output file.
 +   * 
 +   * @param is
 +   * @param outFile
 +   * @throws IOException
 +   *           if the file cannot be created or there is a problem reading the
 +   *           input stream.
 +   */
 +  public static void streamToFile(InputStream is, File outFile)
 +          throws IOException
 +  {
 +    FileOutputStream fio = new FileOutputStream(outFile);
 +    try
 +    {
 +      if (isJS()
 +              && /**
 +                  * @j2sNative outFile.setBytes$O && outFile.setBytes$O(is) &&
 +                  */
 +              true)
 +      {
 +        return;
 +      }
 +      byte[] bb = new byte[32 * 1024];
 +      int l;
 +      while ((l = is.read(bb)) > 0)
 +      {
 +        fio.write(bb, 0, l);
 +      }
 +    } finally
 +    {
 +      fio.close();
 +    }
 +  }
 +
 +  /**
 +   * Add a known domain that implements access-control-allow-origin:*
 +   * 
 +   * These should be reviewed periodically.
 +   * 
 +   * @param domain
 +   *          for a service that is not allowing ajax
 +   * 
 +   * @author hansonr@stolaf.edu
 +   * 
 +   */
 +  public static void addJ2SDirectDatabaseCall(String domain)
 +  {
 +
 +    if (isJS())
 +    {
 +      System.out.println(
 +            "Platform adding known access-control-allow-origin * for domain "
 +                    + domain);
 +      /**
 +       * @j2sNative
 +       * 
 +       *            J2S.addDirectDatabaseCall(domain);
 +       */
 +    }
 +
 +  }
 +
 +  public static void getURLCommandArguments()
 +  {
 +
 +    /**
 +     * Retrieve the first query field as command arguments to Jalview. Include
 +     * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's __Info.args
 +     * element to this value.
 +     * 
 +     * @j2sNative var a =
 +     *            decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
 +     *            + "?").split("?")[1].split("#")[0]); a &&
 +     *            (J2S.thisApplet.__Info.args = a.split(" "));
 +     */
 +
    }
  
+   /**
+    * A (case sensitive) file path comparator that ignores the difference between /
+    * and \
+    * 
+    * @param path1
+    * @param path2
+    * @return
+    */
+   public static boolean pathEquals(String path1, String path2)
+   {
+     if (path1 == null)
+     {
+       return path2 == null;
+     }
+     if (path2 == null)
+     {
+       return false;
+     }
+     String p1 = path1.replace('\\', '/');
+     String p2 = path2.replace('\\', '/');
+     return p1.equals(p2);
+   }
  }
@@@ -37,7 -37,6 +37,7 @@@ import java.beans.PropertyChangeListene
  import java.beans.PropertyChangeSupport;
  import java.util.ArrayList;
  import java.util.Arrays;
 +import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Hashtable;
@@@ -613,7 -612,7 +613,7 @@@ public abstract class FeatureRendererMo
     * @param type
     * @return
     */
 -  protected boolean showFeatureOfType(String type)
 +  public boolean showFeatureOfType(String type)
    {
      return type == null ? false : (av.getFeaturesDisplayed() == null ? true
              : av.getFeaturesDisplayed().isVisible(type));
      {
        featureOrder = new Hashtable<>();
      }
-     featureOrder.put(type, new Float(position));
+     featureOrder.put(type, Float.valueOf(position));
      return position;
    }
  
      }
      if (newGroupsVisible)
      {
-       featureGroups.put(group, new Boolean(true));
+       featureGroups.put(group, Boolean.valueOf(true));
        return true;
      }
      return false;
    @Override
    public void setGroupVisibility(String group, boolean visible)
    {
-     featureGroups.put(group, new Boolean(visible));
+     featureGroups.put(group, Boolean.valueOf(visible));
    }
  
    @Override
        for (String gst : toset)
        {
          Boolean st = featureGroups.get(gst);
-         featureGroups.put(gst, new Boolean(visible));
+         featureGroups.put(gst, Boolean.valueOf(visible));
          if (st != null)
          {
            rdrw = rdrw || (visible != st.booleanValue());
      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 boolean isVisible(SequenceFeature feature)
    {
      }
      return true;
    }
 -
  }
@@@ -21,6 -21,7 +21,7 @@@
  package jalview.ws;
  
  import jalview.analysis.AlignSeq;
+ import jalview.api.FeatureSettingsModelI;
  import jalview.bin.Cache;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.DBRefEntry;
@@@ -39,8 -40,10 +40,10 @@@ import jalview.ws.seqfetcher.DbSourcePr
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Enumeration;
+ import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.List;
+ import java.util.Map;
  import java.util.StringTokenizer;
  import java.util.Vector;
  
@@@ -134,7 -137,8 +137,7 @@@ public class DBRefFetcher implements Ru
      }
      this.dataset = ds;
      // TODO Jalview 2.5 lots of this code should be in the gui package!
 -    sfetcher = jalview.gui.SequenceFetcher
 -            .getSequenceFetcherSingleton(progressIndicatorFrame);
 +    sfetcher = jalview.gui.SequenceFetcher.getSequenceFetcherSingleton();
      // set default behaviour for transferring excess sequence data to the
      // dataset
      trimDsSeqs = Cache.getDefault(TRIM_RETRIEVED_SEQUENCES, true);
              Arrays.asList(dataset));
      List<String> warningMessages = new ArrayList<>();
  
+     // clear any old feature display settings recorded from past sessions
+     featureDisplaySettings = null;
      int db = 0;
      while (sdataset.size() > 0 && db < dbSources.length)
      {
            }
            if (retrieved != null)
            {
-             transferReferences(sdataset, dbsource.getDbSource(), retrieved,
+             transferReferences(sdataset, dbsource, retrieved,
                      trimDsSeqs, warningMessages);
            }
          }
                    && (i < 50); seqIndex++, i++)
            {
              SequenceI sequence = dataset[seqIndex];
 -            DBRefEntry[] uprefs = DBRefUtils
 +            List<DBRefEntry> uprefs = DBRefUtils
                      .selectRefs(sequence.getDBRefs(), new String[]
                      { dbsource.getDbSource() }); // jalview.datamodel.DBRefSource.UNIPROT
              // });
              // check for existing dbrefs to use
 -            if (uprefs != null && uprefs.length > 0)
 +            if (uprefs != null && uprefs.size() > 0)
              {
 -              for (int j = 0; j < uprefs.length; j++)
 +              for (int j = 0, n = uprefs.size(); j < n; j++)
                {
 -                addSeqId(sequence, uprefs[j].getAccessionId());
 +              DBRefEntry upref = uprefs.get(j);
 +                addSeqId(sequence, upref.getAccessionId());
                  queries.addElement(
 -                        uprefs[j].getAccessionId().toUpperCase());
 +                        upref.getAccessionId().toUpperCase());
                }
              }
              else
     * @param warningMessages
     *          a list of messages to add to
     */
-   boolean transferReferences(Vector<SequenceI> sdataset, String dbSource,
+   boolean transferReferences(Vector<SequenceI> sdataset,
+           DbSourceProxy dbSourceProxy,
            AlignmentI retrievedAl, boolean trimDatasetSeqs,
            List<String> warningMessages)
    {
        return false;
      }
  
+     String dbSource = dbSourceProxy.getDbName();
      boolean modified = false;
      SequenceI[] retrieved = recoverDbSequences(
              retrievedAl.getSequencesArray());
        // taking into account all accessionIds and names in the file
        Vector<SequenceI> sequenceMatches = new Vector<>();
        // look for corresponding accession ids
 -      DBRefEntry[] entryRefs = DBRefUtils
 +      List<DBRefEntry> entryRefs = DBRefUtils
                .selectRefs(retrievedSeq.getDBRefs(), new String[]
                { dbSource });
        if (entryRefs == null)
                          + dbSource + " on " + retrievedSeq.getName());
          continue;
        }
 -      for (int j = 0; j < entryRefs.length; j++)
 +      for (int j = 0, n = entryRefs.size(); j < n; j++)
        {
 -        String accessionId = entryRefs[j].getAccessionId();
 +      DBRefEntry ref = entryRefs.get(j);
 +        String accessionId = ref.getAccessionId();
          // match up on accessionId
          if (seqRefs.containsKey(accessionId.toUpperCase()))
          {
        // could be useful to extend this so we try to find any 'significant'
        // information in common between two sequence objects.
        /*
 -       * DBRefEntry[] entryRefs =
 +       * List<DBRefEntry> entryRefs =
         * jalview.util.DBRefUtils.selectRefs(entry.getDBRef(), new String[] {
         * dbSource }); for (int j = 0; j < entry.getName().size(); j++) { String
         * name = entry.getName().elementAt(j).toString(); if
         * seqs.elementAt(jj); if (!sequenceMatches.contains(sequence)) {
         * sequenceMatches.addElement(sequence); } } } }
         */
+       if (sequenceMatches.size() > 0)
+       {
+         addFeatureSettings(dbSourceProxy);
+       }
        // sequenceMatches now contains the set of all sequences associated with
        // the returned db record
        final String retrievedSeqString = retrievedSeq.getSequenceAsString();
          // TODO: test for legacy where uniprot or EMBL refs exist but no
          // mappings are made (but content matches retrieved set)
          boolean updateRefFrame = sequence.getDBRefs() == null
 -                || sequence.getDBRefs().length == 0;
 +                || sequence.getDBRefs().size() == 0;
          // TODO:
          // verify sequence against the entry sequence
  
      return modified;
    }
  
+   Map<String, FeatureSettingsModelI> featureDisplaySettings = null;
+   private void addFeatureSettings(DbSourceProxy dbSourceProxy)
+   {
+     FeatureSettingsModelI fsettings = dbSourceProxy
+             .getFeatureColourScheme();
+     if (fsettings != null)
+     {
+       if (featureDisplaySettings == null)
+       {
+         featureDisplaySettings = new HashMap<>();
+       }
+       featureDisplaySettings.put(dbSourceProxy.getDbName(), fsettings);
+     }
+   }
+   /**
+    * 
+    * @return any feature settings associated with sources that have provided sequences
+    */
+   public List<FeatureSettingsModelI>getFeatureSettingsModels()
+   {
+     return featureDisplaySettings == null
+             ? Arrays.asList(new FeatureSettingsModelI[0])
+             : Arrays.asList(featureDisplaySettings.values()
+                     .toArray(new FeatureSettingsModelI[1]));
+   }
    /**
     * Adds the message to the list unless it already contains it
     * 
     */
    private SequenceI[] recoverDbSequences(SequenceI[] sequencesArray)
    {
 -    Vector<SequenceI> nseq = new Vector<>();
 -    for (int i = 0; sequencesArray != null
 -            && i < sequencesArray.length; i++)
 +      int n;
 +      if (sequencesArray == null || (n = sequencesArray.length) == 0)
 +        return sequencesArray;
 +    ArrayList<SequenceI> nseq = new ArrayList<>();
 +    for (int i = 0;i < n; i++)
      {
 -      nseq.addElement(sequencesArray[i]);
 -      DBRefEntry[] dbr = sequencesArray[i].getDBRefs();
 +      nseq.add(sequencesArray[i]);
 +      List<DBRefEntry> dbr = sequencesArray[i].getDBRefs();
        Mapping map = null;
 -      for (int r = 0; (dbr != null) && r < dbr.length; r++)
 +      if (dbr != null)
        {
 -        if ((map = dbr[r].getMap()) != null)
 +        for (int r = 0, rn = dbr.size(); r < rn; r++)
          {
 -          if (map.getTo() != null && !nseq.contains(map.getTo()))
 +          if ((map = dbr.get(r).getMap()) != null)
            {
 -            nseq.addElement(map.getTo());
 -          }
 +            if (map.getTo() != null && !nseq.contains(map.getTo()))
 +            {
 +              nseq.add(map.getTo());
 +            }
 +          }  
          }
        }
      }
 +    // BH 2019.01.25 question here if this is the right logic. Return the original if nothing found?
      if (nseq.size() > 0)
      {
 -      sequencesArray = new SequenceI[nseq.size()];
 -      nseq.toArray(sequencesArray);
 +      return nseq.toArray(new SequenceI[nseq.size()]);
      }
      return sequencesArray;
    }
@@@ -32,8 -32,6 +32,8 @@@ import jalview.ws.seqfetcher.ASequenceF
  import jalview.ws.seqfetcher.DbSourceProxy;
  
  import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.List;
  
  /**
   * This implements the run-time discovery of sequence database clients.
@@@ -63,30 -61,44 +63,31 @@@ public class SequenceFetcher extends AS
    /**
     * return an ordered list of database sources excluding alignment only databases
     */
 -  public String[] getOrderedSupportedSources()
 +  public String[] getNonAlignmentSources()
    {
      String[] srcs = this.getSupportedDb();
 -    ArrayList<String> src = new ArrayList<>();
 +    List<String> src = new ArrayList<>();
  
      for (int i = 0; i < srcs.length; i++)
      {
 -      boolean skip = false;
 +      boolean accept = true;
        for (DbSourceProxy dbs : getSourceProxy(srcs[i]))
        {
          // Skip the alignment databases for the moment - they're not useful for
          // verifying a single sequence against its reference source
          if (dbs.isAlignmentSource())
          {
 -          skip = true;
 +          accept = false;
 +          break;
          }
        }
 -      if (skip)
 -      {
 -        continue;
 -      }
 +      if (accept)
        {
          src.add(srcs[i]);
        }
      }
 -    String[] tosort = src.toArray(new String[0]),
 -            sorted = src.toArray(new String[0]);
 -    for (int j = 0, jSize = sorted.length; j < jSize; j++)
 -    {
 -      tosort[j] = tosort[j].toLowerCase();
 -    }
 -    jalview.util.QuickSort.sort(tosort, sorted);
 -    // construct array with all sources listed
 -    int i = 0;
 -    for (int j = sorted.length - 1; j >= 0; j--, i++)
 -    {
 -      tosort[i] = sorted[j];
 -    }
 -    return tosort;
++
 +    Collections.sort(src, String.CASE_INSENSITIVE_ORDER);
 +    return src.toArray(new String[src.size()]);
    }
  }
index 313ec1c,fe6d962..fe6d962
mode 100644,100755..100644
@@@ -119,6 -119,7 +119,7 @@@ import java.util.Map
   * 
   */
  public class EpsGraphics2D extends java.awt.Graphics2D
+         implements AutoCloseable
  {
  
    public static final String VERSION = "0.8.8";
     * OutputStream is automatically flushed before being closed. If you forget to
     * do this, the file may be incomplete.
     */
+   @Override
    public void close() throws IOException
    {
      flush();
     * Draws a 3D rectangle outline. If it is raised, light appears to come from
     * the top left.
     */
+   @Override
    public void draw3DRect(int x, int y, int width, int height, boolean raised)
    {
      Color originalColor = getColor();
     * Fills a 3D rectangle. If raised, it has bright fill and light appears to
     * come from the top left.
     */
+   @Override
    public void fill3DRect(int x, int y, int width, int height, boolean raised)
    {
      Color originalColor = getColor();
    /**
     * Draws a Shape on the EPS document.
     */
+   @Override
    public void draw(Shape s)
    {
      draw(s, "stroke");
    /**
     * Draws an Image on the EPS document.
     */
+   @Override
    public boolean drawImage(Image img, AffineTransform xform,
            ImageObserver obs)
    {
    /**
     * Draws a BufferedImage on the EPS document.
     */
+   @Override
    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y)
    {
      BufferedImage img1 = op.filter(img, null);
    /**
     * Draws a RenderedImage on the EPS document.
     */
+   @Override
    public void drawRenderedImage(RenderedImage img, AffineTransform xform)
    {
      Hashtable properties = new Hashtable();
    /**
     * Draws a RenderableImage by invoking its createDefaultRendering method.
     */
+   @Override
    public void drawRenderableImage(RenderableImage img, AffineTransform xform)
    {
      drawRenderedImage(img.createDefaultRendering(), xform);
    /**
     * Draws a string at (x,y)
     */
+   @Override
    public void drawString(String str, int x, int y)
    {
      drawString(str, (float) x, (float) y);
    /**
     * Draws a string at (x,y)
     */
+   @Override
    public void drawString(String s, float x, float y)
    {
      if (s != null && s.length() > 0)
     * Draws the characters of an AttributedCharacterIterator, starting from
     * (x,y).
     */
+   @Override
    public void drawString(AttributedCharacterIterator iterator, int x, int y)
    {
      drawString(iterator, (float) x, (float) y);
     * Draws the characters of an AttributedCharacterIterator, starting from
     * (x,y).
     */
+   @Override
    public void drawString(AttributedCharacterIterator iterator, float x,
            float y)
    {
    /**
     * Draws a GlyphVector at (x,y)
     */
+   @Override
    public void drawGlyphVector(GlyphVector g, float x, float y)
    {
      Shape shape = g.getOutline(x, y);
    /**
     * Fills a Shape on the EPS document.
     */
+   @Override
    public void fill(Shape s)
    {
      draw(s, "fill");
     * Checks whether or not the specified Shape intersects the specified
     * Rectangle, which is in device space.
     */
+   @Override
    public boolean hit(Rectangle rect, Shape s, boolean onStroke)
    {
      return s.intersects(rect);
    /**
     * Returns the device configuration associated with this EpsGraphics2D object.
     */
+   @Override
    public GraphicsConfiguration getDeviceConfiguration()
    {
      GraphicsConfiguration gc = null;
     * Sets the Composite to be used by this EpsGraphics2D. EpsGraphics2D does not
     * make use of these.
     */
+   @Override
    public void setComposite(Composite comp)
    {
      _composite = comp;
     * Sets the Paint attribute for the EpsGraphics2D object. Only Paint objects
     * of type Color are respected by EpsGraphics2D.
     */
+   @Override
    public void setPaint(Paint paint)
    {
      _paint = paint;
     * Sets the stroke. Only accepts BasicStroke objects (or subclasses of
     * BasicStroke).
     */
+   @Override
    public void setStroke(Stroke s)
    {
      if (s instanceof BasicStroke)
    /**
     * Sets a rendering hint. These are not used by EpsGraphics2D.
     */
+   @Override
    public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
    {
      // Do nothing.
     * Returns the value of a single preference for the rendering algorithms.
     * Rendering hints are not used by EpsGraphics2D.
     */
+   @Override
    public Object getRenderingHint(RenderingHints.Key hintKey)
    {
      return null;
    /**
     * Sets the rendering hints. These are ignored by EpsGraphics2D.
     */
+   @Override
    public void setRenderingHints(Map hints)
    {
      // Do nothing.
    /**
     * Adds rendering hints. These are ignored by EpsGraphics2D.
     */
+   @Override
    public void addRenderingHints(Map hints)
    {
      // Do nothing.
    /**
     * Returns the preferences for the rendering algorithms.
     */
+   @Override
    public RenderingHints getRenderingHints()
    {
      return new RenderingHints(null);
     * Translates the origin of the EpsGraphics2D context to the point (x,y) in
     * the current coordinate system.
     */
+   @Override
    public void translate(int x, int y)
    {
      translate((double) x, (double) y);
     * Concatenates the current EpsGraphics2D Transformation with a translation
     * transform.
     */
+   @Override
    public void translate(double tx, double ty)
    {
      transform(AffineTransform.getTranslateInstance(tx, ty));
    /**
     * Concatenates the current EpsGraphics2D Transform with a rotation transform.
     */
+   @Override
    public void rotate(double theta)
    {
      rotate(theta, 0, 0);
     * Concatenates the current EpsGraphics2D Transform with a translated rotation
     * transform.
     */
+   @Override
    public void rotate(double theta, double x, double y)
    {
      transform(AffineTransform.getRotateInstance(theta, x, y));
     * Concatenates the current EpsGraphics2D Transform with a scaling
     * transformation.
     */
+   @Override
    public void scale(double sx, double sy)
    {
      transform(AffineTransform.getScaleInstance(sx, sy));
    /**
     * Concatenates the current EpsGraphics2D Transform with a shearing transform.
     */
+   @Override
    public void shear(double shx, double shy)
    {
      transform(AffineTransform.getShearInstance(shx, shy));
     * Composes an AffineTransform object with the Transform in this EpsGraphics2D
     * according to the rule last-specified-first-applied.
     */
+   @Override
    public void transform(AffineTransform Tx)
    {
      _transform.concatenate(Tx);
    /**
     * Sets the AffineTransform to be used by this EpsGraphics2D.
     */
+   @Override
    public void setTransform(AffineTransform Tx)
    {
      if (Tx == null)
    /**
     * Gets the AffineTransform used by this EpsGraphics2D.
     */
+   @Override
    public AffineTransform getTransform()
    {
      return new AffineTransform(_transform);
    /**
     * Returns the current Paint of the EpsGraphics2D object.
     */
+   @Override
    public Paint getPaint()
    {
      return _paint;
    /**
     * returns the current Composite of the EpsGraphics2D object.
     */
+   @Override
    public Composite getComposite()
    {
      return _composite;
    /**
     * Sets the background color to be used by the clearRect method.
     */
+   @Override
    public void setBackground(Color color)
    {
      if (color == null)
    /**
     * Gets the background color that is used by the clearRect method.
     */
+   @Override
    public Color getBackground()
    {
      return _backgroundColor;
     * Returns the Stroke currently used. Guaranteed to be an instance of
     * BasicStroke.
     */
+   @Override
    public Stroke getStroke()
    {
      return _stroke;
     * Intersects the current clip with the interior of the specified Shape and
     * sets the clip to the resulting intersection.
     */
+   @Override
    public void clip(Shape s)
    {
      if (_clip == null)
    /**
     * Returns the FontRenderContext.
     */
+   @Override
    public FontRenderContext getFontRenderContext()
    {
      return _fontRenderContext;
    /**
     * Returns a new Graphics object that is identical to this EpsGraphics2D.
     */
+   @Override
    public Graphics create()
    {
      return new EpsGraphics2D(this);
     * Returns an EpsGraphics2D object based on this Graphics object, but with a
     * new translation and clip area.
     */
+   @Override
    public Graphics create(int x, int y, int width, int height)
    {
      Graphics g = create();
     * Returns the current Color. This will be a default value (black) until it is
     * changed using the setColor method.
     */
+   @Override
    public Color getColor()
    {
      return _color;
    /**
     * Sets the Color to be used when drawing all future shapes, text, etc.
     */
+   @Override
    public void setColor(Color c)
    {
      if (c == null)
     * Sets the paint mode of this EpsGraphics2D object to overwrite the
     * destination EpsDocument with the current color.
     */
+   @Override
    public void setPaintMode()
    {
      // Do nothing - paint mode is the only method supported anyway.
     * <b><i><font color="red">Not implemented</font></i></b> - performs no
     * action.
     */
+   @Override
    public void setXORMode(Color c1)
    {
      methodNotSupported();
    /**
     * Returns the Font currently being used.
     */
+   @Override
    public Font getFont()
    {
      return _font;
    /**
     * Sets the Font to be used in future text.
     */
+   @Override
    public void setFont(Font font)
    {
      if (font == null)
        font = Font.decode(null);
      }
      _font = font;
-     append("/" + _font.getPSName() + " findfont " + ((int) _font.getSize())
+     append("/" + _font.getPSName() + " findfont " + (_font.getSize())
              + " scalefont setfont");
    }
  
    /**
     * Gets the font metrics of the current font.
     */
+   @Override
    public FontMetrics getFontMetrics()
    {
      return getFontMetrics(getFont());
    /**
     * Gets the font metrics for the specified font.
     */
+   @Override
    public FontMetrics getFontMetrics(Font f)
    {
      BufferedImage image = new BufferedImage(1, 1,
    /**
     * Returns the bounding rectangle of the current clipping area.
     */
+   @Override
    public Rectangle getClipBounds()
    {
      if (_clip == null)
    /**
     * Intersects the current clip with the specified rectangle.
     */
+   @Override
    public void clipRect(int x, int y, int width, int height)
    {
      clip(new Rectangle(x, y, width, height));
    /**
     * Sets the current clip to the rectangle specified by the given coordinates.
     */
+   @Override
    public void setClip(int x, int y, int width, int height)
    {
      setClip(new Rectangle(x, y, width, height));
    /**
     * Gets the current clipping area.
     */
+   @Override
    public Shape getClip()
    {
      if (_clip == null)
    /**
     * Sets the current clipping area to an arbitrary clip shape.
     */
+   @Override
    public void setClip(Shape clip)
    {
      if (clip != null)
     * <b><i><font color="red">Not implemented</font></i></b> - performs no
     * action.
     */
+   @Override
    public void copyArea(int x, int y, int width, int height, int dx, int dy)
    {
      methodNotSupported();
    /**
     * Draws a straight line from (x1,y1) to (x2,y2).
     */
+   @Override
    public void drawLine(int x1, int y1, int x2, int y2)
    {
      Shape shape = new Line2D.Float(x1, y1, x2, y2);
    /**
     * Fills a rectangle with top-left corner placed at (x,y).
     */
+   @Override
    public void fillRect(int x, int y, int width, int height)
    {
      Shape shape = new Rectangle(x, y, width, height);
    /**
     * Draws a rectangle with top-left corner placed at (x,y).
     */
+   @Override
    public void drawRect(int x, int y, int width, int height)
    {
      Shape shape = new Rectangle(x, y, width, height);
     * Clears a rectangle with top-left corner placed at (x,y) using the current
     * background color.
     */
+   @Override
    public void clearRect(int x, int y, int width, int height)
    {
      Color originalColor = getColor();
    /**
     * Draws a rounded rectangle.
     */
+   @Override
    public void drawRoundRect(int x, int y, int width, int height,
            int arcWidth, int arcHeight)
    {
    /**
     * Fills a rounded rectangle.
     */
+   @Override
    public void fillRoundRect(int x, int y, int width, int height,
            int arcWidth, int arcHeight)
    {
    /**
     * Draws an oval.
     */
+   @Override
    public void drawOval(int x, int y, int width, int height)
    {
      Shape shape = new Ellipse2D.Float(x, y, width, height);
    /**
     * Fills an oval.
     */
+   @Override
    public void fillOval(int x, int y, int width, int height)
    {
      Shape shape = new Ellipse2D.Float(x, y, width, height);
    /**
     * Draws an arc.
     */
+   @Override
    public void drawArc(int x, int y, int width, int height, int startAngle,
            int arcAngle)
    {
    /**
     * Fills an arc.
     */
+   @Override
    public void fillArc(int x, int y, int width, int height, int startAngle,
            int arcAngle)
    {
    /**
     * Draws a polyline.
     */
+   @Override
    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
    {
      if (nPoints > 0)
    /**
     * Draws a polygon made with the specified points.
     */
+   @Override
    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
    {
      Shape shape = new Polygon(xPoints, yPoints, nPoints);
    /**
     * Draws a polygon.
     */
+   @Override
    public void drawPolygon(Polygon p)
    {
      draw(p);
    /**
     * Fills a polygon made with the specified points.
     */
+   @Override
    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
    {
      Shape shape = new Polygon(xPoints, yPoints, nPoints);
    /**
     * Fills a polygon.
     */
+   @Override
    public void fillPolygon(Polygon p)
    {
      draw(p, "fill");
    /**
     * Draws the specified characters, starting from (x,y)
     */
+   @Override
    public void drawChars(char[] data, int offset, int length, int x, int y)
    {
      String string = new String(data, offset, length);
    /**
     * Draws the specified bytes, starting from (x,y)
     */
+   @Override
    public void drawBytes(byte[] data, int offset, int length, int x, int y)
    {
      String string = new String(data, offset, length);
    /**
     * Draws an image.
     */
+   @Override
    public boolean drawImage(Image img, int x, int y, ImageObserver observer)
    {
      return drawImage(img, x, y, Color.white, observer);
    /**
     * Draws an image.
     */
+   @Override
    public boolean drawImage(Image img, int x, int y, int width, int height,
            ImageObserver observer)
    {
    /**
     * Draws an image.
     */
+   @Override
    public boolean drawImage(Image img, int x, int y, Color bgcolor,
            ImageObserver observer)
    {
    /**
     * Draws an image.
     */
+   @Override
    public boolean drawImage(Image img, int x, int y, int width, int height,
            Color bgcolor, ImageObserver observer)
    {
    /**
     * Draws an image.
     */
+   @Override
    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
            int sx1, int sy1, int sx2, int sy2, ImageObserver observer)
    {
    /**
     * Draws an image.
     */
+   @Override
    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
            int sx1, int sy1, int sx2, int sy2, Color bgcolor,
            ImageObserver observer)
     * only remaining EpsGraphics2D instance pointing at a EpsDocument object,
     * then the EpsDocument object shall become eligible for garbage collection.
     */
+   @Override
    public void dispose()
    {
      _document = null;
    }
  
+   /* bsoares 2019-03-20
+    * finalize is now deprecated. Implementing AutoCloseable instead
    /**
     * Finalizes the object.
-    */
+   @Override
    public void finalize()
    {
      super.finalize();
    }
+    */
  
    /**
     * Returns the entire contents of the EPS document, complete with headers and
     * bounding box. The returned String is suitable for being written directly to
     * disk as an EPS file.
     */
+   @Override
    public String toString()
    {
      StringWriter writer = new StringWriter();
     * Returns true if the specified rectangular area might intersect the current
     * clipping area.
     */
+   @Override
    public boolean hitClip(int x, int y, int width, int height)
    {
      if (_clip == null)
    /**
     * Returns the bounding rectangle of the current clipping area.
     */
+   @Override
    public Rectangle getClipBounds(Rectangle r)
    {
      if (_clip == null)
@@@ -51,7 -51,6 +51,7 @@@ import jalview.io.FormatAdapter
  import jalview.io.gff.SequenceOntologyI;
  import jalview.util.MapList;
  import jalview.util.MappingUtils;
 +import jalview.ws.params.InvalidArgumentException;
  
  import java.io.IOException;
  import java.util.ArrayList;
@@@ -975,7 -974,7 +975,7 @@@ public class AlignmentUtilsTest
      assertTrue(AlignmentUtils.haveCrossRef(seq2, seq1));
  
      // now the other way round
 -    seq1.setDBRefs(null);
 +      seq1.setDBRefs(null);
      seq2.addDBRef(new DBRefEntry("EMBL", "1", "A12345"));
      assertTrue(AlignmentUtils.haveCrossRef(seq1, seq2));
      assertTrue(AlignmentUtils.haveCrossRef(seq2, seq1));
      DBRefEntry dna1xref = new DBRefEntry("UNIPROT", "ENSEMBL", "pep1",
              new Mapping(mapfordna1));
      dna1.addDBRef(dna1xref);
 -    assertEquals(2, dna1.getDBRefs().length); // to self and to pep1
 +    assertEquals(2, dna1.getDBRefs().size()); // to self and to pep1
      DBRefEntry dna2xref = new DBRefEntry("UNIPROT", "ENSEMBL", "pep2",
              new Mapping(mapfordna2));
      dna2.addDBRef(dna2xref);
 -    assertEquals(2, dna2.getDBRefs().length); // to self and to pep2
 +    assertEquals(2, dna2.getDBRefs().size()); // to self and to pep2
  
      /*
       * execute method under test:
       * verify CDS has a dbref with mapping to peptide
       */
      assertNotNull(cds1Dss.getDBRefs());
 -    assertEquals(2, cds1Dss.getDBRefs().length);
 -    dbref = cds1Dss.getDBRefs()[0];
 +    assertEquals(2, cds1Dss.getDBRefs().size());
 +    dbref = cds1Dss.getDBRefs().get(0);
      assertEquals(dna1xref.getSource(), dbref.getSource());
      // version is via ensembl's primary ref
      assertEquals(dna1xref.getVersion(), dbref.getVersion());
       */
      assertNotNull(pep1.getDBRefs());
      // FIXME pep1.getDBRefs() is 1 - is that the correct behaviour ?
 -    assertEquals(2, pep1.getDBRefs().length);
 -    dbref = pep1.getDBRefs()[1];
 +    assertEquals(2, pep1.getDBRefs().size());
 +    dbref = pep1.getDBRefs().get(1);
      assertEquals("ENSEMBL", dbref.getSource());
      assertEquals("0", dbref.getVersion());
      assertEquals("CDS|dna1", dbref.getAccessionId());
      /*
       * verify cDNA has added a dbref with mapping to CDS
       */
 -    assertEquals(3, dna1.getDBRefs().length);
 -    DBRefEntry dbRefEntry = dna1.getDBRefs()[2];
 +    assertEquals(3, dna1.getDBRefs().size());
 +    DBRefEntry dbRefEntry = dna1.getDBRefs().get(2);
      assertSame(cds1Dss, dbRefEntry.getMap().getTo());
      MapList dnaToCdsMapping = new MapList(new int[] { 4, 6, 10, 12 },
              new int[] { 1, 6 }, 1, 1);
      assertEquals(dnaToCdsMapping, dbRefEntry.getMap().getMap());
 -    assertEquals(3, dna2.getDBRefs().length);
 -    dbRefEntry = dna2.getDBRefs()[2];
 +    assertEquals(3, dna2.getDBRefs().size());
 +    dbRefEntry = dna2.getDBRefs().get(2);
      assertSame(cds2Dss, dbRefEntry.getMap().getTo());
      dnaToCdsMapping = new MapList(new int[] { 1, 3, 7, 9, 13, 15 },
              new int[] { 1, 9 }, 1, 1);
      /*
       * verify CDS has added a dbref with mapping to cDNA
       */
 -    assertEquals(2, cds1Dss.getDBRefs().length);
 -    dbRefEntry = cds1Dss.getDBRefs()[1];
 +    assertEquals(2, cds1Dss.getDBRefs().size());
 +    dbRefEntry = cds1Dss.getDBRefs().get(1);
      assertSame(dna1.getDatasetSequence(), dbRefEntry.getMap().getTo());
      MapList cdsToDnaMapping = new MapList(new int[] { 1, 6 }, new int[] {
          4, 6, 10, 12 }, 1, 1);
      assertEquals(cdsToDnaMapping, dbRefEntry.getMap().getMap());
 -    assertEquals(2, cds2Dss.getDBRefs().length);
 -    dbRefEntry = cds2Dss.getDBRefs()[1];
 +    assertEquals(2, cds2Dss.getDBRefs().size());
 +    dbRefEntry = cds2Dss.getDBRefs().get(1);
      assertSame(dna2.getDatasetSequence(), dbRefEntry.getMap().getTo());
      cdsToDnaMapping = new MapList(new int[] { 1, 9 }, new int[] { 1, 3, 7,
          9, 13, 15 }, 1, 1);
       * transcript 'CDS' is 10-16, 17-21
       * which is 'gene' 158-164, 210-214
       */
-     MapList toMap = toLoci.getMap();
+     MapList toMap = toLoci.getMapping();
      assertEquals(1, toMap.getFromRanges().size());
      assertEquals(2, toMap.getFromRanges().get(0).length);
      assertEquals(1, toMap.getFromRanges().get(0)[0]);
      AlignmentUtils.transferGeneLoci(from, map, to);
      assertEquals("GRCh38", toLoci.getAssemblyId());
      assertEquals("7", toLoci.getChromosomeId());
-     toMap = toLoci.getMap();
+     toMap = toLoci.getMapping();
      assertEquals("[ [1, 12] ] 1:1 to [ [158, 164] [210, 214] ]",
              toMap.toString());
    }
@@@ -75,6 -75,7 +75,7 @@@ public class VCFLoaderTes
      Cache.loadProperties("test/jalview/io/testProps.jvprops");
      Cache.setProperty("VCF_FIELDS", ".*");
      Cache.setProperty("VEP_FIELDS", ".*");
+     Cache.setProperty("VCF_ASSEMBLY", "GRCh38=GRCh38");
      Cache.initLogger();
    }
  
       * verify SNP variant feature(s) computed and added to protein
       * first codon AGC varies to ACC giving S/T
       */
 -    DBRefEntry[] dbRefs = al.getSequenceAt(1).getDBRefs();
 +    List<DBRefEntry> dbRefs = al.getSequenceAt(1).getDBRefs();
      SequenceI peptide = null;
      for (DBRefEntry dbref : dbRefs)
      {
       * verify variant feature(s) computed and added to protein
       * last codon GCT varies to GGT giving A/G in the last peptide position
       */
 -    DBRefEntry[] dbRefs = al.getSequenceAt(3).getDBRefs();
 +    List<DBRefEntry> dbRefs = al.getSequenceAt(3).getDBRefs();
      SequenceI peptide = null;
      for (DBRefEntry dbref : dbRefs)
      {
       * and GAG/GGG which is E/G in position 4
       * the insertion variant is not transferred to the peptide
       */
 -    DBRefEntry[] dbRefs = al.findName("transcript3").getDBRefs();
 +    List<DBRefEntry> dbRefs = al.findName("transcript3").getDBRefs();
      SequenceI peptide = null;
      for (DBRefEntry dbref : dbRefs)
      {
@@@ -34,7 -34,10 +34,10 @@@ import jalview.api.FeatureColourI
  import jalview.api.ViewStyleI;
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.DBRefEntry;
+ import jalview.datamodel.GeneLocus;
  import jalview.datamodel.HiddenSequences;
+ import jalview.datamodel.Mapping;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.PDBEntry.Type;
  import jalview.datamodel.SequenceCollectionI;
@@@ -68,6 -71,7 +71,7 @@@ import jalview.schemes.RNAHelicesColour
  import jalview.schemes.StrandColourScheme;
  import jalview.schemes.TCoffeeColourScheme;
  import jalview.structure.StructureImportSettings;
+ import jalview.util.MapList;
  import jalview.util.matcher.Condition;
  import jalview.viewmodel.AlignmentViewport;
  
@@@ -115,8 -119,7 +119,8 @@@ public class Jalview2xmlTests extends J
              af.getViewport()
                      .getGlobalColourScheme() instanceof RNAHelicesColour,
              "Couldn't apply RNA helices colourscheme");
 -    assertTrue(af.saveAlignment(tfile, FileFormat.Jalview),
 +    af.saveAlignment(tfile, FileFormat.Jalview);
 +    assertTrue(af.isSaveAlignmentSuccessful(),
              "Failed to store as a project.");
      af.closeMenuItem_actionPerformed(true);
      af = null;
                              .getSchemeName()),
              "Recognise T-Coffee score from string");
  
 -    assertTrue(af.saveAlignment(tfile, FileFormat.Jalview),
 +    af.saveAlignment(tfile, FileFormat.Jalview);
 +    assertTrue(af.isSaveAlignmentSuccessful(),
              "Failed to store as a project.");
      af.closeMenuItem_actionPerformed(true);
      af = null;
      sg.addSequence(af.getViewport().getAlignment().getSequenceAt(1), false);
      sg.addSequence(af.getViewport().getAlignment().getSequenceAt(2), true);
      af.alignPanel.alignmentChanged();
 -    assertTrue(af.saveAlignment(tfile, FileFormat.Jalview),
 +    af.saveAlignment(tfile, FileFormat.Jalview);
 +    assertTrue(af.isSaveAlignmentSuccessful(),
              "Failed to store as a project.");
      af.closeMenuItem_actionPerformed(true);
      af = null;
      File tfile = File.createTempFile("JalviewTest", ".jvp");
      tfile.deleteOnExit();
      String filePath = tfile.getAbsolutePath();
 -    assertTrue(af.saveAlignment(filePath, FileFormat.Jalview),
 +    af.saveAlignment(filePath, FileFormat.Jalview);
 +    assertTrue(af.isSaveAlignmentSuccessful(),
              "Failed to store as a project.");
  
      /*
                      .getAlignViewport(),
              "Didn't restore correct view association for the PCA view");
    }
+   /**
+    * Test save and reload of DBRefEntry including GeneLocus in project
+    * 
+    * @throws Exception
+    */
+   @Test(groups = { "Functional" })
+   public void testStoreAndRecoverGeneLocus() throws Exception
+   {
+     Desktop.instance.closeAll_actionPerformed(null);
+     String seqData = ">P30419\nACDE\n>X1235\nGCCTGTGACGAA";
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
+             DataSourceType.PASTE);
+     assertNotNull(af, "Didn't read in the example file correctly.");
+   
+     AlignmentViewPanel ap = Desktop.getAlignmentPanels(null)[0];
+     SequenceI pep = ap.getAlignment().getSequenceAt(0);
+     SequenceI cds = ap.getAlignment().getSequenceAt(1);
+     /*
+      * give 'protein' a dbref to self, a dbref with map to CDS,
+      * and a dbref with map to gene 'locus'
+      */
+     DBRefEntry dbref1 = new DBRefEntry("Uniprot", "1", "P30419", null);
+     pep.addDBRef(dbref1);
+     Mapping cdsmap = new Mapping(cds,
+             new MapList(new int[]
+             { 1, 4 }, new int[] { 1, 12 }, 1, 3));
+     DBRefEntry dbref2 = new DBRefEntry("EMBLCDS", "2", "X1235", cdsmap);
+     pep.addDBRef(dbref2);
+     Mapping locusmap = new Mapping(null,
+             new MapList(new int[]
+             { 1, 4 }, new int[] { 2674123, 2674135 }, 1, 3));
+     DBRefEntry dbref3 = new GeneLocus("human", "GRCh38", "5", locusmap);
+     pep.addDBRef(dbref3);
+     File tfile = File.createTempFile("testStoreAndRecoverGeneLocus",
+             ".jvp");
+     try
+     {
+       new Jalview2XML(false).saveState(tfile);
+     } catch (Throwable e)
+     {
+       Assert.fail("Didn't save the state", e);
+     }
+     Desktop.instance.closeAll_actionPerformed(null);
+   
+     new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
+             DataSourceType.FILE);
+     AlignmentViewPanel rap = Desktop.getAlignmentPanels(null)[0];
+     SequenceI rpep = rap.getAlignment().getSequenceAt(0);
+     assertEquals(rpep.getName(), "P30419");
+     DBRefEntry[] dbrefs = rpep.getDBRefs();
+     assertEquals(dbrefs.length, 3);
+     DBRefEntry dbRef = dbrefs[0];
+     assertFalse(dbRef instanceof GeneLocus);
+     assertNull(dbRef.getMap());
+     assertEquals(dbRef, dbref1);
+     /*
+      * restored dbrefs with mapping have a different 'map to'
+      * sequence but otherwise match the original dbrefs
+      */
+     dbRef = dbrefs[1];
+     assertFalse(dbRef instanceof GeneLocus);
+     assertTrue(dbRef.equalRef(dbref2));
+     assertNotNull(dbRef.getMap());
+     SequenceI rcds = rap.getAlignment().getSequenceAt(1);
+     assertSame(dbRef.getMap().getTo(), rcds);
+     // compare MapList but not map.to
+     assertEquals(dbRef.getMap().getMap(), dbref2.getMap().getMap());
+     /*
+      * GeneLocus map.to is null so can compare Mapping objects
+      */
+     dbRef = dbrefs[2];
+     assertTrue(dbRef instanceof GeneLocus);
+     assertEquals(dbRef, dbref3);
+   }
  }
@@@ -30,10 -30,14 +30,14 @@@ import jalview.ws.seqfetcher.ASequenceF
  import jalview.ws.seqfetcher.DbSourceProxy;
  
  import java.util.Enumeration;
+ import java.util.HashMap;
  import java.util.List;
+ import java.util.Map;
  import java.util.Vector;
  
+ import org.testng.Assert;
  import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.Test;
  
  public class SequenceFetcherTest
  {
      JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
    }
  
+   @Test(groups = "Functional")
+   public void testNoDuplicatesInFetchDbRefs()
+   {
+     Map<String, List<DbSourceProxy>> seen = new HashMap<>();
+     jalview.ws.SequenceFetcher sfetcher = new jalview.ws.SequenceFetcher();
+     String dupes = "";
+     for (String src : sfetcher.getOrderedSupportedSources())
+     {
+       List<DbSourceProxy> seenitem = seen.get(src);
+       if (seenitem != null)
+       {
+         dupes += (dupes.length() > 0 ? "," : "") + src;
+       }
+       else
+       {
+         seen.put(src, sfetcher.getSourceProxy(src));
+       }
+     }
+     if (dupes.length() > 0)
+     {
+       Assert.fail("Duplicate sources : " + dupes);
+     }
+   }
    /**
     * simple run method to test dbsources.
     * 
     * @param argv
 +   * @j2sIgnore
     */
    public static void main(String[] argv)
    {