Merge commit 'alpha/update_2_12_for_2_11_2_series_merge^2' into HEAD
authorJim Procter <j.procter@dundee.ac.uk>
Mon, 21 Nov 2022 17:02:08 +0000 (17:02 +0000)
committerJim Procter <j.procter@dundee.ac.uk>
Mon, 21 Nov 2022 17:02:08 +0000 (17:02 +0000)
133 files changed:
1  2 
THIRDPARTYLIBS
build.gradle
doc/building.html
gradle.properties
help/help/help.jhm
help/help/helpTOC.xml
help/help/html/features/preferences.html
help/help/html/releases.html
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/Finder.java
src/jalview/api/AlignViewportI.java
src/jalview/api/FeatureSettingsModelI.java
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/js/MouseOverStructureListener.java
src/jalview/bin/Cache.java
src/jalview/bin/Jalview.java
src/jalview/bin/JalviewLite.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/PDBEntry.java
src/jalview/datamodel/ResidueCount.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/fts/service/pdb/PDBFTSRestClient.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationColourChooser.java
src/jalview/gui/AnnotationExporter.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AppJmol.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/CutAndPasteTransfer.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/Finder.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/OptsAndParamsPage.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceFetcher.java
src/jalview/gui/SplashScreen.java
src/jalview/gui/SplitFrame.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/StructureViewer.java
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/TreeCanvas.java
src/jalview/gui/TreePanel.java
src/jalview/gui/UserDefinedColours.java
src/jalview/gui/VamsasApplication.java
src/jalview/gui/WebserviceInfo.java
src/jalview/gui/WsJobParameters.java
src/jalview/gui/WsPreferences.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/io/BackupFiles.java
src/jalview/io/FileFormat.java
src/jalview/io/FileFormats.java
src/jalview/io/FileLoader.java
src/jalview/io/IdentifyFile.java
src/jalview/io/NewickFile.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/StockholmFile.java
src/jalview/io/VamsasAppDatastore.java
src/jalview/io/packed/ParsePackedSet.java
src/jalview/jbgui/GPreferences.java
src/jalview/jbgui/GStructureViewer.java
src/jalview/project/Jalview2XML.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/schemes/ColourSchemes.java
src/jalview/schemes/FeatureSettingsAdapter.java
src/jalview/schemes/ResidueProperties.java
src/jalview/structure/StructureImportSettings.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/util/ColorUtils.java
src/jalview/util/Comparison.java
src/jalview/util/DBRefUtils.java
src/jalview/util/HttpUtils.java
src/jalview/util/MapList.java
src/jalview/util/MappingUtils.java
src/jalview/util/MessageManager.java
src/jalview/util/Platform.java
src/jalview/util/StringUtils.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/ws/SequenceFetcher.java
src/jalview/ws/dbsources/EmblCdsSource.java
src/jalview/ws/dbsources/EmblFlatfileSource.java
src/jalview/ws/dbsources/EmblSource.java
src/jalview/ws/dbsources/EmblXmlSource.java
src/jalview/ws/dbsources/Uniprot.java
src/jalview/ws/jws1/JPredClient.java
src/jalview/ws/jws1/MsaWSClient.java
src/jalview/ws/jws2/Jws2Client.java
src/jalview/ws/jws2/MsaWSClient.java
src/jalview/ws/jws2/SequenceAnnotationWSClient.java
src/jalview/ws/jws2/dm/JabaOption.java
src/jalview/ws/rest/InputType.java
src/jalview/ws/rest/RestJobThread.java
src/jalview/ws/sifts/SiftsClient.java
src/jalview/xml/binding/jalview/DoubleVector.java
src/jalview/xml/binding/jalview/JalviewModel.java
test/jalview/analysis/AlignmentGenerator.java
test/jalview/analysis/FinderTest.java
test/jalview/datamodel/ResidueCountTest.java
test/jalview/datamodel/SequenceTest.java
test/jalview/ext/ensembl/EnsemblCdnaTest.java
test/jalview/ext/ensembl/EnsemblGeneTest.java
test/jalview/ext/jmol/JmolCommandsTest.java
test/jalview/ext/jmol/JmolParserTest.java
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java
test/jalview/fts/service/pdb/PDBFTSPanelTest.java
test/jalview/gui/FreeUpMemoryTest.java
test/jalview/gui/SeqCanvasTest.java
test/jalview/gui/SeqPanelTest.java
test/jalview/gui/StructureChooserTest.java
test/jalview/io/BackupFilesTest.java
test/jalview/io/FileFormatsTest.java
test/jalview/project/Jalview2xmlTests.java
test/jalview/structure/StructureSelectionManagerTest.java
test/jalview/structures/models/AAStructureBindingModelTest.java
test/jalview/util/MapListTest.java
test/jalview/util/MappingUtilsTest.java
test/jalview/ws/gui/Jws2ParamView.java
test/jalview/ws/jabaws/DisorderAnnotExportImport.java
test/jalview/ws/jabaws/RNAStructExportImport.java
test/jalview/ws/jws2/ParameterUtilsTest.java

diff --combined THIRDPARTYLIBS
@@@ -28,7 -28,7 +28,7 @@@ htsjdk-2.12.0.jar     built from maven mast
  httpclient-4.0.3.jar
  httpcore-4.0.1.jar
  httpmime-4.0.3.jar
 -intervalstore-v1.0.jar
 +intervalstore-v1.1.jar
  jabaws-min-client-2.2.0.jar
  java-json.jar
  jaxrpc.jar
@@@ -42,7 -42,7 +42,7 @@@ jetty-util-9.2.10.v20150310.ja
  jfreesvg-2.1.jar      GPL v3 licensed library from the JFree suite - http://www.jfree.org/jfreesvg/
  JGoogleAnalytics_0.3.jar      APL 2.0 License - http://code.google.com/p/jgoogleanalytics/
  jhall.jar
- Jmol-14.6.4_2016.10.26.jar    GPL/LGPLv2 http://sourceforge.net/projects/jmol/files/
+ Jmol-14.31.53.jar     GPL/LGPLv2 built manually from commit https://github.com/BobHanson/Jmol-SwingJS/commit/a6a2fb767e3fc2a73e72d926a11fd93a0e4c9f23 (excluded jspecview/application to compile)
  json_simple-1.1.jar   Apache 2.0 license - downloaded from https://code.google.com/p/json-simple/downloads/list (will move to 1.1.1 version when jalview is mavenised and osgi-ised)
  jsoup-1.8.1.jar
  jsr311-api-1.1.1.jar
diff --combined build.gradle
@@@ -12,6 -12,6 +12,7 @@@ import java.security.MessageDiges
  import groovy.transform.ExternalizeMethods
  import groovy.util.XmlParser
  import groovy.xml.XmlUtil
++
  import com.vladsch.flexmark.util.ast.Node
  import com.vladsch.flexmark.html.HtmlRenderer
  import com.vladsch.flexmark.parser.Parser
@@@ -40,8 -40,9 +41,9 @@@ plugins 
    id 'eclipse'
    id "com.diffplug.gradle.spotless" version "3.28.0"
    id 'com.github.johnrengelman.shadow' version '4.0.3'
-   id 'com.install4j.gradle' version '8.0.4'
+   id 'com.install4j.gradle' version '8.0.10'
    id 'com.dorongold.task-tree' version '1.5' // only needed to display task dependency tree with  gradle task1 [task2 ...] taskTree
+   id 'com.palantir.git-version' version '0.12.3'
  }
  
  repositories {
  }
  
  
 -
  // in ext the values are cast to Object. Ensure string values are cast as String (and not GStringImpl) for later use
  def string(Object o) {
    return o == null ? "" : o.toString()
  }
  
 +
- ext {
-   jalviewDirAbsolutePath = file(jalviewDir).getAbsolutePath()
-   jalviewDirRelativePath = jalviewDir
-   // local build environment properties
-   // can be "projectDir/local.properties"
-   def localProps = "${projectDir}/local.properties"
-   def propsFile = null;
-   if (file(localProps).exists()) {
-     propsFile = localProps
+ def overrideProperties(String propsFileName, boolean output = false) {
+   if (propsFileName == null) {
+     return
    }
-   // or "../projectDir_local.properties"
-   def dirLocalProps = projectDir.getParent() + "/" + projectDir.getName() + "_local.properties"
-   if (file(dirLocalProps).exists()) {
-     propsFile = dirLocalProps
-   }
-   if (propsFile != null) {
+   def propsFile = file(propsFileName)
+   if (propsFile != null && propsFile.exists()) {
+     println("Using properties from file '${propsFileName}'")
      try {
        def p = new Properties()
        def localPropsFIS = new FileInputStream(propsFile)
        localPropsFIS.close()
        p.each {
          key, val -> 
-           def oldval = findProperty(key)
-           setProperty(key, val)
-           if (oldval != null) {
-             println("Overriding property '${key}' ('${oldval}') with ${file(propsFile).getName()} value '${val}'")
+           def oldval
+           if (project.hasProperty(key)) {
+             oldval = project.findProperty(key)
+             project.setProperty(key, val)
+             if (output) {
+               println("Overriding property '${key}' ('${oldval}') with ${file(propsFile).getName()} value '${val}'")
+             }
            } else {
-             println("Setting unknown property '${key}' with ${file(propsFile).getName()}s value '${val}'")
+             ext.setProperty(key, val)
+             if (output) {
+               println("Setting ext property '${key}' with ${file(propsFile).getName()}s value '${val}'")
+             }
            }
        }
      } catch (Exception e) {
-       System.out.println("Exception reading local.properties")
+       println("Exception reading local.properties")
+       e.printStackTrace()
      }
    }
+ }
+ ext {
+   jalviewDirAbsolutePath = file(jalviewDir).getAbsolutePath()
+   jalviewDirRelativePath = jalviewDir
+   getdownChannelName = CHANNEL.toLowerCase()
+   // default to "default". Currently only has different cosmetics for "develop", "release", "default"
+   propertiesChannelName = ["develop", "release", "test-release", "jalviewjs", "jalviewjs-release" ].contains(getdownChannelName) ? getdownChannelName : "default"
+   // Import channel_properties
+   channelDir = string("${jalviewDir}/${channel_properties_dir}/${propertiesChannelName}")
+   channelGradleProperties = string("${channelDir}/channel_gradle.properties")
+   overrideProperties(channelGradleProperties, false)
+   // local build environment properties
+   // can be "projectDir/local.properties"
+   overrideProperties("${projectDir}/local.properties", true)
+   // or "../projectDir_local.properties"
+   overrideProperties(projectDir.getParent() + "/" + projectDir.getName() + "_local.properties", true)
  
    ////  
    // Import releaseProps from the RELEASE file
    }
    */
  
+   // datestamp
+   buildDate = new Date().format("yyyyMMdd")
 -
    // essentials
    bareSourceDir = string(source_dir)
    sourceDir = string("${jalviewDir}/${bareSourceDir}")
    //cloverTestClassesDir = cloverClassesDir
    cloverDb = string("${cloverBuildDir}/clover.db")
  
-   resourceClassesDir = useClover ? cloverClassesDir : classesDir
 +
    testSourceDir = useClover ? cloverTestInstrDir : testDir
    testClassesDir = useClover ? cloverTestClassesDir : "${jalviewDir}/${test_output_dir}"
  
    getdownWebsiteDir = string("${jalviewDir}/${getdown_website_dir}/${JAVA_VERSION}")
    buildDist = true
+   buildProperties = null
  
    // the following values might be overridden by the CHANNEL switch
-   getdownChannelName = CHANNEL.toLowerCase()
    getdownDir = string("${getdownChannelName}/${JAVA_VERSION}")
    getdownAppBase = string("${getdown_channel_base}/${getdownDir}")
    getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher}")
    getdownAppDistDir = getdown_app_dir_alt
-   buildProperties = string("${resourceDir}/${build_properties_file}")
+   getdownImagesDir = string("${jalviewDir}/${getdown_images_dir}")
+   getdownSetAppBaseProperty = false // whether to pass the appbase and appdistdir to the application
    reportRsyncCommand = false
    jvlChannelName = CHANNEL.toLowerCase()
    install4jSuffix = CHANNEL.substring(0, 1).toUpperCase() + CHANNEL.substring(1).toLowerCase(); // BUILD -> Build
-   install4jDSStore = "DS_Store-NON-RELEASE"
-   install4jDMGBackgroundImage = "jalview_dmg_background-NON-RELEASE.png"
+   install4jDMGDSStore = "${install4j_images_dir}/${install4j_dmg_ds_store}"
+   install4jDMGBackgroundImage = "${install4j_images_dir}/${install4j_dmg_background}"
    install4jInstallerName = "${jalview_name} Non-Release Installer"
-   install4jExecutableName = jalview_name.replaceAll("[^\\w]+", "_").toLowerCase()
+   install4jExecutableName = install4j_executable_name
    install4jExtraScheme = "jalviewx"
+   install4jMacIconsFile = string("${install4j_images_dir}/${install4j_mac_icons_file}")
+   install4jWindowsIconsFile = string("${install4j_images_dir}/${install4j_windows_icons_file}")
+   install4jPngIconFile = string("${install4j_images_dir}/${install4j_png_icon_file}")
+   install4jBackground = string("${install4j_images_dir}/${install4j_background}")
    switch (CHANNEL) {
  
      case "BUILD":
      install4jExtraScheme = "jalviewb"
      break
  
-     case "RELEASE":
+     case [ "RELEASE", "JALVIEWJS-RELEASE" ]:
      getdownAppDistDir = getdown_app_dir_release
      reportRsyncCommand = true
      install4jSuffix = ""
-     install4jDSStore = "DS_Store"
-     install4jDMGBackgroundImage = "jalview_dmg_background.png"
      install4jInstallerName = "${jalview_name} Installer"
      break
  
  
      case "DEVELOP":
      reportRsyncCommand = true
-     
+     getdownSetAppBaseProperty = true
      // DEVELOP-RELEASE is usually associated with a Jalview release series so set the version
-     JALVIEW_VERSION=JALVIEW_VERSION+"-develop"
+     JALVIEW_VERSION=JALVIEW_VERSION+"-d${buildDate}"
      
      install4jSuffix = "Develop"
-     install4jDSStore = "DS_Store-DEVELOP"
-     install4jDMGBackgroundImage = "jalview_dmg_background-DEVELOP.png"
      install4jExtraScheme = "jalviewd"
      install4jInstallerName = "${jalview_name} Develop Installer"
      break
      }
      JALVIEW_VERSION = JALVIEW_VERSION+"-test"
      install4jSuffix = "Test"
-     install4jDSStore = "DS_Store-TEST-RELEASE"
-     install4jDMGBackgroundImage = "jalview_dmg_background-TEST.png"
      install4jExtraScheme = "jalviewt"
      install4jInstallerName = "${jalview_name} Test Installer"
      break
      }
      JALVIEW_VERSION = "TEST"
      install4jSuffix = "Test-Local"
-     install4jDSStore = "DS_Store-TEST-RELEASE"
-     install4jDMGBackgroundImage = "jalview_dmg_background-TEST.png"
      install4jExtraScheme = "jalviewt"
      install4jInstallerName = "${jalview_name} Test Installer"
      break
  
-     case "LOCAL":
+     case [ "LOCAL", "JALVIEWJS" ]:
      JALVIEW_VERSION = "TEST"
      getdownAppBase = file(getdownWebsiteDir).toURI().toString()
      getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
    }
    // override getdownAppBase if requested
    if (findProperty("getdown_appbase_override") != null) {
-     getdownAppBase = string(getProperty("getdown_appbase_override"))
+     // revert to LOCAL if empty string
+     if (string(getdown_appbase_override) == "") {
+       getdownAppBase = file(getdownWebsiteDir).toURI().toString()
+       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
+     } else if (string(getdown_appbase_override).startsWith("file://")) {
+       getdownAppBase = string(getdown_appbase_override)
+       getdownLauncher = string("${jalviewDir}/${getdown_lib_dir}/${getdown_launcher_local}")
+     } else {
+       getdownAppBase = string(getdown_appbase_override)
+     }
      println("Overriding getdown appbase with '${getdownAppBase}'")
    }
    // sanitise file name for jalview launcher file for this channel
                                      .replaceAll("_*-_*", "-") // collapse _-_
                                      .toLowerCase()
  
+   getdownWrapperLink = install4jUnixApplicationFolder // e.g. "jalview_local"
    getdownAppDir = string("${getdownWebsiteDir}/${getdownAppDistDir}")
    //getdownJ11libDir = "${getdownWebsiteDir}/${getdown_j11lib_dir}"
    getdownResourceDir = string("${getdownWebsiteDir}/${getdown_resource_dir}")
    modules_compileClasspath = fileTree(dir: "${jalviewDir}/${j11modDir}", include: ["*.jar"])
    modules_runtimeClasspath = modules_compileClasspath
    */
-   gitHash = string("")
-   gitBranch = string("")
+   def details = versionDetails()
+   gitHash = details.gitHash
+   gitBranch = details.branchName
  
    println("Using a ${CHANNEL} profile.")
  
      '--add-modules', j11modules
      ]
       */
-   } else if (JAVA_VERSION.equals("12") || JAVA_VERSION.equals("13")) {
-     JAVA_INTEGER_VERSION = JAVA_VERSION
-     libDir = j11libDir
-     libDistDir = j11libDir
-     compile_source_compatibility = JAVA_VERSION
-     compile_target_compatibility = JAVA_VERSION
+   } else if (JAVA_VERSION.equals("17")) {
+     JAVA_INTEGER_VERSION = string("17")
+     libDir = j17libDir
+     libDistDir = j17libDir
+     compile_source_compatibility = 17
+     compile_target_compatibility = 17
      getdownAltJavaMinVersion = string(findProperty("getdown_alt_java11_min_version"))
      getdownAltJavaMaxVersion = string(findProperty("getdown_alt_java11_max_version"))
      getdownAltMultiJavaLocation = string(findProperty("getdown_alt_java11_txt_multi_java_location"))
-     eclipseJavaRuntimeName = string("JavaSE-11")
+     eclipseJavaRuntimeName = string("JavaSE-17")
      /* compile without modules -- using classpath libraries
      additional_compiler_args += [
      '--module-path', modules_compileClasspath.asPath,
      install4jHomeDir = System.getProperty("user.home") + install4jHomeDir.substring(1)
    }
  
+   resourceBuildDir = string("${buildDir}/resources")
+   resourcesBuildDir = string("${resourceBuildDir}/resources_build")
+   helpBuildDir = string("${resourceBuildDir}/help_build")
+   docBuildDir = string("${resourceBuildDir}/doc_build")
  
 +
+   if (buildProperties == null) {
+     buildProperties = string("${resourcesBuildDir}/${build_properties_file}")
+   }
    buildingHTML = string("${jalviewDir}/${doc_dir}/building.html")
-   helpFile = string("${resourceClassesDir}/${help_dir}/help.jhm")
    helpParentDir = string("${jalviewDir}/${help_parent_dir}")
    helpSourceDir = string("${helpParentDir}/${help_dir}")
+   helpFile = string("${helpBuildDir}/${help_dir}/help.jhm")
  
  
    relativeBuildDir = file(jalviewDirAbsolutePath).toPath().relativize(buildDir.toPath())
    jalviewjsTransferSiteSwingJsDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_swingjs")
    jalviewjsTransferSiteCoreDir = string("${jalviewjsBuildDir}/tmp/${jalviewjs_site_dir}_core")
    jalviewjsJalviewCoreHtmlFile = string("")
+   jalviewjsJalviewCoreName = string(jalviewjs_core_name)
    jalviewjsCoreClasslists = []
    jalviewjsJalviewTemplateName = string(jalviewjs_name)
    jalviewjsJ2sSettingsFileName = string("${jalviewDir}/${jalviewjs_j2s_settings}")
@@@ -480,16 -512,14 +514,15 @@@ sourceSets 
      }
  
      resources {
-       srcDirs resourceDir
-       srcDirs += helpParentDir
+       srcDirs = [ resourcesBuildDir, docBuildDir, helpBuildDir ]
      }
  
-     jar.destinationDir = file("${jalviewDir}/${package_dir}")
 +
      compileClasspath = files(sourceSets.main.java.outputDir)
      compileClasspath += fileTree(dir: "${jalviewDir}/${libDir}", include: ["*.jar"])
  
      runtimeClasspath = compileClasspath
+     runtimeClasspath += files(sourceSets.main.resources.srcDirs)
    }
  
    clover {
      compileClasspath += fileTree(dir: "${jalviewDir}/${utils_dir}/testnglibs", include: ["**/*.jar"])
  
      runtimeClasspath = compileClasspath
+     runtimeClasspath += files(sourceSets.test.resources.srcDirs)
    }
  
  }
@@@ -546,14 -577,12 +580,12 @@@ eclipse 
  
    classpath {
      //defaultOutputDir = sourceSets.main.java.outputDir
-     def removeThese = []
-     configurations.each{
-       if (it.isCanBeResolved()) {
-         removeThese += it
+     configurations.each{ c->
+       if (c.isCanBeResolved()) {
+         minusConfigurations += [c]
        }
      }
  
-     minusConfigurations += removeThese
      plusConfigurations = [ ]
      file {
  
@@@ -1003,64 -1032,6 +1035,6 @@@ def getDate(format) 
  }
  
  
- task setGitVals {
-   doFirst {
-     def hashStdOut = new ByteArrayOutputStream()
-     def resultHash = exec {
-       commandLine "git", "rev-parse", "--short", "HEAD"
-       standardOutput = hashStdOut
-       ignoreExitValue true
-     }
-     def branchStdOut = new ByteArrayOutputStream()
-     def resultBranch = exec {
-       commandLine "git", "rev-parse", "--abbrev-ref", "HEAD"
-       standardOutput = branchStdOut
-       ignoreExitValue true
-     }
-     gitHash = resultHash.getExitValue() == 0 ? hashStdOut.toString().trim() : "NO_GIT_COMMITID_FOUND"
-     gitBranch = resultBranch.getExitValue() == 0 ? branchStdOut.toString().trim() : "NO_GIT_BRANCH_FOUND"
-   }
-   outputs.upToDateWhen { false }
- }
- task createBuildProperties(type: WriteProperties) {
-   group = "build"
-   description = "Create the ${buildProperties} file"
-   
-   dependsOn setGitVals
-   inputs.dir(sourceDir)
-   inputs.dir(resourceDir)
-   file(buildProperties).getParentFile().mkdirs()
-   outputFile (buildProperties)
-   // taking time specific comment out to allow better incremental builds
-   comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd HH:mm:ss")
-   //comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd")
-   property "BUILD_DATE", getDate("HH:mm:ss dd MMMM yyyy")
-   property "VERSION", JALVIEW_VERSION
-   property "INSTALLATION", INSTALLATION+" git-commit:"+gitHash+" ["+gitBranch+"]"
-   outputs.file(outputFile)
- }
- clean {
-   doFirst {
-     delete buildProperties
-   }
- }
- task cleanBuildingHTML(type: Delete) {
-   doFirst {
-     delete buildingHTML
-   }
- }
  def convertMdToHtml (FileTree mdFiles, File cssFile) {
    MutableDataSet options = new MutableDataSet()
  
  
      def htmlFilePath = mdFile.getPath().replaceAll(/\..*?$/, ".html")
      def htmlFile = file(htmlFilePath)
+     println("Creating ${htmlFilePath}")
      htmlFile.text = htmlText
    }
  }
  
  
+ task copyDocs(type: Copy) {
+   def inputDir = "${jalviewDir}/${doc_dir}"
+   def outputDir = "${docBuildDir}/${doc_dir}"
+   from(inputDir) {
+     include('**/*.txt')
+     include('**/*.md')
+     include('**/*.html')
+     include('**/*.xml')
+     filter(ReplaceTokens,
+       beginToken: '$$',
+       endToken: '$$',
+       tokens: [
+         'Version-Rel': JALVIEW_VERSION,
+         'Year-Rel': getDate("yyyy")
+       ]
+     )
+   }
+   from(inputDir) {
+     exclude('**/*.txt')
+     exclude('**/*.md')
+     exclude('**/*.html')
+     exclude('**/*.xml')
+   }
+   into outputDir
+   inputs.dir(inputDir)
+   outputs.dir(outputDir)
+ }
  task convertMdFiles {
-   dependsOn cleanBuildingHTML
-   def mdFiles = fileTree(dir: "${jalviewDir}/${doc_dir}", include: "*.md")
+   dependsOn copyDocs
+   def mdFiles = fileTree(dir: docBuildDir, include: "**/*.md")
    def cssFile = file("${jalviewDir}/${flexmark_css}")
  
    doLast {
      def htmlFilePath = mdFile.getPath().replaceAll(/\..*?$/, ".html")
      htmlFiles.add(file(htmlFilePath))
    }
-   outputs.files(htmlFiles)
- }
 +
- task syncDocs(type: Sync) {
-   dependsOn convertMdFiles
-   def syncDir = "${classesDir}/${doc_dir}"
-   from fileTree("${jalviewDir}/${doc_dir}")
-   into syncDir
+   outputs.files(htmlFiles)
  }
  
  
  task copyHelp(type: Copy) {
    def inputDir = helpSourceDir
-   def outputDir = "${resourceClassesDir}/${help_dir}"
+   def outputDir = "${helpBuildDir}/${help_dir}"
    from(inputDir) {
-     exclude '**/*.gif'
-     exclude '**/*.jpg'
-     exclude '**/*.png'
+     include('**/*.txt')
+     include('**/*.md')
+     include('**/*.html')
+     include('**/*.hs')
+     include('**/*.xml')
+     include('**/*.jhm')
      filter(ReplaceTokens,
        beginToken: '$$',
        endToken: '$$',
      )
    }
    from(inputDir) {
-     include '**/*.gif'
-     include '**/*.jpg'
-     include '**/*.png'
+     exclude('**/*.txt')
+     exclude('**/*.md')
+     exclude('**/*.html')
+     exclude('**/*.hs')
+     exclude('**/*.xml')
+     exclude('**/*.jhm')
    }
    into outputDir
  
  }
  
  
- task syncLib(type: Sync) {
-   def syncDir = "${resourceClassesDir}/${libDistDir}"
-   from fileTree("${jalviewDir}/${libDistDir}")
-   into syncDir
+ task copyResources(type: Copy) {
+   group = "build"
+   description = "Copy (and make text substitutions in) the resources dir to the build area"
+   def inputDir = resourceDir
+   def outputDir = resourcesBuildDir
+   from(inputDir) {
+     include('**/*.txt')
+     include('**/*.md')
+     include('**/*.html')
+     include('**/*.xml')
+     filter(ReplaceTokens,
+       beginToken: '$$',
+       endToken: '$$',
+       tokens: [
+         'Version-Rel': JALVIEW_VERSION,
+         'Year-Rel': getDate("yyyy")
+       ]
+     )
+   }
+   from(inputDir) {
+     exclude('**/*.txt')
+     exclude('**/*.md')
+     exclude('**/*.html')
+     exclude('**/*.xml')
+   }
+   into outputDir
+   inputs.dir(inputDir)
+   outputs.dir(outputDir)
  }
  
+ task copyChannelResources(type: Copy) {
+   dependsOn copyResources
+   group = "build"
+   description = "Copy the channel resources dir to the build resources area"
+   def inputDir = "${channelDir}/${resource_dir}"
+   def outputDir = resourcesBuildDir
+   from inputDir
+   into outputDir
  
- task syncResources(type: Sync) {
-   dependsOn createBuildProperties
-   from resourceDir
-   include "**/*.*"
-   into "${resourceClassesDir}"
-   preserve {
-     include "**"
+   inputs.dir(inputDir)
+   outputs.dir(outputDir)
+ }
+ task createBuildProperties(type: WriteProperties) {
+   dependsOn copyResources
+   group = "build"
+   description = "Create the ${buildProperties} file"
+   
+   inputs.dir(sourceDir)
+   inputs.dir(resourcesBuildDir)
+   outputFile (buildProperties)
+   // taking time specific comment out to allow better incremental builds
+   comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd HH:mm:ss")
+   //comment "--Jalview Build Details--\n"+getDate("yyyy-MM-dd")
+   property "BUILD_DATE", getDate("HH:mm:ss dd MMMM yyyy")
+   property "VERSION", JALVIEW_VERSION
+   property "INSTALLATION", INSTALLATION+" git-commit:"+gitHash+" ["+gitBranch+"]"
+   if (getdownSetAppBaseProperty) {
+     property "GETDOWNAPPBASE", getdownAppBase
+     property "GETDOWNAPPDISTDIR", getdownAppDistDir
    }
+   outputs.file(outputFile)
+ }
 -
+ task buildIndices(type: JavaExec) {
+   dependsOn copyHelp
+   classpath = sourceSets.main.compileClasspath
+   main = "com.sun.java.help.search.Indexer"
+   workingDir = "${helpBuildDir}/${help_dir}"
+   def argDir = "html"
+   args = [ argDir ]
+   inputs.dir("${workingDir}/${argDir}")
+   outputs.dir("${classesDir}/doc")
+   outputs.dir("${classesDir}/help")
+   outputs.file("${workingDir}/JavaHelpSearch/DOCS")
+   outputs.file("${workingDir}/JavaHelpSearch/DOCS.TAB")
+   outputs.file("${workingDir}/JavaHelpSearch/OFFSETS")
+   outputs.file("${workingDir}/JavaHelpSearch/POSITIONS")
+   outputs.file("${workingDir}/JavaHelpSearch/SCHEMA")
+   outputs.file("${workingDir}/JavaHelpSearch/TMAP")
+ }
+ task buildResources {
+   dependsOn copyResources
+   dependsOn copyChannelResources
+   dependsOn createBuildProperties
  }
  
 +
  task prepare {
-   dependsOn syncResources
-   dependsOn syncDocs
+   dependsOn buildResources
+   dependsOn copyDocs
    dependsOn copyHelp
+   dependsOn convertMdFiles
+   dependsOn buildIndices
  }
  
  
+ compileJava.dependsOn prepare
+ run.dependsOn compileJava
+ //run.dependsOn prepare
  //testReportDirName = "test-reports" // note that test workingDir will be $jalviewDir
  test {
    dependsOn prepare
-   //dependsOn compileJava ////? DELETE
  
    if (useClover) {
      dependsOn cloverClasses
     } else { //?
-      dependsOn compileJava //?
+     dependsOn compileJava //?
    }
  
    useTestNG() {
    maxHeapSize = "1024m"
  
    workingDir = jalviewDir
-   //systemProperties 'clover.jar' System.properties.clover.jar
+   def testLaf = project.findProperty("test_laf")
+   if (testLaf != null) {
+     println("Setting Test LaF to '${testLaf}'")
+     systemProperty "laf", testLaf
+   }
+   def testHiDPIScale = project.findProperty("test_HiDPIScale")
+   if (testHiDPIScale != null) {
+     println("Setting Test HiDPI Scale to '${testHiDPIScale}'")
+     systemProperty "sun.java2d.uiScale", testHiDPIScale
+   }
    sourceCompatibility = compile_source_compatibility
    targetCompatibility = compile_target_compatibility
    jvmArgs += additional_compiler_args
  }
  
  
- task buildIndices(type: JavaExec) {
-   dependsOn copyHelp
-   classpath = sourceSets.main.compileClasspath
-   main = "com.sun.java.help.search.Indexer"
-   workingDir = "${classesDir}/${help_dir}"
-   def argDir = "html"
-   args = [ argDir ]
-   inputs.dir("${workingDir}/${argDir}")
-   outputs.dir("${classesDir}/doc")
-   outputs.dir("${classesDir}/help")
-   outputs.file("${workingDir}/JavaHelpSearch/DOCS")
-   outputs.file("${workingDir}/JavaHelpSearch/DOCS.TAB")
-   outputs.file("${workingDir}/JavaHelpSearch/OFFSETS")
-   outputs.file("${workingDir}/JavaHelpSearch/POSITIONS")
-   outputs.file("${workingDir}/JavaHelpSearch/SCHEMA")
-   outputs.file("${workingDir}/JavaHelpSearch/TMAP")
- }
 +
 +
  task compileLinkCheck(type: JavaCompile) {
    options.fork = true
    classpath = files("${jalviewDir}/${utils_dir}")
  
  
  task linkCheck(type: JavaExec) {
-   dependsOn prepare, compileLinkCheck
+   dependsOn prepare
+   dependsOn compileLinkCheck
  
    def helpLinksCheckerOutFile = file("${jalviewDir}/${utils_dir}/HelpLinksChecker.out")
    classpath = files("${jalviewDir}/${utils_dir}")
    main = "HelpLinksChecker"
    workingDir = jalviewDir
-   args = [ "${classesDir}/${help_dir}", "-nointernet" ]
+   args = [ "${helpBuildDir}/${help_dir}", "-nointernet" ]
  
    def outFOS = new FileOutputStream(helpLinksCheckerOutFile, false) // false == don't append
-   def errFOS = outFOS
    standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
      outFOS,
-     standardOutput)
+     System.out)
    errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
      outFOS,
-     errorOutput)
+     System.err)
  
-   inputs.dir("${classesDir}/${help_dir}")
+   inputs.dir(helpBuildDir)
    outputs.file(helpLinksCheckerOutFile)
  }
  
 -
  // import the pubhtmlhelp target
  ant.properties.basedir = "${jalviewDir}"
- ant.properties.helpBuildDir = "${jalviewDirAbsolutePath}/${classes_dir}/${help_dir}"
+ ant.properties.helpBuildDir = "${helpBuildDir}/${help_dir}"
  ant.importBuild "${utils_dir}/publishHelp.xml"
  
  
@@@ -1325,28 -1396,34 +1399,33 @@@ task cleanPackageDir(type: Delete) 
  
  
  jar {
+   dependsOn prepare
    dependsOn linkCheck
-   dependsOn buildIndices
-   dependsOn createBuildProperties
  
    manifest {
      attributes "Main-Class": main_class,
      "Permissions": "all-permissions",
-     "Application-Name": "Jalview Desktop",
-     "Codebase": application_codebase
+     "Application-Name": install4jApplicationName,
+     "Codebase": application_codebase,
+     "Implementation-Version": JALVIEW_VERSION
    }
  
-   destinationDir = file("${jalviewDir}/${package_dir}")
-   archiveName = rootProject.name+".jar"
+   def outputDir = "${jalviewDir}/${package_dir}"
+   destinationDirectory = file(outputDir)
+   archiveFileName = rootProject.name+".jar"
+   duplicatesStrategy "EXCLUDE"
  
 -
    exclude "cache*/**"
    exclude "*.jar"
    exclude "*.jar.*"
    exclude "**/*.jar"
    exclude "**/*.jar.*"
  
-   inputs.dir(classesDir)
-   outputs.file("${jalviewDir}/${package_dir}/${archiveName}")
+   inputs.dir(sourceSets.main.java.outputDir)
+   sourceSets.main.resources.srcDirs.each{ dir ->
+     inputs.dir(dir)
+   }
+   outputs.file("${outputDir}/${archiveFileName}")
  }
  
  
@@@ -1358,10 -1435,11 +1437,11 @@@ task copyJars(type: Copy) 
  
  // doing a Sync instead of Copy as Copy doesn't deal with "outputs" very well
  task syncJars(type: Sync) {
+   dependsOn jar
    from fileTree(dir: "${jalviewDir}/${libDistDir}", include: "**/*.jar").files
    into "${jalviewDir}/${package_dir}"
    preserve {
-     include jar.archiveName
+     include jar.archiveFileName.getOrNull()
    }
  }
  
@@@ -1385,6 -1463,7 +1465,6 @@@ task cleanDist 
    dependsOn clean
  }
  
 -
  shadowJar {
    group = "distribution"
    description = "Create a single jar file with all dependency libraries merged. Can be run with java -jar"
      include("*.jar")
    }
    manifest {
-     attributes 'Implementation-Version': JALVIEW_VERSION
+     attributes "Implementation-Version": JALVIEW_VERSION,
+     "Application-Name": install4jApplicationName
    }
 -
+   duplicatesStrategy "INCLUDE"
 -
    mainClassName = shadow_jar_main_class
    mergeServiceFiles()
    classifier = "all-"+JALVIEW_VERSION+"-j"+JAVA_VERSION
@@@ -1423,7 -1506,7 +1505,7 @@@ task getdownWebsite() 
  
      copy {
        from buildProperties
-       rename(build_properties_file, getdown_build_properties)
+       rename(file(buildProperties).getName(), getdown_build_properties)
        into getdownAppDir
      }
      getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}"
      if (getdownAltMultiJavaLocation != null && getdownAltMultiJavaLocation.length() > 0) {
        props.put("getdown_txt_multi_java_location", getdownAltMultiJavaLocation)
      }
+     if (getdownImagesDir != null && file(getdownImagesDir).exists()) {
+       props.put("getdown_txt_ui.background_image", "${getdownImagesDir}/${getdown_background_image}")
+       props.put("getdown_txt_ui.instant_background_image", "${getdownImagesDir}/${getdown_instant_background_image}")
+       props.put("getdown_txt_ui.error_background", "${getdownImagesDir}/${getdown_error_background}")
+       props.put("getdown_txt_ui.progress_image", "${getdownImagesDir}/${getdown_progress_image}")
+       props.put("getdown_txt_ui.icon", "${getdownImagesDir}/${getdown_icon}")
+       props.put("getdown_txt_ui.mac_dock_icon", "${getdownImagesDir}/${getdown_mac_dock_icon}")
+     }
  
      props.put("getdown_txt_title", jalview_name)
      props.put("getdown_txt_ui.name", install4jApplicationName)
          into getdownResourceDir
        }
      }
+     
+     def getdownWrapperScripts = [ getdown_bash_wrapper_script, getdown_powershell_wrapper_script, getdown_batch_wrapper_script ]
+     getdownWrapperScripts.each{ script ->
+       def s = file( "${jalviewDir}/utils/getdown/${getdown_wrapper_script_dir}/${script}" )
+       if (s.exists()) {
+         copy {
+           from s
+           into "${getdownWebsiteDir}/${getdown_wrapper_script_dir}"
+         }
+         getdownTextString += "resource = ${getdown_wrapper_script_dir}/${script}\n"
+       }
+     }
  
      def codeFiles = []
      fileTree(file(package_dir)).each{ f ->
      //getdownTextString += "class = " + file(getdownLauncher).getName() + "\n"
      getdownTextString += "resource = ${getdown_launcher_new}\n"
      getdownTextString += "class = ${main_class}\n"
+     // Not setting these properties in general so that getdownappbase and getdowndistdir will default to release version in jalview.bin.Cache
+     if (getdownSetAppBaseProperty) {
+       getdownTextString += "jvmarg = -Dgetdowndistdir=${getdownAppDistDir}\n"
+       getdownTextString += "jvmarg = -Dgetdownappbase=${getdownAppBase}\n"
+     }
  
      def getdown_txt = file("${getdownWebsiteDir}/getdown.txt")
      getdown_txt.write(getdownTextString)
      def launchJvl = file("${getdownWebsiteDir}/${getdownLaunchJvl}")
      launchJvl.write("appbase=${getdownAppBase}")
  
+     // files going into the getdown website dir: getdown-launcher.jar
      copy {
        from getdownLauncher
        rename(file(getdownLauncher).getName(), getdown_launcher_new)
        into getdownWebsiteDir
      }
  
+     // files going into the getdown website dir: getdown-launcher(-local).jar
      copy {
        from getdownLauncher
        if (file(getdownLauncher).getName() != getdown_launcher) {
        into getdownWebsiteDir
      }
  
+     // files going into the getdown website dir: ./install dir and files
      if (! (CHANNEL.startsWith("ARCHIVE") || CHANNEL.startsWith("DEVELOP"))) {
        copy {
          from getdown_txt
          from getdownLauncher
-         from "${getdownWebsiteDir}/${getdown_build_properties}"
+         from "${getdownAppDir}/${getdown_build_properties}"
          if (file(getdownLauncher).getName() != getdown_launcher) {
            rename(file(getdownLauncher).getName(), getdown_launcher)
          }
          into getdownInstallDir
        }
  
+       // and make a copy in the getdown files dir (these are not downloaded by getdown)
        copy {
          from getdownInstallDir
          into getdownFilesInstallDir
        }
      }
  
+     // files going into the getdown files dir: getdown.txt, getdown-launcher.jar, channel-launch.jvl, build_properties
      copy {
        from getdown_txt
        from launchJvl
        into getdownFilesDir
      }
  
+     // and ./resources (not all downloaded by getdown)
      copy {
        from getdownResourceDir
        into "${getdownFilesDir}/${getdown_resource_dir}"
@@@ -1741,12 -1855,6 +1854,7 @@@ task copyInstall4jTemplate 
        }
      }
  
-     // remove the "Uninstall Old Jalview (optional)" symlink from DMG for non-release DS_Stores
-     if (! (CHANNEL == "RELEASE" || CHANNEL == "TEST-RELEASE" ) ) {
-       def symlink = install4jConfigXml.'**'.topLevelFiles.symlink.find { sl -> sl.'@name' == "Uninstall Old Jalview (optional).app" }
-       symlink.parent().remove(symlink)
-     }
 +
      // write install4j file
      install4jConfFile.text = XmlUtil.serialize(install4jConfigXml)
    }
@@@ -1763,7 -1871,6 +1871,6 @@@ clean 
  task installers(type: com.install4j.gradle.Install4jTask) {
    group = "distribution"
    description = "Create the install4j installers"
-   dependsOn setGitVals
    dependsOn getdown
    dependsOn copyInstall4jTemplate
  
      'JALVIEW_APPLICATION_NAME': install4jApplicationName,
      'JALVIEW_DIR': "../..",
      'OSX_KEYSTORE': OSX_KEYSTORE,
+     'OSX_APPLEID': OSX_APPLEID,
+     'OSX_ALTOOLPASS': OSX_ALTOOLPASS,
      'JSIGN_SH': JSIGN_SH,
      'JRE_DIR': getdown_app_dir_java,
      'INSTALLER_TEMPLATE_VERSION': install4jTemplateVersion,
      'BUNDLE_ID': install4jBundleId,
      'INTERNAL_ID': install4jInternalId,
      'WINDOWS_APPLICATION_ID': install4jWinApplicationId,
-     'MACOS_DS_STORE': install4jDSStore,
+     'MACOS_DMG_DS_STORE': install4jDMGDSStore,
      'MACOS_DMG_BG_IMAGE': install4jDMGBackgroundImage,
+     'WRAPPER_LINK': getdownWrapperLink,
+     'BASH_WRAPPER_SCRIPT': getdown_bash_wrapper_script,
+     'POWERSHELL_WRAPPER_SCRIPT': getdown_powershell_wrapper_script,
+     'WRAPPER_SCRIPT_BIN_DIR': getdown_wrapper_script_dir,
      'INSTALLER_NAME': install4jInstallerName,
      'INSTALL4J_UTILS_DIR': install4j_utils_dir,
      'GETDOWN_WEBSITE_DIR': getdown_website_dir,
      'UNIX_APPLICATION_FOLDER': install4jUnixApplicationFolder,
      'EXECUTABLE_NAME': install4jExecutableName,
      'EXTRA_SCHEME': install4jExtraScheme,
+     'MAC_ICONS_FILE': install4jMacIconsFile,
+     'WINDOWS_ICONS_FILE': install4jWindowsIconsFile,
+     'PNG_ICON_FILE': install4jPngIconFile,
+     'BACKGROUND': install4jBackground,
 -
    ]
  
    //println("INSTALL4J VARIABLES:")
    if (install4j_faster.equals("true") || CHANNEL.startsWith("LOCAL")) {
      faster = true
      disableSigning = true
+     disableNotarization = true
    }
  
    if (OSX_KEYPASS) {
      macKeystorePassword = OSX_KEYPASS
+   } 
+   
+   if (OSX_ALTOOLPASS) {
+     appleIdPassword = OSX_ALTOOLPASS
+     disableNotarization = false
+   } else {
+     disableNotarization = true
    }
  
    doFirst {
      println("Using projectFile "+projectFile)
+     if (!disableNotarization) { println("Will notarize OSX App DMG") }
    }
+   //verbose=true
  
    inputs.dir(getdownWebsiteDir)
    inputs.file(install4jConfFile)
@@@ -1864,17 -1992,13 +1991,12 @@@ spotless 
  task sourceDist(type: Tar) {
    group "distribution"
    description "Create a source .tar.gz file for distribution"
-   
-   dependsOn convertMdFiles
  
+   dependsOn createBuildProperties
+   dependsOn convertMdFiles
 -
    def VERSION_UNDERSCORES = JALVIEW_VERSION.replaceAll("\\.", "_")
    def outputFileName = "${project.name}_${VERSION_UNDERSCORES}.tar.gz"
-   // cater for buildship < 3.1 [3.0.1 is max version in eclipse 2018-09]
-   try {
-     archiveFileName = outputFileName
-   } catch (Exception e) {
-     archiveName = outputFileName
-   }
+   archiveFileName = outputFileName
    
    compression Compression.GZIP
    
  //    exclude(EXCLUDE_FILES)
  //    exclude(PROCESS_FILES)
  //  }
 -
+   from(file(buildProperties).getParent()) {
+     include(file(buildProperties).getName())
+     rename(file(buildProperties).getName(), "build_properties")
+     filter({ line ->
+       line.replaceAll("^INSTALLATION=.*\$","INSTALLATION=Source Release"+" git-commit\\\\:"+gitHash+" ["+gitBranch+"]")
+     })
+   }
 -
  }
  
  
@@@ -1964,7 -2097,7 +2093,7 @@@ task helppages 
    dependsOn copyHelp
    dependsOn pubhtmlhelp
    
-   inputs.dir("${classesDir}/${help_dir}")
+   inputs.dir("${helpBuildDir}/${help_dir}")
    outputs.dir("${buildDir}/distributions/${help_dir}")
  }
  
@@@ -2001,8 -2134,8 +2130,6 @@@ task jalviewjsEnableAltFileProperty(typ
      property(jalviewjs_j2s_alt_file_property_config, jalviewjs_j2s_alt_file_property)
    }
  }
--
--
  task jalviewjsSetEclipseWorkspace {
    def propKey = "jalviewjs_eclipse_workspace"
    def propVal = null
@@@ -2266,13 -2399,19 +2393,17 @@@ task jalviewjsSyncAllLibs (type: Sync) 
    preserve {
      include "**"
    }
 -
+   // should this be exclude really ?
+   duplicatesStrategy "INCLUDE"
 -
    outputs.files outputFiles
    inputs.files inputFiles
  }
  
  
  task jalviewjsSyncResources (type: Sync) {
-   def inputFiles = fileTree(dir: resourceDir)
+   dependsOn buildResources
+   def inputFiles = fileTree(dir: resourcesBuildDir)
    def outputDir = "${jalviewDir}/${jalviewjsSiteDir}/${jalviewjs_j2s_subdir}"
  
    from inputFiles
@@@ -2418,12 -2557,12 +2549,12 @@@ DEBUG: ${eclipseDebug
          new org.apache.tools.ant.util.TeeOutputStream(
            logOutFOS,
            stdout),
-         standardOutput)
+         System.out)
        errorOutput = new org.apache.tools.ant.util.TeeOutputStream(
          new org.apache.tools.ant.util.TeeOutputStream(
            logErrFOS,
            stderr),
-         errorOutput)
+         System.err)
      } else {
        standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
          logOutFOS,
@@@ -2511,7 -2650,7 +2642,7 @@@ def jalviewjsCallCore(String name, File
            new org.apache.tools.ant.util.TeeOutputStream(
              logErrFOS,
              stderr),
-           errorOutput)
+           System.err)
      } else {
        standardOutput = new org.apache.tools.ant.util.TeeOutputStream(
          logOutFOS,
@@@ -2555,6 -2694,10 +2686,10 @@@ task jalviewjsBuildAllCores 
      ]
    }
  
+   // _jmol and _jalview cores. Add any other peculiar classlist.txt files here
+   //classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jmol}"), 'name': "_jvjmol" ]
+   classlistFiles += [ 'file': file("${jalviewDir}/${jalviewjs_classlist_jalview}"), 'name': jalviewjsJalviewCoreName ]
    jalviewjsCoreClasslists = []
  
    classlistFiles.each {
      }
      def list = fileTree(dir: j2sDir, includes: filelist)
  
-     def jsfile = "${outputDir}/core_${name}.js"
-     def zjsfile = "${outputDir}/core_${name}.z.js"
+     def jsfile = "${outputDir}/core${name}.js"
+     def zjsfile = "${outputDir}/core${name}.z.js"
  
      jalviewjsCoreClasslists += [
        'jsfile': jsfile,
      outputs.file(zjsfile)
    }
    
+   // _stevesoft core. add any cores without a classlist here (and the inputs and outputs)
+   def stevesoftClasslistName = "_stevesoft"
+   def stevesoftClasslist = [
+     'jsfile': "${outputDir}/core${stevesoftClasslistName}.js",
+     'zjsfile': "${outputDir}/core${stevesoftClasslistName}.z.js",
+     'list': fileTree(dir: j2sDir, include: "com/stevesoft/pat/**/*.js"),
+     'name': stevesoftClasslistName
+   ]
+   jalviewjsCoreClasslists += stevesoftClasslist
+   inputs.files(stevesoftClasslist['list'])
+   outputs.file(stevesoftClasslist['jsfile'])
+   outputs.file(stevesoftClasslist['zjsfile'])
    // _all core
-   def allClasslistName = "all"
+   def allClasslistName = "_all"
    def allJsFiles = fileTree(dir: j2sDir, include: "**/*.js")
    allJsFiles += fileTree(
      dir: libJ2sDir,
      ]
    )
    def allClasslist = [
-     'jsfile': "${outputDir}/core_${allClasslistName}.js",
-     'zjsfile': "${outputDir}/core_${allClasslistName}.z.js",
+     'jsfile': "${outputDir}/core${allClasslistName}.js",
+     'zjsfile': "${outputDir}/core${allClasslistName}.z.js",
      'list': allJsFiles,
      'name': allClasslistName
    ]
@@@ -2783,11 -2939,7 +2931,7 @@@ task jalviewjsSiteTar(type: Tar) 
    description "Creates a tar.gz file for the website"
    dependsOn jalviewjsBuildSite
    def outputFilename = "jalviewjs-site-${JALVIEW_VERSION}.tar.gz"
-   try {
-     archiveFileName = outputFilename
-   } catch (Exception e) {
-     archiveName = outputFilename
-   }
+   archiveFileName = outputFilename
  
    compression Compression.GZIP
  
@@@ -2805,7 -2957,13 +2949,13 @@@ task jalviewjsServer 
    def htmlFile = "${jalviewDirAbsolutePath}/${filename}"
    doLast {
  
-     SimpleHttpFileServerFactory factory = new SimpleHttpFileServerFactory()
+     def factory
+     try {
+       def f = Class.forName("org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory")
+       factory = f.newInstance()
+     } catch (ClassNotFoundException e) {
+       throw new GradleException("Unable to create SimpleHttpFileServerFactory")
+     }
      def port = Integer.valueOf(jalviewjs_server_port)
      def start = port
      def running = false
diff --combined doc/building.html
@@@ -1,10 -1,10 +1,10 @@@
--<html>
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
++<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <meta http-equiv="Content-Style-Type" content="text/css" />
--    <meta name="generator" content="flexmark" />
++  <title>Building Jalview from Source</title>
    <title>Building Jalview from Source</title>
      <style type="text/css">code{white-space: pre;}</style>
  <style>
@@@ -672,20 -672,20 +672,17 @@@ body :checked+.radio-label 
  </head>
    <body>
  <ul>
--<li><a href="#tldr">tl;dr</a></li>
--<li><a href="#setting-up">Setting up</a>
  <ul>
++<li><a href="#setting-up">Setting up</a>
++<li><a href="#tldr">tl;dr</a></li>
++<li><a href="#java-11-compliant-jdk">Java 11 compliant JDK</a></li>
  <li><a href="#java-11-compliant-jdk">Java 11 compliant JDK</a></li>
  <li><a href="#gradle-and-git">gradle and git</a></li>
--</ul>
  </li>
  <li><a href="#downloading-the-jalview-source-tree">Downloading the Jalview source tree</a>
--<ul>
++<li><a href="#whats-in-the-source-tree">What's in the source tree?</a></li>
  <li><a href="#whats-in-the-source-tree">What's in the source tree?</a></li>
  </ul>
--</li>
--<li><a href="#building-jalview">Building Jalview</a>
--<ul>
  <li><a href="#minimal-jalview-build">Minimal Jalview Build</a></li>
  <li><a href="#jalview-in-a-jar-file">Jalview in a Jar File</a></li>
  <li><a href="#distributed-jar-files">Distributed Jar Files</a></li>
  <li><a href="#building-the-getdown-launcher">Building the <code>getdown</code> launcher</a></li>
  <li><a href="#running-tests">Running tests</a></li>
  <li><a href="#installer-packaging-with-install4j">Installer packaging with <em>install4j</em></a></li>
--</ul>
--</li>
++<li><a href="#building-the-getdown-launcher">Building the <code>getdown</code> launcher</a></li>
  <li><a href="#gradle-properties">Gradle properties</a></li>
  <li><a href="#enabling-code-coverage-with-openclover">Enabling Code Coverage with OpenClover</a></li>
--<li><a href="#setting-up-in-eclipse-ide">Setting up in Eclipse IDE</a>
--<ul>
++</ul>
  <li><a href="#installing-eclipse-ide">Installing Eclipse IDE</a></li>
  <li><a href="#importing-jalview-as-an-eclipse-project">Importing Jalview as an Eclipse project</a></li>
++<li><a href="#enabling-code-coverage-with-openclover">Enabling Code Coverage with OpenClover</a></li>
++<li><a href="#setting-up-in-eclipse-ide">Setting up in Eclipse IDE</a>
  </ul>
  </li>
  </ul>
  <h1 id="building-jalview-from-source"><a href="#building-jalview-from-source" name="building-jalview-from-source" class="anchor"><span class="octicon octicon-link"></span>Building Jalview from Source</a></h1>
--<h2 id="tldr"><a href="#tldr" name="tldr" class="anchor"><span class="octicon octicon-link"></span>tl;dr</a></h2>
++</head>
++<pre><code># download
++git clone http://source.jalview.org/git/jalview.git
++<ul>
++cd jalview
++<li><a href="#tldr">tl;dr</a></li>
++# run
++<li><a href="#java-11-compliant-jdk">Java 11 compliant JDK</a></li>
++<li><a href="#gradle-and-git">gradle and git</a></li>
++# and/or create launcher
++gradle getdown
++<li><a href="#whats-in-the-source-tree">What's in the source tree?</a></li>
++cd getdown/files
++java -jar getdown-launcher.jar . jalview
++<li><a href="#minimal-jalview-build">Minimal Jalview Build</a></li>
++<li><a href="#jalview-in-a-jar-file">Jalview in a Jar File</a></li>
++<li><a href="#distributed-jar-files">Distributed Jar Files</a></li>
++<li><a href="#single-shadow-jar-file">Single <em>shadow</em> Jar File</a></li>
++<li><a href="#building-the-getdown-launcher">Building the <code>getdown</code> launcher</a></li>
++<li><a href="#running-tests">Running tests</a></li>
++<li><a href="#installer-packaging-with-install4j">Installer packaging with <em>install4j</em></a></li>
++<li>Java 11 compliant JDK</li>
++<li><a href="#gradle-properties">Gradle properties</a></li>
++<li><a href="#enabling-code-coverage-with-openclover">Enabling Code Coverage with OpenClover</a></li>
++</ul>
++<li><a href="#installing-eclipse-ide">Installing Eclipse IDE</a></li>
++<li><a href="#importing-jalview-as-an-eclipse-project">Importing Jalview as an Eclipse project</a></li>
++so are known to work).  If you need or wish to use different implementations (particularly
++you might need a bespoke JDK if you are on an exotic architecture) then the general
++</ul>
++bytecode with any JDK Java 11+.  The resulting bytecode (in particular the shadow jar)
++should be runnable in any JRE Java 1.8+.  Remember that because Jalview and the getdown launcher
++are Java bytecode you can build on one system where you might have gradle, and run
  <pre><code># download
  git clone http://source.jalview.org/git/jalview.git
  # compile
  cd jalview
  gradle shadowJar
  # run
- java -jar build/libs/jalview-all-11.jar
 -java -jar build/libs/jalview-all-*-j11.jar
++# compile
  
  # and/or create launcher
  gradle getdown
  # use launcher
  cd getdown/files
--java -jar getdown-launcher.jar . jalview
--</code></pre>
--<h2 id="setting-up"><a href="#setting-up" name="setting-up" class="anchor"><span class="octicon octicon-link"></span>Setting up</a></h2>
++java -jar getdown-launcher.jar . jalview</code></pre>
++<h2 id="setting-up">Setting up</h2>
  <blockquote>
--<p>To get set up using <em>only</em> the Eclipse IDE (<a href="https://www.eclipse.org/">https://www.eclipse.org/</a>) then please see the section <a href="#setting-up-in-eclipse-ide">Setting up in Eclipse IDE</a></p>
++<p>To get set up using <em>only</em> the Eclipse IDE (<a href="https://www.eclipse.org/" class="uri">https://www.eclipse.org/</a>) then please see the section <a href="#setting-up-in-eclipse-ide">Setting up in Eclipse IDE</a></p>
  </blockquote>
--<p>The method here is described in terms of using a command line.  You can easily do this on linux or in a Terminal window in macOS.  You can do it in Windows.</p>
++<p>The method here is described in terms of using a command line. You can easily do this on linux or in a Terminal window in macOS. You can do it in Windows.</p>
  <ul>
  <li>Java 11 compliant JDK</li>
- <li>gradle 5.2 or above</li>
 -<li>gradle 5.2 or above <em>(NB gradle 6.6 and above currently produces NullPointerExceptions during the build. This is non-fatal and does not affect the build. Use gradle 6.5.1 to avoid this)</em></li>
++<p>To get set up using <em>only</em> the Eclipse IDE (<a href="https://www.eclipse.org/">https://www.eclipse.org/</a>) then please see the section <a href="#setting-up-in-eclipse-ide">Setting up in Eclipse IDE</a></p>
  <li>git</li>
  </ul>
  <blockquote>
--<p>The versions and installation methods here are just suggestions (which we have tested
--so are known to work).  If you need or wish to use different implementations (particularly
--you might need a bespoke JDK if you are on an exotic architecture) then the general
--build instructions should work with any gradle 5+.  You should be able to compile the
--bytecode with any JDK Java 11+.  The resulting bytecode (in particular the shadow jar)
--should be runnable in any JRE Java 1.8+.  Remember that because Jalview and the getdown launcher
--are Java bytecode you can build on one system where you might have gradle, and run
--on another where you don't (JRE 1.8+ required).</p>
++<p>The versions and installation methods here are just suggestions (which we have tested so are known to work). If you need or wish to use different implementations (particularly you might need a bespoke JDK if you are on an exotic architecture) then the general build instructions should work with any gradle 5+. You should be able to compile the bytecode with any JDK Java 11+. The resulting bytecode (in particular the shadow jar) should be runnable in any JRE Java 1.8+. Remember that because Jalview and the getdown launcher are Java bytecode you can build on one system where you might have gradle, and run on another where you don't (JRE 1.8+ required).</p>
  </blockquote>
--<h3 id="java-11-compliant-jdk"><a href="#java-11-compliant-jdk" name="java-11-compliant-jdk" class="anchor"><span class="octicon octicon-link"></span>Java 11 compliant JDK</a></h3>
--<h4 id="all-platforms"><a href="#all-platforms" name="all-platforms" class="anchor"><span class="octicon octicon-link"></span>All platforms</a></h4>
--<p>We recommend obtaining an OpenJDK JDK 11 (since 11 is the long term support release) from AdoptOpenJDK: <a href="https://adoptopenjdk.net/?variant=openjdk11&amp;jvmVariant=hotspot">https://adoptopenjdk.net/?variant=openjdk11&amp;jvmVariant=hotspot</a>, either the <em>Installer</em> or <code>.zip</code>/<code>.tar.gz</code> variants whichever you prefer (if you're not sure, choose the <em>Installer</em>).</p>
++<h3 id="java-11-compliant-jdk">Java 11 compliant JDK</h3>
++<h4 id="all-platforms">All platforms</h4>
++<p>We recommend obtaining an OpenJDK JDK 11 (since 11 is the long term support release) from AdoptOpenJDK: <a href="https://adoptopenjdk.net/?variant=openjdk11&amp;jvmVariant=hotspot" class="uri">https://adoptopenjdk.net/?variant=openjdk11&amp;jvmVariant=hotspot</a>, either the <em>Installer</em> or <code>.zip</code>/<code>.tar.gz</code> variants whichever you prefer (if you're not sure, choose the <em>Installer</em>).</p>
  <blockquote>
--<h5 id="alternativecli-install-of-adoptopenjdk-11"><a href="#alternativecli-install-of-adoptopenjdk-11" name="alternativecli-install-of-adoptopenjdk-11" class="anchor"><span class="octicon octicon-link"></span>Alternative/CLI install of AdoptOpenJDK 11</a></h5>
--<p>You can also install adoptopenjdk11 using either <code>brew</code> (macOS), <code>choco</code> (Windows)
--(see the section on <code>gradle</code> and <code>git</code> for more informaiton on <code>brew</code> and <code>choco</code>)
--or <code>yum</code> or <code>apt</code> (Linux):</p>
--<h6 id="alternative-for-macos-and-homebrew"><a href="#alternative-for-macos-and-homebrew" name="alternative-for-macos-and-homebrew" class="anchor"><span class="octicon octicon-link"></span>alternative for MacOS and Homebrew</a></h6>
++<h5 id="alternativecli-install-of-adoptopenjdk-11">Alternative/CLI install of AdoptOpenJDK 11</h5>
++<p>You can also install adoptopenjdk11 using either <code>brew</code> (macOS), <code>choco</code> (Windows) (see the section on <code>gradle</code> and <code>git</code> for more informaiton on <code>brew</code> and <code>choco</code>) or <code>yum</code> or <code>apt</code> (Linux):</p>
++<h6 id="alternative-for-macos-and-homebrew">alternative for MacOS and Homebrew</h6>
  <pre><code>brew tap adoptopenjdk/openjdk
--brew cask install adoptopenjdk11
--</code></pre>
--<h6 id="alternative-for-windows-and-chocolatey"><a href="#alternative-for-windows-and-chocolatey" name="alternative-for-windows-and-chocolatey" class="anchor"><span class="octicon octicon-link"></span>alternative for Windows and Chocolatey</a></h6>
--<pre><code>choco install adoptopenjdk11
--</code></pre>
--<h6 id="alternative-for-linux-with-yumapt"><a href="#alternative-for-linux-with-yumapt" name="alternative-for-linux-with-yumapt" class="anchor"><span class="octicon octicon-link"></span>alternative for Linux with yum/apt</a></h6>
--<p>see <a href="https://adoptopenjdk.net/installation.html#linux-pkg">https://adoptopenjdk.net/installation.html#linux-pkg</a></p>
++brew cask install adoptopenjdk11</code></pre>
++<h6 id="alternative-for-windows-and-chocolatey">alternative for Windows and Chocolatey</h6>
++<pre><code>choco install adoptopenjdk11</code></pre>
++<h6 id="alternative-for-linux-with-yumapt">alternative for Linux with yum/apt</h6>
++<p>see <a href="https://adoptopenjdk.net/installation.html#linux-pkg" class="uri">https://adoptopenjdk.net/installation.html#linux-pkg</a></p>
  </blockquote>
--<h3 id="gradle-and-git"><a href="#gradle-and-git" name="gradle-and-git" class="anchor"><span class="octicon octicon-link"></span>gradle and git</a></h3>
++<h3 id="gradle-and-git">gradle and git</h3>
  <p>You should be able to install the latest (or sufficiently recent) versions of gradle and git using your OS package manager.</p>
--<h4 id="macos"><a href="#macos" name="macos" class="anchor"><span class="octicon octicon-link"></span>MacOS</a></h4>
--<p>we recommend using <code>brew</code>, which can be installed following the instructions at <a href="https://brew.sh/">https://brew.sh/</a>.
--After installing <code>brew</code>, open a Terminal window and type in (using an Administrator privileged user):</p>
--<pre><code class="language-bash">brew install gradle git
--</code></pre>
++<h4 id="macos">MacOS</h4>
++<p>we recommend using <code>brew</code>, which can be installed following the instructions at <a href="https://brew.sh/" class="uri">https://brew.sh/</a>. After installing <code>brew</code>, open a Terminal window and type in (using an Administrator privileged user):</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">brew</span> install gradle git</code></pre></div>
  <p>or if you aready have them installed but need to upgrade the version:</p>
--<pre><code class="language-bash">brew upgrade gradle git
--</code></pre>
--<h4 id="windows"><a href="#windows" name="windows" class="anchor"><span class="octicon octicon-link"></span>Windows</a></h4>
--<p>we suggest using the <strong>Chocolatey</strong> package manager.  See install instructions at <a href="https://chocolatey.org/">https://chocolatey.org/</a>, and you will just need</p>
--<pre><code class="language-bash">choco install gradle
--choco install git
--</code></pre>
--<p>Alternatively, you could install a real <code>bash</code> shell and install both <code>gradle</code> and <code>git</code> through <code>apt-get</code>.
--See <a href="https://devblogs.microsoft.com/commandline/bash-on-ubuntu-on-windows-download-now-3/">https://devblogs.microsoft.com/commandline/bash-on-ubuntu-on-windows-download-now-3/</a>
--for how to install the ubuntu bash shell in Windows 10.</p>
--<p>Another alternative would be to install them separately. For <code>gradle</code> follow the instructions at <a href="https://gradle.org/install/">https://gradle.org/install/</a>, and for <code>git</code> here are a couple of suggestions: Git for Windows <a href="https://gitforwindows.org/">https://gitforwindows.org/</a>.
--Getting the individual installs working together on the command line will be trickier
--so we recommend using Chocolatey or bash.</p>
--<h4 id="linux"><a href="#linux" name="linux" class="anchor"><span class="octicon octicon-link"></span>Linux</a></h4>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">brew</span> upgrade gradle git</code></pre></div>
++<h4 id="windows">Windows</h4>
++<p>we suggest using the <strong>Chocolatey</strong> package manager. See install instructions at <a href="https://chocolatey.org/" class="uri">https://chocolatey.org/</a>, and you will just need</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">choco</span> install gradle
++<span class="ex">choco</span> install git</code></pre></div>
++<p>Alternatively, you could install a real <code>bash</code> shell and install both <code>gradle</code> and <code>git</code> through <code>apt-get</code>. See <a href="https://devblogs.microsoft.com/commandline/bash-on-ubuntu-on-windows-download-now-3/" class="uri">https://devblogs.microsoft.com/commandline/bash-on-ubuntu-on-windows-download-now-3/</a> for how to install the ubuntu bash shell in Windows 10.</p>
++<p>Another alternative would be to install them separately. For <code>gradle</code> follow the instructions at <a href="https://gradle.org/install/" class="uri">https://gradle.org/install/</a>, and for <code>git</code> here are a couple of suggestions: Git for Windows <a href="https://gitforwindows.org/" class="uri">https://gitforwindows.org/</a>. Getting the individual installs working together on the command line will be trickier so we recommend using Chocolatey or bash.</p>
++<h4 id="linux">Linux</h4>
  <p>this will depend on which distribution you're using.</p>
--<h5 id="for-debian-based-distributions-eg-mint-ubuntu-debian"><a href="#for-debian-based-distributions-eg-mint-ubuntu-debian" name="for-debian-based-distributions-eg-mint-ubuntu-debian" class="anchor"><span class="octicon octicon-link"></span>For <em>Debian</em> based distributions (e.g. Mint, Ubuntu, Debian)</a></h5>
++<h5 id="for-debian-based-distributions-e.g.-mint-ubuntu-debian">For <em>Debian</em> based distributions (e.g. Mint, Ubuntu, Debian)</h5>
  <p>run</p>
--<pre><code class="language-bash"> sudo apt-get install gradle git
--</code></pre>
--<h5 id="for-rpm-based-distributions-eg-fedora-centos-redhat"><a href="#for-rpm-based-distributions-eg-fedora-centos-redhat" name="for-rpm-based-distributions-eg-fedora-centos-redhat" class="anchor"><span class="octicon octicon-link"></span>for RPM-based distributions (e.g. Fedora, CentOS, RedHat)</a></h5>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"> <span class="fu">sudo</span> apt-get install gradle git</code></pre></div>
++<h5 id="for-rpm-based-distributions-e.g.-fedora-centos-redhat">for RPM-based distributions (e.g. Fedora, CentOS, RedHat)</h5>
  <p>run</p>
--<pre><code class="language-bash">sudo yum install gradle git
--</code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">sudo</span> yum install gradle git</code></pre></div>
  <p>If you have some other version of linux you'll probably be able to work it out!</p>
--<h2 id="downloading-the-jalview-source-tree"><a href="#downloading-the-jalview-source-tree" name="downloading-the-jalview-source-tree" class="anchor"><span class="octicon octicon-link"></span>Downloading the Jalview source tree</a></h2>
--<p>This can be done with <code>git</code>.
--On the command line, change directory to where you want to download Jalview's build-tree
--top level directory.  Then run</p>
--<pre><code class="language-bash">git clone http://source.jalview.org/git/jalview.git
--</code></pre>
--<p>You'll get some progress output and after a minute or two you should have the full
--Jalview build-tree in the folder <code>jalview</code>.</p>
--<h3 id="whats-in-the-source-tree"><a href="#whats-in-the-source-tree" name="whats-in-the-source-tree" class="anchor"><span class="octicon octicon-link"></span>What's in the source tree?</a></h3>
--<p>Jalview is a mature product with its codebase going back many years.  As such it doesn't
--have a folder structure that most new gradle projects would have, so you might not
--find everything in the place you might expect.  Here's a brief description of what
--you might find in the main folders under the <code>jalview</code> tree.</p>
++<h2 id="downloading-the-jalview-source-tree">Downloading the Jalview source tree</h2>
++<p>This can be done with <code>git</code>. On the command line, change directory to where you want to download Jalview's build-tree top level directory. Then run</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">git</span> clone http://source.jalview.org/git/jalview.git</code></pre></div>
++<p>You'll get some progress output and after a minute or two you should have the full Jalview build-tree in the folder <code>jalview</code>.</p>
++<h3 id="whats-in-the-source-tree">What's in the source tree?</h3>
++<p>Jalview is a mature product with its codebase going back many years. As such it doesn't have a folder structure that most new gradle projects would have, so you might not find everything in the place you might expect. Here's a brief description of what you might find in the main folders under the <code>jalview</code> tree.</p>
  <p>Within the <code>jalview</code> folder you will find (of possible interest):</p>
  <table>
++<colgroup>
++<col width="16%" />
++<col width="83%" />
++</colgroup>
  <thead>
--<tr><th>dir/ or file</th><th>contains</th></tr>
++<tr class="header">
++<th>dir/ or file</th>
++<th>contains</th>
++</tr>
  </thead>
  <tbody>
--<tr><td><code>bin/</code></td><td>used by eclipse for compiled classes -- no need to touch this</td></tr>
--<tr><td><code>build/</code></td><td>the gradle build dir</td></tr>
--<tr><td><code>classes/</code></td><td>contains the compiled Java classes for the Jalview application</td></tr>
--<tr><td><code>dist/</code></td><td>assembled <code>.jar</code> files needed to run Jalview application</td></tr>
--<tr><td><code>examples/</code></td><td>example input files usable by Jalview</td></tr>
--<tr><td><code>getdown/</code></td><td>the libraries used by the Javliew launcher (getdown)</td></tr>
--<tr><td><code>getdown/src/</code></td><td>our modified source for <code>getdown</code></td></tr>
--<tr><td><code>getdown/website/</code></td><td>the assembled &quot;download&quot; folder used by getdown for downloads/upgrades</td></tr>
--<tr><td><code>getdown/files/</code></td><td>the minimal fileset to launch the Jalview launcher, which can then download the rest of the Jalview application</td></tr>
--<tr><td><code>help/</code></td><td>the help documents</td></tr>
--<tr><td><code>j8lib/</code></td><td>libraries needed to run Jalview under Java 1.8</td></tr>
--<tr><td><code>j11lib/</code></td><td>libraries needed to run Jalivew under Java 11</td></tr>
--<tr><td><code>resource/</code></td><td>non-java resources used in the Jalview application</td></tr>
--<tr><td><code>src/</code></td><td>the Jalview application source <code>.java</code> files</td></tr>
--<tr><td><code>test/</code></td><td>Test class source files</td></tr>
--<tr><td><code>utils/</code></td><td>helper applications used in the build process</td></tr>
--<tr><td><code>utils/install4j/</code></td><td>files used by the packaging tool, install4j</td></tr>
--<tr><td><code>build.gradle</code></td><td>the build file used by gradle</td></tr>
--<tr><td><code>gradle.properties</code></td><td>configurable properties for the build process</td></tr>
--<tr><td><code>RELEASE</code></td><td>propertyfile configuring JALVIEW_VERSION (from jalview.version) and the release branch (from jalview.release). An alternative file can be specified via JALVIEW_RELEASE_FILE property</td></tr>
++<tr class="odd">
++<td><code>bin/</code></td>
++<td>used by eclipse for compiled classes -- no need to touch this</td>
++</tr>
++<tr class="even">
++<td><code>build/</code></td>
++<td>the gradle build dir</td>
++</tr>
++<tr class="odd">
++<td><code>classes/</code></td>
++<td>contains the compiled Java classes for the Jalview application</td>
++</tr>
++<tr class="even">
++<td><code>dist/</code></td>
++<td>assembled <code>.jar</code> files needed to run Jalview application</td>
++</tr>
++<tr class="odd">
++<td><code>examples/</code></td>
++<td>example input files usable by Jalview</td>
++</tr>
++<tr class="even">
++<td><code>getdown/</code></td>
++<td>the libraries used by the Javliew launcher (getdown)</td>
++</tr>
++<tr class="odd">
++<td><code>getdown/src/</code></td>
++<td>our modified source for <code>getdown</code></td>
++</tr>
++<tr class="even">
++<td><code>getdown/website/</code></td>
++<td>the assembled &quot;download&quot; folder used by getdown for downloads/upgrades</td>
++</tr>
++<tr class="odd">
++<td><code>getdown/files/</code></td>
++<td>the minimal fileset to launch the Jalview launcher, which can then download the rest of the Jalview application</td>
++</tr>
++<tr class="even">
++<td><code>help/</code></td>
++<td>the help documents</td>
++</tr>
++<tr class="odd">
++<td><code>j8lib/</code></td>
++<td>libraries needed to run Jalview under Java 1.8</td>
++</tr>
++<tr class="even">
++<td><code>j11lib/</code></td>
++<td>libraries needed to run Jalivew under Java 11</td>
++</tr>
++<tr class="odd">
++<td><code>resource/</code></td>
++<td>non-java resources used in the Jalview application</td>
++</tr>
++<tr class="even">
++<td><code>src/</code></td>
++<td>the Jalview application source <code>.java</code> files</td>
++</tr>
++<tr class="odd">
++<td><code>test/</code></td>
++<td>Test class source files</td>
++</tr>
++<tr class="even">
++<td><code>utils/</code></td>
++<td>helper applications used in the build process</td>
++</tr>
++<tr class="odd">
++<td><code>utils/install4j/</code></td>
++<td>files used by the packaging tool, install4j</td>
++</tr>
++<tr class="even">
++<td><code>build.gradle</code></td>
++<td>the build file used by gradle</td>
++</tr>
++<tr class="odd">
++<td><code>gradle.properties</code></td>
++<td>configurable properties for the build process</td>
++</tr>
  </tbody>
  </table>
  <p>Note that you need a Java 11 JDK to compile Jalview whether your target build is Java 1.8 or Java 11.</p>
--<h2 id="building-jalview"><a href="#building-jalview" name="building-jalview" class="anchor"><span class="octicon octicon-link"></span>Building Jalview</a></h2>
--<p>You will need to have the Java 11 <code>javac</code> in your path, or alternatively you can configure
--gradle to know where this is by putting</p>
--<pre><code>org.gradle.java.home=/path_to_jdk_directory
--</code></pre>
++<h2 id="building-jalview">Building Jalview</h2>
++<p>You will need to have the Java 11 <code>javac</code> in your path, or alternatively you can configure gradle to know where this is by putting</p>
++<pre><code>org.gradle.java.home=/path_to_jdk_directory</code></pre>
  <p>in the <code>gradle.properties</code> file.</p>
  <blockquote>
  <p><em>You may want to see some of the other properties you can change at the end of this document.</em></p>
  </blockquote>
--<h3 id="minimal-jalview-build"><a href="#minimal-jalview-build" name="minimal-jalview-build" class="anchor"><span class="octicon octicon-link"></span>Minimal Jalview Build</a></h3>
++<h3 id="minimal-jalview-build">Minimal Jalview Build</h3>
  <p>To compile the necessary class files, just run</p>
--<pre><code class="language-bash">gradle compileJava
--</code></pre>
--<p>to compile the classes into the <code>classes</code> folder.
--You should now be able to run the Jalview application directly with</p>
--<pre><code class="language-bash">java -cp &quot;classes:resources:help:j11lib/*&quot; jalview.bin.Jalview
--</code></pre>
--<p>You can also run with an automatic large memory setting (which will set the maximum
--memory heap of the Jalview JVM to 90% of your local physical memory) and docked icon setting
--(if possible in your OS) with</p>
--<pre><code class="language-bash">java -cp &quot;classes:resources:help:j11lib/*&quot; jalview.bin.Launcher
--</code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> compileJava</code></pre></div>
++<p>to compile the classes into the <code>classes</code> folder. You should now be able to run the Jalview application directly with</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">java</span> -cp <span class="st">&quot;classes:resources:help:j11lib/*&quot;</span> jalview.bin.Jalview</code></pre></div>
++<p>You can also run with an automatic large memory setting (which will set the maximum memory heap of the Jalview JVM to 90% of your local physical memory) and docked icon setting (if possible in your OS) with</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">java</span> -cp <span class="st">&quot;classes:resources:help:j11lib/*&quot;</span> jalview.bin.Launcher</code></pre></div>
  <blockquote>
--<p><em>You must use just &quot;<code>j11lib/*</code>&quot; and not &quot;<code>j11lib/*.jar</code>&quot; as this is a special Java
--classpath argument wildcard interpreted by <code>java</code>, <strong>not</strong> a shell expansion wildcard interpreted
--by the shell.</em></p>
++<p><em>You must use just &quot;<code>j11lib/*</code>&quot; and not &quot;<code>j11lib/*.jar</code>&quot; as this is a special Java classpath argument wildcard interpreted by <code>java</code>, <strong>not</strong> a shell expansion wildcard interpreted by the shell.</em></p>
  </blockquote>
--<p>Note that <code>jalview.bin.Launcher</code> is a simplified launcher class that re-launches <code>jalview.bin.Jalview</code>
--with the same JRE (<em>not</em> the same JVM instance), classpath and arguments, but with an automatically determined <code>-Xmx...</code>
--memory setting if one hasn't been provided.</p>
--<h3 id="jalview-in-a-jar-file"><a href="#jalview-in-a-jar-file" name="jalview-in-a-jar-file" class="anchor"><span class="octicon octicon-link"></span>Jalview in a Jar File</a></h3>
++<p>Note that <code>jalview.bin.Launcher</code> is a simplified launcher class that re-launches <code>jalview.bin.Jalview</code> with the same JRE (<em>not</em> the same JVM instance), classpath and arguments, but with an automatically determined <code>-Xmx...</code> memory setting if one hasn't been provided.</p>
++<h3 id="jalview-in-a-jar-file">Jalview in a Jar File</h3>
  <p>To package the <code>classes</code>, <code>resources</code>, and <code>help</code> into one jar, you can run</p>
--<pre><code class="language-bash">gradle jar
--</code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> jar</code></pre></div>
  <p>which assembles the Jalview classes and resources into <code>dist/jalview.jar</code></p>
  <p>To run this, use</p>
--<pre><code class="language-bash">java -cp &quot;dist/jalview.jar:j11lib/*&quot; jalview.bin.Jalview
--</code></pre>
--<h3 id="distributed-jar-files"><a href="#distributed-jar-files" name="distributed-jar-files" class="anchor"><span class="octicon octicon-link"></span>Distributed Jar Files</a></h3>
--<p>To simplify this, all required <code>.jar</code> files can be assembled into the <code>dist</code> folder
--using</p>
--<pre><code class="language-bash">gradle makeDist
--</code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">java</span> -cp <span class="st">&quot;dist/jalview.jar:j11lib/*&quot;</span> jalview.bin.Jalview</code></pre></div>
++<h3 id="distributed-jar-files">Distributed Jar Files</h3>
++<p>To simplify this, all required <code>.jar</code> files can be assembled into the <code>dist</code> folder using</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> makeDist</code></pre></div>
  <p>which puts all required jar files into <code>dist</code> so you can run with</p>
--<pre><code class="language-bash">java -cp &quot;dist/*&quot; jalview.bin.Jalview
--</code></pre>
--<h3 id="single-shadow-jar-file"><a href="#single-shadow-jar-file" name="single-shadow-jar-file" class="anchor"><span class="octicon octicon-link"></span>Single <em>shadow</em> Jar File</a></h3>
--<p>The shadow jar file is a single <code>.jar</code> that contains all required classes and resources from <code>jalview.jar</code>
--and all of the supporting libraries in <code>j11lib/*.jar</code> merged into one <code>.jar</code> archive
--file.  A default launching class (<code>MAIN-CLASS: jalview.bin.Launcher</code>) is specified in the <code>.jar</code>
--manifest file (<code>META/MANIFEST.MF</code>) so a start class doesn't need to be specified.</p>
 -<p>Build the shadow jar file in <code>build/libs/jalview-all-VERSION-j11.jar</code> with</p>
 -<pre><code class="language-bash">gradle shadowJar
 -</code></pre>
 -<p><strong>NB</strong> <code>VERSION</code> will be replaced with a version number or &quot;<code>DEVELOPMENT</code>&quot; or &quot;<code>TEST</code>&quot; depending on how the branch is set up.</p>
 -<p>Run it with</p>
 -<pre><code class="language-bash">java -jar build/libs/jalview-all-VERSION-j11.jar
 -</code></pre>
 -<p>Because no arguments are required, most OSes will associate a <code>.jar</code> file with the
 -<code>java</code> application (if this has been installed through the OS and not just a local
 -unzip) as a <code>-jar</code> argument so you may find you can launch <code>jalview-all-VERSION-j11.jar</code>
 -just by double-clicking on it)!</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">java</span> -cp <span class="st">&quot;dist/*&quot;</span> jalview.bin.Jalview</code></pre></div>
++<h3 id="single-shadow-jar-file">Single <em>shadow</em> Jar File</h3>
++<p>The shadow jar file is a single <code>.jar</code> that contains all required classes and resources from <code>jalview.jar</code> and all of the supporting libraries in <code>j11lib/*.jar</code> merged into one <code>.jar</code> archive file. A default launching class (<code>MAIN-CLASS: jalview.bin.Launcher</code>) is specified in the <code>.jar</code> manifest file (<code>META/MANIFEST.MF</code>) so a start class doesn't need to be specified.</p>
 +<p>Build the shadow jar file in <code>build/lib/jalview-all-11.jar</code> with</p>
- <pre><code class="language-bash">gradle shadowJar
- </code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> shadowJar</code></pre></div>
 +<p>and run it with</p>
- <pre><code class="language-bash">java -jar build/lib/jalview-all-11.jar
- </code></pre>
- <p>Because no arguments are required, most OSes will associate a <code>.jar</code> file with the
- <code>java</code> application (if this has been installed through the OS and not just a local
- unzip) as a <code>-jar</code> argument so you may find you can launch <code>jalview-all-11.jar</code>
- just by double-clicking on it)!</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">java</span> -jar build/lib/jalview-all-11.jar</code></pre></div>
++<p>Because no arguments are required, most OSes will associate a <code>.jar</code> file with the <code>java</code> application (if this has been installed through the OS and not just a local unzip) as a <code>-jar</code> argument so you may find you can launch <code>jalview-all-11.jar</code> just by double-clicking on it)!</p>
  <blockquote>
--<p>The <code>shadowJar</code> task is not a requirement for any other task, so to build the shadow
--jar file you must specify the <code>shadowJar</code> task.</p>
++<p>The <code>shadowJar</code> task is not a requirement for any other task, so to build the shadow jar file you must specify the <code>shadowJar</code> task.</p>
  </blockquote>
  <blockquote>
--<p>The shadow jar file represents probably the simplest way to distribute the Jalview application to machines that already have a Java 11 installed,
--although without the many and compelling benefits of the <code>getdown</code> launcher.</p>
++<p>The shadow jar file represents probably the simplest way to distribute the Jalview application to machines that already have a Java 11 installed, although without the many and compelling benefits of the <code>getdown</code> launcher.</p>
  </blockquote>
--<h3 id="building-the-getdown-launcher"><a href="#building-the-getdown-launcher" name="building-the-getdown-launcher" class="anchor"><span class="octicon octicon-link"></span>Building the <code>getdown</code> launcher</a></h3>
--<p>We have made significant customisations to the <code>getdown</code> launcher which you can find
--in <code>getdown/src/getdown</code>.</p>
++<h3 id="building-the-getdown-launcher">Building the <code>getdown</code> launcher</h3>
++<p>We have made significant customisations to the <code>getdown</code> launcher which you can find in <code>getdown/src/getdown</code>.</p>
  <blockquote>
--<p>You don't need to build this afresh as the required <code>gradle-core.jar</code>
--and <code>gradle-launcher.jar</code> files are already distributed in <code>j11lib</code> and <code>getdown/lib</code> but if you want to, then
--you'll need a working Maven and also a Java 8 JDK.  Ensure the Java 8 <code>javac</code> is forefront
--in your path and do</p>
--<pre><code class="language-bash">cd getdown/src/getdown
--mvn clean package -Dgetdown.host.whitelist=&quot;jalview.org,*.jalview.org&quot;
--</code></pre>
--<p>and you will find the required <code>.jar</code> files in <code>core/target/gradle-core-XXX.jar</code>
--and <code>launcher/target/gradle-launcher-XXX.jar</code>.  The <code>gradle-core.jar</code> should then be copied
--to all three of the <code>j8lib</code>, <code>j11lib</code> and <code>getdown/lib</code> folders, whilst the <code>gradle-launcher.jar</code> only
--needs to be copied to <code>getdown/lib</code>.</p>
--<p>The <code>mvn</code> command should ideally include the <code>-Dgetdown.host.whitelist=*.jalview.org</code> setting.
--This, and the necessary file copying commands, can be found in <code>getdown/src/getdown/mvn_cmd</code>.</p>
++<p>You don't need to build this afresh as the required <code>gradle-core.jar</code> and <code>gradle-launcher.jar</code> files are already distributed in <code>j11lib</code> and <code>getdown/lib</code> but if you want to, then you'll need a working Maven and also a Java 8 JDK. Ensure the Java 8 <code>javac</code> is forefront in your path and do</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="bu">cd</span> getdown/src/getdown
++<span class="ex">mvn</span> clean package -Dgetdown.host.whitelist=<span class="st">&quot;jalview.org,*.jalview.org&quot;</span></code></pre></div>
++<p>and you will find the required <code>.jar</code> files in <code>core/target/gradle-core-XXX.jar</code> and <code>launcher/target/gradle-launcher-XXX.jar</code>. The <code>gradle-core.jar</code> should then be copied to all three of the <code>j8lib</code>, <code>j11lib</code> and <code>getdown/lib</code> folders, whilst the <code>gradle-launcher.jar</code> only needs to be copied to <code>getdown/lib</code>.</p>
++<p>The <code>mvn</code> command should ideally include the <code>-Dgetdown.host.whitelist=*.jalview.org</code> setting. This, and the necessary file copying commands, can be found in <code>getdown/src/getdown/mvn_cmd</code>.</p>
  </blockquote>
  <p>To assemble Jalview with <code>getdown</code> use the following gradle task:</p>
--<pre><code class="language-bash">gradle getdown
--</code></pre>
--<p>This puts all the necessary files to launch Jalview with <code>getdown</code>
--into <code>getdown/website/11/</code>.  This could be treated as the reference folder
--for <code>getdown</code>, which is where a getdown launcher will check to see if the Jalview application
--files it has are up to date, and download if they aren't or it simply doesn't have
--them.</p>
--<p>A minimal getdown-launcher can be found in <code>getdown/files/11/</code> which checks its up-to-date
--status with (the absolute path to) <code>getdown/website/11/</code>.</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> getdown</code></pre></div>
++<p>This puts all the necessary files to launch Jalview with <code>getdown</code> into <code>getdown/website/11/</code>. This could be treated as the reference folder for <code>getdown</code>, which is where a getdown launcher will check to see if the Jalview application files it has are up to date, and download if they aren't or it simply doesn't have them.</p>
++<p>A minimal getdown-launcher can be found in <code>getdown/files/11/</code> which checks its up-to-date status with (the absolute path to) <code>getdown/website/11/</code>.</p>
  <p>This can be launched with</p>
--<pre><code class="language-bash">java -jar getdown/files/11/getdown-launcher.jar getdown/files/11/ jalview
--</code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">java</span> -jar getdown/files/11/getdown-launcher.jar getdown/files/11/ jalview</code></pre></div>
  <blockquote>
--<p>We've already met the <code>-jar file.jar</code> arguments.  The next argument is the working folder for
--getdown, and the final argument, &quot;<code>jalview</code>&quot;, is a getdown application id (only &quot;<code>jalview</code>&quot;
--is defined here).</p>
++<p>We've already met the <code>-jar file.jar</code> arguments. The next argument is the working folder for getdown, and the final argument, &quot;<code>jalview</code>&quot;, is a getdown application id (only &quot;<code>jalview</code>&quot; is defined here).</p>
  </blockquote>
--<h3 id="running-tests"><a href="#running-tests" name="running-tests" class="anchor"><span class="octicon octicon-link"></span>Running tests</a></h3>
++<h3 id="running-tests">Running tests</h3>
  <p>There are substantial tests written for Jalview that use TestNG, which you can run with</p>
--<pre><code class="language-bash">gradle test
--</code></pre>
--<p>These normally take around 5 - 10 minutes to complete and outputs its full results into
--the <code>tests/</code> folder.  A summary of results should appear in your console.</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> test</code></pre></div>
++<p>These normally take around 5 - 10 minutes to complete and outputs its full results into the <code>tests/</code> folder. A summary of results should appear in your console.</p>
  <p>You can run different defined groups of tests with</p>
--<pre><code class="language-bash">gradle test -PtestngGroups=Network
--</code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> test -PtestngGroups=Network</code></pre></div>
  <p>Available groups include Functional (default), Network, External.</p>
--<h4 id="excluding-some-tests"><a href="#excluding-some-tests" name="excluding-some-tests" class="anchor"><span class="octicon octicon-link"></span>Excluding some tests</a></h4>
++<h4 id="excluding-some-tests">Excluding some tests</h4>
  <p>Some of Jalview's Functional tests don't pass reliably in all environments. We tag these tests with a group like 'Not-bamboo' to mark them for exclusion when we run tests as part of continuous integration.</p>
  <p>To exclude one or more groups of tests, add them as a comma separated list in testngExcludedGroups.</p>
--<pre><code class="language-bash">gradle test -PtestngExcludedGroups=Not-bamboo
--</code></pre>
--<h3 id="installer-packaging-with-install4j"><a href="#installer-packaging-with-install4j" name="installer-packaging-with-install4j" class="anchor"><span class="octicon octicon-link"></span>Installer packaging with <em>install4j</em></a></h3>
--<p>Jalview is currently using <em>install4j</em> <a href="https://www.ej-technologies.com/products/install4j/overview.html">https://www.ej-technologies.com/products/install4j/overview.html</a>
--as its installer packaging tool.</p>
--<p>If you have a licensed installation of <em>install4j</em> you can build Jalview installers
--by running</p>
--<pre><code class="language-bash">gradle installers
--</code></pre>
--<p>though you may need to fiddle with the <code>install4j</code> and <code>copyInstall4jTemplate</code> tasks
--in <code>build.gradle</code> file to point to your installation of <em>install4j</em> and also to bundled
--JREs if you want to bundle those into the installers.</p>
--<p>If you want more details, get in touch on our development mailing list <a href="mailto:jalview-dev@jalview.org">jalview-dev@jalview.org</a>.
--Sign up at <a href="http://www.compbio.dundee.ac.uk/mailman/listinfo/jalview-dev">http://www.compbio.dundee.ac.uk/mailman/listinfo/jalview-dev</a>.</p>
--<h2 id="gradle-properties"><a href="#gradle-properties" name="gradle-properties" class="anchor"><span class="octicon octicon-link"></span>Gradle properties</a></h2>
--<p>There are a lot of properties configured in <code>gradle.properties</code> which we strongly recommend
--being left as they are unless you have a specific problem with the build process.</p>
--<p>There are a few gradle properties you might want to set on the command line with the
--<code>-P</code> flag when building a version of Jalview with specific requirements:</p>
--<h4 id="java-version"><a href="#java-version" name="java-version" class="anchor"><span class="octicon octicon-link"></span><code>JAVA_VERSION</code></a></h4>
--<p>This changes the <em>target</em> java bytecode version</p>
--<blockquote>
--<p>NOTE that you will need to use a Java 11 (or greater) JDK Java compiler to build
--Jalview for any byte-code target version.</p>
--</blockquote>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> test -PtestngExcludedGroups=Not-bamboo</code></pre></div>
++<h3 id="installer-packaging-with-install4j">Installer packaging with <em>install4j</em></h3>
++<p>Jalview is currently using <em>install4j</em> <a href="https://www.ej-technologies.com/products/install4j/overview.html" class="uri">https://www.ej-technologies.com/products/install4j/overview.html</a> as its installer packaging tool.</p>
++<p>If you have a licensed installation of <em>install4j</em> you can build Jalview installers by running</p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> installers</code></pre></div>
++<p>though you may need to fiddle with the <code>install4j</code> and <code>copyInstall4jTemplate</code> tasks in <code>build.gradle</code> file to point to your installation of <em>install4j</em> and also to bundled JREs if you want to bundle those into the installers.</p>
++<p>If you want more details, get in touch on our development mailing list <a href="mailto:jalview-dev@jalview.org">jalview-dev@jalview.org</a>. Sign up at <a href="http://www.compbio.dundee.ac.uk/mailman/listinfo/jalview-dev" class="uri">http://www.compbio.dundee.ac.uk/mailman/listinfo/jalview-dev</a>.</p>
++<h2 id="gradle-properties">Gradle properties</h2>
++<p>There are a lot of properties configured in <code>gradle.properties</code> which we strongly recommend being left as they are unless you have a specific problem with the build process.</p>
++<p>There are a few gradle properties you might want to set on the command line with the <code>-P</code> flag when building a version of Jalview with specific requirements:</p>
++<h4 id="java_version"><code>JAVA_VERSION</code></h4>
++<p>This changes the <em>target</em> java bytecode version &gt; NOTE that you will need to use a Java 11 (or greater) JDK Java compiler to build Jalview for any byte-code target version.</p>
  <p>Valid values are <code>11</code> and <code>1.8</code>.</p>
  <p>e.g.</p>
--<pre><code class="language-bash">gradle shadowJar -PJAVA_VERSION=1.8
--</code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> shadowJar -PJAVA_VERSION=1.8</code></pre></div>
  <p>When using <code>-PJAVA_VERSION=1.8</code> the libraries from <code>j8lib</code> (instead of <code>j11lib</code>) will be used in the compile<br />
--and runtime classpath and also used in the <code>makeDist</code> build step.  Where a Java version of <code>11</code> is used in folder and file names, it will
--instead use <code>1.8</code>.  Also if you are building installer packages with <em>install4j</em> the
--package builder will look for JRE 1.8 bundles to package in the installers.</p>
++and runtime classpath and also used in the <code>makeDist</code> build step. Where a Java version of <code>11</code> is used in folder and file names, it will instead use <code>1.8</code>. Also if you are building installer packages with <em>install4j</em> the package builder will look for JRE 1.8 bundles to package in the installers.</p>
  <blockquote>
--<p>Note that continued development of Jalview will assume a Java 11+ runtime environment,
--the 2.11.0 release will run under a Java 1.8 JRE with a few minor features disabled.</p>
++<p>Note that continued development of Jalview will assume a Java 11+ runtime environment, the 2.11.0 release will run under a Java 1.8 JRE with a few minor features disabled.</p>
  </blockquote>
--<h4 id="channel"><a href="#channel" name="channel" class="anchor"><span class="octicon octicon-link"></span><code>CHANNEL</code></a></h4>
--<p>This changes the <code>appbase</code> setting in <code>getdown.txt</code> (<code>appbase</code> is where the getdown launcher
--looks to see if there's an updated file) to point to a particular Jalview channel or some other appropriate
--place to look for required files.  If the selected channel type requires the getdown <code>appbase</code> to be a local
--directory on the filesystem (instead of a website URL) then a modified version of the <code>getdown-launcher.jar</code> will
--be used to allow this.  The two versions of the <code>getdown-launcher.jar</code> can be found in <code>getdown/lib</code>.
--Some other variables used in the build process might also be set differently depending on the value of <code>CHANNEL</code>
--to allow smooth operation of getdown in the given context.</p>
--<p>There are several values of <code>CHANNEL</code> that can be chosen, with a default of <code>LOCAL</code>.  Here's what they're for and what they do:</p>
++<h4 id="channel"><code>CHANNEL</code></h4>
++<p>This changes the <code>appbase</code> setting in <code>getdown.txt</code> (<code>appbase</code> is where the getdown launcher looks to see if there's an updated file) to point to a particular Jalview channel or some other appropriate place to look for required files. If the selected channel type requires the getdown <code>appbase</code> to be a local directory on the filesystem (instead of a website URL) then a modified version of the <code>getdown-launcher.jar</code> will be used to allow this. The two versions of the <code>getdown-launcher.jar</code> can be found in <code>getdown/lib</code>. Some other variables used in the build process might also be set differently depending on the value of <code>CHANNEL</code> to allow smooth operation of getdown in the given context.</p>
++<p>There are several values of <code>CHANNEL</code> that can be chosen, with a default of <code>LOCAL</code>. Here's what they're for and what they do:</p>
  <ul>
--<li><code>LOCAL</code>: This is for running the compiled application from the development directory.
--It will set
++<li><code>LOCAL</code>: This is for running the compiled application from the development directory. It will set
  <ul>
--<li><code>appbase</code> as <code>file://PATH/TO/YOUR/DEVELOPMENT/getdown/files/JAVA_VERSION</code>
--(e.g. <code>file://home/user/git/jalview/getdown/files/11</code>)</li>
++<li><code>appbase</code> as <code>file://PATH/TO/YOUR/DEVELOPMENT/getdown/files/JAVA_VERSION</code> (e.g. <code>file://home/user/git/jalview/getdown/files/11</code>)</li>
  <li>application subdir as <code>alt</code></li>
  <li>Getdown launcher can use a <code>file://</code> scheme appbase.</li>
--</ul>
--</li>
--<li><code>BUILD</code>: This is for creating an appbase channel on the build server by an automatic or manually started build.
--It will set
++</ul></li>
++<li><code>BUILD</code>: This is for creating an appbase channel on the build server by an automatic or manually started build. It will set
  <ul>
--<li><code>appbase</code> as <code>https://builds.jalview.org/browse/${bamboo_planKey}/latest/artifact/shared/getdown-channel/JAVA_VERSION</code>
--Note that bamboo_planKey should be set by the build plan with <code>-Pbamboo_planKey=${bamboo.planKey}</code></li>
++<li><code>appbase</code> as <code>https://builds.jalview.org/browse/${bamboo_planKey}/latest/artifact/shared/getdown-channel/JAVA_VERSION</code> Note that bamboo_planKey should be set by the build plan with <code>-Pbamboo_planKey=${bamboo.planKey}</code></li>
  <li>application subdir as <code>alt</code></li>
  <li>Getdown launcher cannot use a <code>file://</code> scheme appbase.</li>
--</ul>
--</li>
--<li><code>DEVELOP</code>: This is for creating a <code>develop</code> appbase channel on the main web server. This won't become live until the actual getdown artefact is synced to the web server.
--It will set
++</ul></li>
++<li><code>DEVELOP</code>: This is for creating a <code>develop</code> appbase channel on the main web server. This won't become live until the actual getdown artefact is synced to the web server. It will set
  <ul>
  <li><code>appbase</code> as <code>http://www.jalview.org/getdown/develop/JAVA_VERSION</code></li>
  <li>application subdir as <code>alt</code></li>
  <li>Getdown launcher cannot use a <code>file://</code> scheme appbase.</li>
--</ul>
--</li>
--<li><code>SCRATCH-NAME</code>: This is for creating a temporary scratch appbase channel on the main web server.  This won't become live until the actual getdown artefact is synced to the web server.  This is meant for testing an over-the-air update without interfering with the live <code>release</code> or <code>develop</code> channels.  The value of <code>NAME</code> can be any &quot;word-character&quot; [A-Za-z0-9_]
--It will set
++</ul></li>
++<li><code>SCRATCH-NAME</code>: This is for creating a temporary scratch appbase channel on the main web server. This won't become live until the actual getdown artefact is synced to the web server. This is meant for testing an over-the-air update without interfering with the live <code>release</code> or <code>develop</code> channels. The value of <code>NAME</code> can be any &quot;word-character&quot; [A-Za-z0-9_] It will set
  <ul>
  <li><code>appbase</code> as <code>http://www.jalview.org/getdown/SCRATCH-NAME/JAVA_VERSION</code></li>
  <li>application subdir as <code>alt</code></li>
  <li>Getdown launcher cannot use a <code>file://</code> scheme appbase.</li>
--</ul>
--</li>
--<li><code>TEST-LOCAL</code>:  Like <code>SCRATCH</code> but with a specific <code>test-local</code> channel name and a local filesystem appbase.  This is meant for testing an over-the-air update on the local filesystem.  An extra property <code>LOCALDIR</code> must be given (e.g. <code>-PLOCALDIR=/home/user/tmp/test</code>)
--It will set
++</ul></li>
++<li><code>TEST-LOCAL</code>: Like <code>SCRATCH</code> but with a specific <code>test-local</code> channel name and a local filesystem appbase. This is meant for testing an over-the-air update on the local filesystem. An extra property <code>LOCALDIR</code> must be given (e.g. <code>-PLOCALDIR=/home/user/tmp/test</code>) It will set
  <ul>
  <li><code>appbase</code> as <code>file://${LOCALDIR}</code></li>
  <li>application subdir as <code>alt</code></li>
  <li>Getdown launcher can use a <code>file://</code> scheme appbase.</li>
--</ul>
--</li>
--<li><code>TEST-RELEASE</code>:  Like <code>SCRATCH</code> but with a specific <code>test-release</code> channel name.  This won't become live until the actual getdown artefact is synced to the web server.  This is meant for testing an over-the-air update without interfering with the live <code>release</code> or <code>develop</code> channels.
--It will set
++</ul></li>
++<li><code>TEST-RELEASE</code>: Like <code>SCRATCH</code> but with a specific <code>test-release</code> channel name. This won't become live until the actual getdown artefact is synced to the web server. This is meant for testing an over-the-air update without interfering with the live <code>release</code> or <code>develop</code> channels. It will set
  <ul>
  <li><code>appbase</code> as <code>http://www.jalview.org/getdown/test-release/JAVA_VERSION</code></li>
  <li>application subdir as <code>alt</code></li>
  <li>Getdown launcher cannot use a <code>file://</code> scheme appbase.</li>
--</ul>
--</li>
--<li><code>RELEASE</code>:  This is for an actual release build, and will use an appbase on the main web server with the main <code>release</code> channel name.  This won't become live until the actual getdown artefact is synced to the web server.
--It will set
++</ul></li>
++<li><code>RELEASE</code>: This is for an actual release build, and will use an appbase on the main web server with the main <code>release</code> channel name. This won't become live until the actual getdown artefact is synced to the web server. It will set
  <ul>
  <li><code>appbase</code> as <code>http://www.jalview.org/getdown/release/JAVA_VERSION</code></li>
  <li>application subdir as <code>release</code></li>
  <li>Getdown launcher cannot use a <code>file://</code> scheme appbase.</li>
--</ul>
--</li>
--<li><code>ARCHIVE</code>:  This is a helper to create a channel for a specific release version, and will use an appbase on the main web server with a specific <code>archive/JALVIEW_VERSION</code> channel name.  This won't become live until the actual getdown artefact is synced to the web server.
--You must also specify an <code>ARCHIVEDIR</code> property that points to an earlier version of Jalview with a <code>dist</code> directory containing the required jar files.  This should create a getdown structure and digest with the older jar files.
--It will set
++</ul></li>
++<li><code>ARCHIVE</code>: This is a helper to create a channel for a specific release version, and will use an appbase on the main web server with a specific <code>archive/JALVIEW_VERSION</code> channel name. This won't become live until the actual getdown artefact is synced to the web server. You must also specify an <code>ARCHIVEDIR</code> property that points to an earlier version of Jalview with a <code>dist</code> directory containing the required jar files. This should create a getdown structure and digest with the older jar files. It will set
  <ul>
  <li><code>appbase</code> as <code>http://www.jalview.org/getdown/archive/JALVIEW_VERSION/JAVA_VERSION</code></li>
  <li>application subdir as <code>alt</code></li>
  <li>Getdown launcher cannot use a <code>file://</code> scheme appbase.</li>
--</ul>
--</li>
--<li><code>ARCHIVELOCAL</code>:  Like <code>ARCHIVE</code> but with a local filesystem appbase for local testing.
--You must also specify an <code>ARCHIVEDIR</code> property that points to an earlier version of Jalview with a <code>dist</code> directory containing the required jar files.  This should create a getdown structure and digest with the older jar files.
--It will set
++</ul></li>
++<li><code>ARCHIVELOCAL</code>: Like <code>ARCHIVE</code> but with a local filesystem appbase for local testing. You must also specify an <code>ARCHIVEDIR</code> property that points to an earlier version of Jalview with a <code>dist</code> directory containing the required jar files. This should create a getdown structure and digest with the older jar files. It will set
  <ul>
  <li><code>appbase</code> as <code>file://PATH/TO/YOUR/DEVELOPMENT/getdown/website/JAVA_VERSION</code> (where the old jars will have been copied and digested)</li>
  <li>application subdir as <code>alt</code></li>
  <li>Getdown launcher can use a <code>file://</code> scheme appbase.</li>
--</ul>
--</li>
++</ul></li>
  </ul>
  <p>e.g.</p>
--<pre><code class="language-bash">gradle getdown -PCHANNEL=SCRATCH-my_test_version
--</code></pre>
--<h4 id="jalview-version-and-the-release-file"><a href="#jalview-version-and-the-release-file" name="jalview-version-and-the-release-file" class="anchor"><span class="octicon octicon-link"></span>JALVIEW_VERSION and the RELEASE file</a></h4>
--<p>Any Jalview build will include the value of JALVIEW_VERSION in various places, including the 'About' and Jalview Desktop window title, and in filenames for the stand-alone executable jar. You can specify a custom version for a build via the JALVIEW_VERSION property, but for most situations, JALVIEW_VERSION will be automatically configured according to the value of the CHANNEL property, using the <code>jalview.version</code> property specified in the RELEASE file:</p>
--<ul>
--<li><code>CHANNEL=RELEASE</code> will set version to jalview.version</li>
--<li><code>CHANNEL=TEST or DEVELOP</code> will append '-test' or '-develop' to jalview.version</li>
--</ul>
--<p>It is also possible to specify a custom location for the RELEASE file via an optional JALVIEW_RELEASE_FILE property.</p>
--<h4 id="install4jmediatypes"><a href="#install4jmediatypes" name="install4jmediatypes" class="anchor"><span class="octicon octicon-link"></span><code>install4jMediaTypes</code></a></h4>
--<p>If you are building <em>install4j</em> installers (requires <em>install4j</em> to be installed) then this property specifies a comma-separated
--list of media types (i.e. platform specific installers) <em>install4j</em> should actually build.</p>
--<p>Currently the valid values are
--<code>linuxDeb</code>,
--<code>linuxRPM</code>,
--<code>macosArchive</code>,
--<code>unixArchive</code>,
--<code>unixInstaller</code>,
--<code>windows</code></p>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> getdown -PCHANNEL=SCRATCH-my_test_version</code></pre></div>
++<h4 id="install4jmediatypes"><code>install4jMediaTypes</code></h4>
++<p>If you are building <em>install4j</em> installers (requires <em>install4j</em> to be installed) then this property specifies a comma-separated list of media types (i.e. platform specific installers) <em>install4j</em> should actually build.</p>
++<p>Currently the valid values are <code>linuxDeb</code>, <code>linuxRPM</code>, <code>macosArchive</code>, <code>unixArchive</code>, <code>unixInstaller</code>, <code>windows</code></p>
  <p>The default value is all of them.</p>
  <p>e.g.</p>
--<pre><code class="language-bash">gradle installers -PJAVA_VERSION=1.8 -Pinstall4jMediaTypes=macosArchive
--</code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">gradle</span> installers -PJAVA_VERSION=1.8 -Pinstall4jMediaTypes=macosArchive</code></pre></div>
  <p>To get an up-to-date list of possible values, you can run</p>
--<pre><code class="language-bash">perl -n -e 'm/^\s*&lt;(\w+)[^&gt;]*\bmediaFileName=/ &amp;&amp; print &quot;$1\n&quot;;' utils/install4j/install4j_template.install4j  | sort -u
--</code></pre>
++<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">perl</span> -n -e <span class="st">&#39;m/^\s*&lt;(\w+)[^&gt;]*\bmediaFileName=/ &amp;&amp; print &quot;$1\n&quot;;&#39;</span> utils/install4j/install4j_template.install4j  <span class="kw">|</span> <span class="fu">sort</span> -u</code></pre></div>
  <p>in the <code>jalview</code> root folder.</p>
--<h2 id="enabling-code-coverage-with-openclover"><a href="#enabling-code-coverage-with-openclover" name="enabling-code-coverage-with-openclover" class="anchor"><span class="octicon octicon-link"></span>Enabling Code Coverage with OpenClover</a></h2>
++<h2 id="enabling-code-coverage-with-openclover">Enabling Code Coverage with OpenClover</h2>
  <p>Bytecode instrumentation tasks are enabled by specifying 'true' (or just a non-whitespace non-numeric word) in the 'clover' property. This adds the 'openclover' plugin to the build script's classpath, making it possible to track code execution during test which can be viewed as an HTML report published at build/reports/clover/index.html.</p>
  <p><code>gradle -Pclover=true test cloverReport</code></p>
--<h4 id="troubleshooting-report-generation"><a href="#troubleshooting-report-generation" name="troubleshooting-report-generation" class="anchor"><span class="octicon octicon-link"></span>Troubleshooting report generation</a></h4>
++<h4 id="troubleshooting-report-generation">Troubleshooting report generation</h4>
  <p>The build forks a new JVM process to run the clover report generation tools (both XML and HTML reports are generated by default). The following properties can be used to specify additional options or adjust JVM memory settings. Default values for these options are:</p>
--<h5 id="jvm-memory-settings---increase-if-out-of-memory-errors-are-reported"><a href="#jvm-memory-settings---increase-if-out-of-memory-errors-are-reported" name="jvm-memory-settings---increase-if-out-of-memory-errors-are-reported" class="anchor"><span class="octicon octicon-link"></span>JVM Memory settings - increase if out of memory errors are reported</a></h5>
++<h5 id="jvm-memory-settings---increase-if-out-of-memory-errors-are-reported">JVM Memory settings - increase if out of memory errors are reported</h5>
  <p><code>cloverReportJVMHeap = 2g</code></p>
--<h5 id="-dfileencodingutf-8-is-an-essential-parameters-for-report-generation-add-additional-ones-separated-by-a-space"><a href="#-dfileencodingutf-8-is-an-essential-parameters-for-report-generation-add-additional-ones-separated-by-a-space" name="-dfileencodingutf-8-is-an-essential-parameters-for-report-generation-add-additional-ones-separated-by-a-space" class="anchor"><span class="octicon octicon-link"></span>-Dfile.encoding=UTF-8 is an essential parameters for report generation. Add additional ones separated by a space.</a></h5>
++<h5 id="dfile.encodingutf-8-is-an-essential-parameters-for-report-generation.-add-additional-ones-separated-by-a-space.">-Dfile.encoding=UTF-8 is an essential parameters for report generation. Add additional ones separated by a space.</h5>
  <p><code>cloverReportJVMArgs = -Dfile.encoding=UTF-8</code></p>
--<h5 id="add--v-to-debug-velocity-html-generation-errors-or--d-to-track-more-detailed-issues-with-the-coverage-database"><a href="#add--v-to-debug-velocity-html-generation-errors-or--d-to-track-more-detailed-issues-with-the-coverage-database" name="add--v-to-debug-velocity-html-generation-errors-or--d-to-track-more-detailed-issues-with-the-coverage-database" class="anchor"><span class="octicon octicon-link"></span>Add -v to debug velocity html generation errors, or -d to track more detailed issues with the coverage database</a></h5>
++<h5 id="add--v-to-debug-velocity-html-generation-errors-or--d-to-track-more-detailed-issues-with-the-coverage-database">Add -v to debug velocity html generation errors, or -d to track more detailed issues with the coverage database</h5>
  <p><code>cloverReportHTMLOptions =</code></p>
--<h5 id="-v-for-verbose--d-for-debug-level-messages-as-above"><a href="#-v-for-verbose--d-for-debug-level-messages-as-above" name="-v-for-verbose--d-for-debug-level-messages-as-above" class="anchor"><span class="octicon octicon-link"></span>-v for verbose, -d for debug level messages (as above)</a></h5>
++<h5 id="v-for-verbose--d-for-debug-level-messages-as-above">-v for verbose, -d for debug level messages (as above)</h5>
  <p><code>cloverReportXMLOptions =</code></p>
  <p><em>Note</em> do not forget to include the -Dfile.encoding=UTF-8 option: this is essential for some platforms in order for Clover to correctly parse some Jalview source files that contain characters that are UTF-8 encoded.</p>
--<h2 id="setting-up-in-eclipse-ide"><a href="#setting-up-in-eclipse-ide" name="setting-up-in-eclipse-ide" class="anchor"><span class="octicon octicon-link"></span>Setting up in Eclipse IDE</a></h2>
--<h3 id="installing-eclipse-ide"><a href="#installing-eclipse-ide" name="installing-eclipse-ide" class="anchor"><span class="octicon octicon-link"></span>Installing Eclipse IDE</a></h3>
--<p>We develop in Eclipse, and support settings to develop and save Jalview source code
--in our preferred style.  We also support running the Jalview application, debugging
--and running tests with TestNG from within Eclipse.</p>
- <p>To get Jalview set up as a project in Eclipse, we recommend using at least the 2019-12
- version of Eclipse IDE for Java Developers which you can download from the Eclipse
- website: <a href="https://www.eclipse.org/downloads/">https://www.eclipse.org/downloads/</a>.  Since Eclipse 2020-03 you are encouraged to use the Eclipse Installer (see the Eclipse Downloads page).
- In the installer, when given a choice of packages for Eclipse you should choose the &quot;Eclipse IDE for Enterprise Java Developers&quot; package.</p>
- <p><img src="./images/eclipse_installer.png" alt="" title="Eclipse Installer screenshot" /></p>
 -<p>To get Jalview set up as a project in Eclipse, we recommend using at least the 2020-03
 -version of Eclipse IDE for Java Developers which you can download from the Eclipse
 -website: <a href="https://www.eclipse.org/downloads/">https://www.eclipse.org/downloads/</a>.  Since Eclipse 2020-03 you are encouraged to use the Eclipse Installer (see the Eclipse Downloads page).
 -In the installer, when given a choice of packages for Eclipse you should choose the &quot;Eclipse IDE for Enterprise Java Developers&quot; package.</p>
 -<p><img src="./images/eclipse_installer.png" alt="" title="Eclipse Installer screenshot" /></p>
++<h2 id="setting-up-in-eclipse-ide">Setting up in Eclipse IDE</h2>
++<h3 id="installing-eclipse-ide">Installing Eclipse IDE</h3>
++<p>We develop in Eclipse, and support settings to develop and save Jalview source code in our preferred style. We also support running the Jalview application, debugging and running tests with TestNG from within Eclipse.</p>
++<p>To get Jalview set up as a project in Eclipse, we recommend using at least the 2019-12 version of Eclipse IDE for Java Developers which you can download from the Eclipse website: <a href="https://www.eclipse.org/downloads/" class="uri">https://www.eclipse.org/downloads/</a>. Since Eclipse 2020-03 you are encouraged to use the Eclipse Installer (see the Eclipse Downloads page). In the installer, when given a choice of packages for Eclipse you should choose the &quot;Eclipse IDE for Enterprise Java Developers&quot; package.</p>
++<div class="figure">
++<img src="./images/eclipse_installer.png" title="Eclipse Installer screenshot" />
++
++</div>
  <p>Once Eclipse is installed, we also recommend installing several plugins from the Eclipse Marketplace.</p>
  <p>Some of these should already be installed with the Enterprise Java Developer package:</p>
--<ol>
++<ol style="list-style-type: decimal">
  <li>Buildship Gradle Integration 3.0 (or greater)</li>
  <li>EclEmma Java Code Coverage</li>
  <li>Egit - Git Integration for Eclipse</li>
  </ol>
  <p>To install the others, launch Eclipse, and go to Help-&gt;Eclipse Marketplace...</p>
  <p>Search for and install:</p>
--<ol>
++<ol style="list-style-type: decimal">
  <li>Groovy Development Tools 3.4.0 (or greater)</li>
  <li>Checkstyle Plug-in (optional)</li>
  <li>TestNG for Eclipse (optional -- only needed if you want to run tests from Eclipse)</li>
  </ol>
  <blockquote>
--<p>At time of writing, TestNG for Eclipse does not show up in the Eclipse Marketplace
- as the latest released version does not install in Eclipse 2019-03.
- However, you can install a working release of TestNG for Eclipse by going to</p>
 -as the latest released version does not install in Eclipse 2020-03.
 -However, you can install a working release of TestNG for Eclipse by going to</p>
++<p>At time of writing, TestNG for Eclipse does not show up in the Eclipse Marketplace as the latest released version does not install in Eclipse 2019-03. However, you can install a working release of TestNG for Eclipse by going to</p>
  <p>Help-&gt;Install New Software...</p>
  <p>and entering</p>
  <p><code>TestNG Release - https://dl.bintray.com/testng-team/testng-eclipse-release</code></p>
  <p>into the <em>Work with</em> box and click on the <em>Add...</em> button.</p>
--<p>Eclipse might pause for a bit with the word <em>Pending</em> in the table below at this point, but it will eventually list TestNG with
--a selection box under the <em>Name</em> column.</p>
--<p>Select <em>TestNG</em> and carry on through the
--install process to install the TestNG plugin.</p>
++<p>Eclipse might pause for a bit with the word <em>Pending</em> in the table below at this point, but it will eventually list TestNG with a selection box under the <em>Name</em> column.</p>
++<p>Select <em>TestNG</em> and carry on through the install process to install the TestNG plugin.</p>
  </blockquote>
  <p>After installing the plugins, check that Java 11 is set up in Eclipse as the default JRE (see section <a href="#java-11-compliant-jdk">Java 11 compliant JDK</a>).</p>
--<p>To do this go to Preferences (Eclipse-&gt;Preferences in macOS, File-&gt;Preferences
--on Windows or Window-&gt;Preferences on Linux) and find</p>
++<p>To do this go to Preferences (Eclipse-&gt;Preferences in macOS, File-&gt;Preferences on Windows or Window-&gt;Preferences on Linux) and find</p>
  <p>Java -&gt; Installed JREs</p>
  <p>If your Java 11 installation is not listed, click on</p>
  <p><em>Add</em> -&gt; Standard VM -&gt; <em>Next</em></p>
--<p>and enter the JRE home.  You can browse to where it is installed. Give it a name (like &quot;AdoptOpenJDK 11&quot;).  Select this JDK
--as the default JRE and click on <em>Apply and Close</em>.</p>
++<p>and enter the JRE home. You can browse to where it is installed. Give it a name (like &quot;AdoptOpenJDK 11&quot;). Select this JDK as the default JRE and click on <em>Apply and Close</em>.</p>
  <p>You can now import Jalview.</p>
--<h3 id="importing-jalview-as-an-eclipse-project"><a href="#importing-jalview-as-an-eclipse-project" name="importing-jalview-as-an-eclipse-project" class="anchor"><span class="octicon octicon-link"></span>Importing Jalview as an Eclipse project</a></h3>
--<h4 id="importing-an-already-downloaded-git-repo"><a href="#importing-an-already-downloaded-git-repo" name="importing-an-already-downloaded-git-repo" class="anchor"><span class="octicon octicon-link"></span>Importing an already downloaded git repo</a></h4>
++<h3 id="importing-jalview-as-an-eclipse-project">Importing Jalview as an Eclipse project</h3>
++<h4 id="importing-an-already-downloaded-git-repo">Importing an already downloaded git repo</h4>
  <p>If you have already downloaded Jalview using <code>git clone</code> then you can import this folder into Eclipse directly.</p>
- <p>It is important to import
- Jalview as a Gradle project (not as a Java project), so go to</p>
 -<p><strong>Before importing the cloned git repo you must create the Eclipse project files.</strong> You can do this by either running</p>
 -<p><code>gradle eclipse</code></p>
 -<p>or</p>
 -<p>Unzipping the file <code>utils/eclipse/eclipse_startup_files.zip</code> in the base repo directory (<code>jalview</code>)</p>
 -<p>It is important to import
 -Jalview as a Gradle project (not as a Java project), so go to</p>
++<p>It is important to import Jalview as a Gradle project (not as a Java project), so go to</p>
  <p>File-&gt;Import...</p>
  <p>find and select</p>
  <p>Gradle-&gt;Existing Gradle Project</p>
  <p>and then click on the <em>Next</em> button.</p>
--<p>In the following options, it is the <strong>Project Root Directory</strong> you should set to be the
--<code>jalview</code> folder that git downloaded.  Then you can click on the <em>Finish</em> button.</p>
--<h4 id="using-eclipse-ide-to-download-the-git-repo"><a href="#using-eclipse-ide-to-download-the-git-repo" name="using-eclipse-ide-to-download-the-git-repo" class="anchor"><span class="octicon octicon-link"></span>Using Eclipse IDE to download the git repo</a></h4>
--<p>If you don't have git as a command line tool or would prefer to work entirely within Eclipse IDE then
--Eclipse's eGit plugin can set up a git repo of the jalview source.  Go to</p>
++<p>In the following options, it is the <strong>Project Root Directory</strong> you should set to be the <code>jalview</code> folder that git downloaded. Then you can click on the <em>Finish</em> button.</p>
++<h4 id="using-eclipse-ide-to-download-the-git-repo">Using Eclipse IDE to download the git repo</h4>
++<p>If you don't have git as a command line tool or would prefer to work entirely within Eclipse IDE then Eclipse's eGit plugin can set up a git repo of the jalview source. Go to</p>
  <p>File-&gt;Import...</p>
  <p>Find and select</p>
  <p>Git-&gt;Projects from Git</p>
  <p>and then click on the <em>Next</em> button.</p>
  <p>Then select Clone URI and click on <em>Next</em>.</p>
--<p>In the next window (Source Git Repository) you should put the <code>git clone</code> URL in the text box labelled <code>URI</code>.  If you have a Jalview developer account (with a username and password for the Jalview git repository) then you should enter
--<code>https://source.jalview.org/git/jalview.git</code>.
--If you do not have a Jalview developer account then you should enter
--<code>http://source.jalview.org/git/jalview.git</code>.
--You will not be able to push any of your changes back to the Jalview git repository. However you can still pull all branches of the Jalview source code to your computer and develop the code there.</p>
--<blockquote>
--<p>You can sign up for a Jalview developer account at <a href="https://source.jalview.org/crucible/">https://source.jalview.org/crucible/</a></p>
--</blockquote>
--<p>If you have a Jalview developer account, enter the username and password and decide if you want to use Eclipse's secure storage.  If you don't have an account you can leave the Authentication section blank.</p>
--<p><img src="./images/eclipse_egit_connection.png" alt="Eclipse eGit connection configuration" /></p>
++<p>In the next window (Source Git Repository) you should put the <code>git clone</code> URL in the text box labelled <code>URI</code>. If you have a Jalview developer account (with a username and password for the Jalview git repository) then you should enter <code>https://source.jalview.org/git/jalview.git</code>. If you do not have a Jalview developer account then you should enter <code>http://source.jalview.org/git/jalview.git</code>. You will not be able to push any of your changes back to the Jalview git repository. However you can still pull all branches of the Jalview source code to your computer and develop the code there. &gt; You can sign up for a Jalview developer account at <a href="https://source.jalview.org/crucible/" class="uri">https://source.jalview.org/crucible/</a></p>
++<p>If you have a Jalview developer account, enter the username and password and decide if you want to use Eclipse's secure storage. If you don't have an account you can leave the Authentication section blank.</p>
++<div class="figure">
++<img src="./images/eclipse_egit_connection.png" alt="Eclipse eGit connection configuration" />
++<p class="caption">Eclipse eGit connection configuration</p>
++</div>
  <p>Click on the <em>Next</em> button.</p>
--<p>The next window (Branch Selection) gives a list of the many Jalview branches, which by default will be all checked.  You probably only want to download one branch (you can always download others at a later time).  This is likely to be the <code>develop</code> branch so you can click on the <em>Deselect All</em> button, find the <code>develop</code> branch (the filter text helps), select that, and then click on the <em>Next</em> button.</p>
--<p>Choose a directory to your copy of the git repo in, and leave the other options as they are and click on the <em>Next</em> button.  The next stage may take a minute or two as it checks out the selected branch(es) from the Jalview git repository.</p>
--<p>When it has finished it is important to select <strong>Import as general project</strong> and then click on <em>Next</em>.</p>
--<blockquote>
--<p>Ideally there would be an <em>Import as gradle project</em> here but there isn't -- we'll sort that out later.</p>
--</blockquote>
--<p><img src="./images/eclipse_egit_import.png" alt="Eclipse eGit import choice" /></p>
++<p>The next window (Branch Selection) gives a list of the many Jalview branches, which by default will be all checked. You probably only want to download one branch (you can always download others at a later time). This is likely to be the <code>develop</code> branch so you can click on the <em>Deselect All</em> button, find the <code>develop</code> branch (the filter text helps), select that, and then click on the <em>Next</em> button.</p>
++<p>Choose a directory to your copy of the git repo in, and leave the other options as they are and click on the <em>Next</em> button. The next stage may take a minute or two as it checks out the selected branch(es) from the Jalview git repository.</p>
++<p>When it has finished it is important to select <strong>Import as general project</strong> and then click on <em>Next</em>. &gt; Ideally there would be an <em>Import as gradle project</em> here but there isn't -- we'll sort that out later.</p>
++<div class="figure">
++<img src="./images/eclipse_egit_import.png" alt="Eclipse eGit import choice" />
++<p class="caption">Eclipse eGit import choice</p>
++</div>
  <p>Click on the <em>Next</em> button.</p>
--<p>You can change the project name here.  By default it will show as <strong>jalview</strong> which is fine unless you have another instance of the a Jalview project also called jalview, in which case you could change this project's name now to avoid a conflict within Eclipse.</p>
++<p>You can change the project name here. By default it will show as <strong>jalview</strong> which is fine unless you have another instance of the a Jalview project also called jalview, in which case you could change this project's name now to avoid a conflict within Eclipse.</p>
  <p>Click on <em>Finish</em>!</p>
  <p>However, we haven't finished...</p>
--<p>You should now see, and be able to expand, the jalview project in the Project Explorer.  We need to tell eclipse that this is a Gradle project, which will then allow the Eclipse Buildship plugin to automatically configure almost everything else!</p>
++<p>You should now see, and be able to expand, the jalview project in the Project Explorer. We need to tell eclipse that this is a Gradle project, which will then allow the Eclipse Buildship plugin to automatically configure almost everything else!</p>
  <p>Right click on the project name (jalview) in the Project Explorer and find Configure towards the bottom of this long context menu, then choose Add Gradle Nature.</p>
--<p><img src="./images/eclipse_add_gradle_nature.png" alt="Eclipse Add Gradle Nature" /></p>
++<div class="figure">
++<img src="./images/eclipse_add_gradle_nature.png" alt="Eclipse Add Gradle Nature" />
++<p class="caption">Eclipse Add Gradle Nature</p>
++</div>
  <p>The project should now reconfigure itself using the <code>build.gradle</code> file to dynamically set various aspects of the project including classpath.</p>
--<h4 id="additional-views"><a href="#additional-views" name="additional-views" class="anchor"><span class="octicon octicon-link"></span>Additional views</a></h4>
--<p>Some views that are automatically added when Importing a Gradle Project are not added when simply Adding a Gradle Nature, but we can add these manually by clicking on
--Window-&gt;Show View-&gt;Console
--and
--Window-&gt;Show View-&gt;Other...
--Filter with the word &quot;gradle&quot; and choose both <strong>Gradle Executions</strong> and <strong>Gradle Tasks</strong> and then click on the <em>Open</em> button.</p>
--<p>Okay, ready to code!  Use of Eclipse is beyond the scope of this document, but you can find more information about developing jalview and our developer workflow in the google doc <a href="https://docs.google.com/document/d/1lZo_pZRkazDBJGNachXr6qCVlw8ByuMYG6e9SZlPUlQ/edit?usp=sharing">https://docs.google.com/document/d/1lZo_pZRkazDBJGNachXr6qCVlw8ByuMYG6e9SZlPUlQ/edit?usp=sharing</a></p>
++<h4 id="additional-views">Additional views</h4>
++<p>Some views that are automatically added when Importing a Gradle Project are not added when simply Adding a Gradle Nature, but we can add these manually by clicking on Window-&gt;Show View-&gt;Console and Window-&gt;Show View-&gt;Other... Filter with the word &quot;gradle&quot; and choose both <strong>Gradle Executions</strong> and <strong>Gradle Tasks</strong> and then click on the <em>Open</em> button.</p>
++<p>Okay, ready to code! Use of Eclipse is beyond the scope of this document, but you can find more information about developing jalview and our developer workflow in the google doc <a href="https://docs.google.com/document/d/1lZo_pZRkazDBJGNachXr6qCVlw8ByuMYG6e9SZlPUlQ/edit?usp=sharing" class="uri">https://docs.google.com/document/d/1lZo_pZRkazDBJGNachXr6qCVlw8ByuMYG6e9SZlPUlQ/edit?usp=sharing</a></p>
  <hr />
  <p><a href="mailto:help@jalview.org">Jalview Development Team</a></p>
--
--  </body>
++</body>
  </html>
diff --combined gradle.properties
@@@ -61,7 -61,6 +61,6 @@@ jalview_name = Jalvie
  getdown_local = false
  getdown_website_dir = getdown/website
  getdown_resource_dir = resource
- #getdown_j11lib_dir = j11lib
  getdown_files_dir = getdown/files
  getdown_lib_dir = getdown/lib
  getdown_launcher = getdown-launcher.jar
@@@ -70,11 -69,17 +69,17 @@@ getdown_launcher_new = getdown-launcher
  getdown_core = getdown/lib/getdown-core.jar
  getdown_build_properties = build_properties
  getdown_launch_jvl_name = channel_launch
+ getdown_images_dir = utils/getdown
+ getdown_background_image = jalview_logo_background_getdown-640x480.png
+ getdown_instant_background_image = jalview_logo_background_getdown_instant-640x480.png
+ getdown_error_background = jalview_logo_background_getdown_error-640x480.png
+ getdown_progress_image = jalview_logo_background_getdown-progress.png
+ getdown_mac_dock_icon = jalview_logo.icns
+ getdown_icon = Jalview-Logo.png
  getdown_txt_allow_offline = true
  getdown_txt_max_concurrent_downloads = 10
- # now got better defaults when not set
+ # now got better (dynamic) defaults when jvmmem* not set
  #getdown_txt_jalview.jvmmempc = 90
- # now got better defaults when not set
  #getdown_txt_jalview.jvmmemmax = 32G
  getdown_txt_multi_jvmarg = -Dgetdownappdir=%APPDIR%
  getdown_txt_strict_comments = true
@@@ -83,27 -88,20 +88,20 @@@ getdown_txt_ui.progress_sync_after_show
  getdown_txt_ui.keep_on_top = true
  getdown_txt_ui.display_appbase = true
  getdown_txt_ui.display_version = true
- getdown_txt_ui.min_show_seconds = 6
+ getdown_txt_ui.min_show_seconds = 8
  getdown_txt_ui.background = FFFFFF
- getdown_txt_ui.background_image = utils/getdown/jalview_logo_background_getdown-640x480.png
- getdown_txt_ui.instant_background_image = utils/getdown/jalview_logo_background_getdown_instant-640x480.png
- getdown_txt_ui.error_background = utils/getdown/jalview_logo_background_getdown_error-640x480.png
- getdown_txt_ui.progress_image = utils/getdown/jalview_logo_background_getdown-progress.png
- getdown_txt_ui.icon = resources/images/JalviewLogo_Huge.png
  getdown_txt_ui.progress = 20, 440, 600, 22
  getdown_txt_ui.progress_bar = AAAAFF
  getdown_txt_ui.progress_text = 000000
  getdown_txt_ui.status = 20, 380, 600, 58
  getdown_txt_ui.status_text = 000066
- #getdown_txt_ui.text_shadow = FFFFFF
  getdown_txt_ui.install_error = https://www.jalview.org/faq/getdownerror
- getdown_txt_ui.mac_dock_icon = resources/images/jalview_logos.ico
  getdown_alt_java8_min_version  = 01080000
  getdown_alt_java8_max_version  = 01089999
  getdown_alt_java11_min_version = 11000000
  getdown_alt_java11_max_version =
- #getdown_alt_java11_txt_multi_java_location = [windows-amd64] /getdown/jre/windows-jre11.jar,[linux-amd64] /getdown/jre/linux-jre11.tgz,[mac os x] /getdown/jre/macos-jre11.tgz
- #getdown_alt_java8_txt_multi_java_location = [windows-amd64] /getdown/jre/windows-jre1.8.tgz,[linux-amd64] /getdown/jre/linux-jre1.8.tgz,[mac os x] /getdown/jre/macos-jre1.8.tgz
+ #getdown_alt_java11_txt_multi_java_location = [windows-amd64] /getdown/jre/jre-11-windows-x64.zip,[linux-amd64] /getdown/jre/jre-11-linux-x64.zip,[mac os x] /getdown/jre/jre-11-mac-x64.zip
+ #getdown_alt_java8_txt_multi_java_location = [windows-amd64] /getdown/jre/jre-8-windows-x64.zip,[linux-amd64] /getdown/jre/jre-8-linux-x64.zip,[mac os x] /getdown/jre/jre-8-mac-x64.zip
  jre_installs_dir = ~/buildtools/jre
  
  j8libDir = j8lib
@@@ -113,24 -111,40 +111,40 @@@ j11modules = com.sun.istack.runtime,com
  
  flexmark_css = utils/doc/github.css
  
+ channel_properties_dir = utils/channels
 -
  install4j_home_dir = ~/buildtools/install4j8
  install4j_copyright_message = ...
  install4j_bundle_id = org.jalview.jalview-desktop
  install4j_utils_dir = utils/install4j
+ install4j_images_dir = utils/install4j
  install4j_template = install4j8_template.install4j
  install4j_info_plist_file_associations = file_associations_auto-Info_plist.xml
  install4j_installer_file_associations = file_associations_auto-install4j8.xml
  #install4j_DMG_uninstaller_app_files = uninstall_old_jalview_files.xml
  install4j_build_dir = build/install4j
+ install4j_executable_name = jalviewg
  install4j_media_types = windows,macosArchive,unixArchive,unixInstaller
  install4j_faster = false
  install4j_application_categories = Science;Biology;Java;
  install4j_release_win_application_id = 6595-2347-1923-0725
+ install4j_mac_icons_file = jalview_logo.icns
+ install4j_windows_icons_file = jalview_logo.ico
+ install4j_png_icon_file = jalview_logo.png
+ install4j_background = jalview_logo_background_fade-640x480.png
+ install4j_dmg_background = jalview_dmg_background-NON-RELEASE.png
+ install4j_dmg_ds_store = jalview_dmg_DS_Store
+ getdown_wrapper_script_dir = bin
+ getdown_bash_wrapper_script = jalview.sh
+ getdown_powershell_wrapper_script = jalview.ps1
+ getdown_batch_wrapper_script = jalview.bat
  
  OSX_KEYSTORE =
  OSX_KEYPASS =
  JSIGN_SH = echo
 +
+ OSX_APPLEID =
+ OSX_ALTOOLPASS =
  CHANNEL=LOCAL
  getdown_channel_base = https://www.jalview.org/getdown
  getdown_app_dir_release = release
@@@ -163,7 -177,7 +177,6 @@@ jalviewjs_j2s_alt_settings = build/jalv
  # these are used for actual .j2s file to pass on to alt j2s file
  jalviewjs_j2s_settings = .j2s
  jalviewjs_j2s_alt_file_property_config = j2s.config.altfileproperty
--
  # for developing in Eclipse as IDE, set this to automatically copy current swingjs/net.sf.j2s.core.jar to your dropins dir
  jalviewjs_eclipseIDE_auto_copy_j2s_plugin = false
  # Override this in a local.properties file
@@@ -190,6 -204,7 +203,7 @@@ jalviewjs_eclipse_build_arg = cleanBuil
  jalviewjs_server_port = 9001
  jalviewjs_server_wait = 30
  jalviewjs_server_resource = /jalview_bin_Jalview.html
+ jalviewjs_core_name = _jalview
  jalviewjs_name = JalviewJS
  jalviewjs_core_key = core
  #jalviewjs_core_key = preloadCore
diff --combined help/help/help.jhm
     <mapID target="alwCalc" url="html/menus/alwcalculate.html"/>
     
     <mapID target="wsMenu" url="html/menus/wsmenu.html"/>
 +   <mapID target="alwHmmer" url="html/menus/alwhmmer.html"/>
     <mapID target="popMenu" url="html/menus/popupMenu.html"/>
     <mapID target="popMenuAddref" url="html/menus/popupMenu.html#addrefannot"/>
     <mapID target="annotPanelMenu" url="html/menus/alwannotationpanel.html"/>
     
     <mapID target="importvcf" url="html/features/importvcf.html" />
     <mapID target="importvcf.attribs" url="html/features/importvcf.html#attribs" />
+    <mapID target="logging" url="html/logging.html" />
  </map>
diff --combined help/help/helpTOC.xml
@@@ -27,7 -27,7 +27,7 @@@
          <tocitem text="Virtual Features in CDS/Protein Views" target="splitframe.virtualfeats"/>
                                <tocitem text="VCF Variant Attributes" target="importvcf.attribs"/>
                                <tocitem text="Feature Filters and Attribute Colourschemes" target="features.featureschemes" />
-                               
+         <tocitem text="The Java Console, Logging and Reporting Bugs" target="logging" />
                </tocitem>
                
                <tocitem text="Editing Alignments" target="edit" />
                </tocitem>
                <tocitem text="3D Structure Data" target="viewingpdbs" expand="false">
                        <tocitem text="PDB Sequence Fetcher" target="pdbfetcher" />
-                       <tocitem text="PDB Structure Chooser" target="pdbchooser" />
+                       <tocitem text="PDB &amp; 3D-Beacons Structure Chooser" target="pdbchooser" />
                        <tocitem text="Jmol Viewer" target="pdbjmol" />
                        <tocitem text="Chimera Viewer" target="chimera" />                      
                </tocitem>
                                <tocitem text="Colour Menu" target="alwColour" />
                                <tocitem text="Calculate Menu" target="alwCalc" />
                                <tocitem text="Web Service Menu" target="wsMenu" />
 +                              <tocitem text="HMMER Menu" target="alwHmmer" />
                                <tocitem text="Annotation Panel Menu" target="annotPanelMenu" />
                                <tocitem text="Popup Menu" target="popMenu" />
                        </tocitem>
                </tocitem>
                <tocitem text="Preferences" target="preferences" />
+     <tocitem text="The Java Console, Logging and Reporting Bugs" target="logging" />
                <tocitem text="Scripting with Groovy" target="groovy">
                        <tocitem text="Groovy Features Counter example" target="groovy.featurescounter"/>
                </tocitem>
@@@ -64,9 -64,6 +64,9 @@@
      <li>The <a href="#editing"><strong>&quot;Editing&quot;</strong>
          Preferences</a> tab contains settings affecting behaviour when editing alignments.
      </li>
 +    <li>The <a href="#hmmer"><strong>&quot;HMMER&quot;</strong>
 +        Preferences</a> tab allows you to configure locally installed HMMER tools.
 +    </li>
      <li>The <a href="../webServices/webServicesPrefs.html"><strong>&quot;Web
            Service&quot;</strong> Preferences</a> tab allows you to configure the <a
        href="http://www.compbio.dundee.ac.uk/jabaws">JABAWS</a>
      <em>Add Temperature Factor annotation to alignment</em> - if
      selected, values extracted from the Temperature Factor column for
      the backbone atoms in the PDB file will be extracted as annotation
-     lines shown on the alignment.
+     lines shown on the alignment.<br/><em>Since 2.11.2, scores from the Temperature Column for structures imported via the 3D-Beacons network may be shown instead as model quality or reliability scores.</em>
    <p>
-     <em>Default structure viewer</em> - choose Jmol or CHIMERA for
+     <em>Default structure viewer</em> - choose Jmol, CHIMERA, CHIMERAX or PYMOL for
      viewing 3D structures.
    <p>
-     <em>Path to Chimera program</em> - Optional, as Jalview will search
-     standard installation paths for Windows, Linux or MacOS. If you have
-     installed Chimera in a non-standard location, you can specify it
-     here, by entering the full path to the Chimera executable program.
-     Double-click this field to open a file chooser dialog.
-   <p>
+     <em>Path to Chimera/X/Pymol program</em> - Optional, as Jalview will search
+     standard installation paths for Windows, Linux or MacOS. If Jalview cannot locate the installation for your selected structure viewer, a dialog will be shown. If you have
+     installed the chosen viewer in a non-standard location, you can specify it
+     here, by entering the full path to its executable.<br/>For Chimera, locate the path to the chimera program, similarly for ChimeraX and Pymol. Rather than typing in the path, you can also <em>double-click this field</em> to open a file chooser dialog.</p>
    <p>
      <em>PDB Fields shown in Search and Structure Summaries</em> - ticks
      in this table indicate fields shown by default when browsing results
      browser application.
    </p>
    <p>
-     <em>Proxy Server</em><br> If you normally use a proxy server
-     for using the internet, you must tick the box &quot;Use a Proxy
-     Server&quot; and enter the address and port details as necessary.
+     <em>Proxy Server</em><br>
+     There are three settings to choose from:<br>
+     <ul>
+           <li><em>No proxy servers</em> will configure Jalview to use a
+                   direct internet connection.</li>
+           <li><em>System proxy servers</em> will configure Jalview to use
+                   the proxy server passed to it by your system at startup.</li>
+           <li><em>Use these proxy servers:</em> allows you to set a custom
+                   proxy server.</li>
+     </ul>
+     If you normally use a proxy server for using the internet, you must
+     choose one of <em>System proxy servers</em>, or if these have not been
+     passed correctly you should set your own proxy servers to use by selecting
+     <em>Use these proxy servers</em>.
+     You will then need to enter the host and port details as necessary.
      Web Services will not work if you are using a proxy server and do
-     not enter the settings here.
+     not choose the system proxy or enter your own settings here.<br>
+     There are separate host and port settings for HTTP and HTTPS proxies.
+     Often these are the same but you should enter the host and port into both
+     rows.<br>
+     You can also check the <em>Authentication required</em> box if your proxy
+     requires username and password authentication.  You can enter both the
+     <em>Username</em> and <em>Password</em> but only the <em>Username</em> will
+     be stored in Jalview's preferences file, the password will only be stored
+     until the end of the current Jalview session.<br>
+     This means that if the proxy settings are still valid, Jalview will ask for
+     the password when it starts the next session.
    </p>
    <p>
      <em>Usage statistics, Questionnaire and Version checks</em><br>
      <em>Sort with New Tree</em> - When selected, any trees calculated or
      loaded onto the alignment will automatically sort the alignment.
    </p>
 +    <p>
 +    <a name="hmmer"><strong>&quot;HMMER&quot; Preferences tab</strong></a>
 +  </p>
 +  <p>If you have installed HMMER tools (available from <a href="http://hmmerorg">hmmer.org</a>),
 +  then you should specify on this screen the location of the installation (the path to the folder 
 +  containing binary executable programs). Double-click in the input field to open a file browser.</p>
 +  <p>When this path is configured, the <a href="../menus/alwhmmer.html">HMMER menu</a> will be
 +  enabled in the Alignment window.</p>
    <p>&nbsp;</p>
    <em>Web Services Preferences</em> - documentation for this tab is
    given in the
@@@ -1,3 -1,4 +1,3 @@@
 -
  <html>
  <!--
   * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
@@@ -51,27 -52,358 +51,12 @@@ li:before 
    </p>
    <table border="1">
      <tr>
 -      <th nowrap><a id="Jalview.$$Version-Rel$$"><em>Release</em></th>
 +      <th nowrap><em>Release</em></th>
        <th><em>New Features</em></th>
        <th><em>Issues Resolved</em></th>
      </tr>
      <tr>
        <td width="60" align="center" nowrap><strong><a
-           id="Jalview.2.11.1">2.11.1</a><a id="Jalview.2.11.1.1">.1</a><br />
-           <em>13/07/2020</em></strong></td>
-       <td align="left" valign="top">
-         <ul>
-         <!-- -->
-       </ul>
-       </td>
-       <td align="left" valign="top">
-         <ul>
-          <li><!-- JAL-3493 -->Escape does not clear highlights on the alignment (Since Jalview 2.10.3)</li>
-       </ul>
-       </td>
-     </tr>
-     <tr>
-       <td width="60" align="center" nowrap><strong><a
 -          id="Jalview.2.11.2">2.11.2</a><a id="Jalview.2.11.2.0">.0</a><br />
 -          <em>29/09/2021</em></strong></td>
 -      <td align="left" valign="top">
 -      <ul>
 -        <li><!-- --></li>
 -      </ul>
 -      <em>Development</em>
 -        <ul>
 -          <li>Updated building instructions</li>
 -        </ul></td>
 -      <td>
 -        <ul>
 -          <li>
 -            <!-- JAL-3840 -->Occupancy calculation is incorrect for
 -            alignment columns with over -1+2^32 gaps (breaking filtering
 -            and display)
 -          </li>
 -          <li>
 -            <!-- JAL-3833 -->Caps on Hi-DPI scaling to prevent crazy
 -            scale factors being set with buggy window-managers (linux
 -            only)
 -          </li>
 -          <li>
 -              <!-- JAL-3915 -->Removed RNAview checkbox and logic from Structure Preferences
 -          </li>
 -        </ul> <em>Development</em>
 -        <ul>
 -          <li>Fixed non-fatal gradle errors during build</li>
 -        </ul>
 -      </td>
 -    </tr>
 -    <tr>
 -      <td width="60" align="center" nowrap><strong><a
 -          id="Jalview.2.11.1">2.11.1</a><a id="Jalview.2.11.1.4">.4</a><br />
 -          <em>09/03/2021</em></strong></td>
 -      <td align="left" valign="top"><em>Improved control of
 -          Jalview's use of network services via jalview_properties</em>
 -        <ul>
 -          <li>
 -            <!-- JAL-3814 -->New .jalview_properties token controlling
 -            launch of the news browser (like -nonews argument)
 -          </li>
 -          <li>
 -            <!-- JAL-3813 -->New .jalview_properties token controlling
 -            download of linkout URLs from
 -            www.jalview.org/services/identifiers
 -          </li>
 -          <li>
 -            <!-- JAL-3812 -->New .jalview_properties token controlling
 -            download of BIOJSHTML templates
 -          </li>
 -          <li>
 -            <!-- JAL-3811 -->New 'Discover Web Services' option to
 -            trigger a one off JABAWS discovery if autodiscovery was
 -            disabled
 -          </li>
 -        </ul></td>
 -      <td align="left" valign="top">
 -        <ul>
 -          <li>
 -            <!-- JAL-3818 -->Intermittent deadlock opening structure in
 -            Jmol
 -          </li>
 -        </ul> <em>New Known defects</em>
 -        <ul>
 -          <li>
 -            <!-- JAL-3705 -->Protein Cross-Refs for Gene Sequence not
 -            always restored from project (since 2.10.3)
 -          </li>
 -          <li>
 -            <!-- JAL-3806 -->Selections from tree built from CDS aren't
 -            propagated to Protein alignment (since 2.11.1.3)
 -          </li>
 -        </ul>
 -      </td>
 -    </tr>
 -    <tr>
 -      <td width="60" align="center" nowrap><strong><a
 -          id="Jalview.2.11.1">2.11.1</a><a id="Jalview.2.11.1.3">.3</a><br />
 -          <em>29/10/2020</em></strong></td>
 -      <td align="left" valign="top">
 -        <ul>
 -
 -        </ul>
 -      </td>
 -      <td align="left" valign="top">
 -        <ul>
 -          <li>
 -            <!-- JAL-3765 -->Find doesn't always highlight all matching
 -            positions in a sequence (bug introduced in 2.11.1.2)
 -          </li>
 -          <li>
 -            <!-- JAL-3760 -->Alignments containing one or more protein
 -            sequences can be classed as nucleotide
 -          </li>
 -          <li>
 -            <!-- JAL-3748 -->CDS alignment doesn't match original CDS
 -            sequences after alignment of protein products (known defect
 -            first reported for 2.11.1.0)
 -          </li>
 -          <li>
 -            <!-- JAL-3725 -->No tooltip or popup menu for genomic
 -            features outwith CDS shown overlaid on protein
 -          </li>
 -          <li>
 -            <!-- JAL-3751 -->Overlapping CDS in ENA accessions are not
 -            correctly mapped by Jalview (e.g. affects viral CDS with
 -            ribosomal slippage, since 2.9.0)
 -          </li>
 -          <li>
 -            <!-- JAL-3763 -->Spliced transcript CDS sequences don't show
 -            CDS features
 -          </li>
 -          <li>
 -            <!-- JAL-3700 -->Selections in CDS sequence panel don't
 -            always select corresponding protein sequences
 -          </li>
 -          <li>
 -            <!-- JAL-3759 --> <em>Make groups from selection</em> for a
 -            column selection doesn't always ignore hidden columns
 -          </li>
 -        </ul> <em>Installer</em>
 -        <ul>
 -          <li>
 -            <!-- JAL-3611 -->Space character in Jalview install path on
 -            Windows prevents install4j launching getdown
 -          </li>
 -        </ul> <em>Development</em>
 -        <ul>
 -          <li>
 -            <!-- JAL-3248 -->Fixed typos and specified compatible gradle
 -            version numbers in doc/building.md
 -          </li>
 -        </ul>
 -      </td>
 -    </tr>
 -    <tr>
 -      <td width="60" align="center" nowrap><strong><a
 -          id="Jalview.2.11.1">2.11.1</a><a id="Jalview.2.11.1.2">.2</a><br />
 -          <em>25/09/2020</em></strong></td>
 -      <td align="left" valign="top">
 -        <ul>
 -        </ul>
 -      </td>
 -      <td align="left" valign="top">
 -        <ul>
 -          <li>
 -            <!-- JAL-3757 -->Fresh install of Jalview 2.11.1.1 reports
 -            "Encountered problems opening
 -            https://www.jalview.org/examples/exampleFile_2_7.jvp"
 -          </li>
 -        </ul>
 -      </td>
 -    </tr>
 -    <tr>
 -      <td width="60" align="center" nowrap><strong><a
 -          id="Jalview.2.11.1">2.11.1</a><a id="Jalview.2.11.1.1">.1</a><br />
 -          <em>17/09/2020</em></strong></td>
 -      <td align="left" valign="top">
 -        <ul>
 -          <li>
 -            <!-- JAL-3638 -->Shift+arrow keys navigate to next gap or
 -            residue in cursor mode
 -          </li>
 -          <li>
 -            <!-- JAL-3695 -->Support import of VCF 4.3 by updating
 -            HTSJDK from 2.12 to 2.23
 -          </li>
 -          <li>
 -            <!-- JAL-3621 -->IntervalStore library updated to v.1.1:
 -            optimisations and improvements suggested by Bob Hanson and
 -            improved compatibility with JalviewJS
 -          </li>
 -          <li>
 -            <!-- JAL-3615 -->Retrieve GZipped stockholm formatted
 -            alignments from Pfam and Rfam
 -          </li>
 -          <li>
 -            <!-- JAL-2656 -->Recognise GZipped content for URLs and File
 -            import (no longer based on .gz extension)
 -          </li>
 -          <li>
 -            <!-- JAL-3570 -->Updated Spanish Translation for 2.11.1
 -          </li>
 -          <li>
 -            <!-- JAL-3692 -->Migrate EMBL record retrieval to use latest
 -            ENA Browser (https://www.ebi.ac.uk/ena/browser/home) and
 -            EMBL flat file
 -          </li>
 -          <li>
 -            <!-- JAL-3667 -->Improved warning messages, debug logging
 -            and fixed Retry action when Jalview encounters errors when
 -            saving or making backup files.
 -          </li>
 -          <li>
 -            <!-- JAL-3676 -->Enhanced Jalview Java Console:
 -            <ul>
 -              <li>Jalview's logging level can be configured</li>
 -              <li>Copy to Clipboard Buttion</li>
 -            </ul>
 -          </li>
 -          <li>
 -            <!-- JAL-3541 -->Improved support for Hi-DPI (4K) screens
 -            when running on Linux (Requires Java 11+)
 -          </li>
 -        </ul> <em>Launching Jalview</em>
 -        <ul>
 -          <li>
 -            <!-- JAL-3608 -->Configure Jalview Desktop's look and feel
 -            through a system property
 -          </li>
 -          <li>
 -            <!-- JAL-3477 -->Improved built-in documentation and command
 -            line help for configuring Jalview's memory
 -          </li>                   
 -        </ul>
 -      </td>
 -      <td align="left" valign="top">
 -        <ul>
 -          <li>
 -            <!-- JAL-3691 -->Conservation and Quality tracks are shown
 -            but not calculated and no protein or DNA score models are
 -            available for tree/PCA calculation when launched with
 -            Turkish language locale
 -          </li>
 -          <li>
 -            <!-- JAL-3493 -->Escape does not clear highlights on the
 -            alignment (Since Jalview 2.10.3)
 -          </li>
 -          <li>
 -            <!--  JAL-3680 -->Alt+Left or Right arrow in cursor mode
 -            doesn't slide selected sequences, just sequence under cursor
 -          </li>
 -          <li>
 -            <!-- JAL-3732 -->Alt+Up/Down in cursor mode doesn't move
 -            sequence under the cursor
 -          </li>
 -          <li>
 -            <!-- JAL-3613 -->Peptide-to-CDS tracking broken when
 -            multiple EMBL gene products shown for a single contig
 -          </li>
 -          <li>
 -            <!-- JAL-3696 -->Errors encountered when processing variants
 -            from VCF files yield "Error processing VCF: Format specifier
 -            '%s'" on the console
 -          </li>
 -          <li>
 -            <!-- JAL-3697 -->Count of features not shown can be wrong
 -            when there are both local and complementary features mapped
 -            to the position under the cursor
 -          </li>
 -          <li>
 -            <!-- JAL-3673 -->Sequence ID for reference sequence is
 -            clipped when Right align Sequence IDs enabled
 -          </li>
 -          <li>
 -            <!-- JAL-2983 -->Slider with negative range values not
 -            rendered correctly in VAqua4 (Since 2.10.4)
 -          </li>
 -          <li>
 -            <!-- JAL-3685 -->Single quotes not displayed correctly in
 -            internationalised text for some messages and log output
 -          </li>
 -          <li>
 -            <!-- JAL-3490 -->Find doesn't report matches that span
 -            hidden gapped columns
 -          </li>
 -          <li>
 -            <!-- JAL-3597 -->Resolved memory leaks in Tree and PCA
 -            panels, Alignment viewport and annotation renderer.
 -          </li>
 -          <li>
 -            <!-- JAL-3561 -->Jalview ignores file format parameter
 -            specifying output format when exporting an alignment via the
 -            command line
 -          </li>
 -          <li>
 -            <!-- JAL-3667 -->Windows 10: For a minority of users, if
 -            backups are not enabled, Jalview sometimes fails to
 -            overwrite an existing file and raises a warning dialog. (in
 -            2.11.0, and 2.11.1.0, the workaround is to try to save the
 -            file again, and if that fails, delete the original file and
 -            save in place.)
 -          </li>
 -          <li>
 -            <!-- JAL-3750 -->Cannot process alignments from HTTPS urls
 -            via command line
 -          </li>
 -          <li>
 -            <!-- JAL-3741 -->References to http://www.jalview.org in
 -            program and documentation
 -          </li>
 -        </ul> <em>Launching Jalview</em>
 -        <ul>
 -          <li>
 -            <!-- JAL-3718 -->Jalview application fails when launched the
 -            first time for a version that has different jars to the
 -            previous launched version.
 -          </li>
 -        </ul> <em>Developing Jalview</em>
 -        <ul>
 -          <li>
 -            <!-- JAL-3541 -->Fixed issue with cleaning up old coverage
 -            data, causing cloverReport gradle task to fail with an
 -            OutOfMemory error.
 -          </li>
 -          <li>
 -            <!-- JAL-3280 -->Migrated the Jalview Version Checker to
 -            monitor the release channel
 -          </li>
 -        </ul> <em>New Known defects</em>
 -        <ul>
 -          <li>
 -            <!-- JAL-3748 -->CDS shown in result of submitting proteins
 -            in a CDS/Protein alignment to a web service is wrong when
 -            proteins share a common transcript sequence (e.g.
 -            genome of RNA viruses)
 -          </li>
 -          <li>
 -            <!-- JAL-3576 -->Co-located features exported and re-imported
 -            are ordered differently when shown on alignment and in
 -            tooltips. (Also affects v2.11.1.0)
 -          </li>
 -          <li>
 -            <!-- JAL-3702 -->Drag and drop of alignment file onto
 -            alignment window when in a HiDPI scaled mode in Linux only
 -            works for the top left quadrant of the alignment window
 -          </li>
 -          <li>
 -            <!-- JAL-3701 -->Stale build data in jalview standalone jar
 -            builds (only affects 2.11.1.1 branch)
 -          </li>
 -          <li>
 -            <!-- JAL-3127 -->Sequence ID colourscheme not re-applied
 -            when alignment view restored from project (since Jalview 2.11.0)
 -          </li>
 -          <li>
 -            <!-- JAL-3749 -->Duplicate CDS sequences are generated when
 -            protein products for certain ENA records are repeatedly
 -            shown via Calculate-&gt;Show Cross Refs
 -          </li>
 -        </ul>
 -      </td>
 -    </tr>
 -    <tr>
 -      <td width="60" align="center" nowrap><strong><a
            id="Jalview.2.11.1">2.11.1</a><a id="Jalview.2.11.1.0">.0</a><br />
            <em>22/04/2020</em></strong></td>
        <td align="left" valign="top">
@@@ -11,7 -11,6 +11,7 @@@ action.paste = Past
  action.show_html_source = Show HTML Source
  action.print = Print...
  action.web_service = Web Service
 +action.hmmer = HMMER
  action.cancel_job = Cancel Job
  action.start_job = Start Job
  action.revert = Revert
@@@ -60,8 -59,6 +60,8 @@@ action.boxes = Boxe
  action.text = Text
  action.by_pairwise_id = By Pairwise Identity
  action.by_id = By Id
 +action.by_evalue = By E-Value
 +action.by_bit_score = By Bit Score
  action.by_length = By Length
  action.by_group = By Group
  action.unmark_as_reference = Unmark as Reference 
@@@ -101,7 -98,6 +101,7 @@@ action.edit_group = Edit Grou
  action.border_colour = Border colour
  action.edit_new_group = Edit New Group
  action.hide_sequences = Hide Sequences
 +action.add_background_frequencies = Add Background Frequencies
  action.sequences = Sequences
  action.ids = IDS
  action.ids_sequences = IDS and sequences
@@@ -122,6 -118,7 +122,7 @@@ action.paste_annotations = Paste Annota
  action.format = Format
  action.select = Select
  action.new_view = New View
+ action.new_structure_view_with = Open new structure view with {0}
  action.close = Close
  action.add = Add
  action.save_as = Save as...
@@@ -135,8 -132,6 +136,8 @@@ action.select_highlighted_columns = Sel
  tooltip.select_highlighted_columns = Press B to mark highlighted columns, Ctrl-(or Cmd)-B to toggle, and Alt-B to mark all but highlighted columns 
  action.deselect_all = Deselect all
  action.invert_selection = Invert selection
 +action.filter_by_evalue = Filter by E-Value
 +action.filter_by_score = Filter by Score
  action.using_jmol = Using Jmol
  action.undo_changes_to_feature_settings = Undo all unapplied changes to feature settings
  action.undo_changes_to_feature_settings_and_close_the_dialog = Undo all pending changes and close the feature settings dialog
@@@ -206,9 -201,6 +207,9 @@@ label.colourScheme_turnpropensity = Tur
  label.colourScheme_buriedindex = Buried Index
  label.colourScheme_purine/pyrimidine = Purine/Pyrimidine
  label.colourScheme_nucleotide = Nucleotide
 +label.colourScheme_hmmer-uniprot = HMMER profile v global background
 +label.colourScheme_hmmer-alignment = HMMER profile v alignment background
 +label.colourScheme_hmm_match_score = HMM Match Score
  label.colourScheme_t-coffeescores = T-Coffee Scores
  label.colourScheme_rnahelices = By RNA Helices
  label.colourScheme_sequenceid = Sequence ID Colour
@@@ -277,11 -269,11 +278,11 @@@ label.autoadd_secstr = Add secondary st
  label.autoadd_temp = Add Temperature Factor annotation to alignment
  label.structure_viewer = Default structure viewer
  label.double_click_to_browse = Double-click to browse for file
- label.chimera_path = Path to Chimera program
- label.chimera_path_tip = Jalview will first try any path entered here, else standard installation locations.<br>Double-click to browse for file.
- label.invalid_chimera_path = Chimera path not found or not executable
- label.chimera_missing = Chimera structure viewer not found.<br/>Please enter the path to Chimera (if installed),<br/>or download and install UCSF Chimera.
- label.chimera_failed = Error opening Chimera - is it installed?\nCheck path in Preferences, Structure
+ label.viewer_path = Path to {0} program
+ label.viewer_path_tip = Jalview will first try any path entered here, else standard installation locations.<br>Double-click to browse for file.
+ label.invalid_viewer_path = Path not found or not executable
+ label.viewer_missing = Structure viewer not found.<br/>Please enter the path to the executable (if installed),<br/>or download and install the program.
+ label.open_viewer_failed = Error opening {0} - is it installed?\nCheck path in Preferences, Structure
  label.min_colour = Minimum Colour
  label.max_colour = Maximum Colour
  label.no_colour = No Colour
@@@ -337,6 -329,7 +338,7 @@@ label.successfully_pasted_alignment_fil
  label.paste_your_alignment_file = Paste your alignment file here
  label.paste_your = Paste your
  label.finished_searching = Finished searching
+ label.subsequence_matches_found = {0} subsequence matches found
  label.search_results= Search results {0} : {1}
  label.found_match_for = Found match for {0}
  label.font = Font:
@@@ -363,12 -356,6 +365,6 @@@ label.status = Statu
  label.channels = Channels
  label.channel_title_item_count = {0} ({1})
  label.blog_item_published_on_date = {0} {1} 
- label.session_update = Session Update
- label.new_vamsas_session = New Vamsas Session
- action.load_vamsas_session = Load Vamsas Session...
- action.save_vamsas_session = Save Vamsas Session
- label.select_vamsas_session_opened_as_new_vamsas_session= Select a vamsas session to be opened as a new vamsas session.
- label.open_saved_vamsas_session = Open a saved VAMSAS session
  label.groovy_console = Groovy Console...
  label.lineart = Lineart
  label.dont_ask_me_again = Don't ask me again
@@@ -387,7 -374,7 +383,7 @@@ label.example = Exampl
  label.example_param = Example: {0}
  label.select_file_format_before_saving = You must select a file format before saving!
  label.file_format_not_specified = File format not specified
- label.couldnt_save_file = Couldn't save file: {0}
+ label.couldnt_save_file = Couldn''t save file: {0}
  label.error_saving_file = Error Saving File
  label.remove_from_default_list = Remove from default list?
  label.remove_user_defined_colour = Remove user defined colour
@@@ -420,13 -407,10 +416,10 @@@ label.couldnt_read_pasted_text = Couldn
  label.error_parsing_text = Error parsing text
  label.input_alignment_from_url = Input Alignment From URL
  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 = 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
- label.wrapped_view_no_edit = Wrapped view - no edit
  label.error_retrieving_data = Error Retrieving Data
  label.user_colour_scheme_must_have_name = User colour scheme must have a name
  label.no_name_colour_scheme = No name for colour scheme
@@@ -511,10 -495,9 +504,9 @@@ label.insert_gaps = Insert {0} gap
  label.delete_gap = Delete 1 gap
  label.delete_gaps = Delete {0} gaps
  label.sequence_details = Sequence Details
- label.jmol_help = Jmol Help
- label.chimera_help = Chimera Help
+ label.viewer_help = {0} Help
  label.close_viewer = Close Viewer
- label.confirm_close_chimera = This will close Jalview''s connection to {0}.<br>Do you want to close the Chimera window as well?
+ label.confirm_close_viewer = This will close Jalview''s connection to {0}.<br>Do you want to close the {1} window as well?
  label.all = All
  label.sort_by = Sort alignment by
  label.sort_by_score = Sort by Score
@@@ -530,9 -513,14 +522,14 @@@ label.load_tree_file = Load a tree fil
  label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences = Retrieve and parse sequence database records for the alignment or the currently selected sequences
  label.standard_databases = Standard Databases
  label.fetch_embl_uniprot = Fetch from EMBL/EMBLCDS or Uniprot/PDB and any selected DAS sources
+ label.fetch_uniprot_references = Fetch Uniprot references
+ label.search_3dbeacons = 3D-Beacons Search
+ label.find_models_from_3dbeacons = Search 3D-Beacons for 3D structures and models
+ label.3dbeacons = 3D-Beacons
+ label.fetch_references_for = Fetch database references for {0} sequences ?
+ label.fetch_references_for_3dbeacons = 3D Beacons needs Uniprot References. Fetch database references for {0} sequences ?
  label.reset_min_max_colours_to_defaults = Reset min and max colours to defaults from user preferences.
  label.align_structures_using_linked_alignment_views = Superpose structures using {0} selected alignment view(s)
- label.connect_to_session = Connect to session {0}
  label.threshold_feature_display_by_score = Threshold the feature display by score.
  label.threshold_feature_no_threshold = No Threshold
  label.threshold_feature_above_threshold = Above Threshold
@@@ -566,9 -554,6 +563,6 @@@ label.right_align_sequence_id = Right A
  label.sequence_id_tooltip = Sequence ID Tooltip
  label.no_services = <No Services>
  label.select_copy_raw_html = Select this if you want to copy raw html
- label.share_data_vamsas_applications = Share data with other vamsas applications
- label.connect_to = Connect to
- label.join_existing_vamsas_session = Join an existing vamsas session
  label.from_url = from URL
  label.any_trees_calculated_or_loaded_alignment_automatically_sort = When selected, any trees calculated or loaded onto the alignment will automatically sort the alignment
  label.sort_with_new_tree = Sort With New Tree
@@@ -578,7 -563,6 +572,6 @@@ label.preferences = Preference
  label.tools = Tools
  label.fetch_sequences = Fetch Sequences
  action.fetch_sequences = Fetch Sequences...
- label.stop_vamsas_session = Stop Vamsas Session
  label.collect_garbage = Collect Garbage
  label.show_memory_usage = Show Memory Usage
  label.show_java_console = Show Java Console
@@@ -601,13 -585,21 +594,21 @@@ label.gap_symbol = Gap Symbo
  label.prot_alignment_colour = Protein Alignment Colour
  label.nuc_alignment_colour = Nucleotide Alignment Colour
  label.address = Address
+ label.host = Host
  label.port = Port
  label.default_browser_unix = Default Browser (Unix)
  label.send_usage_statistics = Send usage statistics
  label.check_for_questionnaires = Check for questionnaires
  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.no_proxy = No proxy servers
+ label.system_proxy = System proxy servers (http={0}; https={1})
+ label.use_proxy_server = Use these proxy servers
+ label.auth_required = Authentication required
+ label.username = Username
+ label.password = Password
+ label.proxy_password_required = Proxy password required
+ label.not_stored = not stored in Preferences file
  label.rendering_style = {0} rendering style
  label.append_start_end = Append /start-end (/15-380)
  label.full_sequence_id = Full Sequence Id
@@@ -633,7 -625,6 +634,6 @@@ label.editing = Editin
  label.web_services = Web Services
  label.right_click_to_edit_currently_selected_parameter = Right click to edit currently selected parameter.
  label.let_jmol_manage_structure_colours = Let Jmol manage structure colours
- label.let_chimera_manage_structure_colours = Let Chimera manage structure colours
  label.fetch_chimera_attributes = Fetch Chimera attributes
  label.fetch_chimera_attributes_tip = Copy Chimera attribute to Jalview feature
  label.marks_leaves_tree_not_associated_with_sequence = Marks leaves of tree not associated with a sequence
@@@ -645,7 -636,7 +645,7 @@@ label.delete_service_url = Delete Servi
  label.details = Details
  label.options = Options
  label.parameters = Parameters
- label.proxy_server = Proxy Server
+ label.proxy_servers = Proxy Servers
  label.file_output = File Output
  label.select_input_type = Select input type
  label.set_options_for_type = Set options for type
@@@ -720,14 -711,13 +720,13 @@@ label.associate_nodes_with = Associate 
  label.link_name = Link Name
  label.pdb_file = PDB file
  label.colour_with_jmol = Colour with Jmol
- label.colour_with_chimera = Colour with Chimera
+ label.let_viewer_manage_structure_colours = Let viewer manage structure colours
+ label.colour_with_viewer = Colour in structure viewer
  label.superpose_structures = Superpose Structures
  error.superposition_failed = Superposition failed: {0}
  label.insufficient_residues = Not enough aligned residues ({0}) to perform superposition
- label.jmol = Jmol
- label.chimera = Chimera
- label.create_chimera_attributes = Write Jalview features
- label.create_chimera_attributes_tip = Set Chimera residue attributes for visible features
+ label.create_viewer_attributes = Write Jalview features
+ label.create_viewer_attributes_tip = Set structure residue attributes for Jalview features
  label.attributes_set = {0} attribute values set on Chimera
  label.sort_alignment_by_tree = Sort Alignment By Tree
  label.mark_unlinked_leaves = Mark Unlinked Leaves
@@@ -819,8 -809,8 +818,8 @@@ label.fetch_retrieve_from_all_sources 
  label.feature_settings_click_drag = Drag up or down to change render order.<br/>Double click to select columns containing feature.
  label.transparency_tip = Adjust transparency to 'see through' feature colours.
  label.opt_and_params_further_details = see further details by right-clicking
- label.opt_and_params_show_brief_desc_image_link = Click to show brief description<br><img src="{0}"/> Right click for further information. 
- label.opt_and_params_show_brief_desc = Click to show brief description<br>
+ label.opt_and_params_show_brief_desc_image_link = <html>Click to show brief description<br><img src="{0}"/> Right click for further information.</html> 
+ label.opt_and_params_show_brief_desc = <html>Click to show brief description<br></html>
  label.adjusts_width_generated_eps_png = <html>Adjusts the width of the generated EPS or PNG file to ensure even the longest sequence ID or annotation label is displayed</html>
  label.manually_specify_width_left_column = <html>Manually specify the width of the left hand column where sequence IDs and annotation labels will be rendered in exported alignment figures. This setting will be ignored if 'Automatically set ID width' is set</html>
  label.job_created_when_checked = <html>When checked, a job is created for every sequence in the current selection.</html>
@@@ -886,9 -876,6 +885,7 @@@ label.save_text_to_file = Save Text to 
  label.save_state = Save State
  label.restore_state = Restore State
  label.saving_jalview_project = Saving jalview project {0}
 +label.loading_jalview_project = Loading jalview project {0}
- label.save_vamsas_document_archive = Save Vamsas Document Archive
- label.saving_vamsas_doc = Saving VAMSAS Document to {0}
  label.load_feature_colours = Load Feature Colours
  label.save_feature_colours = Save Feature Colour Scheme
  label.select_startup_file = Select startup file
@@@ -908,7 -895,6 +905,6 @@@ label.visible = Visibl
  label.select_unselect_visible_regions_from = select and unselected {0} regions from {1}
  label.visible_region_of = visible region of
  label.webservice_job_title_on = {0} using {1} on {2}
- label.updating_vamsas_session = Updating vamsas session
  label.loading_file = Loading File: {0}
  label.edit_params = Edit {0}
  label.as_percentage = As Percentage
@@@ -949,26 -935,20 +945,20 @@@ 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.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
  label.couldnt_create_groovy_shell = Couldn't create the groovy Shell. Check the error log for the details of what went wrong.
  error.unsupported_version_calcIdparam = Unsupported Version for calcIdparam {0}
  error.implementation_error_cant_reorder_tree = Implementation Error: Can't reorder this tree. Not DefaultMutableTreeNode.
+ error.invalid_value_for_option = Invalid value ''{0}'' for option ''{1}''
  error.implementation_error_cannot_import_vamsas_doc = Implementation Error - cannot import existing vamsas document into an existing session, Yet!
  label.vamsas_doc_couldnt_be_opened_as_new_session = VAMSAS Document could not be opened as a new session - please choose another
- error.implementation_error_vamsas_operation_not_init = Impementation error! Vamsas Operations when client not initialised and connected
- error.jalview_no_connected_vamsas_session = Jalview not connected to Vamsas session
- error.implementation_error_cannot_recover_vamsas_object_mappings = IMPLEMENTATION ERROR: Cannot recover vamsas object mappings - no backup was made
  error.setstatus_called_non_existent_job_pane = setStatus called for non-existent job pane {0}
  error.implementation_error_cannot_find_marshaller_for_param_set =Implementation error: Can't find a marshaller for the parameter set
  error.implementation_error_old_jalview_object_not_bound =IMPLEMENTATION ERROR: old jalview object is not bound ! ({0})
  error.implementation_error_vamsas_doc_class_should_bind_to_type = Implementation Error: Vamsas Document Class {0} should bind to a {1} (found a {2})
  error.invalid_vamsas_rangetype_cannot_resolve_lists = Invalid vamsas RangeType - cannot resolve both lists of Pos and Seg from choice!
- error.implementation_error_maplist_is_null = Implementation error. MapList is null for initMapType.
  error.implementation_error_cannot_have_null_alignment = Implementation error: Cannot have null alignment property key
  error.implementation_error_null_fileparse = Implementation error. Null FileParse in copy constructor
- error.implementation_error_cannot_map_alignment_sequences = IMPLEMENTATION ERROR: Cannot map an alignment of sequences from different datasets into a single alignment in the vamsas document.
  error.implementation_error_structure_selection_manager_null = Implementation error. Structure selection manager's context is 'null'
  exception.ssm_context_is_null = SSM context is null
  error.idstring_seqstrings_only_one_per_sequence = idstrings and seqstrings contain one string each per sequence
@@@ -983,7 -963,6 +973,7 @@@ error.implementation_error_minlen_must_
  error.implementation_error_msawbjob_called = Implementation error - StartJob(MsaWSJob) called on a WSJobInstance {0}
  error.implementation_error_cannot_attach_ws_menu_entry = IMPLEMENTATION ERROR: cannot attach WS Menu Entry without service handle reference!
  error.parameter_migration_not_implemented_yet = Parameter migration not implemented yet
 +error.implementation_error_cannot_set_jaba_option = Implementation error: cannot set Jaba Option to a value outside its allowed value range!
  error.implementation_error_valuetype_doesnt_support_jabaws_type = IMPLEMENTATION ERROR: jalview.ws.params.ValueConstrainI.ValueType does not support the JABAWS type : {0}
  error.cannot_create_jabaws_param_set = Cannot create a JabaWSParamSet from non-JabaWS parameters
  error.cannot_set_arguments_to_jabaws_param_set = Cannot set arguments to a JabaWSParamSet that are not JabaWS arguments
@@@ -1005,6 -984,7 +995,7 @@@ error.dbrefsource_implementation_except
  error.implementation_error_dbinstance_must_implement_interface = Implmentation Error - getDbInstances must be given a class that implements jalview.ws.seqfetcher.DbSourceProxy (was given{0})
  error.implementation_error_must_init_dbsources =Implementation error. Must initialise dbSources
  label.view_controller_toggled_marked = {0} {1} columns {2} features of type {3}  across {4} sequence(s)
+ label.no_highlighted_regions_marked = No highlighted regions marked
  label.toggled = Toggled
  label.marked = Marked
  label.containing = containing
@@@ -1062,7 -1042,6 +1053,7 @@@ exception.ranml_couldnt_process_data = 
  exception.ranml_invalid_file = Invalid RNAML file ({0})
  exception.ranml_problem_parsing_data = Problem parsing data as RNAML ({0})
  exception.pfam_no_sequences_found = No sequences found (PFAM input)
 +exception.hmmer_no_valid_sequences_found = No valid sequences found
  exception.stockholm_invalid_format = This file is not in valid STOCKHOLM format: First line does not contain '# STOCKHOLM'
  exception.couldnt_parse_sequence_line = Could not parse sequence line: {0}
  exception.unknown_annotation_detected = Unknown annotation detected: {0} {1}
@@@ -1077,7 -1056,6 +1068,7 @@@ exception.unable_to_create_internet_con
  exception.invocation_target_calling_url = InvocationTargetException while calling openURL: {0}
  exception.illegal_access_calling_url = IllegalAccessException while calling openURL: {0}
  exception.interrupted_launching_browser = InterruptedException while launching browser: {0}
 +exception.ebiembl_retrieval_failed_on = EBI EMBL XML retrieval failed on {0}:{1}
  exception.no_pdb_records_for_chain = No PDB Records for {0} chain {1}
  exception.unexpected_handling_rnaml_translation_for_pdb = Unexpected exception when handling RNAML translation of PDB data
  exception.couldnt_recover_sequence_properties_for_alignment = Couldn't recover sequence properties for alignment
@@@ -1119,10 -1097,7 +1110,9 @@@ label.png_image = PNG imag
  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}
  status.opening_params = Opening {0}
 +status.waiting_sequence_database_fetchers_init = Waiting for Sequence Database Fetchers to initialise
 +status.init_sequence_database_fetchers = Initialising Sequence Database Fetchers
  status.fetching_sequence_queries_from = Fetching {0} sequence queries from {1}
  status.finshed_querying = Finished querying
  status.parsing_results = Parsing results.
@@@ -1132,22 -1107,17 +1122,24 @@@ status.collecting_job_results = Collect
  status.fetching_db_refs = Fetching db refs
  status.loading_cached_pdb_entries = Loading Cached PDB Entries
  status.searching_for_pdb_structures = Searching for PDB Structures
+ status.searching_3d_beacons = Searching 3D Beacons
+ status.no_structures_discovered_from_3d_beacons = No models discovered from 3D Beacons
  status.opening_file_for = opening file for
- status.colouring_chimera = Colouring Chimera
 +status.running_hmmbuild = Building Hidden Markov Model
 +status.running_hmmalign = Creating alignment with Hidden Markov Model
 +status.running_search = Searching for matching sequences
+ status.colouring_structures = Colouring structures
  label.font_doesnt_have_letters_defined = Font doesn't have letters defined\nso cannot be used\nwith alignment data
  label.font_too_small = Font size is too small
 +label.error_loading_file_params = Error loading file {0}
 +label.error_loading_jalview_file = Error loading Jalview file
  warn.out_of_memory_when_action = Out of memory when {0}\!\!\nSee help files for increasing Java Virtual Machine memory.
  warn.out_of_memory_loading_file = Out of memory loading file {0}\!\!\nSee help files for increasing Java Virtual Machine memory.
  label.out_of_memory = Out of memory
  label.invalid_id_column_width = Invalid ID Column width
  warn.user_defined_width_requirements = The user defined width for the\nannotation and sequence ID columns\nin exported figures must be\nat least 12 pixels wide.
 +label.couldnt_create_sequence_fetcher = Couldn't create SequenceFetcher
 +warn.couldnt_create_sequence_fetcher_client = Could not create the sequence fetcher client. Check error logs for details.
  warn.server_didnt_pass_validation = Service did not pass validation.\nCheck the Jalview Console for more details.
  warn.url_must_contain = Sequence URL must contain $SEQUENCE_ID$, $DB_ACCESSION$, or a regex
  warn.urls_not_contacted = URLs that could not be contacted
@@@ -1172,6 -1142,7 +1164,7 @@@ label.add_annotations_for = Add annotat
  action.choose_annotations = Choose Annotations...
  label.choose_annotations = Choose Annotations
  label.find = Find
+ label.in = in
  label.invalid_search = Search string invalid
  error.invalid_regex = Invalid regular expression
  label.ignore_gaps_consensus = Ignore Gaps In Consensus
@@@ -1330,7 -1301,6 +1323,7 @@@ option.enable_disable_autosearch = Whe
  option.autosearch = Autosearch
  label.retrieve_ids = Retrieve IDs
  label.display_settings_for = Display settings for {0} features
 +label.simple = Simple
  label.simple_colour = Simple Colour
  label.colour_by_text = Colour by text
  label.graduated_colour = Graduated Colour
@@@ -1353,85 -1323,13 +1346,86 @@@ label.alignment = alignmen
  label.pca = PCA
  label.create_image_of = Create {0} image of {1}
  label.click_to_edit = Click to edit, right-click for menu
 +label.hmmalign = hmmalign
 +label.use_hmm = HMM profile to use
 +label.use_sequence = Sequence to use
 +label.hmmbuild = hmmbuild
 +label.hmmsearch = hmmsearch
 +label.jackhmmer = jackhmmer
 +label.installation = Installation
 +label.hmmer_location = HMMER Binaries Installation Location
 +label.cygwin_location = Cygwin Binaries Installation Location (Windows)
 +label.information_annotation = Information Annotation
 +label.ignore_below_background_frequency = Ignore Below Background Frequency
 +label.information_description = Information content, measured in bits
 +warn.no_hmm = No Hidden Markov model found.\nRun hmmbuild or load an HMM file first.
 +label.no_sequences_found = No matching sequences, or an error occurred.
 +label.hmmer = HMMER
 +label.trim_termini = Trim Non-Matching Termini
 +label.trim_termini_desc = If true, non-matching regions on either end of the resulting alignment are removed.
 +label.no_of_sequences = Number of sequences returned
 +label.reporting_cutoff = Reporting Cut-off
 +label.inclusion_threshold = Inlcusion Threshold
 +label.freq_alignment = Use alignment background frequencies
 +label.freq_uniprot = Use Uniprot background frequencies
 +label.hmmalign_options = hmmalign options
 +label.hmmsearch_options = hmmsearch options
 +label.jackhmmer_options = jackhmmer options
 +label.executable_not_found = The ''{0}'' executable file was not found
 +warn.command_failed = {0} failed
 +label.invalid_folder = Invalid Folder
 +label.number_of_results = Number of Results to Return
 +label.number_of_iterations = Number of jackhmmer Iterations
 +label.auto_align_seqs = Automatically Align Fetched Sequences
 +label.new_returned = new sequences returned
 +label.use_accessions = Return Accessions
 +label.check_for_new_sequences = Return Number of New Sequences
 +label.evalue = E-Value
 +label.reporting_seq_evalue = Reporting Sequence E-value Cut-off
 +label.reporting_seq_score = Reporting Sequence Score Threshold
 +label.reporting_dom_evalue = Reporting Domain E-value Cut-off
 +label.reporting_dom_score = Reporting Domain Score Threshold
 +label.inclusion_seq_evalue = Inclusion Sequence E-value Cut-off
 +label.inclusion_seq_score = Inclusion Sequence Score Threshold
 +label.inclusion_dom_evalue = Inclusion Domain E-value Cut-off
 +label.inclusion_dom_score = Inclusion Domain Score Threshold
 +label.number_of_results_desc = The maximum number of hmmsearch results to display
 +label.number_of_iterations_desc = The number of iterations jackhmmer will complete when searching for new sequences
 +label.auto_align_seqs_desc = If true, all fetched sequences will be aligned to the hidden Markov model with which the search was performed
 +label.check_for_new_sequences_desc = Display number of new sequences returned from hmmsearch compared to the previous alignment 
 +label.use_accessions_desc = If true, the accession number of each sequence is returned, rather than that sequence's name
 +label.reporting_seq_e_value_desc = The E-value cutoff for returned sequences 
 +label.reporting_seq_score_desc = The score threshold for returned sequences 
 +label.reporting_dom_e_value_desc = The E-value cutoff for returned domains 
 +label.reporting_dom_score_desc = The score threshold for returned domains 
 +label.inclusion_seq_e_value_desc = Sequences with an E-value less than this cut-off are classed as significant
 +label.inclusion_seq_score_desc = Sequences with a bit score greater than this threshold are classed as significant
 +label.inclusion_dom_e_value_desc = Domains with an E-value less than this cut-off are classed as significant
 +label.inclusion_dom_score_desc = Domains with a bit score greater than this threshold are classed as significant
 +label.add_database = Add Database
 +label.this_alignment = This alignment
 +warn.invalid_format = This is not a valid database file format. The current supported formats are Fasta, Stockholm and Pfam.
 +label.database_for_hmmsearch = The database hmmsearch will search through
 +label.use_reference = Use Reference Annotation
 +label.use_reference_desc = If true, hmmbuild will keep all columns defined as a reference position by the reference annotation
 +label.hmm_name = Alignment HMM Name
 +label.hmm_name_desc = The name given to the HMM for the alignment
 +warn.no_reference_annotation = No reference annotation found
 +label.hmmbuild_for = Build HMM for
 +label.hmmbuild_for_desc = Build an HMM for the selected sets of sequences
 +label.alignment = Alignment
 +label.groups_and_alignment = All groups and alignment
 +label.groups = All groups
 +label.selected_group = Selected group
 +label.use_info_for_height = Use Information Content as Letter Height
 +action.search = Search
  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
  label.backupfiles_confirm_save_file_backupfiles_roll_wrong = Something possibly went wrong with the backups of this file.
  label.backupfiles_confirm_save_new_saved_file_ok = The new saved file seems okay.
  label.backupfiles_confirm_save_new_saved_file_not_ok = The new saved file might not be okay.
+ label.continue_operation = Continue operation?
  label.backups = Backups
  label.backup = Backup
  label.backup_files = Backup Files
@@@ -1440,7 -1338,6 +1434,7 @@@ label.backup_filename_strategy = Backu
  label.append_to_filename = Append to filename (%n is replaced by the backup number)
  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.
@@@ -1469,13 -1366,11 +1463,13 @@@ label.single_file_description = Keep th
  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
  label.cancel_changes = Cancel changes
  label.warning_confirm_change_reverse = Warning!\nIf you change the increment/decrement of the backup filename number, without changing the suffix or number of digits,\nthis may cause loss of backup files created with the previous backup filename scheme.\nAre you sure you wish to do this?
  label.change_increment_decrement = Change increment/decrement?
 +label.was_previous = was {0}
  label.newerdelete_replacement_line = Backup file\n''{0}''\t(modified {2}, size {4})\nis to be deleted and replaced by apparently older file\n''{1}''\t(modified {3}, size {5}).
  label.confirm_deletion_or_rename = Confirm deletion of ''{0}'' or rename to ''{1}''?
  label.newerdelete_line = Backup file\n''{0}''\t(modified {2}, size {4})\nis to be deleted but is newer than the oldest remaining backup file\n''{1}''\t(modified {3}, size {5}).
@@@ -1498,4 -1393,9 +1492,9 @@@ label.include_linked_features = Includ
  label.include_linked_tooltip = Include visible {0} features<br>converted to local sequence coordinates
  label.features_not_shown = {0} feature(s) not shown
  label.no_features_to_sort_by = No features to sort by
+ label.ignore_hidden = Ignore hidden columns
+ label.ignore_hidden_tooltip = Ignore any characters in hidden columns when matching
+ label.log_level = Log level
+ label.log_level_tooltip = Temporarily set the log level for this console. The log level will revert to {0} when this Java console is closed.
+ label.copy_to_clipboard = Copy to clipboard
+ label.copy_to_clipboard_tooltip = Copy all of the log text in this console to the system clipboard
@@@ -211,7 -211,7 +211,7 @@@ label.show_non_conserved = Mostrar no c
  label.overview_window = Ventana resumen
  label.none = Ninguno
  label.above_identity_threshold = Por encima del umbral de identidad
 -label.show_sequence_features = Mostrar las características de secuencia
 +label.show_sequence_features = Mostrar las características de las secuencias
  label.nucleotide = Nucleótido
  label.to_new_alignment = A nuevo alineamiento
  label.to_this_alignment = Añadir a este alineamiento
@@@ -253,7 -253,7 +253,7 @@@ label.min_value = Valor mínim
  label.no_value = Sin valor
  label.colour_by_label = Color por etiquetas
  label.new_feature = Nueva función
- label.match_case = Hacer corresponder mayúsculas y minúsculas
+ label.match_case = Distinguir min/mayúsculas
  label.view_alignment_editor = Ver en el editor de alineamientos
  label.labels = Etiquetas
  label.output_values = Valores de salida...
@@@ -294,6 -294,7 +294,7 @@@ label.successfully_pasted_alignment_fil
  label.paste_your_alignment_file = Pegar su fichero de alineamiento aquí
  label.paste_your = Pegar su
  label.finished_searching = Búsqueda finalizada
+ label.subsequence_matches_found = {0} resultados encontrados en subsequencias
  label.search_results= Buscar Resultados {0} : {1}
  label.found_match_for = Buscar coincidencia para {0}
  label.font = Fuente:
@@@ -320,11 -321,6 +321,6 @@@ label.status =  [Estado
  label.channels = Canales
  label.channel_title_item_count = {0} ({1})
  label.blog_item_published_on_date = {0} {1} 
- label.session_update = Actualizar sesión
- label.new_vamsas_session = Nueva sesión Vamsas
- action.save_vamsas_session = Guardar Sesión Vamsas
- label.select_vamsas_session_opened_as_new_vamsas_session= Selecciones una sesión vamsas para abrirla como una nueva sesión.
- label.open_saved_vamsas_session = Abrir una sesión VAMSAS guardada
  label.groovy_console = Consola Groovy 
  label.lineart = Lineart
  label.dont_ask_me_again = No volver a preguntar
@@@ -375,13 -371,10 +371,12 @@@ label.couldnt_read_pasted_text = No se 
  label.error_parsing_text = Error analizando el texto
  label.input_alignment_from_url = Alineamiento de entrada desde URL
  label.input_alignment = Alineamiento de entrada
- label.couldnt_import_as_vamsas_session = No se pudo importar {0} como una nueva sesión Vamsas.
  label.vamsas_document_import_failed =  Fallo en la importación del documento Vamsas
  label.couldnt_locate = No se pudo localizar {0}
  label.url_not_found = URL no encontrada
  label.new_sequence_url_link = Enlace a una nueva secuencia URL
 +label.cannot_edit_annotations_in_wrapped_view = No se pueden editar anotaciones en vista envolvente
 +label.wrapped_view_no_edit = Vista envolvente - no editar
  label.error_retrieving_data = Error en la recuperación de datos
  label.user_colour_scheme_must_have_name = El esquema de colores del usuario debe tener un nombre
  label.no_name_colour_scheme = No hay nombre para el esquema de colores 
@@@ -465,7 -458,7 +460,7 @@@ label.insert_gaps = Insertar {0} hueco
  label.delete_gap = Borrar 1 hueco
  label.delete_gaps = Borrar {0} huecos
  label.sequence_details = Detalles de la secuencia
- label.jmol_help = Ayuda de Jmol
+ label.viewer_help = Ayuda sobre {0}
  # Todos/Todas is gender-sensitive, but currently only used for feminine (cadena / anotación)! 
  label.all = Todas
  label.sort_by = Ordenar por
@@@ -481,7 -474,6 +476,6 @@@ label.standard_databases = Bases de dat
  label.fetch_embl_uniprot = Recuperar de EMBL/EMBLCDS o Uniprot/PDB y de cualquier fuente DAS seleccionada
  label.reset_min_max_colours_to_defaults = Reiniciar los colores min y max colours a los valores por defecto establecidos en las preferencias de usuario
  label.align_structures_using_linked_alignment_views = Alinear las estructuras utilizando las {0} vista(s) de alineamiento enlazada(s)
- label.connect_to_session = Conectar a la sesión {0}
  label.threshold_feature_display_by_score = Filtrar la característica mostrada por puntuación.
  label.threshold_feature_no_threshold = Sin umbral
  label.threshold_feature_above_threshold = Por encima del umbral
@@@ -512,9 -504,6 +506,6 @@@ label.right_align_sequence_id = Alinea
  label.sequence_id_tooltip = Ayuda del ID de la secuencia
  label.no_services = <Sin Servicios>
  label.select_copy_raw_html = Seleccione esta opción si desea copiar el html en bruto
- label.share_data_vamsas_applications = Compartir datos con otras aplicaciones vamsas
- label.connect_to = Conectar a
- label.join_existing_vamsas_session = Unirse a una sesión vamsas existente
  label.from_url = desde una URL
  label.any_trees_calculated_or_loaded_alignment_automatically_sort = Cuando está habilitado, cualquier Ã¡rbol calculado o cargado en el alineamiento lo ordenará
  label.sort_with_new_tree = Ordenar con el nuevo Ã¡rbol
@@@ -523,7 -512,6 +514,6 @@@ label.window = Ventan
  label.preferences = Preferencias
  label.tools = Herramientas
  label.fetch_sequences = Recuperar secuencia(s)
- label.stop_vamsas_session = Parar sesión vamsas
  label.collect_garbage = Recolector de basura
  label.show_memory_usage = Mostrar uso de memoria
  label.show_java_console = Mostrar consola de Java
@@@ -544,13 -532,19 +534,19 @@@ label.database_references = Referencia
  #label.scroll_highlighted_regions = Desplazarse hasta las regiones resaltadas
  label.gap_symbol = Símbolo del hueco
  label.address = Dirección
+ label.host = Host
  label.port = Puerto
  label.default_browser_unix = Navegador por defecto (Unix)
  label.send_usage_statistics = Enviar estadísticas de uso
  label.check_for_questionnaires = Comprobar los cuestionarios
  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.no_proxy = Sin servidores proxy
+ label.system_proxy = Servidores proxy del sistema (http={0}; https={1})
+ label.use_proxy_server = Utilizar estos servidores proxy
+ label.auth_required = Autenticacion requerida
+ label.username = Usario
+ label.password = Contraseña
  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
@@@ -585,7 -579,7 +581,7 @@@ label.delete_service_url = Borrar la UR
  label.details = Detalles
  label.options = Opciones
  label.parameters = Paramétros
- label.proxy_server = Servidor proxy
+ label.proxy_servers = Servidores proxy
  label.file_output = Fichero de salida
  label.select_input_type = Seleccionar el tipo de entrada
  label.set_options_for_type = Establecer opciones para el tipo
@@@ -653,7 -647,6 +649,7 @@@ label.associate_nodes_with = Asociar no
  label.link_name = Nombre del enalce
  label.pdb_file = Fichero PDB
  label.colour_with_jmol = Colorear con Jmol
 +label.jmol = Jmol
  label.sort_alignment_by_tree = Ordenar alineamiento por Ã¡rbol
  label.mark_unlinked_leaves = Marcar las hojas como no enlazadas
  label.associate_leaves_with = Asociar hojas con
@@@ -694,7 -687,12 +690,12 @@@ label.annotations_for_params = Anotacio
  label.generating_features_for_params = Generando características de - {0}
  label.generating_annotations_for_params = Generando anotaciones de - {0}
  label.varna_params = VARNA - {0}
- label.sequence_feature_settings = Configuración de las características de la secuencia
+ label.sequence_feature_settings = Configuración de las características de secuencia
+ label.sequence_feature_settings_for = Configuración de las características de secuencia para {0}
+ label.sequence_feature_settings_for_view = Configuración de las características de secuencia para vista "{0}"
+ label.sequence_feature_settings_for_CDS_and_Protein = Configuración de las características de secuencia para CDS y Proteína 
+ action.undo_changes_to_feature_settings = Deshacer todos los cambios no aplicados
+ action.undo_changes_to_feature_settings_and_close_the_dialog = Deshacer cambios pendientes, cerrar diálogo
  label.pairwise_aligned_sequences = Secuencias alineadas a pares
  label.original_data_for_params = Datos originales de {0}
  label.points_for_params = Puntos de {0}
@@@ -796,9 -794,6 +797,7 @@@ label.save_text_to_file = Guardar Text
  label.save_state = Guardar estado
  label.restore_state = Restaurar estado
  label.saving_jalview_project = Guardando el proyecto de Jalview {0}
 +label.loading_jalview_project = Cargando el proyecto de Jalview {0}
- label.save_vamsas_document_archive = Guardar el archivo de documento Vamsas
- label.saving_vamsas_doc = Guardando el documento VAMSAS en {0}
  label.load_feature_colours = Cargar colores de características
  label.save_feature_colours = Guardar esquema cromático de características
  label.select_startup_file = Seleccionar fichero de arranque
@@@ -818,7 -813,6 +817,6 @@@ label.visible = Visibl
  label.select_unselect_visible_regions_from = seleccionada y deseleccionadas {0} regiones de {1}
  label.visible_region_of = región visible de
  label.webservice_job_title_on = {0} usando {1} de {2}
- label.updating_vamsas_session = Actualizando sesión VAMSAS
  label.loading_file = Cargando fichero: {0}
  label.edit_params = Editar {0}
  label.as_percentage = Como Porcentaje
@@@ -859,26 -853,20 +857,21 @@@ 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.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
  label.couldnt_create_groovy_shell = No es posible crear el shell de Groovy. Compruebe el fichero de log para conocer los detalles.
  error.unsupported_version_calcIdparam = Versión no soportada de {0}
  error.implementation_error_cant_reorder_tree = Error de implementación: no es posible reordenar este Ã¡rbol. No DefaultMutableTreeNode.
+ error.invalid_value_for_option = Valor no válido de ''{0}'' para la opción ''{1}''
  error.implementation_error_cannot_import_vamsas_doc = Error de implementación - todavía no es posible importar el documento VAMSAS existente en una sesión existente.
  label.vamsas_doc_couldnt_be_opened_as_new_session = El documento VAMSAS no ha podido abrirse como una nueva sesión. Por favor, escoja otra.
- error.implementation_error_vamsas_operation_not_init = Â¡Error de implementación! Operaciones VAMSAS cuando el cliente no estaba inicializado ni conectado
- error.jalview_no_connected_vamsas_session = Jalview está conectado a una sesión VAMSAS
- error.implementation_error_cannot_recover_vamsas_object_mappings = Error de implementación: no es posible recuperar los mapeos del objeto VAMSAS - no se ha hecho ningún backup 
  error.setstatus_called_non_existent_job_pane = se lllamado a setStatus para el panel de trabajo {0} no existente
  error.implementation_error_cannot_find_marshaller_for_param_set =Error de implementación: no puede encontrar un marshaller para el conjunto de parámetros
  error.implementation_error_old_jalview_object_not_bound =Error de implementación: Â¡el objeto Jalview antiguo no está enlazado! ({0})
  error.implementation_error_vamsas_doc_class_should_bind_to_type = Error de implementación: la clase de documento VAMSAS {0} debe enlazar a {1} (pero se ha encontrado que lo está a {2})
  error.invalid_vamsas_rangetype_cannot_resolve_lists = RangeType VAMSAS no válido - Â¡no es posible resolver ambas listas de Pos y Seg con los valores elegidos!
 +error.implementation_error_maplist_is_null = Error de implementación. MapList es nulo en initMapType.
  error.implementation_error_cannot_have_null_alignment = Error de implementación: no es posible tener una clave nula en el alineamiento
  error.implementation_error_null_fileparse = Error de implementación. FileParse nulo en el construictor de copia
- error.implementation_error_cannot_map_alignment_sequences = Error de implementación: no es posible maper un alineamiento de secuencias desde distintos conjuntos de datos en un Ãºnico alineamiento en el documento VAMSAS.
  error.implementation_error_structure_selection_manager_null = Error de implementación. El contexto structure selection manager's es nulo
  exception.ssm_context_is_null = El contexto SSM es nulo
  error.idstring_seqstrings_only_one_per_sequence = idstrings y seqstrings contienen una cadena por cada secuencia
@@@ -893,7 -881,6 +886,7 @@@ error.implementation_error_minlen_must_
  error.implementation_error_msawbjob_called = Error de implementación - StartJob(MsaWSJob) invocado en un WSJobInstance {0}
  error.implementation_error_cannot_attach_ws_menu_entry = Error de implementación: Â¡no es posible adjunto una WS Menu Entry sin una referencia a un manejador del servicio!
  error.parameter_migration_not_implemented_yet = La migración de parámetros no se ha implementado todavía
 +error.implementation_error_cannot_set_jaba_option = Error de implementación: no es posible establecer el valor de Jaba Option a un valor fuera de su rango permitido
  error.implementation_error_valuetype_doesnt_support_jabaws_type = Error de implementación: jalview.ws.params.ValueConstrainI.ValueType no soporta el tipo JABAWS: {0}
  error.cannot_create_jabaws_param_set = No es posible crear un JabaWSParamSet con parámetros no JabaWS
  error.cannot_set_arguments_to_jabaws_param_set = No es posible establecer argumentos en JabaWSParamSet que no sean argumentos JabaWS 
@@@ -915,6 -902,7 +908,7 @@@ error.dbrefsource_implementation_except
  error.implementation_error_dbinstance_must_implement_interface = Error de Implementación- getDbInstances debe recibir una clase que implemente jalview.ws.seqfetcher.DbSourceProxy (recibió {0})
  error.implementation_error_must_init_dbsources =Error de implementación. Debe inicializar dbSources
  label.view_controller_toggled_marked = {0} {1} columnas {2} características del tipo {3} en {4} secuencia(s)
+ label.no_highlighted_regions_marked = No hay regiones resaltadas marcadas
  label.toggled = Invertida
  label.marked = Marcada
  label.containing = conteniendo
@@@ -986,7 -974,6 +980,6 @@@ exception.unable_to_create_internet_con
  exception.invocation_target_calling_url = InvocationTargetException mientras se invocaba openURL: {0}
  exception.illegal_access_calling_url = IllegalAccessException mientras se invocaba openURL: {0}
  exception.interrupted_launching_browser = InterruptedException mientras se lanzaba el navegador: {0}
- exception.ebiembl_retrieval_failed_on = La recuperación de datos EBI EMBL XML ha fallado en {0}:{1}
  exception.no_pdb_records_for_chain = No se han encontrado registros {0} para la cadena {1}
  exception.unexpected_handling_rnaml_translation_for_pdb = Excepcion inesperada cuando se traducían a RNAML los datos PDB
  exception.couldnt_recover_sequence_properties_for_alignment = No es posible recuperar las propiedades de la secuencia para el alineamiento
@@@ -1025,10 -1012,7 +1018,7 @@@ label.png_image = Imagen PN
  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}
  status.opening_params = Abriendo {0}
- status.waiting_sequence_database_fetchers_init = Esperando inicialización de los recuperadores de bases de datos de secuencias
- status.init_sequence_database_fetchers = Inicializando recuperadores de bases de datos de secuencias
  status.fetching_sequence_queries_from = Recuperando {0} consultas de secuencias de {1}
  status.finshed_querying = Consulta finalizada
  status.parsing_results = Parseando resultados.
@@@ -1038,15 -1022,11 +1028,13 @@@ status.collecting_job_results = Recolec
  status.fetching_db_refs = Recuperando db refs
  label.font_doesnt_have_letters_defined = La fuente no tiene letras definidas\npor lo que no puede emplease\ncon datos de alineamientos
  label.font_too_small = Tamaño de la letra es demasiado pequeña
 +label.error_loading_file_params = Error cargando el fichero {0}
 +label.error_loading_jalview_file = Error cargando el fichero Jalview 
  warn.out_of_memory_when_action = Sin memoria al {0}\!\!\nConsulte los ficheros de ayuda para ajustar la memoria de la m\u00E1quina virtual de Java.
  warn.out_of_memory_loading_file = Sin memoria al cargar el fichero {0}\!\!\nConsulte los ficheros de ayuda para ajustar la memoria de la m\u00E1quina virtual de Java.
  label.out_of_memory = Sin memoria
  label.invalid_id_column_width = Identificador de anchura de columna no válido
  warn.user_defined_width_requirements = La anchura definida por el usuario para la \nlas columnas de anotaci\u00F3n e identificador de secuencias\nen figuras exportadas debe ser\na, al menos, de 12 p\u00EDxels
- label.couldnt_create_sequence_fetcher = No es posible crear SequenceFetcher
- warn.couldnt_create_sequence_fetcher_client = No es posible crear el cliente de recuperador de secuencias. Comprueba el fichero de log para más detalles.
  warn.server_didnt_pass_validation = El servicio no ha pasado la validaci\u00F3n.\nCompruebe la consola de Jalview para m\u00E1s detalles.
  warn.url_must_contain = La URL de la secuencia debe contener $SEQUENCE_ID$, $DB_ACCESSION$ o un regex
  info.validate_jabaws_server = \u00BFValidar el servidor JabaWS?\n(Consulte la consola de salida para obtener los resultados)
@@@ -1089,7 -1069,6 +1077,6 @@@ label.rnalifold_calculations=Predicció
  label.rnalifold_settings=Cambiar ajustes RNAAliFold...
  label.sort_ann_by=Ordenar anotaciones por
  info.enter_search_text_here=Introducir texto de búsqueda aquí
- action.load_vamsas_session=Cargar Sesión Vamsas...
  label.show_all_al_annotations=Mostrar anotaciones relacionadas con el alineamiento 
  label.hide_all_al_annotations=Ocultar anotaciones relacionadas con el alineamiento
  label.show_all_seq_annotations=Mostrar anotaciones relacionadas con las secuencias
@@@ -1117,9 -1096,8 +1104,8 @@@ label.autoadd_secstr=Añadir anotación d
  action.annotations=Anotaciones
  label.nuc_alignment_colour=Color del Alineamiento Nucleotídico
  label.copy_format_from=Copiar formato de
- label.chimera=Chimera
- label.create_chimera_attributes = Escribir características de Jalview
- label.create_chimera_attributes_tip = Establecer atributos en Chimera para características visibles 
+ label.create_viewer_attributes = Escribir características de Jalview
+ label.create_viewer_attributes_tip = Establecer atributos de residuos para características visibles 
  label.attributes_set = {0} valores de atributos establecidos en Chimera
  label.open_split_window=Abrir ventana dividida
  label.open_split_window?=¿Quieres abrir ventana dividida, con cDNA y proteína vinculadas?
@@@ -1139,8 -1117,9 +1125,9 @@@ label.invalid_search=Texto de búsqueda 
  action.export_annotations=Exportar Anotaciones
  action.set_as_reference=Marcar como Referencia
  action.unmark_as_reference=Desmarcar como Referencia
- label.chimera_failed=Error al abrir Chimera - está instalado?\nCompruebe ruta en Preferencias, Estructura
+ label.open_viewer_failed=Error al abrir {0} - está instalado?\nCompruebe ruta en Preferencias, Estructura
  label.find=Buscar
+ label.in = en
  label.select_pdb_file=Seleccionar Fichero PDB
  label.structures_filter=Filtro de Estructuras
  label.scale_protein_to_cdna=Adaptar proteína a cDNA
@@@ -1152,7 -1131,6 +1139,6 @@@ action.export_features=Exportar Caracte
  error.invalid_regex=Expresión regular inválida
  label.autoadd_temp=Añadir anotación factor de temperatura al alineamiento
  label.double_click_to_browse = Haga doble clic para buscar fichero 
- label.chimera_path_tip=Jalview intentará primero las rutas introducidas aquí, Y si no las rutas usuales de instalación
  label.structure_chooser=Selector de Estructuras
  label.structure_chooser_manual_association=Selector de Estructuras - asociación manual
  label.threshold_filter=Filtro de Umbral
@@@ -1160,11 -1138,11 +1146,11 @@@ label.add_reference_annotations=Añadir 
  label.hide_insertions=Ocultar Inserciones
  info.change_threshold_mode_to_enable=Cambiar Modo de Umbral para Habilitar
  label.separate_multiple_query_values=Introducir uno o mas {0}s separados por punto y coma ";"
  label.fetch_chimera_attributes = Buscar atributos desde Chimera
  label.fetch_chimera_attributes_tip = Copiar atributo de Chimera a característica de Jalview
  label.view_rna_structure=Estructura 2D VARNA
- label.colour_with_chimera=Colorear con Chimera
+ label.colour_with_viewer = Colorear con visualizador de estructuras
+ label.let_viewer_manage_structure_colours = Deja que el visualizador maneje los colores de estructuras
  label.superpose_structures = Superponer estructuras
  error.superposition_failed = Superposición fallido: {0}
  label.insufficient_residues = Residuos alineados ({0}) insuficentes para superponer
@@@ -1175,7 -1153,6 +1161,6 @@@ tooltip.aacon_settings=Cambiar ajustes 
  label.mark_as_representative=Marcar como representativa
  label.include_description=Incluir Descripción
  label.for=para
- label.invalid_chimera_path=Ruta de Chimera no encontrada o no ejecutable
  info.search_in_annotation_label=Buscar en etiqueta de {0}
  info.search_in_annotation_description=Buscar en descripción de {0}
  label.select_many_views=Seleccionar múltiples vistas
@@@ -1185,23 -1162,26 +1170,26 @@@ warn.urls_not_contacted=URLs que no pud
  label.prot_alignment_colour=Color del Alineamiento Proteico
  info.associate_wit_sequence=Asociar con secuencia
  label.protein=Proteína
+ label.CDS=CDS
  warn.oneseq_msainput_selection=La selección actual sólo contiene una Ãºnica secuencia. Â¿Quieres enviar todas las secuencias para la alineación en su lugar?
  label.use_rnaview=Usar RNAView para estructura secondaria
  label.search_all=Introducir uno o más valores de búsqueda separados por punto y coma ";" (Nota: buscará en toda la base de datos PDB)
- label.confirm_close_chimera=Cerrará la conexión de Jalview a {0}.<br>¿Quieres cerrar la ventana Chimera también?
+ label.confirm_close_viewer=Cerrará la conexión de Jalview a {0}.<br>¿Quieres cerrar la ventana {1} también?
  tooltip.rnalifold_calculations=Se calcularán predicciones de estructura secondaria de RNA para el alineaminento, y se actualizarán si se efectuan cambios
  tooltip.rnalifold_settings=Modificar la configuración de la predicción RNAAlifold. Ãšselo para ocultar o mostrar resultados del cálculo de RNA, o cambiar parámetros de el plegado de RNA.
  label.show_selected_annotations=Mostrar anotaciones seleccionadas
- status.colouring_chimera=Coloreando Chimera
+ status.colouring_structures=Coloreando estructuras
  label.configure_displayed_columns=Configurar Columnas Mostradas
  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
- label.chimera_path=Ruta de acceso a Chimera
+ label.viewer_path=Ruta de acceso a {0}
+ label.viewer_path_tip=Jalview intentará primero las rutas introducidas aquí, Y si no las rutas usuales de instalación
+ label.invalid_viewer_path=Ruta no encontrada o no ejecutable
+ label.viewer_missing=Visualizador de estructura no encontrado.<br/>Por favor, introduzca la ruta de la ejecutable,<br/>o descargar e instalar el programa.
  warn.delete_all=<html>Borrar todas las secuencias cerrará la ventana del alineamiento.<br>Confirmar o Cancelar.
  label.select_all=Seleccionar Todos
  label.alpha_helix=Hélice Alfa
- label.chimera_help=Ayuda para Chimera
  label.find_tip=Buscar alineamiento, selección o IDs de secuencia para una subsecuencia (sin huecos)
  label.structure_viewer=Visualizador por defecto
  label.embbed_biojson=Incrustar BioJSON al exportar HTML
@@@ -1213,12 -1193,10 +1201,11 @@@ label.aacon_settings=Cambiar Ajustes AA
  tooltip.aacon_calculations=Actualizar cálculos AACon automáticamente.
  info.select_filter_option=Escoger Opción de Filtro / Entrada Manual
  info.invalid_msa_input_mininfo=Necesita por lo menos dos secuencias con al menos 3 residuos cada una, sin regiones ocultas entre ellas.
- label.chimera_missing=Visualizador de estructura Chimera no encontrado.<br/>Por favor, introduzca la ruta de Chimera,<br/>o descargar e instalar la UCSF Chimera.
  exception.fts_server_unreachable=Jalview no puede conectar con el servidor {0}. \nPor favor asegúrese de que está conectado a Internet y vuelva a intentarlo.
  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
 +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
@@@ -1313,7 -1291,6 +1300,7 @@@ option.enable_disable_autosearch = Marc
  option.autosearch = Auto búsqueda
  label.retrieve_ids = Recuperar IDs
  label.display_settings_for = Visualización de características {0}
 +label.simple = Simple
  label.simple_colour = Color simple
  label.colour_by_text = Colorear por texto
  label.graduated_colour = Color graduado
@@@ -1336,13 -1313,13 +1323,13 @@@ label.alignment = alineamient
  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ú  
- action.search = Buscar
  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
  label.backupfiles_confirm_save_file_backupfiles_roll_wrong = Posiblemente algo está mal con los archivos de respaldos.
  label.backupfiles_confirm_save_new_saved_file_ok = El nuevo archivo guardado parece estar bien.
  label.backupfiles_confirm_save_new_saved_file_not_ok = El nuevo archivo guardado podría no estar bien.
+ label.continue_operation = Â¿Continuar operación?
  label.backups = Respaldos
  label.backup = Respaldo
  label.backup_files = Archivos de respaldos
@@@ -1351,7 -1328,6 +1338,6 @@@ label.backup_filename_strategy = Estrat
  label.append_to_filename = Adjuntar texto (%n es reemplazado por el número de respaldo)
  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
@@@ -1380,13 -1356,11 +1366,11 @@@ label.single_file_description = Conserv
  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
  label.cancel_changes = Cancelar cambios
  label.warning_confirm_change_reverse = Â¡Advertencia!\nSi cambia el incremento/decremento del número de archivos de respaldos, sin cambiar el sufijo o número de dígitos,\nesto puede causar la pérdida de los archivos de respaldos creados con el esquema anterior de nombre de archivo de respaldos.\n¿Está seguro de que desea hacer esto?
  label.change_increment_decrement = Â¿Cambiar de incremento/decremento?
- label.was_previous = era {0}
  label.newerdelete_replacement_line = El archivo de respaldo\n''{0}''\t(modificado {2}, tamaño {4})\nserá borrado y reemplazarse por un archivo aparentemente más antiguo\n''{1}''\t(modificado {3}, tamaño {5}).
  label.confirm_deletion_or_rename = Confirmar borrar ''{0}'', o cambiar el nombre a ''{1}''?
  label.newerdelete_line = El archivo de respaldo\n''{0}''\t(modificado {2}, tamaño {4})\nserá borrado pero es mas nuevo que el archivo de respaldo restante más antiguo\n''{1}''\t(modified {3}, size {5}).
@@@ -1409,3 -1383,9 +1393,9 @@@ label.include_linked_features = Inclui
  label.include_linked_tooltip = Incluir características de {0}<br>convertidas a coordenadas de secuencia local
  label.features_not_shown = {0} característica(s) no mostradas
  label.no_features_to_sort_by = No hay características para ordenar
+ label.ignore_hidden = Ignorar columnas ocultas
+ label.ignore_hidden_tooltip = Ignorar caracteres en columnas ocultas
+ label.log_level = Nivel del registro
+ label.log_level_tooltip = Establezca temporalmente el nivel de registro para esta consola. El nivel de registro volverá a {0} cuando se cierre esta consola de Java.
+ label.copy_to_clipboard = Copiar en el portapapeles
+ label.copy_to_clipboard_tooltip = Copie todo el texto de registro en esta consola al portapapeles del sistema
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.analysis;
  
+ import java.util.Locale;
  import jalview.commands.RemoveGapColCommand;
  import jalview.datamodel.AlignedCodon;
  import jalview.datamodel.AlignedCodonFrame;
@@@ -181,9 -183,9 +183,9 @@@ public class AlignmentUtil
        // TODO use Character.toLowerCase to avoid creating String objects?
        char[] upstream = new String(ds
                .getSequence(s.getStart() - 1 - ustream_ds, s.getStart() - 1))
-                       .toLowerCase().toCharArray();
+                       .toLowerCase(Locale.ROOT).toCharArray();
        char[] downstream = new String(
-               ds.getSequence(s_end - 1, s_end + dstream_ds)).toLowerCase()
+               ds.getSequence(s_end - 1, s_end + dstream_ds)).toLowerCase(Locale.ROOT)
                        .toCharArray();
        char[] coreseq = s.getSequence();
        char[] nseq = new char[offset + upstream.length + downstream.length
      if (cdnaLength != mappedLength && cdnaLength > 2)
      {
        String lastCodon = String.valueOf(cdnaSeqChars,
-               cdnaLength - CODON_LENGTH, CODON_LENGTH).toUpperCase();
+               cdnaLength - CODON_LENGTH, CODON_LENGTH).toUpperCase(Locale.ROOT);
        for (String stop : ResidueProperties.STOP_CODONS)
        {
          if (lastCodon.equals(stop))
       */
      int startOffset = 0;
      if (cdnaLength != mappedLength && cdnaLength > 2
-             && String.valueOf(cdnaSeqChars, 0, CODON_LENGTH).toUpperCase()
+             && String.valueOf(cdnaSeqChars, 0, CODON_LENGTH).toUpperCase(Locale.ROOT)
                      .equals(ResidueProperties.START))
      {
        startOffset += CODON_LENGTH;
        final List<AlignmentAnnotation> result = new ArrayList<>();
        for (AlignmentAnnotation dsann : datasetAnnotations)
        {
 -        /*
 -         * Find matching annotations on the alignment. If none is found, then
 -         * add this annotation to the list of 'addable' annotations for this
 -         * sequence.
 -         */
 -        final Iterable<AlignmentAnnotation> matchedAlignmentAnnotations = al
 -                .findAnnotations(seq, dsann.getCalcId(), dsann.label);
 -        if (!matchedAlignmentAnnotations.iterator().hasNext())
 +        if (dsann.annotations != null) // ignore non-positional annotation
          {
 -          result.add(dsann);
 -          if (labelForCalcId != null)
 +          /*
 +           * Find matching annotations on the alignment. If none is found, then
 +           * add this annotation to the list of 'addable' annotations for this
 +           * sequence.
 +           */
 +          final Iterable<AlignmentAnnotation> matchedAlignmentAnnotations = al
 +                  .findAnnotations(seq, dsann.getCalcId(), dsann.label);
 +          if (!matchedAlignmentAnnotations.iterator().hasNext())
            {
 -            labelForCalcId.put(dsann.getCalcId(), dsann.label);
 +            result.add(dsann);
 +            if (labelForCalcId != null)
 +            {
 +              labelForCalcId.put(dsann.getCalcId(), dsann.label);
 +            }
            }
          }
 -      }
 -      /*
 -       * Save any addable annotations for this sequence
 -       */
 -      if (!result.isEmpty())
 -      {
 -        candidates.put(seq, result);
 +        /*
 +         * Save any addable annotations for this sequence
 +         */
 +        if (!result.isEmpty())
 +        {
 +          candidates.put(seq, result);
 +        }
        }
      }
    }
   */
  package jalview.analysis;
  
+ import java.util.Locale;
  import jalview.api.AlignViewportI;
  import jalview.api.FinderI;
  import jalview.datamodel.AlignmentI;
- import jalview.datamodel.Range;
  import jalview.datamodel.SearchResultMatchI;
  import jalview.datamodel.SearchResults;
  import jalview.datamodel.SearchResultsI;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
- import jalview.datamodel.VisibleContigsIterator;
  import jalview.util.Comparison;
 +import jalview.util.Platform;
+ import jalview.util.MapList;
  
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.Iterator;
  import java.util.List;
- import java.util.Vector;
  
  import com.stevesoft.pat.Regex;
  
@@@ -51,7 -53,7 +54,7 @@@ public class Finder implements Finder
    /*
     * sequences matched by id or description
     */
-   private Vector<SequenceI> idMatches;
+   private List<SequenceI> idMatches;
  
    /*
     * the viewport to search over
    private int sequenceIndex;
  
    /*
-    * column position in sequence to search from, base 0
-    * - absolute column number including any hidden columns
-    * (position after start of last match for a repeat search)
+    * position offset in sequence to search from, base 0
+    * (position after start of last match for a 'find next')
+    */
+   private int residueIndex;
+   /*
+    * the true sequence position of the start of the 
+    * last sequence searched (when 'ignore hidden regions' does not apply)
     */
-   private int columnIndex;
+   private int searchedSequenceStartPosition;
+   /*
+    * when 'ignore hidden regions' applies, this holds the mapping from
+    * the visible sequence positions (1, 2, ...) to true sequence positions
+    */
+   private MapList searchedSequenceMap;
+   private String seqToSearch;
  
    /**
     * Constructor for searching a viewport
    {
      this.viewport = av;
      this.sequenceIndex = 0;
-     this.columnIndex = -1;
+     this.residueIndex = -1;
    }
  
    @Override
    public void findAll(String theSearchString, boolean matchCase,
-           boolean searchDescription)
+           boolean searchDescription, boolean ignoreHidden)
    {
      /*
       * search from the start
       */
      sequenceIndex = 0;
-     columnIndex = -1;
+     residueIndex = -1;
  
-     doFind(theSearchString, matchCase, searchDescription, true);
+     doFind(theSearchString, matchCase, searchDescription, true,
+             ignoreHidden);
  
      /*
       * reset to start for next search
       */
      sequenceIndex = 0;
-     columnIndex = -1;
+     residueIndex = -1;
    }
  
    @Override
    public void findNext(String theSearchString, boolean matchCase,
-           boolean searchDescription)
+           boolean searchDescription, boolean ignoreHidden)
    {
-     doFind(theSearchString, matchCase, searchDescription, false);
+     doFind(theSearchString, matchCase, searchDescription, false,
+             ignoreHidden);
      
      if (searchResults.isEmpty() && idMatches.isEmpty())
      {
         * search failed - reset to start for next search
         */
        sequenceIndex = 0;
-       columnIndex = -1;
+       residueIndex = -1;
      }
    }
  
     * @param matchCase
     * @param searchDescription
     * @param findAll
+    * @param ignoreHidden
     */
    protected void doFind(String theSearchString, boolean matchCase,
-           boolean searchDescription, boolean findAll)
+           boolean searchDescription, boolean findAll, boolean ignoreHidden)
    {
+     searchResults = new SearchResults();
+     idMatches = new ArrayList<>();
      String searchString = matchCase ? theSearchString
-             : theSearchString.toUpperCase();
-     Regex searchPattern = Platform.newRegex(searchString);
+             : theSearchString.toUpperCase(Locale.ROOT);
 -    Regex searchPattern = new Regex(searchString);
++  Regex searchPattern = Platform.newRegex(searchString);
      searchPattern.setIgnoreCase(!matchCase);
  
-     searchResults = new SearchResults();
-     idMatches = new Vector<>();
      SequenceGroup selection = viewport.getSelectionGroup();
      if (selection != null && selection.getSize() < 1)
      {
      AlignmentI alignment = viewport.getAlignment();
      int end = alignment.getHeight();
  
-     while (sequenceIndex < end)
+     getSequence(ignoreHidden);
+     boolean found = false;
+     while ((!found || findAll) && sequenceIndex < end)
      {
-       SequenceI seq = alignment.getSequenceAt(sequenceIndex);
-       boolean found = findNextMatch(seq, searchString, searchPattern,
-               searchDescription);
-       if (found && !findAll)
+       found = findNextMatch(searchString, searchPattern, searchDescription,
+               ignoreHidden);
+     }
+   }
+   /**
+    * Calculates and saves the sequence string to search. The string is restricted
+    * to the current selection region if there is one, and is saved with all gaps
+    * removed.
+    * <p>
+    * If there are hidden columns, and option {@ignoreHidden} is selected, then
+    * only visible positions of the sequence are included, and a mapping is also
+    * constructed from the returned string positions to the true sequence
+    * positions.
+    * <p>
+    * Note we have to do this each time {@code findNext} or {@code findAll} is
+    * called, in case the alignment, selection group or hidden columns have
+    * changed. In particular, if the sequence at offset {@code sequenceIndex} in
+    * the alignment is (no longer) in the selection group, search is advanced to
+    * the next sequence that is.
+    * <p>
+    * Sets sequence string to the empty string if there are no more sequences (in
+    * selection group if any) at or after {@code sequenceIndex}.
+    * <p>
+    * Returns true if a sequence could be found, false if end of alignment was
+    * reached
+    * 
+    * @param ignoreHidden
+    * @return
+    */
+   private boolean getSequence(boolean ignoreHidden)
+   {
+     AlignmentI alignment = viewport.getAlignment();
+     if (sequenceIndex >= alignment.getHeight())
+     {
+       seqToSearch = "";
+       return false;
+     }
+     SequenceI seq = alignment.getSequenceAt(sequenceIndex);
+     SequenceGroup selection = viewport.getSelectionGroup();
+     if (selection != null && !selection.contains(seq))
+     {
+       if (!nextSequence(ignoreHidden))
        {
-         return;
+         return false;
        }
-       if (!found)
+       seq = alignment.getSequenceAt(sequenceIndex);
+     }
+     String seqString = null;
+     if (ignoreHidden)
+     {
+       seqString = getVisibleSequence(seq);
+       this.searchedSequenceStartPosition = 1;
+     }
+     else
+     {
+       int startCol = 0;
+       int endCol = seq.getLength() - 1;
+       this.searchedSequenceStartPosition = seq.getStart();
+       if (selection != null)
        {
-         sequenceIndex++;
-         columnIndex = -1;
+         startCol = selection.getStartRes();
+         endCol = Math.min(endCol, selection.getEndRes());
+         this.searchedSequenceStartPosition = seq.findPosition(startCol);
        }
+       seqString = seq.getSequenceAsString(startCol, endCol + 1);
      }
+     /*
+      * remove gaps; note that even if this leaves an empty string, we 'search'
+      * the sequence anyway (for possible match on name or description)
+      */
+     String ungapped = AlignSeq.extractGaps(Comparison.GapChars, seqString);
+     this.seqToSearch = ungapped;
+     return true;
    }
  
    /**
-    * Answers the start-end column range of the visible region of
-    * <code>sequence</code> starting at or after the given <code>column</code>.
-    * If there are no hidden columns, this just returns the remaining width of
-    * the sequence. The range is restricted to the current <code>selection</code>
-    * if there is one. Answers null if there are no visible columns at or after
-    * <code>column</code>.
+    * Returns a string consisting of only the visible residues of {@code seq} from
+    * alignment column {@ fromColumn}, restricted to the current selection region
+    * if there is one.
+    * <p>
+    * As a side-effect, also computes the mapping from the true sequence positions
+    * to the positions (1, 2, ...) of the returned sequence. This is to allow
+    * search matches in the visible sequence to be converted to sequence positions.
+    * 
+    * @param seq
+    * @return
     */
-   protected Range getNextVisibleSequenceRegion(SequenceI sequence,
-           int column)
+   private String getVisibleSequence(SequenceI seq)
    {
-     int seqColStart = column;
-     int seqColEnd = sequence.getLength() - 1;
      /*
-      * restrict search to (next) visible column region, 
-      * in case there are hidden columns
+      * get start / end columns of sequence and convert to base 0
+      * (so as to match the visible column ranges)
       */
-     AlignmentI alignment = viewport.getAlignment();
-     VisibleContigsIterator visibleRegions = alignment.getHiddenColumns()
-             .getVisContigsIterator(column, alignment.getWidth(),
-                     false);
-     int[] visible = visibleRegions.hasNext() ? visibleRegions.next() : null;
-     if (visible == null)
-     {
-       columnIndex = seqColEnd + 1;
-       return null;
-     }
-     seqColStart = Math.max(seqColStart, visible[0]);
-     seqColEnd = Math.min(seqColEnd, visible[1]);
+     int seqStartCol = seq.findIndex(seq.getStart()) - 1;
+     int seqEndCol = seq.findIndex(seq.getStart() + seq.getLength() - 1) - 1;
+     Iterator<int[]> visibleColumns = viewport.getViewAsVisibleContigs(true);
+     StringBuilder visibleSeq = new StringBuilder(seqEndCol - seqStartCol);
+     List<int[]> fromRanges = new ArrayList<>();
  
-     /*
-      * restrict search to selected region if there is one
-      */
-     SequenceGroup selection = viewport.getSelectionGroup();
-     if (selection != null)
+     while (visibleColumns.hasNext())
      {
-       int selectionStart = selection.getStartRes();
-       int selectionEnd = selection.getEndRes();
-       if (selectionStart > seqColEnd || selectionEnd < seqColStart)
+       int[] range = visibleColumns.next();
+       if (range[0] > seqEndCol)
+       {
+         // beyond the end of the sequence
+         break;
+       }
+       if (range[1] < seqStartCol)
+       {
+         // before the start of the sequence
+         continue;
+       }
+       String subseq = seq.getSequenceAsString(range[0], range[1] + 1);
+       String ungapped = AlignSeq.extractGaps(Comparison.GapChars, subseq);
+       visibleSeq.append(ungapped);
+       if (!ungapped.isEmpty())
        {
          /*
-          * sequence region doesn't overlap selection region 
+          * visible region includes at least one non-gap character,
+          * so add the range to the mapping being constructed
           */
-         columnIndex = seqColEnd + 1;
-         return null;
+         int seqResFrom = seq.findPosition(range[0]);
+         int seqResTo = seqResFrom + ungapped.length() - 1;
+         fromRanges.add(new int[] { seqResFrom, seqResTo });
        }
-       seqColStart = Math.max(seqColStart, selectionStart);
-       seqColEnd = Math.min(seqColEnd, selectionEnd);
      }
  
-     return new Range(seqColStart, seqColEnd);
+     /*
+      * construct the mapping
+      * from: visible sequence positions 1..length
+      * to:   true residue positions of the alignment sequence
+      */
+     List<int[]> toRange = Arrays
+             .asList(new int[]
+             { 1, visibleSeq.length() });
+     searchedSequenceMap = new MapList(fromRanges, toRange, 1, 1);
+     return visibleSeq.toString();
    }
  
    /**
-    * Finds the next match in the given sequence, starting at column at
-    * <code>columnIndex</code>. Answers true if a match is found, else false. If
-    * a match is found, <code>columnIndex</code> is advanced to the column after
-    * the start of the matched region, ready for a search from the next position.
+    * Advances the search to the next sequence in the alignment. Sequences not in
+    * the current selection group (if there is one) are skipped. The (sub-)sequence
+    * to be searched is extracted, gaps removed, and saved, or set to null if there
+    * are no more sequences to search.
+    * <p>
+    * Returns true if a sequence could be found, false if end of alignment was
+    * reached
     * 
-    * @param seq
+    * @param ignoreHidden
+    */
+   private boolean nextSequence(boolean ignoreHidden)
+   {
+     sequenceIndex++;
+     residueIndex = -1;
+     return getSequence(ignoreHidden);
+   }
+   /**
+    * Finds the next match in the given sequence, starting at offset
+    * {@code residueIndex}. Answers true if a match is found, else false.
+    * <p>
+    * If a match is found, {@code residueIndex} is advanced to the position after
+    * the start of the matched region, ready for the next search.
+    * <p>
+    * If no match is found, {@code sequenceIndex} is advanced ready to search the
+    * next sequence.
+    * 
+    * @param seqToSearch
     * @param searchString
     * @param searchPattern
     * @param matchDescription
+    * @param ignoreHidden
     * @return
     */
-   protected boolean findNextMatch(SequenceI seq, String searchString,
-           Regex searchPattern, boolean matchDescription)
+   protected boolean findNextMatch(String searchString,
+           Regex searchPattern, boolean matchDescription,
+           boolean ignoreHidden)
    {
-     SequenceGroup selection = viewport.getSelectionGroup();
-     if (selection != null && !selection.contains(seq))
-     {
-       /*
-        * this sequence is not in the selection - advance to next sequence
-        */
-       return false;
-     }
-     if (columnIndex < 0)
+     if (residueIndex < 0)
      {
        /*
         * at start of sequence; try find by residue number, in sequence id,
         * or (optionally) in sequence description
         */
-       if (doNonMotifSearches(seq, searchString, searchPattern,
+       if (doNonMotifSearches(searchString, searchPattern,
                matchDescription))
        {
          return true;
      /*
       * search for next match in sequence string
       */
-     int end = seq.getLength();
-     while (columnIndex < end)
+     int end = seqToSearch.length();
+     while (residueIndex < end)
      {
-       if (searchNextVisibleRegion(seq, searchPattern))
+       boolean matched = searchPattern.searchFrom(seqToSearch, residueIndex);
+       if (matched)
        {
-         return true;
+         if (recordMatch(searchPattern, ignoreHidden))
+         {
+           return true;
+         }
+       }
+       else
+       {
+         residueIndex = Integer.MAX_VALUE;
        }
-     }
-     return false;
-   }
-   /**
-    * Searches the sequence, starting from <code>columnIndex</code>, and adds the
-    * next match (if any) to <code>searchResults</code>. The search is restricted
-    * to the next visible column region, and to the <code>selection</code> region
-    * if there is one. Answers true if a match is added, else false.
-    * 
-    * @param seq
-    * @param searchPattern
-    * @return
-    */
-   protected boolean searchNextVisibleRegion(SequenceI seq, Regex searchPattern)
-   {
-     Range visible = getNextVisibleSequenceRegion(seq, columnIndex);
-     if (visible == null)
-     {
-       return false;
-     }
-     String seqString = seq.getSequenceAsString(visible.start, visible.end + 1);
-     String noGaps = AlignSeq.extractGaps(Comparison.GapChars, seqString);
-     if (searchPattern.search(noGaps))
-     {
-       int sequenceStartPosition = seq.findPosition(visible.start);
-       recordMatch(seq, searchPattern, sequenceStartPosition);
-       return true;
-     }
-     else
-     {
-       /*
-        * no match - advance columnIndex past this visible region
-        * so the next visible region (if any) is searched next
-        */
-       columnIndex = visible.end + 1;
      }
  
+     nextSequence(ignoreHidden);
      return false;
    }
  
    /**
     * Adds the match held in the <code>searchPattern</code> Regex to the
     * <code>searchResults</code>, unless it is a subregion of the last match
-    * recorded. <code>columnIndex</code> is advanced to the position after the
+    * recorded. <code>residueIndex</code> is advanced to the position after the
     * start of the matched region, ready for the next search. Answers true if a
     * match was added, else false.
+    * <p>
+    * Matches that lie entirely within hidden regions of the alignment are not
+    * added.
     * 
-    * @param seq
     * @param searchPattern
-    * @param firstResiduePosition
+    * @param ignoreHidden
     * @return
     */
-   protected boolean recordMatch(SequenceI seq, Regex searchPattern,
-           int firstResiduePosition)
+   protected boolean recordMatch(Regex searchPattern, boolean ignoreHidden)
    {
+     SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
      /*
-      * get start/end of the match in sequence coordinates
+      * convert start/end of the match to sequence coordinates
       */
      int offset = searchPattern.matchedFrom();
-     int matchStartPosition = firstResiduePosition + offset;
+     int matchStartPosition = this.searchedSequenceStartPosition + offset;
      int matchEndPosition = matchStartPosition
              + searchPattern.charsMatched() - 1;
  
      /*
-      * update columnIndex to next column after the start of the match
+      * update residueIndex to next position after the start of the match
       * (findIndex returns a value base 1, columnIndex is held base 0)
       */
-     columnIndex = seq.findIndex(matchStartPosition);
+     residueIndex = searchPattern.matchedFrom()+1;
+     /*
+      * return false if the match is entirely in a hidden region
+      */
+     if (allHidden(seq, matchStartPosition, matchEndPosition))
+     {
+       return false;
+     }
  
      /*
       * check that this match is not a subset of the previous one (JAL-2302)
      if (lastMatch == null || !lastMatch.contains(seq, matchStartPosition,
              matchEndPosition))
      {
-       searchResults.addResult(seq, matchStartPosition, matchEndPosition);
+       addMatch(seq, matchStartPosition, matchEndPosition, ignoreHidden);
        return true;
      }
  
    }
  
    /**
+    * Adds one match to the stored list. If hidden residues are being skipped, then
+    * the match may need to be split into contiguous positions of the sequence (so
+    * it does not include skipped residues).
+    * 
+    * @param seq
+    * @param matchStartPosition
+    * @param matchEndPosition
+    * @param ignoreHidden
+    */
+   private void addMatch(SequenceI seq, int matchStartPosition,
+           int matchEndPosition, boolean ignoreHidden)
+   {
+     if (!ignoreHidden)
+     {
+       /*
+        * simple case
+        */
+       searchResults.addResult(seq, matchStartPosition, matchEndPosition);
+       return;
+     }
+     /*
+      *  get start-end contiguous ranges in underlying sequence
+      */
+     int[] truePositions = searchedSequenceMap
+             .locateInFrom(matchStartPosition, matchEndPosition);
+     searchResults.addResult(seq, truePositions);
+   }
+   /**
+    * Returns true if all residues are hidden, else false
+    * 
+    * @param seq
+    * @param fromPos
+    * @param toPos
+    * @return
+    */
+   private boolean allHidden(SequenceI seq, int fromPos, int toPos)
+   {
+     if (!viewport.hasHiddenColumns())
+     {
+       return false;
+     }
+     for (int res = fromPos; res <= toPos; res++)
+     {
+       if (isVisible(seq, res))
+       {
+         return false;
+       }
+     }
+     return true;
+   }
+   /**
     * Does searches other than for residue patterns. Currently this includes
     * <ul>
     * <li>find residue by position (if search string is a number)</li>
     * </ul>
     * Answers true if a match is found, else false.
     * 
-    * @param seq
     * @param searchString
     * @param searchPattern
     * @param includeDescription
     * @return
     */
-   protected boolean doNonMotifSearches(SequenceI seq, String searchString,
+   protected boolean doNonMotifSearches(String searchString,
            Regex searchPattern, boolean includeDescription)
    {
+     SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
      /*
       * position sequence search to start of sequence
       */
-     columnIndex = 0;
-     if (searchForResidueNumber(seq, searchString))
+     residueIndex = 0;
+     try
      {
-       return true;
+       int res = Integer.parseInt(searchString);
+       return searchForResidueNumber(seq, res);
+     } catch (NumberFormatException ex)
+     {
+       // search pattern is not a number
      }
      if (searchSequenceName(seq, searchPattern))
      {
        return true;
      String desc = seq.getDescription();
      if (desc != null && searchPattern.search(desc) && !idMatches.contains(seq))
      {
-       idMatches.addElement(seq);
+       idMatches.add(seq);
        return true;
      }
      return false;
    {
      if (searchPattern.search(seq.getName()) && !idMatches.contains(seq))
      {
-       idMatches.addElement(seq);
+       idMatches.add(seq);
        return true;
      }
      return false;
    }
  
    /**
-    * Tries to interpret the search string as a residue position, and if valid,
-    * adds the position to the search results and returns true, else answers
-    * false
+    * If the residue position is valid for the sequence, and in a visible column,
+    * adds the position to the search results and returns true, else answers false.
+    * 
+    * @param seq
+    * @param resNo
+    * @return
     */
-   protected boolean searchForResidueNumber(SequenceI seq, String searchString)
+   protected boolean searchForResidueNumber(SequenceI seq, int resNo)
    {
-     try
+     if (seq.getStart() <= resNo && seq.getEnd() >= resNo)
      {
-       int res = Integer.parseInt(searchString);
-       if (seq.getStart() <= res && seq.getEnd() >= res)
+       if (isVisible(seq, resNo))
        {
-         searchResults.addResult(seq, res, res);
+         searchResults.addResult(seq, resNo, resNo);
          return true;
        }
-     } catch (NumberFormatException ex)
-     {
      }
      return false;
    }
  
-   /* (non-Javadoc)
-    * @see jalview.analysis.FinderI#getIdMatch()
+   /**
+    * Returns true if the residue is in a visible column, else false
+    * 
+    * @param seq
+    * @param res
+    * @return
     */
+   private boolean isVisible(SequenceI seq, int res)
+   {
+     if (!viewport.hasHiddenColumns())
+     {
+       return true;
+     }
+     int col = seq.findIndex(res); // base 1
+     return viewport.getAlignment().getHiddenColumns().isVisible(col - 1); // base 0
+   }
    @Override
-   public Vector<SequenceI> getIdMatches()
+   public List<SequenceI> getIdMatches()
    {
      return idMatches;
    }
  
-   /* (non-Javadoc)
-    * @see jalview.analysis.FinderI#getSearchResults()
-    */
    @Override
    public SearchResultsI getSearchResults()
    {
@@@ -39,6 -39,7 +39,7 @@@ import jalview.viewmodel.ViewportRanges
  import java.awt.Color;
  import java.awt.Font;
  import java.util.Hashtable;
+ import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
@@@ -55,7 -56,7 +56,7 @@@ public interface AlignViewportI extend
     * 
     * @return
     */
 -  public ViewportRanges getRanges();
 +  ViewportRanges getRanges();
  
    /**
     * calculate the height for visible annotation, revalidating bounds where
@@@ -63,7 -64,7 +64,7 @@@
     * 
     * @return total height of annotation
     */
 -  public int calcPanelHeight();
 +  int calcPanelHeight();
  
    /**
     * Answers true if the viewport has at least one column selected
  
    boolean isNormaliseSequenceLogo();
  
 +  boolean isShowInformationHistogram();
 +
 +  boolean isShowHMMSequenceLogo();
 +
 +  boolean isNormaliseHMMSequenceLogo();
 +
    ColourSchemeI getGlobalColourScheme();
  
    /**
  
    boolean isIgnoreGapsConsensus();
  
 +  boolean isIgnoreBelowBackground();
 +
    boolean isCalculationInProgress(AlignmentAnnotation alignmentAnnotation);
  
    AlignmentAnnotation getAlignmentQualityAnnot();
     * 
     * @return
     */
 -  AlignCalcManagerI getCalcManager();
 +  AlignCalcManagerI2 getCalcManager();
  
    /**
     * get the percentage gaps allowed in a conservation calculation
    @Override
    void setProteinFontAsCdna(boolean b);
  
 -  TreeModel getCurrentTree();
 +  void setHmmProfiles(ProfilesI info);
  
 -  void setCurrentTree(TreeModel tree);
 +  ProfilesI getHmmProfiles();
 +
 +  /**
 +   * Registers and starts a worker thread to calculate Information Content
 +   * annotation, if it is not already registered
 +   * 
 +   * @param ap
 +   */
 +  void initInformationWorker(AlignmentViewPanel ap);
 +
 +  boolean isInfoLetterHeight();
 +
 +  public abstract TreeModel getCurrentTree();
  
    /**
     * Answers a data bean containing data for export as configured by the
     */
    AlignmentExportData getAlignExportData(AlignExportSettingsI options);
  
 +  public abstract void setCurrentTree(TreeModel tree);
 +
    /**
     * @param update
     *          - set the flag for updating structures on next repaint
     *          - a group defined on sequences in the alignment held by the view
     */
    void addSequenceGroup(SequenceGroup sequenceGroup);
+   /**
+    * Returns an interator over the [start, end] column positions of the visible
+    * regions of the alignment
+    * 
+    * @param selectedRegionOnly
+    *                             if true, and the view has a selection region,
+    *                             then only the intersection of visible columns
+    *                             with the selection region is returned
+    * @return
+    */
+   Iterator<int[]> getViewAsVisibleContigs(boolean selectedRegionOnly);
  }
@@@ -20,8 -20,6 +20,8 @@@
   */
  package jalview.api;
  
 +import jalview.datamodel.features.FeatureMatcherSetI;
 +
  import java.util.Comparator;
  
  /**
@@@ -37,7 -35,8 +37,8 @@@ public interface FeatureSettingsModelI 
    // interface, simplifying instantiating classes
  
    /**
-    * Answers true if the specified feature type is displayed
+    * Answers true if the specified feature type is to be displayed, false if no
+    * preference
     * 
     * @param type
     * @return
    boolean isFeatureDisplayed(String type);
  
    /**
+    * Answers true if the specified feature type is to be hidden, false if no
+    * preference
+    * 
+    * @param type
+    * @return
+    */
+   boolean isFeatureHidden(String type);
+   /**
     * Answers true if the specified feature group is displayed
     * 
     * @param group
    FeatureColourI getFeatureColour(String type);
  
    /**
 +   * Returns any filters defined for the feature type, or null if not known
 +   * 
 +   * @param type
 +   * @return
 +   */
 +
 +  FeatureMatcherSetI getFeatureFilters(String type);
 +
 +  /**
     * Returns the transparency value, from 0 (fully transparent) to 1 (fully
     * opaque)
     * 
@@@ -773,7 -773,8 +773,7 @@@ public class APopupMenu extends java.aw
  
            ap.alignFrame.addHistoryItem(editCommand);
  
 -          ap.av.firePropertyChange("alignment", null,
 -                  ap.av.getAlignment().getSequences());
 +          ap.av.notifyAlignment();
          }
        }
      }
  
          ap.alignFrame.addHistoryItem(caseCommand);
  
 -        ap.av.firePropertyChange("alignment", null,
 -                ap.av.getAlignment().getSequences());
 +        ap.av.notifyAlignment();
  
        }
      }
      BLOSUM62Colour.setName(JalviewColourScheme.Blosum62.toString());
      BLOSUM62Colour.addItemListener(this);
      PIDColour.setLabel(
-             MessageManager.getString("label.colourScheme_%_identity"));
+             MessageManager.getString("label.colourScheme_%identity"));
      PIDColour.setName(JalviewColourScheme.PID.toString());
      PIDColour.addItemListener(this);
      zappoColour
              .setName(JalviewColourScheme.Hydrophobic.toString());
      hydrophobicityColour.addItemListener(this);
      helixColour.setLabel(MessageManager
-             .getString("label.colourScheme_helix_propensity"));
+             .getString("label.colourScheme_helixpropensity"));
      helixColour.setName(JalviewColourScheme.Helix.toString());
      helixColour.addItemListener(this);
      strandColour.setLabel(MessageManager
-             .getString("label.colourScheme_strand_propensity"));
+             .getString("label.colourScheme_strandpropensity"));
      strandColour.setName(JalviewColourScheme.Strand.toString());
      strandColour.addItemListener(this);
      turnColour.setLabel(
-             MessageManager.getString("label.colourScheme_turn_propensity"));
+             MessageManager.getString("label.colourScheme_turnpropensity"));
      turnColour.setName(JalviewColourScheme.Turn.toString());
      turnColour.addItemListener(this);
      buriedColour.setLabel(
-             MessageManager.getString("label.colourScheme_buried_index"));
+             MessageManager.getString("label.colourScheme_buriedindex"));
      buriedColour.setName(JalviewColourScheme.Buried.toString());
      buriedColour.addItemListener(this);
      nucleotideColour.setLabel(
@@@ -236,7 -236,6 +236,7 @@@ public class AlignFrame extends Embmenu
              alignPanel);
      viewport.updateConservation(alignPanel);
      viewport.updateConsensus(alignPanel);
 +    viewport.initInformationWorker(alignPanel);
  
      displayNonconservedMenuItem.setState(viewport.getShowUnconserved());
      followMouseOverFlag.setState(viewport.isFollowHighlight());
      }
      else if (source == autoCalculate)
      {
 -      viewport.autoCalculateConsensus = autoCalculate.getState();
 +      viewport.setAutoCalculateConsensusAndConservation(autoCalculate.getState());
      }
      else if (source == sortByTree)
      {
      {
        sortGroupMenuItem_actionPerformed();
      }
 +    else if (source == sortEValueMenuItem)
 +    {
 +      sortEValueMenuItem_actionPerformed();
 +    }
 +    else if (source == sortBitScoreMenuItem)
 +    {
 +      sortBitScoreMenuItem_actionPerformed();
 +    }
      else if (source == removeRedundancyMenuItem)
      {
        removeRedundancyMenuItem_actionPerformed();
                                            // viewport.getColumnSelection().getHiddenColumns()
                                            // != null;
      updateEditMenuBar();
 -    originalSource.firePropertyChange("alignment", null,
 -            originalSource.getAlignment().getSequences());
 +    originalSource.notifyAlignment();
    }
  
    /**
                                            // != null;
  
      updateEditMenuBar();
 -    originalSource.firePropertyChange("alignment", null,
 -            originalSource.getAlignment().getSequences());
 +    originalSource.notifyAlignment();
    }
  
    AlignmentViewport getOriginatingSource(CommandI command)
      viewport.getRanges().setEndSeq(viewport.getAlignment().getHeight() - 1); // BH
                                                                               // 2019.04.18
      viewport.getAlignment().getWidth();
 -    viewport.firePropertyChange("alignment", null,
 -            viewport.getAlignment().getSequences());
 +    viewport.notifyAlignment();
  
    }
  
      viewport.setSelectionGroup(null);
      viewport.getAlignment().deleteGroup(sg);
  
 -    viewport.firePropertyChange("alignment", null,
 -            viewport.getAlignment().getSequences());
 +    viewport.notifyAlignment();
  
      if (viewport.getAlignment().getHeight() < 1)
      {
          }
        }
  
 -      viewport.firePropertyChange("alignment", null, al.getSequences());
 +      viewport.notifyAlignment();
      }
    }
  
      // if (viewport.hasHiddenColumns)
      // viewport.getColumnSelection().compensateForEdits(shifts);
      ranges.setStartRes(seq.findIndex(startRes) - 1);
 -    viewport.firePropertyChange("alignment", null, al.getSequences());
 +    viewport.notifyAlignment();
  
    }
  
  
      ranges.setStartRes(seq.findIndex(startRes) - 1);
  
 -    viewport.firePropertyChange("alignment", null, al.getSequences());
 +    viewport.notifyAlignment();
  
    }
  
  
    }
  
 +  public void sortEValueMenuItem_actionPerformed()
 +  {
 +    SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
 +    AlignmentSorter.sortByEValue(viewport.getAlignment());
 +    addHistoryItem(new OrderCommand("Group Sort", oldOrder,
 +            viewport.getAlignment()));
 +    alignPanel.paintAlignment(true, false);
 +
 +  }
 +
 +  public void sortBitScoreMenuItem_actionPerformed()
 +  {
 +    SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
 +    AlignmentSorter.sortByBitScore(viewport.getAlignment());
 +    addHistoryItem(new OrderCommand("Group Sort", oldOrder,
 +            viewport.getAlignment()));
 +    alignPanel.paintAlignment(true, false);
 +
 +  }
 +
    public void removeRedundancyMenuItem_actionPerformed()
    {
      new RedundancyPanel(alignPanel);
  
    MenuItem sortGroupMenuItem = new MenuItem();
  
 +  MenuItem sortEValueMenuItem = new MenuItem();
 +
 +  MenuItem sortBitScoreMenuItem = new MenuItem();
 +
    MenuItem removeRedundancyMenuItem = new MenuItem();
  
    MenuItem pairwiseAlignmentMenuItem = new MenuItem();
              MessageManager.getString("label.colourScheme_hydrophobic"));
      hydrophobicityColour.addActionListener(this);
      helixColour.setLabel(MessageManager
-             .getString("label.colourScheme_helix_propensity"));
+             .getString("label.colourScheme_helixpropensity"));
      helixColour.addActionListener(this);
      strandColour.setLabel(MessageManager
-             .getString("label.colourScheme_strand_propensity"));
+             .getString("label.colourScheme_strandpropensity"));
      strandColour.addActionListener(this);
      turnColour.setLabel(
-             MessageManager.getString("label.colourScheme_turn_propensity"));
+             MessageManager.getString("label.colourScheme_turnpropensity"));
      turnColour.addActionListener(this);
      buriedColour.setLabel(
-             MessageManager.getString("label.colourScheme_buried_index"));
+             MessageManager.getString("label.colourScheme_buriedindex"));
      buriedColour.addActionListener(this);
      purinePyrimidineColour.setLabel(MessageManager
              .getString("label.colourScheme_purine/pyrimidine"));
      // .getString("label.rna_interaction"));
      // RNAInteractionColour.addActionListener(this);
      RNAHelixColour.setLabel(
-             MessageManager.getString("label.colourScheme_rna_helices"));
+             MessageManager.getString("label.colourScheme_rnahelices"));
      RNAHelixColour.addActionListener(this);
      userDefinedColour
              .setLabel(MessageManager.getString("action.user_defined"));
      userDefinedColour.addActionListener(this);
      PIDColour.setLabel(
-             MessageManager.getString("label.colourScheme_%_identity"));
+             MessageManager.getString("label.colourScheme_%identity"));
      PIDColour.addActionListener(this);
      BLOSUM62Colour.setLabel(
              MessageManager.getString("label.colourScheme_blosum62"));
      BLOSUM62Colour.addActionListener(this);
      tcoffeeColour.setLabel(
-             MessageManager.getString("label.colourScheme_t-coffee_scores"));
+             MessageManager.getString("label.colourScheme_t-coffeescores"));
      // it will be enabled only if a score file is provided
      tcoffeeColour.setEnabled(false);
      tcoffeeColour.addActionListener(this);
     * without an additional javascript library to exchange messages between the
     * distinct applets. See http://issues.jalview.org/browse/JAL-621
     * 
-    * @param viewer
+    * @param jmolViewer
     *          JmolViewer instance
     * @param sequenceIds
     *          - sequence Ids to search for associations
@@@ -345,7 -345,7 +345,7 @@@ public class AlignmentPanel extends Pan
            int verticalOffset, boolean redrawOverview, boolean centre)
    {
      // do we need to scroll the panel?
-     if (results != null && results.getSize() > 0)
+     if (results != null && results.getCount() > 0)
      {
        AlignmentI alignment = av.getAlignment();
        int seqIndex = alignment.findIndex(results);
      // this is called after loading new annotation onto alignment
      if (alignFrame.getSize().height == 0)
      {
 -      System.out.println(
 -              "adjustAnnotationHeight frame size zero NEEDS FIXING");
 +      // panel not laid out yet?
 +      return;
      }
      fontChanged();
      validateAnnotationDimensions(true);
   * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
   * The Jalview Authors are detailed in the 'AUTHORS' file.
   */
 -package jalview.javascript;
 +package jalview.appletgui.js;
  
  import jalview.api.AlignmentViewPanel;
  import jalview.api.FeatureRenderer;
  import jalview.api.SequenceRenderer;
  import jalview.appletgui.AlignFrame;
 +import jalview.appletgui.js.JsCallBack;
  import jalview.bin.JalviewLite;
  import jalview.datamodel.SequenceI;
  import jalview.ext.jmol.JmolCommands;
  import jalview.structure.AtomSpec;
  import jalview.structure.StructureListener;
  import jalview.structure.StructureMapping;
- import jalview.structure.StructureMappingcommandSet;
  import jalview.structure.StructureSelectionManager;
  import jalview.util.HttpUtils;
  
@@@ -221,21 -219,22 +220,22 @@@ public class MouseOverStructureListene
  
        // Form a colour command from the given alignment panel for each distinct
        // structure
-       ArrayList<String[]> ccomands = new ArrayList<String[]>();
-       ArrayList<String> pdbfn = new ArrayList<String>();
-       StructureMappingcommandSet[] colcommands = JmolCommands
-               .getColourBySequenceCommand(ssm, modelSet, sequence, sr,
+       ArrayList<String[]> ccomands = new ArrayList<>();
+       ArrayList<String> pdbfn = new ArrayList<>();
+       String[] colcommands = new JmolCommands()
+               .colourBySequence(ssm, modelSet, sequence, sr,
                        (AlignmentViewPanel) source);
        if (colcommands == null)
        {
          return;
        }
        int sz = 0;
-       for (jalview.structure.StructureMappingcommandSet ccset : colcommands)
+       // for (jalview.structure.StructureMappingcommandSet ccset : colcommands)
+       for (String command : colcommands)
        {
-         sz += ccset.commands.length;
-         ccomands.add(ccset.commands);
-         pdbfn.add(ccset.mapping);
+         // sz += ccset.commands.length;
+         // ccomands.add(command); // ccset.commands);
+         // pdbfn.add(ccset.mapping);
        }
  
        String mclass, mhandle;
@@@ -22,13 -22,19 +22,19 @@@ package jalview.bin
  
  import java.awt.Color;
  import java.io.BufferedReader;
+ import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.InputStream;
  import java.io.InputStreamReader;
+ import java.io.PrintWriter;
+ import java.io.StringWriter;
+ import java.net.Authenticator;
+ import java.net.PasswordAuthentication;
  import java.net.URL;
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
+ import java.util.Arrays;
  import java.util.Collections;
  import java.util.Date;
  import java.util.Enumeration;
@@@ -36,21 -42,27 +42,28 @@@ import java.util.Locale
  import java.util.Properties;
  import java.util.StringTokenizer;
  import java.util.TreeSet;
+ import java.util.regex.Pattern;
+ import javax.swing.LookAndFeel;
+ import javax.swing.UIManager;
  
  import org.apache.log4j.ConsoleAppender;
  import org.apache.log4j.Level;
  import org.apache.log4j.Logger;
  import org.apache.log4j.SimpleLayout;
  
 +import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.datamodel.PDBEntry;
+ import jalview.gui.Preferences;
  import jalview.gui.UserDefinedColours;
  import jalview.schemes.ColourSchemeLoader;
  import jalview.schemes.ColourSchemes;
  import jalview.schemes.UserColourScheme;
  import jalview.structure.StructureImportSettings;
  import jalview.urls.IdOrgSettings;
+ import jalview.util.ChannelProperties;
  import jalview.util.ColorUtils;
+ import jalview.util.MessageManager;
  import jalview.util.Platform;
  import jalview.ws.sifts.SiftsSettings;
  
   * when shading by annotation</li>
   * <li>ANNOTATIONCOLOUR_MAX (red) Shade used for maximum value of annotation
   * when shading by annotation</li>
-  * <li>www.jalview.org (http://www.jalview.org) a property enabling all HTTP
-  * requests to be redirected to a mirror of http://www.jalview.org</li>
+  * <li>www.jalview.org (https://www.jalview.org) a property enabling all HTTP
+  * requests to be redirected to a mirror of https://www.jalview.org</li>
   * <li>FIGURE_AUTOIDWIDTH (false) Expand the left hand column of an exported
   * alignment figure to accommodate even the longest sequence ID or annotation
   * label.</li>
   * @author $author$
   * @version $Revision$
   */
 -public class Cache
 +public class Cache implements ApplicationSingletonI
  {
 +
 +  private Cache()
 +  {
 +    // private singleton
 +  }
 +
 +  /**
 +   * In Java, this will be a static field instance, which will be
 +   * application-specific; in JavaScript it will be an applet-specific instance
 +   * tied to the applet's ThreadGroup.
 +   * 
 +   * @return
 +   */
 +  public static Cache getInstance()
 +  {
 +    return (Cache) ApplicationSingletonProvider.getInstance(Cache.class);
 +  }
 +
    /**
     * property giving log4j level for CASTOR loggers
     */
    /**
     * Sifts settings
     */
 -  public static final String DEFAULT_SIFTS_DOWNLOAD_DIR = System
 -          .getProperty("user.home") + File.separatorChar
 -          + ".sifts_downloads" + File.separatorChar;
 -
 +  public static final String DEFAULT_SIFTS_DOWNLOAD_DIR = Platform.getUserPath(".sifts_downloads/");
 +  
    private final static String DEFAULT_CACHE_THRESHOLD_IN_DAYS = "2";
  
    private final static String DEFAULT_FAIL_SAFE_PID_THRESHOLD = "30";
    /**
     * Identifiers.org download settings
     */
 -  private static final String ID_ORG_FILE = System.getProperty("user.home")
 -          + File.separatorChar + ".identifiers.org.ids.json";
 +  private static final String ID_ORG_FILE = Platform.getUserPath(".identifiers.org.ids.json");
  
    /**
     * Allowed values are PDB or mmCIF
     */
    public static Logger log;
  
+   // save the proxy properties set at startup
+   public final static String[] startupProxyProperties = {
+       System.getProperty("http.proxyHost"),
+       System.getProperty("http.proxyPort"),
+       System.getProperty("https.proxyHost"),
+       System.getProperty("https.proxyPort"),
+       System.getProperty("http.proxyUser"),
+       System.getProperty("http.proxyPassword"),
+       System.getProperty("https.proxyUser"),
+       System.getProperty("https.proxyPassword"),
+       System.getProperty("http.nonProxyHosts") };
+   public final static String PROXYTYPE_NONE = "none";
+   // "false" and "true" for backward compatibility
+   public final static String PROXYTYPE_SYSTEM = "false";
+   public final static String PROXYTYPE_CUSTOM = "true";
+   // in-memory only storage of proxy password, safer to use char array
+   public static char[] proxyAuthPassword = null;
    /** Jalview Properties */
 -  public static Properties applicationProperties = new Properties()
 +  private Properties applicationProperties = new Properties()
    {
      // override results in properties output in alphabetical order
      @Override
      }
    };
  
+   /* build Properties (not all saved to .jalview_properties) */
+   public static Properties buildProperties = new Properties();
    /** Default file is ~/.jalview_properties */
    static String propertiesFile;
  
        // lcastor = Logger.getLogger("org.exolab.castor.xml.Marshaller");
        // lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
        // Level.INFO.toString())));
+       // we shouldn't need to do this
+       org.apache.log4j.Logger.getRootLogger()
+               .setLevel(org.apache.log4j.Level.INFO);
        jalview.bin.Cache.log.setLevel(Level.toLevel(Cache
                .getDefault("logs.Jalview.level", Level.INFO.toString())));
        // laxis.addAppender(ap);
        // lcastor.addAppender(ap);
        // jalview.bin.Cache.log.addAppender(ap);
        // Tell the user that debug is enabled
-       jalview.bin.Cache.log.debug("Jalview Debugging Output Follows.");
+       jalview.bin.Cache.log.debug(ChannelProperties.getProperty("app_name")
+               + " Debugging Output Follows.");
      } catch (Exception ex)
      {
        System.err.println("Problems initializing the log4j system\n");
    }
  
    /**
-    * Loads properties from the given properties file. Any existing properties
-    * are first cleared.
+    * Loads properties from the given properties file. Any existing properties are
+    * first cleared.
     */
    public static void loadProperties(String propsFile)
    {
 +
 +    getInstance().loadPropertiesImpl(propsFile);
 +
 +  }
 +
 +  private void loadPropertiesImpl(String propsFile)
 +  {
 +
      propertiesFile = propsFile;
+     String releasePropertiesFile = null;
+     boolean defaultProperties = false;
      if (propsFile == null && !propsAreReadOnly)
      {
-       propertiesFile = Platform.getUserPath(".jalview_properties");
++      // TODO: @bsoares - for 2.12 testing: check test,develop,release props are located correctly
+       String channelPrefsFilename = ChannelProperties
+               .getProperty("preferences.filename");
+       String releasePrefsFilename = ".jalview_properties";
 -      propertiesFile = System.getProperty("user.home") + File.separatorChar
 -              + channelPrefsFilename;
 -      releasePropertiesFile = System.getProperty("user.home")
 -              + File.separatorChar + releasePrefsFilename;
++      propertiesFile = Platform.getUserPath(channelPrefsFilename);
++      releasePropertiesFile = Platform.getUserPath(releasePrefsFilename);
+       defaultProperties = true;
      }
      else
      {
          InputStream fis;
          try
          {
-           fis = new java.net.URL(propertiesFile).openStream();
+           // props file provided as URL
+           fis = new 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);
+           String readPropertiesFile = propertiesFile;
+           // if we're using the usual properties file and the channel properties
+           // file doesn't exist, read .jalview_properties
+           // (but we'll still save to the channel properties file).
+           if (defaultProperties && (!new File(propertiesFile).exists())
+                   && (new File(releasePropertiesFile).exists()))
+           {
+             readPropertiesFile = releasePropertiesFile;
+           }
+           fis = new FileInputStream(readPropertiesFile);
          }
          applicationProperties.clear();
          applicationProperties.load(fis);
          System.out.println("Error reading properties file: " + ex);
        }
      }
+     /* TO BE REPLACED WITH PROXY_TYPE SETTINGS 
      if (getDefault("USE_PROXY", false))
      {
        String proxyServer = getDefault("PROXY_SERVER", ""),
                proxyPort = getDefault("PROXY_PORT", "8080");
+     }
+     */
  
-       System.out.println("Using proxyServer: " + proxyServer
-               + " proxyPort: " + proxyPort);
-       System.setProperty("http.proxyHost", proxyServer);
-       System.setProperty("http.proxyPort", proxyPort);
+     // PROXY TYPE settings (now three options "none", "false", "true", but using
+     // backward compatible strings)
+     String proxyType = getDefault("USE_PROXY", PROXYTYPE_SYSTEM);
+     // default to upgrading old settings
+     switch (proxyType)
+     {
+     case PROXYTYPE_NONE:
+       clearProxyProperties();
+       break;
+     case PROXYTYPE_SYSTEM: // use system settings
+       resetProxyProperties();
+       break;
+     case PROXYTYPE_CUSTOM: // use specified proxy settings
+       String httpHost = getDefault("PROXY_SERVER", "");
+       String httpPort = getDefault("PROXY_PORT", "8080");
+       String httpsHost = getDefault("PROXY_SERVER_HTTPS", httpHost);
+       String httpsPort = getDefault("PROXY_PORT_HTTPS", httpPort);
+       String httpUser = getDefault("PROXY_AUTH_USER", null);
+       // https.proxyUser and https.proxyPassword are not able to be
+       // independently set in Preferences yet (or http.nonProxyHosts)
+       String httpsUser = getDefault("PROXY_AUTH_USER_HTTPS", httpUser);
+       setProxyProperties(httpHost, httpPort, httpsHost, httpsPort, httpUser,
+               proxyAuthPassword, httpsUser, proxyAuthPassword, "localhost");
+       break;
+     default:
+       String message = "Incorrect PROXY_TYPE - should be 'none' (clear proxy properties), 'false' (system settings), 'true' (custom settings): "
+               + proxyType;
+       Cache.warn(message);
      }
  
      // LOAD THE AUTHORS FROM THE authors.props file
                      DEFAULT_CACHE_THRESHOLD_IN_DAYS));
  
      IdOrgSettings.setUrl(getDefault("ID_ORG_HOSTURL",
-             "http://www.jalview.org/services/identifiers"));
+             "https://www.jalview.org/services/identifiers"));
      IdOrgSettings.setDownloadLocation(ID_ORG_FILE);
  
      StructureImportSettings.setDefaultStructureFileFormat(jalview.bin.Cache
      String jnlpVersion = System.getProperty("jalview.version");
  
      // jnlpVersion will be null if a latest version check for the channel needs
-     // to
-     // be done
+     // 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")))
      {
-       new Thread()
+       class VersionChecker extends Thread
        {
          @Override
          public void run()
          {
+           String remoteBuildPropertiesUrl = Cache
+                   .getAppbaseBuildProperties();
            String orgtimeout = System
                    .getProperty("sun.net.client.defaultConnectTimeout");
            if (orgtimeout == null)
            {
              System.setProperty("sun.net.client.defaultConnectTimeout",
                      "5000");
-             java.net.URL url = new java.net.URL(Cache
-                     .getDefault("www.jalview.org", "http://www.jalview.org")
-                     + "/webstart/jalview.jnlp");
+             java.net.URL url = new java.net.URL(remoteBuildPropertiesUrl);
              BufferedReader in = new BufferedReader(
                      new InputStreamReader(url.openStream()));
-             String line = null;
-             while ((line = in.readLine()) != null)
-             {
-               if (line.indexOf("jalview.version") == -1)
-               {
-                 continue;
-               }
  
-               line = line.substring(line.indexOf("value=") + 7);
-               line = line.substring(0, line.lastIndexOf("\""));
-               remoteVersion = line;
-               break;
-             }
+             Properties remoteBuildProperties = new Properties();
+             remoteBuildProperties.load(in);
+             remoteVersion = remoteBuildProperties.getProperty("VERSION");
            } catch (Exception ex)
            {
-             System.out.println(
-                     "Non-fatal exception when checking version at www.jalview.org :");
+             System.out
+                     .println("Non-fatal exception when checking version at "
+                             + remoteBuildPropertiesUrl + ":");
              System.out.println(ex);
              remoteVersion = getProperty("VERSION");
            }
  
            setProperty("LATEST_VERSION", remoteVersion);
          }
-       }.start();
+       }
+       VersionChecker vc = new VersionChecker();
+       vc.start();
      }
      else
      {
      return url;
    }
  
 -  public static void loadBuildProperties(boolean reportVersion)
 +  public void loadBuildProperties(boolean reportVersion)
    {
      String codeInstallation = getProperty("INSTALLATION");
      boolean printVersion = codeInstallation == null;
        String buildDetails = resolveResourceURLFor("/.build_properties");
        URL localJarFileURL = new URL(buildDetails);
        InputStream in = localJarFileURL.openStream();
-       applicationProperties.load(in);
+       buildProperties.load(in);
        in.close();
+       if (buildProperties.getProperty("BUILD_DATE", null) != null)
+       {
+         applicationProperties.put("BUILD_DATE",
+                 buildProperties.getProperty("BUILD_DATE"));
+       }
+       if (buildProperties.getProperty("INSTALLATION", null) != null)
+       {
+         applicationProperties.put("INSTALLATION",
+                 buildProperties.getProperty("INSTALLATION"));
+       }
+       if (buildProperties.getProperty("VERSION", null) != null)
+       {
+         applicationProperties.put("VERSION",
+                 buildProperties.getProperty("VERSION"));
+       }
      } catch (Exception ex)
      {
        System.out.println("Error reading build details: " + ex);
      new BuildDetails(codeVersion, null, codeInstallation);
      if (printVersion && reportVersion)
      {
-       System.out.println(
-               "Jalview Version: " + codeVersion + codeInstallation);
+       System.out.println(ChannelProperties.getProperty("app_name")
+               + " Version: " + codeVersion + codeInstallation);
      }
    }
  
 -  private static void deleteBuildProperties()
 +  private void deleteBuildProperties()
    {
      applicationProperties.remove("LATEST_VERSION");
      applicationProperties.remove("VERSION");
    }
  
    /**
-    * Gets Jalview application property of given key. Returns null if key not
-    * found
+    * Gets Jalview application property of given key. Returns null if key not found
     * 
     * @param key
-    *          Name of property
+    *              Name of property
     * 
     * @return Property value
     */
    public static String getProperty(String key)
    {
 -    String prop = applicationProperties.getProperty(key);
 -    if (prop == null && Platform.isJS())
 -    {
 -      prop = applicationProperties.getProperty(Platform.getUniqueAppletID()
 -              + "_" + JS_PROPERTY_PREFIX + key);
 -    }
 +    String prop = getInstance().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
+    * These methods are used when checking if the saved preference is different to
+    * the default setting
     */
  
    public static boolean getDefault(String property, boolean def)
    }
  
    /**
-    * Answers the value of the given property, or the supplied default value if
-    * the property is not set
+    * 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)
    {
     * Stores property in the file "HOME_DIR/.jalview_properties"
     * 
     * @param key
-    *          Name of object
+    *              Name of object
     * @param obj
-    *          String value of property
+    *              String value of property
     * 
     * @return previous value of property (or null)
     */
    public static Object setProperty(String key, String obj)
    {
 -    Object oldValue = null;
 -    try
 -    {
 -      oldValue = applicationProperties.setProperty(key, obj);
 -      if (propertiesFile != null && !propsAreReadOnly)
 -      {
 -        FileOutputStream out = new FileOutputStream(propertiesFile);
 -        applicationProperties.store(out, "---JalviewX Properties File---");
 -        out.close();
 -      }
 -    } catch (Exception ex)
 -    {
 -      System.out.println(
 -              "Error setting property: " + key + " " + obj + "\n" + ex);
 -    }
 -    return oldValue;
 +    return getInstance().setPropertyImpl(key, obj, true);
 +  }
 +
 +  /**
 +   * Removes the specified property from the jalview properties file
 +   * 
 +   * @param key
 +   */
 +  public static void removeProperty(String key)
 +  {
 +    getInstance().removePropertyImpl(key, true);
    }
  
    /**
 -   * remove the specified property from the jalview properties file
 +   * Removes the named property for the running application, without saving the
 +   * properties file
     * 
 -   * @param string
 +   * BH noting that ColourMenuHelper calls this. If the intent is to save, then
 +   * simply chanet that call to removeProperty(key).
 +   * 
 +   * @param key
     */
 -  public static void removeProperty(String string)
 +  public static void removePropertyNoSave(String key)
    {
 -    applicationProperties.remove(string);
 -    saveProperties();
 +
 +    getInstance().
 +
 +            removePropertyImpl(key, false);
 +  }
 +
 +  /**
 +   * Removes the named property, and optionally saves the current properties to
 +   * file
 +   * 
 +   * @param key
 +   * @param andSave
 +   */
 +  private void removePropertyImpl(String key, boolean andSave)
 +  {
 +    applicationProperties.remove(key);
 +    if (andSave)
 +      saveProperties();
    }
  
    /**
     */
    public static void saveProperties()
    {
 +    getInstance().savePropertiesImpl();
 +  }
 +
 +  /**
 +   * save the properties to the jalview properties path
 +   */
 +  private void savePropertiesImpl()
 +
 +  {
      if (!propsAreReadOnly)
      {
        try
  
            lvclient.addAppender(log.getAppender("JalviewLogger"));
            // Tell the user that debug is enabled
-           lvclient.debug("Jalview Vamsas Client Debugging Output Follows.");
+           lvclient.debug(ChannelProperties.getProperty("app_name")
+                   + " Vamsas Client Debugging Output Follows.");
          }
        } catch (Exception e)
        {
  
            lgclient.addAppender(log.getAppender("JalviewLogger"));
            // Tell the user that debug is enabled
-           lgclient.debug("Jalview Groovy Client Debugging Output Follows.");
+           lgclient.debug(ChannelProperties.getProperty("app_name")
+                   + " Groovy Client Debugging Output Follows.");
          }
        } catch (Error e)
        {
                  .getConstructor(new Class[]
                  { String.class, String.class, String.class })
                  .newInstance(new Object[]
-                 { "Jalview Desktop",
+                 { ChannelProperties.getProperty("app_name") + " Desktop",
                      (vrs = jalview.bin.Cache.getProperty("VERSION") + "_"
                              + jalview.bin.Cache.getDefault("BUILD_DATE",
                                      "unknown")),
     * @param property
     * @param colour
     */
 -  public static void setColourProperty(String property, Color colour)
 +  public static void setColourPropertyNoSave(String property, Color colour)
    {
 -    setProperty(property, jalview.util.Format.getHexString(colour));
 +    setPropertyNoSave(property, jalview.util.Format.getHexString(colour));
    }
  
    /**
    public static Date getDateProperty(String propertyName)
    {
      String val = getProperty(propertyName);
 +    
      if (val != null)
      {
        try
        {
 -        return date_format.parse(val);
 +        if ((val = val.trim()).indexOf(",") < 0 && val.indexOf("-") >= 0 && val.indexOf(" ") == val.lastIndexOf(" ")) {
 +          val = val.replace(" ",", ").replace('-',' ');
 +        }
 +        Date date =  date_format.parse(val);
 +        return date;
        } catch (Exception ex)
        {
          System.err.println("Invalid or corrupt date in property '"
      }
      if (value == null || value.trim().length() < 1)
      {
 -      Cache.applicationProperties.remove(propName);
 +      getInstance().applicationProperties.remove(propName);
      }
      else
      {
 -      Cache.applicationProperties.setProperty(propName, value);
 +      getInstance().applicationProperties.setProperty(propName, value);
      }
    }
  
     * Loads in user colour schemes from files.
     * 
     * @param files
-    *          a '|'-delimited list of file paths
+    *                a '|'-delimited list of file paths
     */
    public static void initUserColourSchemes(String files)
    {
        }
        else
        {
 -        applicationProperties
 +        getInstance().applicationProperties
                  .remove(UserDefinedColours.USER_DEFINED_COLOURS);
        }
      }
    public static String getVersionDetailsForConsole()
    {
      StringBuilder sb = new StringBuilder();
-     sb.append("Jalview Version: "
-             + jalview.bin.Cache.getDefault("VERSION", "TEST"));
+     sb.append(ChannelProperties.getProperty("app_name"))
+             .append(" Version: ");
+     sb.append(jalview.bin.Cache.getDefault("VERSION", "TEST"));
      sb.append("\n");
-     sb.append("Jalview Installation: "
-             + jalview.bin.Cache.getDefault("INSTALLATION", "unknown"));
+     sb.append(ChannelProperties.getProperty("app_name"))
+             .append(" Installation: ");
+     sb.append(jalview.bin.Cache.getDefault("INSTALLATION", "unknown"));
      sb.append("\n");
 -    sb.append("Build Date: ");
 -    sb.append(jalview.bin.Cache.getDefault("BUILD_DATE", "unknown"));
 +    sb.append("Build Date: "
 +            + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown"));
      sb.append("\n");
-     sb.append("Java version: " + System.getProperty("java.version"));
+     sb.append("Java version: ");
+     sb.append(System.getProperty("java.version"));
      sb.append("\n");
-     sb.append(System.getProperty("os.arch") + " "
-             + System.getProperty("os.name") + " "
-             + System.getProperty("os.version"));
+     sb.append(System.getProperty("os.arch"));
+     sb.append(" ");
+     sb.append(System.getProperty("os.name"));
+     sb.append(" ");
+     sb.append(System.getProperty("os.version"));
      sb.append("\n");
      appendIfNotNull(sb, "Install4j version: ",
              System.getProperty("sys.install4jVersion"), "\n", null);
              System.getProperty("installer_template_version"), "\n", null);
      appendIfNotNull(sb, "Launcher version: ",
              System.getProperty("launcher_version"), "\n", null);
-     if (jalview.bin.Cache.getDefault("VERSION", "TEST")
-             .equals("DEVELOPMENT"))
+     LookAndFeel laf = UIManager.getLookAndFeel();
+     String lafName = laf == null ? "Not obtained" : laf.getName();
+     String lafClass = laf == null ? "unknown" : laf.getClass().getName();
+     sb.append("LookAndFeel: ");
+     sb.append(lafName);
+     sb.append(" (");
+     sb.append(lafClass);
+     sb.append(")\n");
+     // Not displayed in release version ( determined by possible version number
+     // regex 9[9.]*9[.-_a9]* )
+     if (Pattern.matches("^\\d[\\d\\.]*\\d[\\.\\-\\w]*$",
+             jalview.bin.Cache.getDefault("VERSION", "TEST")))
      {
        appendIfNotNull(sb, "Getdown appdir: ",
                System.getProperty("getdownappdir"), "\n", null);
+       appendIfNotNull(sb, "Getdown appbase: ",
+               System.getProperty("getdownappbase"), "\n", null);
        appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
                "\n", "unknown");
      }
      return jalview.bin.Cache.getDefault("INSTALLATION", "unknown");
    }
  
 +  /**
 +   * 
 +   * For AppletParams and Preferences ok_actionPerformed and
 +   * startupFileTextfield_mouseClicked
 +   * 
 +   * Sets a property value for the running application, without saving it to the
 +   * properties file
 +   * 
 +   * @param key
 +   * @param obj
 +   */
 +  public static void setPropertyNoSave(String key, String obj)
 +  {
 +    getInstance().setPropertyImpl(key, obj, false);
 +  }
 +
 +  /**
 +   * Sets a property value, and optionally also saves the current properties to
 +   * file
 +   * 
 +   * @param key
 +   * @param obj
 +   * @param andSave
 +   * @return
 +   */
 +  private Object setPropertyImpl(
 +          String key, String obj, boolean andSave)
 +  {
 +    Object oldValue = null;
 +    try
 +    {
 +      oldValue = applicationProperties.setProperty(key, obj);
 +      if (andSave && !propsAreReadOnly && propertiesFile != null)
 +      {
 +        FileOutputStream out = new FileOutputStream(propertiesFile);
 +        applicationProperties.store(out, "---JalviewX Properties File---");
 +        out.close();
 +      }
 +    } catch (Exception ex)
 +    {
 +      System.out.println(
 +              "Error setting property: " + key + " " + obj + "\n" + ex);
 +    }
 +    return oldValue;
 +  }
 +
+   public static String getStackTraceString(Throwable t)
+   {
+     StringWriter sw = new StringWriter();
+     PrintWriter pw = new PrintWriter(sw);
+     t.printStackTrace(pw);
+     return sw.toString();
+   }
+   // proxy properties methods
+   public static void clearProxyProperties()
+   {
+     setProxyProperties(null, null, null, null, null, null, null, null,
+             null);
+   }
+   public static void resetProxyProperties()
+   {
+     setProxyProperties(startupProxyProperties[0], startupProxyProperties[1],
+             startupProxyProperties[2], startupProxyProperties[3],
+             startupProxyProperties[4],
+             startupProxyProperties[5] == null ? null
+                     : startupProxyProperties[5].toCharArray(),
+             startupProxyProperties[6],
+             startupProxyProperties[7] == null ? null
+                     : startupProxyProperties[7].toCharArray(),
+             startupProxyProperties[8]);
+     StringBuilder sb = new StringBuilder();
+     sb.append("Setting proxy properties to: http.proxyHost=")
+             .append(startupProxyProperties[0]).append(", http.proxyPort=")
+             .append(startupProxyProperties[1])
+             .append(startupProxyProperties[4] != null
+                     && !startupProxyProperties[4].isEmpty()
+                             ? " [" + startupProxyProperties[4] + "]"
+                             : "")
+             .append(", https.proxyHost=").append(startupProxyProperties[2])
+             .append(", https.proxyPort=").append(startupProxyProperties[3])
+             .append(startupProxyProperties[6] != null
+                     && !startupProxyProperties[6].isEmpty()
+                             ? " [" + startupProxyProperties[6] + "]"
+                             : "");
+     Cache.debug(sb.toString());
+   }
+   public static void setProxyPropertiesFromPreferences()
+   {
+     setProxyPropertiesFromPreferences(Cache.PROXYTYPE_SYSTEM);
+   }
+   public static void setProxyPropertiesFromPreferences(
+           String previousProxyType)
+   {
+     String proxyType = Cache.getDefault("USE_PROXY",
+             Cache.PROXYTYPE_SYSTEM);
+     if (previousProxyType != null
+             && !proxyType.equals(Cache.PROXYTYPE_CUSTOM) // always apply
+                                                          // customProxy
+             && proxyType.equals(previousProxyType))
+     {
+       // no change
+       return;
+     }
+     switch (proxyType)
+     {
+     case Cache.PROXYTYPE_NONE:
+       if (!previousProxyType.equals(proxyType))
+       {
+         Cache.log.info("Setting no proxy settings");
+         Cache.setProxyProperties(null, null, null, null, null, null, null,
+                 null, null);
+       }
+       break;
+     case Cache.PROXYTYPE_CUSTOM:
+       // always re-set a custom proxy -- it might have changed, particularly
+       // password
+       Cache.log.info("Setting custom proxy settings");
+       boolean proxyAuthSet = Cache.getDefault("PROXY_AUTH", false);
+       Cache.setProxyProperties(Cache.getDefault("PROXY_SERVER", null),
+               Cache.getDefault("PROXY_PORT", null),
+               Cache.getDefault("PROXY_SERVER_HTTPS", null),
+               Cache.getDefault("PROXY_PORT_HTTPS", null),
+               proxyAuthSet ? Cache.getDefault("PROXY_AUTH_USERNAME", "")
+                       : null,
+               proxyAuthSet ? Cache.proxyAuthPassword : null,
+               proxyAuthSet ? Cache.getDefault("PROXY_AUTH_USERNAME", "")
+                       : null,
+               proxyAuthSet ? Cache.proxyAuthPassword : null, "localhost");
+       break;
+     default: // system proxy settings by default
+       Cache.log.info("Setting system proxy settings");
+       Cache.resetProxyProperties();
+     }
+   }
+   public static void setProxyProperties(String httpHost, String httpPort,
+           String httpsHost, String httpsPort, String httpUser,
+           char[] httpPassword, String httpsUser, char[] httpsPassword,
+           String nonProxyHosts)
+   {
+     setOrClearSystemProperty("http.proxyHost", httpHost);
+     setOrClearSystemProperty("http.proxyPort", httpPort);
+     setOrClearSystemProperty("https.proxyHost", httpsHost);
+     setOrClearSystemProperty("https.proxyPort", httpsPort);
+     setOrClearSystemProperty("http.proxyUser", httpUser);
+     setOrClearSystemProperty("https.proxyUser", httpsUser);
+     // note: passwords for http.proxyPassword and https.proxyPassword are sent
+     // via the Authenticator, properties do not need to be set
+     // are we using a custom proxy (password prompt might be required)?
+     boolean customProxySet = getDefault("USE_PROXY", PROXYTYPE_SYSTEM)
+             .equals(PROXYTYPE_CUSTOM);
+     /*
+      * A bug in Java means the AuthCache does not get reset, so once it has working credentials,
+      * it never asks for more, so changing the Authenticator has no effect (as getPasswordAuthentication()
+      * is not re-called).
+      * This could lead to password leak to a hostile proxy server, so I'm putting in a hack to clear
+      * the AuthCache.
+      * see https://www.generacodice.com/en/articolo/154918/Reset-the-Authenticator-credentials
+      * ...
+      * Turns out this is only accessible in Java 8, and not in Java 9 onwards, so commenting out
+      */
+     /*
+     try
+     {
+       sun.net.www.protocol.http.AuthCacheValue
+               .setAuthCache(new sun.net.www.protocol.http.AuthCacheImpl());
+     } catch (Throwable t)
+     {
+       Cache.error(t.getMessage());
+       Cache.debug(getStackTraceString(t));
+     }
+     */
+     if (httpUser != null || httpsUser != null)
+     {
+       try
+       {
+         char[] displayHttpPw = new char[httpPassword == null ? 0
+                 : httpPassword.length];
+         Arrays.fill(displayHttpPw, '*');
+         Cache.debug("CACHE Proxy: setting new Authenticator with httpUser='"
+                 + httpUser + "' httpPassword='" + displayHttpPw + "'");
+         if (!Platform.isJS())
+         /* *
+          * java.net.Authenticator not implemented in SwingJS yet
+          * 
+          * @j2sIgnore
+          * 
+          */
+         {
+           Authenticator.setDefault(new Authenticator()
+           {
+             @Override
+             protected PasswordAuthentication getPasswordAuthentication()
+             {
+               if (getRequestorType() == RequestorType.PROXY)
+               {
+                 String protocol = getRequestingProtocol();
+                 boolean needProxyPasswordSet = false;
+                 if (customProxySet &&
+                 // we have a username but no password for the scheme being
+                 // requested
+                 (protocol.equalsIgnoreCase("http")
+                         && (httpUser != null && httpUser.length() > 0
+                                 && (httpPassword == null
+                                         || httpPassword.length == 0)))
+                         || (protocol.equalsIgnoreCase("https")
+                                 && (httpsUser != null
+                                         && httpsUser.length() > 0
+                                         && (httpsPassword == null
+                                                 || httpsPassword.length == 0))))
+                 {
+                   // open Preferences -> Connections
+                   String message = MessageManager
+                           .getString("label.proxy_password_required");
+                   Preferences.openPreferences(
+                           Preferences.TabRef.CONNECTIONS_TAB, message);
+                   Preferences.getInstance()
+                           .proxyAuthPasswordCheckHighlight(true, true);
+                 }
+                 else
+                 {
+                   try
+                   {
+                     if (protocol.equalsIgnoreCase("http")
+                             && getRequestingHost()
+                                     .equalsIgnoreCase(httpHost)
+                             && getRequestingPort() == Integer
+                                     .valueOf(httpPort))
+                     {
+                       Cache.debug(
+                               "AUTHENTICATOR returning PasswordAuthentication(\""
+                                       + httpUser + "\", '"
+                                       + new String(displayHttpPw) + "')");
+                       return new PasswordAuthentication(httpUser,
+                               httpPassword);
+                     }
+                     if (protocol.equalsIgnoreCase("https")
+                             && getRequestingHost()
+                                     .equalsIgnoreCase(httpsHost)
+                             && getRequestingPort() == Integer
+                                     .valueOf(httpsPort))
+                     {
+                       char[] displayHttpsPw = new char[httpPassword.length];
+                       Arrays.fill(displayHttpsPw, '*');
+                       Cache.debug(
+                               "AUTHENTICATOR returning PasswordAuthentication(\""
+                                       + httpsUser + "\", '" + displayHttpsPw
+                                       + "'");
+                       return new PasswordAuthentication(httpsUser,
+                               httpsPassword);
+                     }
+                   } catch (NumberFormatException e)
+                   {
+                     Cache.error("Problem with proxy port values [http:"
+                             + httpPort + ", https:" + httpsPort + "]");
+                   }
+                   Cache.debug(
+                           "AUTHENTICATOR after trying to get PasswordAuthentication");
+                 }
+               }
+               // non proxy request
+               Cache.debug("AUTHENTICATOR returning null");
+               return null;
+             }
+           });
+         } // end of j2sIgnore for java.net.Authenticator
+         // required to re-enable basic authentication (should be okay for a
+         // local proxy)
+         Cache.debug(
+                 "AUTHENTICATOR setting property 'jdk.http.auth.tunneling.disabledSchemes' to \"\"");
+         System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
+       } catch (SecurityException e)
+       {
+         Cache.error("Could not set default Authenticator");
+         Cache.debug(getStackTraceString(e));
+       }
+     }
+     else
+     {
+       // reset the Authenticator to protect http.proxyUser and
+       // http.proxyPassword Just In Case
+       /* as noted above, due to bug in java this doesn't work if the sun.net.www.protocol.http.AuthCache
+        * has working credentials. No workaround for Java 11.
+        */
+       if (!Platform.isJS())
+       /* *
+        * java.net.Authenticator not implemented in SwingJS yet
+        * 
+        * @j2sIgnore
+        * 
+        */
+       {
+         Cache.debug("AUTHENTICATOR setting default Authenticator to null");
+         Authenticator.setDefault(null);
+       }
+     }
+     // nonProxyHosts not currently configurable in Preferences
+     Cache.debug("AUTHENTICATOR setting property 'http.nonProxyHosts' to \""
+             + nonProxyHosts + "\"");
+     setOrClearSystemProperty("http.nonProxyHosts", nonProxyHosts);
+   }
+   public static void setOrClearSystemProperty(String key, char[] value)
+   {
+     setOrClearSystemProperty(key,
+             (value == null) ? null : new String(value));
+   }
+   public static void setOrClearSystemProperty(String key, String value)
+   {
+     if (key == null)
+     {
+       return;
+     }
+     if (value == null)
+     {
+       System.clearProperty(key);
+     }
+     else
+     {
+       System.setProperty(key, value);
+     }
+   }
+   public final static int TRACE = 10;
+   public final static int DEBUG = 20;
+   public final static int INFO = 30;
+   public final static int WARN = 40;
+   public final static int ERROR = 50;
+   public static boolean println(int level, String message)
+   {
+     if (Cache.log == null)
+     {
+       if (level >= WARN)
+         System.err.println(message);
+       else if (level >= INFO)
+         System.out.println(message);
+       // not printing debug or trace messages
+       return false;
+     }
+     if (level >= ERROR)
+     {
+       Cache.log.error(message);
+     }
+     else if (level >= WARN)
+     {
+       Cache.log.warn(message);
+     }
+     else if (level >= INFO)
+     {
+       Cache.log.info(message);
+     }
+     else if (level >= DEBUG)
+     {
+       Cache.log.debug(message);
+     }
+     else
+     {
+       Cache.log.trace(message);
+     }
+     return true;
+   }
+   public static void trace(String message)
+   {
+     println(TRACE, message);
+   }
+   public static void debug(String message)
+   {
+     println(DEBUG, message);
+   }
+   public static void info(String message)
+   {
+     println(INFO, message);
+   }
+   public static void warn(String message)
+   {
+     println(WARN, message);
+   }
+   public static void error(String message)
+   {
+     println(ERROR, message);
+   }
+   /**
+    * Getdown appbase methods
+    */
+   private static final String releaseAppbase;
+   private static String getdownAppbase;
+   private static String getdownDistDir;
+   static
+   {
+     if (!Platform.isJS())
+     {
+       Float specversion = Float
+               .parseFloat(System.getProperty("java.specification.version"));
+       releaseAppbase = (specversion < 9)
+               ? "https://www.jalview.org/getdown/release/1.8"
+               : "https://www.jalview.org/getdown/release/11";
+     }
+     else
+     {
+       // this value currenly made up, can be changed to URL that will be
+       // "https://www.jalview.org/jalview-js/swingjs/j2s/build_properties"
+       releaseAppbase = "https://www.jalview.org/jalview-js";
+       getdownAppbase = releaseAppbase;
+       getdownDistDir = "/swingjs/j2s";
+     }
+   }
+   // look for properties (passed in by getdown) otherwise default to release
+   private static void setGetdownAppbase()
+   {
+     if (getdownAppbase != null)
+     {
+       return;
+     }
+     String appbase = System.getProperty("getdownappbase");
+     String distDir = System.getProperty("getdowndistdir");
+     if (appbase == null)
+     {
+       appbase = buildProperties.getProperty("GETDOWNAPPBASE");
+       distDir = buildProperties.getProperty("GETDOWNAPPDISTDIR");
+     }
+     if (appbase == null)
+     {
+       appbase = releaseAppbase;
+       distDir = "release";
+     }
+     if (appbase.endsWith("/"))
+     {
+       appbase = appbase.substring(0, appbase.length() - 1);
+     }
+     if (distDir == null)
+     {
+       distDir = appbase.equals(releaseAppbase) ? "release" : "alt";
+     }
+     getdownAppbase = appbase;
+     getdownDistDir = distDir;
+   }
+   public static String getGetdownAppbase()
+   {
+     setGetdownAppbase();
+     return getdownAppbase;
+   }
+   public static String getAppbaseBuildProperties()
+   {
+     String appbase = getGetdownAppbase();
+     return appbase + "/" + getdownDistDir + "/build_properties";
+   }
  }
@@@ -20,7 -20,8 +20,8 @@@
   */
  package jalview.bin;
  
 -import java.util.Locale;
 +import java.awt.GraphicsEnvironment;
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.FileOutputStream;
@@@ -38,23 -39,22 +39,27 @@@ import java.security.PermissionCollecti
  import java.security.Permissions;
  import java.security.Policy;
  import java.util.HashMap;
++import java.util.Locale;
  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 javax.swing.UIManager.LookAndFeelInfo;
  
  import com.threerings.getdown.util.LaunchUtil;
  
  import groovy.lang.Binding;
  import groovy.util.GroovyScriptEngine;
 +import jalview.api.AlignCalcWorkerI;
 +import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.ext.so.SequenceOntology;
  import jalview.gui.AlignFrame;
 +import jalview.gui.AlignViewport;
  import jalview.gui.Desktop;
 +import jalview.gui.Preferences;
  import jalview.gui.PromptUserConfig;
  import jalview.io.AppletFormatAdapter;
  import jalview.io.BioJsHTMLOutput;
@@@ -70,6 -70,8 +75,8 @@@ import jalview.io.NewickFile
  import jalview.io.gff.SequenceOntologyFactory;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ColourSchemeProperty;
+ import jalview.util.ChannelProperties;
+ import jalview.util.HttpUtils;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
  import jalview.ws.jws2.Jws2Discoverer;
   * @author $author$
   * @version $Revision$
   */
 -public class Jalview
 +public class Jalview implements ApplicationSingletonI
  {
 -  static
 +  // for testing those nasty messages you cannot ever find.
 +  // static
 +  // {
 +  // System.setOut(new PrintStream(new ByteArrayOutputStream())
 +  // {
 +  // @Override
 +  // public void println(Object o)
 +  // {
 +  // if (o != null)
 +  // {
 +  // System.err.println(o);
 +  // }
 +  // }
 +  //
 +  // });
 +  // }
 +  public static Jalview getInstance()
 +  {
 +    return (Jalview) ApplicationSingletonProvider
 +            .getInstance(Jalview.class);
 +  }
 +
 +  private Jalview()
    {
+     Platform.getURLCommandArguments();
    }
  
  
    private Desktop desktop;
  
 -  public static AlignFrame currentAlignFrame;
 +  public AlignFrame currentAlignFrame;
 +
 +  public String appletResourcePath;
 +
 +  public String j2sAppletID;
 +
 +  private boolean noCalculation, noMenuBar, noStatus;
 +
 +  private boolean noAnnotation;
 +
 +  public boolean getStartCalculations()
 +  {
 +    return !noCalculation;
 +  }
 +
 +  public boolean getAllowMenuBar()
 +  {
 +    return !noMenuBar;
 +  }
 +
 +  public boolean getShowStatus()
 +  {
 +    return !noStatus;
 +  }
 +
 +  public boolean getShowAnnotation()
 +  {
 +    return !noAnnotation;
 +  }
  
    static
    {
-     if (Platform.isJS()) {
-         Platform.getURLCommandArguments();
-     } else /** @j2sIgnore */
+     if (!Platform.isJS())
+     /**
+      * Java only
+      * 
+      * @j2sIgnore
+      */
      {
        // grab all the rights we can for the JVM
        Policy.setPolicy(new Policy()
    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;
  
    }
  
 -  public static Jalview getInstance()
 -  {
 -    return instance;
 -  }
 +  private final static boolean doPlatformLogging = false;
  
    /**
     * main class for Jalview application
     */
    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
 -     * 
 -     */
 +    if (doPlatformLogging)
      {
 -      System.out.println("not in js");
 +      Platform.startJavaLogging();
      }
 -    // 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");
 -    }
 +    getInstance().doMain(args);
    }
++  
++
++  
  
    /**
     * @param args
    void doMain(String[] args)
    {
  
 -    if (!Platform.isJS())
 +    boolean isJS = Platform.isJS();
 +    if (!isJS)
      {
        System.setSecurityManager(null);
      }
      }
  
      // report Jalview version
 -    Cache.loadBuildProperties(true);
 +    Cache.getInstance().loadBuildProperties(true);
  
      ArgsParser aparser = new ArgsParser(args);
-     headless = false;
+     boolean headless = false;
  
      String usrPropsFile = aparser.getValue("props");
      Cache.loadProperties(usrPropsFile); // must do this before
 -    if (usrPropsFile != null)
 +    boolean allowServices = true;
 +    
 +    if (isJS)
      {
 -      System.out.println(
 -              "CMD [-props " + usrPropsFile + "] executed successfully!");
 +      j2sAppletID = Platform.getAppID(null);
 +      Preferences.setAppletDefaults();
 +      Cache.loadProperties(usrPropsFile); // again, because we
 +      // might be changing defaults here?
 +      appletResourcePath = (String) aparser.getAppletValue("resourcepath",
 +              null, true);
      }
 -    if (!Platform.isJS())
 +    else
      /**
       * Java only
       * 
       * @j2sIgnore
       */
      {
 +      if (usrPropsFile != null)
 +      {
 +        System.out.println(
 +                "CMD [-props " + usrPropsFile + "] executed successfully!");
 +      }
        if (aparser.contains("help") || aparser.contains("h"))
        {
          showUsage();
          System.exit(0);
        }
 +      // BH note: Only -nodisplay is official; others are deprecated?
        if (aparser.contains("nodisplay") || aparser.contains("nogui")
 -              || aparser.contains("headless"))
 +              || aparser.contains("headless")
 +              || GraphicsEnvironment.isHeadless())
        {
 -        System.setProperty("java.awt.headless", "true");
 +        if (!isJS) {
 +          // BH Definitely not a good idea in JavaScript; 
 +          // probably should not be here for Java, either.  
 +          System.setProperty("java.awt.headless", "true");
 +        }
          headless = true;
        }
+       // anything else!
  
 -      final String jabawsUrl = aparser.getValue("jabaws");
 -      if (jabawsUrl != null)
 +      final String jabawsUrl = aparser.getValue(ArgsParser.JABAWS);
 +      allowServices = !("none".equals(jabawsUrl));
 +      if (allowServices && jabawsUrl != null)
        {
          try
          {
 -          Jws2Discoverer.getDiscoverer().setPreferredUrl(jabawsUrl);
 +          Jws2Discoverer.getInstance().setPreferredUrl(jabawsUrl);
            System.out.println(
                    "CMD [-jabaws " + jabawsUrl + "] executed successfully!");
          } catch (MalformedURLException e)
                    "Invalid jabaws parameter: " + jabawsUrl + " ignored");
          }
        }
 -    }
  
 -    String defs = aparser.getValue("setprop");
 +    }
 +    String defs = aparser.getValue(ArgsParser.SETPROP);
      while (defs != null)
      {
        int p = defs.indexOf('=');
        else
        {
          System.out.println("Executing setprop argument: " + defs);
 -        if (Platform.isJS())
 +        if (isJS)
          {
            Cache.setProperty(defs.substring(0, p), defs.substring(p + 1));
          }
        }
        defs = aparser.getValue("setprop");
      }
+     if (System.getProperty("java.awt.headless") != null
+             && System.getProperty("java.awt.headless").equals("true"))
+     {
+       headless = true;
+     }
      System.setProperty("http.agent",
              "Jalview Desktop/" + Cache.getDefault("VERSION", "Unknown"));
      try
        System.exit(0);
      }
  
-     if (!isJS)
-     /** @j2sIgnore */
-     {
-       try
-       {
-         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-       } catch (Exception ex)
-       {
-         System.err.println("Unexpected Look and Feel Exception");
-         ex.printStackTrace();
-       }
-       if (Platform.isMac())
-       {
+     desktop = null;
+     setLookAndFeel();
  
-         LookAndFeel lookAndFeel = ch.randelshofer.quaqua.QuaquaManager
-                 .getLookAndFeel();
-         System.setProperty(
-                 "com.apple.mrj.application.apple.menu.about.name",
-                 "Jalview");
-         System.setProperty("apple.laf.useScreenMenuBar", "true");
-         if (lookAndFeel != null)
-         {
-           try
-           {
-             UIManager.setLookAndFeel(lookAndFeel);
-           } catch (Throwable e)
-           {
-             System.err.println(
-                     "Failed to set QuaQua look and feel: " + e.toString());
-           }
-         }
-         if (lookAndFeel == null
-                 || !(lookAndFeel.getClass().isAssignableFrom(
-                         UIManager.getLookAndFeel().getClass()))
-                 || !UIManager.getLookAndFeel().getClass().toString()
-                         .toLowerCase().contains("quaqua"))
-         {
-           try
-           {
-             System.err.println(
-                     "Quaqua LaF not available on this plaform. Using VAqua(4).\nSee https://issues.jalview.org/browse/JAL-2976");
-             UIManager.setLookAndFeel("org.violetlib.aqua.AquaLookAndFeel");
-           } catch (Throwable e)
-           {
-             System.err.println(
-                     "Failed to reset look and feel: " + e.toString());
-           }
-         }
-       }
-     }
      /*
       * configure 'full' SO model if preferences say to, else use the default (full SO)
       * - as JS currently doesn't have OBO parsing, it must use 'Lite' version
       */
 -    boolean soDefault = !Platform.isJS();
 +    boolean soDefault = !isJS;
      if (Cache.getDefault("USE_FULL_SO", soDefault))
      {
-       SequenceOntologyFactory.setSequenceOntology(new SequenceOntology());
+       SequenceOntologyFactory.setInstance(new SequenceOntology());
      }
  
-     desktop = null;
      if (!headless)
      {
+       Desktop.nosplash = aparser.contains("nosplash");
 -      desktop = new Desktop();
 +      desktop = Desktop.getInstance();
        desktop.setInBatchMode(true); // indicate we are starting up
        try
        {
          JalviewTaskbar.setTaskbar(this);
+       } catch (Exception e)
+       {
+         Cache.log.info("Cannot set Taskbar");
+         Cache.log.error(e.getMessage());
+         // e.printStackTrace();
        } catch (Throwable t)
        {
-         System.out.println("Error setting Taskbar: " + t.getMessage());
+         Cache.log.info("Cannot set Taskbar");
+         Cache.log.error(t.getMessage());
+         // t.printStackTrace();
        }
 -
+       // set Proxy settings before all the internet calls
+       Cache.setProxyPropertiesFromPreferences();
        desktop.setVisible(true);
-       if (Platform.isJS())
 -      if (!Platform.isJS())
++      if (isJS)
++      {
 +        Cache.setProperty("SHOW_JWS2_SERVICES", "false");
-       if (allowServices)
++      }
++      if (allowServices && !aparser.contains("nowebservicediscovery"))
 +      {
 +        desktop.startServiceDiscovery();
 +      }
++
 +      if (!isJS)
        /**
         * Java only
         * 
         * @j2sIgnore
         */
        {
 -        if (!aparser.contains("nowebservicediscovery"))
 -        {
 -          desktop.startServiceDiscovery();
 -        }
          if (!aparser.contains("nousagestats"))
          {
            startUsageStats(desktop);
                // String defurl =
                // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
                // //
-               String defurl = "http://www.jalview.org/cgi-bin/questionnaire.pl";
+               String defurl = "https://www.jalview.org/cgi-bin/questionnaire.pl";
                Cache.log.debug(
                        "Starting questionnaire with default url: " + defurl);
                desktop.checkForQuestionnaire(defurl);
          BioJsHTMLOutput.updateBioJS();
        }
      }
 +    parseArguments(aparser, true);
 +  }
 +
 +  /**
 +   * Parse all command-line String[] arguments as well as all JavaScript-derived
 +   * parameters from Info.
 +   * 
 +   * We allow for this method to be run from JavaScript. Basically allowing
 +   * simple scripting.
 +   * 
 +   * @param aparser
 +   * @param isStartup
 +   */
 +  public void parseArguments(ArgsParser aparser, boolean isStartup)
 +  {
  
 -    // 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)
 +    String groovyscript = null; // script to execute after all loading is
 +    boolean isJS = Platform.isJS();
 +    if (!isJS)
 +    /** @j2sIgnore */
      {
 -      final File appdir = new File(appdirString);
 -      new Thread()
 +      // 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)
        {
 -        @Override
 -        public void run()
 +        final File appdir = new File(appdirString);
 +        new Thread()
          {
 -          LaunchUtil.upgradeGetdown(
 -                  new File(appdir, "getdown-launcher-old.jar"),
 -                  new File(appdir, "getdown-launcher.jar"),
 -                  new File(appdir, "getdown-launcher-new.jar"));
 -        }
 -      }.start();
 -    }
 +          @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;
 -    FileLoader fileLoader = new FileLoader(!headless);
 +      // completed one way or another
 +      // extract groovy argument and execute if necessary
 +      groovyscript = aparser.getValue("groovy", true);
 +    }
  
 -    String groovyscript = null; // script to execute after all loading is
 -    // completed one way or another
 -    // extract groovy argument and execute if necessary
 -    groovyscript = aparser.getValue("groovy", true);
 -    file = aparser.getValue("open", true);
 +    String file = aparser.getValue("open", true);
  
 -    if (file == null && desktop == null)
 +    if (!isJS && file == null && desktop == null)
      {
        System.out.println("No files to open!");
        System.exit(1);
      }
 +    setDisplayParameters(aparser);
 +    
 +    // time to open a file.
      long progress = -1;
 +    DataSourceType protocol = null;
 +    FileLoader fileLoader = new FileLoader(!headless);
 +    FileFormatI format = null;
      // Finally, deal with the remaining input data.
 -    if (file != null)
 +    AlignFrame af = null;
 +
 +    JalviewJSApp jsApp = (isJS ? new JalviewJSApp(this, aparser) : null);
 +
 +    if (file == null)
 +    {
 +      if (isJS)
 +      {
 +        // JalviewJS allows sequence1 sequence2 ....
 +        
 +      }
 +      else if (!headless && Cache.getDefault("SHOW_STARTUP_FILE", true))
 +      /**
 +       * Java only
 +       * 
 +       * @j2sIgnore
 +       */
 +      {
 +
 +        // We'll only open the default file if the desktop is visible.
 +        // And the user
 +        // ////////////////////
 +
 +        file = Cache.getDefault("STARTUP_FILE",
 +                Cache.getDefault("www.jalview.org",
 +                        "http://www.jalview.org")
 +                        + "/examples/exampleFile_2_7.jar");
 +        if (file.equals(
 +                "http://www.jalview.org/examples/exampleFile_2_3.jar"))
 +        {
 +          // hardwire upgrade of the startup file
 +          file.replace("_2_3.jar", "_2_7.jar");
 +          // and remove the stale setting
 +          Cache.removeProperty("STARTUP_FILE");
 +        }
 +
 +        protocol = DataSourceType.FILE;
 +
 +        if (file.indexOf("http:") > -1)
 +        {
 +          protocol = DataSourceType.URL;
 +        }
 +
 +        if (file.endsWith(".jar"))
 +        {
 +          format = FileFormat.Jalview;
 +        }
 +        else
 +        {
 +          try
 +          {
 +            format = new IdentifyFile().identify(file, protocol);
 +          } catch (FileFormatException e)
 +          {
 +            // TODO what?
 +          }
 +        }
 +        af = fileLoader.LoadFileWaitTillLoaded(file, protocol, format);
 +      }
 +    }
 +    else
      {
        if (!headless)
        {
         * @j2sIgnore
         */
        {
-         if (!file.startsWith("http://") && !file.startsWith("https://"))
-         // BH 2019 added https check for Java
+         if (!HttpUtils.startsWithHttpOrHttps(file))
          {
            if (!(new File(file)).exists())
            {
            }
          }
        }
-       
++      // JS Only argument to provide a format parameter to specify what format to use
 +      String fileFormat = (isJS
 +              ? (String) aparser.getAppletValue("format", null, true)
 +              : null);
        protocol = AppletFormatAdapter.checkProtocol(file);
        try
        {
 -        format = new IdentifyFile().identify(file, protocol);
 +        format = (fileFormat != null
 +                ? FileFormats.getInstance().forName(fileFormat)
 +                : null);
 +        if (format == null)
 +        {
 +          format = new IdentifyFile().identify(file, protocol);
 +        }
        } catch (FileFormatException e1)
        {
          // TODO ?
        }
  
 -      AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
 +      af = new FileLoader(!headless).LoadFileWaitTillLoaded(file, protocol,
                format);
        if (af == null)
        {
 -        System.out.println("error");
 +        System.out.println("jalview error - AlignFrame was not created");
        }
        else
        {
 -        setCurrentAlignFrame(af);
 -        data = aparser.getValue("colour", true);
 -        if (data != null)
++        
 +        // JalviewLite interface for JavaScript allows second file open
 +        String file2 = aparser.getValue(ArgsParser.OPEN2, true);
 +        if (file2 != null)
          {
 -          data.replaceAll("%20", " ");
 -
 -          ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
 -                  af.getViewport(), af.getViewport().getAlignment(), data);
 -
 -          if (cs != null)
 +          protocol = AppletFormatAdapter.checkProtocol(file2);
 +          try
            {
 -            System.out.println(
 -                    "CMD [-color " + data + "] executed successfully!");
 +            format = new IdentifyFile().identify(file2, protocol);
 +          } catch (FileFormatException e1)
 +          {
 +            // TODO ?
            }
 -          af.changeColour(cs);
 -        }
 -
 -        // Must maintain ability to use the groups flag
 -        data = aparser.getValue("groups", true);
 -        if (data != null)
 -        {
 -          af.parseFeaturesFile(data,
 -                  AppletFormatAdapter.checkProtocol(data));
 -          // System.out.println("Added " + data);
 -          System.out.println(
 -                  "CMD groups[-" + data + "]  executed successfully!");
 -        }
 -        data = aparser.getValue("features", true);
 -        if (data != null)
 -        {
 -          af.parseFeaturesFile(data,
 -                  AppletFormatAdapter.checkProtocol(data));
 -          // System.out.println("Added " + data);
 -          System.out.println(
 -                  "CMD [-features " + data + "]  executed successfully!");
 -        }
 -
 -        data = aparser.getValue("annotations", true);
 -        if (data != null)
 -        {
 -          af.loadJalviewDataFile(data, null, null, null);
 -          // System.out.println("Added " + data);
 -          System.out.println(
 -                  "CMD [-annotations " + data + "] executed successfully!");
 -        }
 -        // set or clear the sortbytree flag.
 -        if (aparser.contains("sortbytree"))
 -        {
 -          af.getViewport().setSortByTree(true);
 -          if (af.getViewport().getSortByTree())
 +          AlignFrame af2 = new FileLoader(!headless)
 +                  .LoadFileWaitTillLoaded(file2, protocol, format);
 +          if (af2 == null)
            {
 -            System.out.println("CMD [-sortbytree] executed successfully!");
 +            System.out.println("error");
            }
 -        }
 -        if (aparser.contains("no-annotation"))
 -        {
 -          af.getViewport().setShowAnnotation(false);
 -          if (!af.getViewport().isShowAnnotation())
 +          else
            {
 -            System.out.println("CMD no-annotation executed successfully!");
 +            AlignViewport.openLinkedAlignmentAs(af,
 +                    af.getViewport().getAlignment(),
 +                    af2.getViewport().getAlignment(), "",
 +                    AlignViewport.SPLIT_FRAME);
 +            System.out.println(
 +                    "CMD [-open2 " + file2 + "] executed successfully!");
            }
          }
 -        if (aparser.contains("nosortbytree"))
++        // af is loaded - so set it as current frame
 +        setCurrentAlignFrame(af);
 +
 +        setFrameDependentProperties(aparser, af);
 +        
 +        if (isJS)
          {
 -          af.getViewport().setSortByTree(false);
 -          if (!af.getViewport().getSortByTree())
 -          {
 -            System.out
 -                    .println("CMD [-nosortbytree] executed successfully!");
 -          }
 +          jsApp.initFromParams(af);
          }
 -        data = aparser.getValue("tree", true);
 -        if (data != null)
 +        else
 +        /**
 +         * Java only
 +         * 
 +         * @j2sIgnore
 +         */
          {
 -          try
 +          if (groovyscript != null)
            {
 -            System.out.println(
 -                    "CMD [-tree " + data + "] executed successfully!");
 -            NewickFile nf = new NewickFile(data,
 -                    AppletFormatAdapter.checkProtocol(data));
 -            af.getViewport()
 -                    .setCurrentTree(af.showNewickTree(nf, data).getTree());
 -          } catch (IOException ex)
 -          {
 -            System.err.println("Couldn't add tree " + data);
 -            ex.printStackTrace(System.err);
 +            // Execute the groovy script after we've done all the rendering
 +            // stuff
 +            // and before any images or figures are generated.
 +            System.out.println("Executing script " + groovyscript);
 +            executeGroovyScript(groovyscript, af);
 +            System.out.println("CMD groovy[" + groovyscript
 +                    + "] executed successfully!");
 +            groovyscript = null;
            }
          }
 -        // TODO - load PDB structure(s) to alignment JAL-629
 -        // (associate with identical sequence in alignment, or a specified
 -        // sequence)
 -        if (groovyscript != null)
 -        {
 -          // Execute the groovy script after we've done all the rendering stuff
 -          // and before any images or figures are generated.
 -          System.out.println("Executing script " + groovyscript);
 -          executeGroovyScript(groovyscript, af);
 -          System.out.println("CMD groovy[" + groovyscript
 -                  + "] executed successfully!");
 -          groovyscript = null;
 +        if (!isJS || !isStartup) {
 +          createOutputFiles(aparser, format);
          }
 -        String imageName = "unnamed.png";
 -        while (aparser.getSize() > 1)
 -        {
 -          String outputFormat = aparser.nextValue();
 -          file = aparser.nextValue();
 +      }
 +      if (headless)
 +      {
 +        af.getViewport().getCalcManager().shutdown();
 +      }
 +    }
 +    // extract groovy arguments before anything else.
 +    // Once all other stuff is done, execute any groovy scripts (in order)
 +    if (!isJS && groovyscript != null)
 +    {
 +      if (Cache.groovyJarsPresent())
 +      {
++        // TODO: DECIDE IF THIS SECOND PASS AT GROOVY EXECUTION IS STILL REQUIRED !!
 +        System.out.println("Executing script " + groovyscript);
 +        executeGroovyScript(groovyscript, af);
++        System.out.println("CMD groovy[" + groovyscript
++                    + "] executed successfully!");
 -          if (outputFormat.equalsIgnoreCase("png"))
 -          {
 -            af.createPNG(new File(file));
 -            imageName = (new File(file)).getName();
 -            System.out.println("Creating PNG image: " + file);
 -            continue;
 -          }
 -          else if (outputFormat.equalsIgnoreCase("svg"))
 -          {
 -            File imageFile = new File(file);
 -            imageName = imageFile.getName();
 -            af.createSVG(imageFile);
 -            System.out.println("Creating SVG image: " + file);
 -            continue;
 -          }
 -          else if (outputFormat.equalsIgnoreCase("html"))
 -          {
 -            File imageFile = new File(file);
 -            imageName = imageFile.getName();
 -            HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
 -            htmlSVG.exportHTML(file);
 +      }
 +      else
 +      {
 +        System.err.println(
 +                "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
 +                        + groovyscript);
 +      }
 +    }
  
 -            System.out.println("Creating HTML image: " + file);
 -            continue;
 -          }
 -          else if (outputFormat.equalsIgnoreCase("biojsmsa"))
 -          {
 -            if (file == null)
 -            {
 -              System.err.println("The output html file must not be null");
 -              return;
 -            }
 -            try
 -            {
 -              BioJsHTMLOutput.refreshVersionInfo(
 -                      BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
 -            } catch (URISyntaxException e)
 -            {
 -              e.printStackTrace();
 -            }
 -            BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
 -            bjs.exportHTML(file);
 -            System.out
 -                    .println("Creating BioJS MSA Viwer HTML file: " + file);
 -            continue;
 -          }
 -          else if (outputFormat.equalsIgnoreCase("imgMap"))
 -          {
 -            af.createImageMap(new File(file), imageName);
 -            System.out.println("Creating image map: " + file);
 -            continue;
 -          }
 -          else if (outputFormat.equalsIgnoreCase("eps"))
 -          {
 -            File outputFile = new File(file);
 -            System.out.println(
 -                    "Creating EPS file: " + outputFile.getAbsolutePath());
 -            af.createEPS(outputFile);
 -            continue;
 -          }
 -          FileFormatI outFormat = null;
 -          try
 -          {
 -            outFormat = FileFormats.getInstance().forName(outputFormat);
 -          } catch (Exception formatP)
 -          {
 -            System.out.println("Couldn't parse " + outFormat
 -                    + " as a valid Jalview format string.");
 -          }
 -          if (outFormat != null)
 -          {
 -            if (!outFormat.isWritable())
 -            {
 -              System.out.println(
 -                      "This version of Jalview does not support alignment export as "
 -                              + outputFormat);
 -            }
 -            else
 -            {
 -              af.saveAlignment(file, outFormat);
 -              if (af.isSaveAlignmentSuccessful())
 -              {
 -                System.out.println("Written alignment in "
 -                        + outFormat.getName() + " format to " + file);
 -              }
 -              else
 -              {
 -                System.out.println("Error writing file " + file + " in "
 -                        + outFormat.getName() + " format!!");
 -              }
 -            }
 -          }
 +    // and finally, turn off batch mode indicator - if the desktop still exists
 +    if (desktop != null)
 +    {
 +      if (progress != -1)
 +      {
 +        desktop.setProgressBar(null, progress);
 +      }
 +      desktop.setInBatchMode(false);
 +    }
 +    
 +    if (jsApp != null) {
 +      jsApp.callInitCallback();
 +    }
 +  }
 +  
 +  /**
 +   * Set general display parameters irrespective of file loading or headlessness.
 +   * 
 +   * @param aparser
 +   */
 +  private void setDisplayParameters(ArgsParser aparser)
 +  {
 +    if (aparser.contains(ArgsParser.NOMENUBAR))
 +    {
 +      noMenuBar = true;
 +      System.out.println("CMD [nomenu] executed successfully!");
 +    }
  
 -        }
 +    if (aparser.contains(ArgsParser.NOSTATUS))
 +    {
 +      noStatus = true;
 +      System.out.println("CMD [nostatus] executed successfully!");
 +    }
  
 -        while (aparser.getSize() > 0)
 -        {
 -          System.out.println("Unknown arg: " + aparser.nextValue());
 -        }
 -      }
 +    if (aparser.contains(ArgsParser.NOANNOTATION)
 +            || aparser.contains(ArgsParser.NOANNOTATION2))
 +    {
 +      noAnnotation = true;
 +      System.out.println("CMD no-annotation executed successfully!");
 +    }
 +    if (aparser.contains(ArgsParser.NOCALCULATION))
 +    {
 +      noCalculation = true;
 +      System.out.println("CMD [nocalculation] executed successfully!");
      }
 -    AlignFrame startUpAlframe = null;
 -    // We'll only open the default file if the desktop is visible.
 -    // And the user
 -    // ////////////////////
 +  }
  
 -    if (!Platform.isJS() && !headless && file == null
 -            && Cache.getDefault("SHOW_STARTUP_FILE", true))
 -    /**
 -     * Java only
 -     * 
 -     * @j2sIgnore
 -     */
 +
 +  private void setFrameDependentProperties(ArgsParser aparser,
 +          AlignFrame af)
 +  {
 +    String data = aparser.getValue(ArgsParser.COLOUR, true);
 +    if (data != null)
      {
 -      file = jalview.bin.Cache.getDefault("STARTUP_FILE",
 -              jalview.bin.Cache.getDefault("www.jalview.org",
 -                      "https://www.jalview.org")
 -                      + "/examples/exampleFile_2_7.jvp");
 -      if (file.equals(
 -              "http://www.jalview.org/examples/exampleFile_2_3.jar") || file.equals(
 -                      "http://www.jalview.org/examples/exampleFile_2_7.jar"))
 +      data.replaceAll("%20", " ");
 +
 +      ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
 +              af.getViewport(), af.getViewport().getAlignment(), data);
 +
 +      if (cs != null)
        {
 -        file.replace("http:", "https:");
 -        // hardwire upgrade of the startup file
 -        file.replace("_2_3", "_2_7");
 -        file.replace("2_7.jar", "2_7.jvp");
 -        // and remove the stale setting
 -        Cache.removeProperty("STARTUP_FILE");
 +        System.out.println(
 +                "CMD [-color " + data + "] executed successfully!");
        }
 +      af.changeColour(cs);
 +    }
  
 -      protocol = AppletFormatAdapter.checkProtocol(file);
 +    // Must maintain ability to use the groups flag
 +    data = aparser.getValue(ArgsParser.GROUPS, true);
 +    if (data != null)
 +    {
 +      af.parseFeaturesFile(data,
 +              AppletFormatAdapter.checkProtocol(data));
 +      // System.out.println("Added " + data);
 +      System.out.println(
 +              "CMD groups[-" + data + "]  executed successfully!");
 +    }
 +    data = aparser.getValue(ArgsParser.FEATURES, true);
 +    if (data != null)
 +    {
 +      af.parseFeaturesFile(data,
 +              AppletFormatAdapter.checkProtocol(data));
 +      // System.out.println("Added " + data);
 +      System.out.println(
 +              "CMD [-features " + data + "]  executed successfully!");
 +    }
 +    data = aparser.getValue(ArgsParser.ANNOTATIONS, true);
 +    if (data != null)
 +    {
 +      af.loadJalviewDataFile(data, null, null, null);
 +      // System.out.println("Added " + data);
 +      System.out.println(
 +              "CMD [-annotations " + data + "] executed successfully!");
 +    }
  
 -      if (file.endsWith(".jar"))
 +    // JavaScript feature
 +
 +    if (aparser.contains(ArgsParser.SHOWOVERVIEW))
 +    {
 +      af.overviewMenuItem_actionPerformed(null);
 +      System.out.println("CMD [showoverview] executed successfully!");
 +    }
 +
 +    // set or clear the sortbytree flag.
 +    if (aparser.contains(ArgsParser.SORTBYTREE))
 +    {
 +      af.getViewport().setSortByTree(true);
 +      if (af.getViewport().getSortByTree())
        {
 -        format = FileFormat.Jalview;
 +        System.out.println("CMD [-sortbytree] executed successfully!");
        }
 -      else
 +    }
 +
 +    boolean doUpdateAnnotation = false;
 +    /**
 +     * we do this earlier in JalviewJS because of a complication with
 +     * SHOWOVERVIEW
 +     * 
 +     * For now, just fixing this in JalviewJS.
 +     *
 +     * 
 +     * @j2sIgnore
 +     * 
 +     */
 +    {
 +      if (noAnnotation)
        {
 -        try
 -        {
 -          format = new IdentifyFile().identify(file, protocol);
 -        } catch (FileFormatException e)
 +        af.getViewport().setShowAnnotation(false);
 +        if (!af.getViewport().isShowAnnotation())
          {
 -          // TODO what?
 +          doUpdateAnnotation = true;
          }
        }
 -
 -      startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
 -              format);
 -      // extract groovy arguments before anything else.
      }
  
 -    // Once all other stuff is done, execute any groovy scripts (in order)
 -    if (groovyscript != null)
 +    if (aparser.contains(ArgsParser.NOSORTBYTREE))
      {
 -      if (Cache.groovyJarsPresent())
 +      af.getViewport().setSortByTree(false);
 +      if (!af.getViewport().getSortByTree())
        {
 -        System.out.println("Executing script " + groovyscript);
 -        executeGroovyScript(groovyscript, startUpAlframe);
 +        doUpdateAnnotation = true;
 +        System.out
 +                .println("CMD [-nosortbytree] executed successfully!");
        }
 -      else
 +    }
 +    if (doUpdateAnnotation)
 +    { // BH 2019.07.24
 +      af.setMenusForViewport();
 +      af.alignPanel.updateLayout();
 +    }
 +
 +    data = aparser.getValue(ArgsParser.TREE, true);
 +    if (data != null)
 +    {
 +      try
        {
 -        System.err.println(
 -                "Sorry. Groovy Support is not available, so ignoring the provided groovy script "
 -                        + groovyscript);
 +        NewickFile nf = new NewickFile(data,
 +                AppletFormatAdapter.checkProtocol(data));
 +        af.getViewport()
 +                .setCurrentTree(af.showNewickTree(nf, data).getTree());
 +        System.out.println(
 +                "CMD [-tree " + data + "] executed successfully!");
 +      } catch (IOException ex)
 +      {
 +        System.err.println("Couldn't add tree " + data);
 +        ex.printStackTrace(System.err);
        }
      }
 -    // and finally, turn off batch mode indicator - if the desktop still exists
 -    if (desktop != null)
 +    // TODO - load PDB structure(s) to alignment JAL-629
 +    // (associate with identical sequence in alignment, or a specified
 +    // sequence)
 +
 +  }
 +
 +  /**
 +   * Writes an output file for each format (if any) specified in the
 +   * command-line arguments. Supported formats are currently
 +   * <ul>
 +   * <li>png</li>
 +   * <li>svg</li>
 +   * <li>html</li>
 +   * <li>biojsmsa</li>
 +   * <li>imgMap</li>
 +   * <li>eps</li>
 +   * </ul>
 +   * A format parameter should be followed by a parameter specifying the output
 +   * file name. {@code imgMap} parameters should follow those for the
 +   * corresponding alignment image output.
 +   * 
 +   * @param aparser
 +   * @param format
 +   */
 +  private void createOutputFiles(ArgsParser aparser, FileFormatI format)
 +  {
++    // logic essentially the same as 2.11.2/2.11.3 but uses a switch instead
 +    AlignFrame af = currentAlignFrame;
 +    while (aparser.getSize() >= 2)
      {
 -      if (progress != -1)
 +      String outputFormat = aparser.nextValue();
 +      File imageFile;
 +      String fname;
-       switch (outputFormat.toLowerCase())
++      switch (outputFormat.toLowerCase(Locale.ROOT))
        {
 -        desktop.setProgressBar(null, progress);
 +      case "png":
 +        imageFile = new File(aparser.nextValue());
 +        af.createPNG(imageFile);
 +        System.out.println(
 +                "Creating PNG image: " + imageFile.getAbsolutePath());
 +        continue;
 +      case "svg":
 +        imageFile = new File(aparser.nextValue());
 +        af.createSVG(imageFile);
 +        System.out.println(
 +                "Creating SVG image: " + imageFile.getAbsolutePath());
 +        continue;
 +      case "eps":
 +        imageFile = new File(aparser.nextValue());
 +        System.out.println(
 +                "Creating EPS file: " + imageFile.getAbsolutePath());
 +        af.createEPS(imageFile);
 +        continue;
 +      case "biojsmsa":
 +        fname = new File(aparser.nextValue()).getAbsolutePath();
 +        try
 +        {
 +          BioJsHTMLOutput.refreshVersionInfo(
 +                  BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
 +        } catch (URISyntaxException e)
 +        {
 +          e.printStackTrace();
 +        }
 +        BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
 +        bjs.exportHTML(fname);
 +        System.out.println("Creating BioJS MSA Viwer HTML file: " + fname);
 +        continue;
 +      case "html":
 +        fname = new File(aparser.nextValue()).getAbsolutePath();
 +        HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
 +        htmlSVG.exportHTML(fname);
 +        System.out.println("Creating HTML image: " + fname);
 +        continue;
 +      case "imgmap":
 +        imageFile = new File(aparser.nextValue());
 +        af.alignPanel.makePNGImageMap(imageFile, "unnamed.png");
 +        System.out.println(
 +                "Creating image map: " + imageFile.getAbsolutePath());
 +        continue;
 +      default:
 +        // fall through - try to parse as an alignment data export format
 +        FileFormatI outFormat = null;
 +        try
 +        {
 +          outFormat = FileFormats.getInstance().forName(outputFormat);
 +        } catch (Exception formatP)
 +        {
 +        }
 +        if (outFormat == null)
 +        {
 +          System.out.println("Couldn't parse " + outputFormat
 +                  + " as a valid Jalview format string.");
 +          continue;
 +        }
 +        if (!outFormat.isWritable())
 +        {
 +          System.out.println(
 +                  "This version of Jalview does not support alignment export as "
 +                          + outputFormat);
 +          continue;
 +        }
 +        // record file as it was passed to Jalview so it is recognisable to the CLI
 +        // caller
 +        String file;
 +        fname = new File(file = aparser.nextValue()).getAbsolutePath();
 +        // JBPNote - yuck - really wish we did have a bean returned from this which gave
 +        // success/fail like before !
 +        af.saveAlignment(fname, outFormat);
 +        if (!af.isSaveAlignmentSuccessful())
 +        {
 +          System.out.println("Written alignment in " + outputFormat
 +                  + " format to " + file);
 +          continue;
 +        }
 +        else
 +        {
 +          System.out.println("Error writing file " + file + " in "
 +                  + outputFormat + " format!!");
 +        }
        }
 -      desktop.setInBatchMode(false);
 +    }
 +    // ??? Should report - 'ignoring' extra args here...
 +    while (aparser.getSize() > 0)
 +    {
 +      System.out.println("Ignoring extra argument: " + aparser.nextValue());
      }
    }
  
+   private static void setLookAndFeel()
+   {
+     // property laf = "crossplatform", "system", "gtk", "metal", "nimbus" or
+     // "mac"
+     // If not set (or chosen laf fails), use the normal SystemLaF and if on Mac,
+     // try Quaqua/Vaqua.
+     String lafProp = System.getProperty("laf");
+     String lafSetting = Cache.getDefault("PREFERRED_LAF", null);
+     String laf = "none";
+     if (lafProp != null)
+     {
+       laf = lafProp;
+     }
+     else if (lafSetting != null)
+     {
+       laf = lafSetting;
+     }
+     boolean lafSet = false;
+     switch (laf)
+     {
+     case "crossplatform":
+       lafSet = setCrossPlatformLookAndFeel();
+       if (!lafSet)
+       {
+         Cache.log.error("Could not set requested laf=" + laf);
+       }
+       break;
+     case "system":
+       lafSet = setSystemLookAndFeel();
+       if (!lafSet)
+       {
+         Cache.log.error("Could not set requested laf=" + laf);
+       }
+       break;
+     case "gtk":
+       lafSet = setGtkLookAndFeel();
+       if (!lafSet)
+       {
+         Cache.log.error("Could not set requested laf=" + laf);
+       }
+       break;
+     case "metal":
+       lafSet = setMetalLookAndFeel();
+       if (!lafSet)
+       {
+         Cache.log.error("Could not set requested laf=" + laf);
+       }
+       break;
+     case "nimbus":
+       lafSet = setNimbusLookAndFeel();
+       if (!lafSet)
+       {
+         Cache.log.error("Could not set requested laf=" + laf);
+       }
+       break;
+     case "quaqua":
+       lafSet = setQuaquaLookAndFeel();
+       if (!lafSet)
+       {
+         Cache.log.error("Could not set requested laf=" + laf);
+       }
+       break;
+     case "vaqua":
+       lafSet = setVaquaLookAndFeel();
+       if (!lafSet)
+       {
+         Cache.log.error("Could not set requested laf=" + laf);
+       }
+       break;
+     case "mac":
+       lafSet = setMacLookAndFeel();
+       if (!lafSet)
+       {
+         Cache.log.error("Could not set requested laf=" + laf);
+       }
+       break;
+     case "none":
+       break;
+     default:
+       Cache.log.error("Requested laf=" + laf + " not implemented");
+     }
+     if (!lafSet)
+     {
+       setSystemLookAndFeel();
+       if (Platform.isLinux())
+       {
+         setMetalLookAndFeel();
+       }
+       if (Platform.isMac())
+       {
+         setMacLookAndFeel();
+       }
+     }
+   }
+   private static boolean setCrossPlatformLookAndFeel()
+   {
+     boolean set = false;
+     try
+     {
+       UIManager.setLookAndFeel(
+               UIManager.getCrossPlatformLookAndFeelClassName());
+       set = true;
+     } catch (Exception ex)
+     {
+       Cache.log.error("Unexpected Look and Feel Exception");
+       Cache.log.error(ex.getMessage());
+       Cache.log.debug(Cache.getStackTraceString(ex));
+     }
+     return set;
+   }
+   private static boolean setSystemLookAndFeel()
+   {
+     boolean set = false;
+     try
+     {
+       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+       set = true;
+     } catch (Exception ex)
+     {
+       Cache.log.error("Unexpected Look and Feel Exception");
+       Cache.log.error(ex.getMessage());
+       Cache.log.debug(Cache.getStackTraceString(ex));
+     }
+     return set;
+   }
+   private static boolean setSpecificLookAndFeel(String name,
+           String className, boolean nameStartsWith)
+   {
+     boolean set = false;
+     try
+     {
+       for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
+       {
+         if (info.getName() != null && nameStartsWith
+                 ? info.getName().toLowerCase(Locale.ROOT)
+                         .startsWith(name.toLowerCase(Locale.ROOT))
+                 : info.getName().toLowerCase(Locale.ROOT).equals(name.toLowerCase(Locale.ROOT)))
+         {
+           className = info.getClassName();
+           break;
+         }
+       }
+       UIManager.setLookAndFeel(className);
+       set = true;
+     } catch (Exception ex)
+     {
+       Cache.log.error("Unexpected Look and Feel Exception");
+       Cache.log.error(ex.getMessage());
+       Cache.log.debug(Cache.getStackTraceString(ex));
+     }
+     return set;
+   }
+   private static boolean setGtkLookAndFeel()
+   {
+     return setSpecificLookAndFeel("gtk",
+             "com.sun.java.swing.plaf.gtk.GTKLookAndFeel", true);
+   }
+   private static boolean setMetalLookAndFeel()
+   {
+     return setSpecificLookAndFeel("metal",
+             "javax.swing.plaf.metal.MetalLookAndFeel", false);
+   }
+   private static boolean setNimbusLookAndFeel()
+   {
+     return setSpecificLookAndFeel("nimbus",
+             "javax.swing.plaf.nimbus.NimbusLookAndFeel", false);
+   }
+   private static boolean setQuaquaLookAndFeel()
+   {
+     return setSpecificLookAndFeel("quaqua",
+             ch.randelshofer.quaqua.QuaquaManager.getLookAndFeel().getClass()
+                     .getName(),
+             false);
+   }
+   private static boolean setVaquaLookAndFeel()
+   {
+     return setSpecificLookAndFeel("vaqua",
+             "org.violetlib.aqua.AquaLookAndFeel", false);
+   }
+   private static boolean setMacLookAndFeel()
+   {
+     boolean set = false;
+     System.setProperty("com.apple.mrj.application.apple.menu.about.name",
+             ChannelProperties.getProperty("app_name"));
+     System.setProperty("apple.laf.useScreenMenuBar", "true");
+     set = setQuaquaLookAndFeel();
+     if ((!set) || !UIManager.getLookAndFeel().getClass().toString()
+             .toLowerCase(Locale.ROOT).contains("quaqua"))
+     {
+       set = setVaquaLookAndFeel();
+     }
+     return set;
+   }
 -
    private static void showUsage()
    {
      System.out.println(
                      + "-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"
                      + "-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");
+                     + "-jvmmempc=PERCENT\tOnly available with standalone executable jar or jalview.bin.Launcher. Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected. See https://www.jalview.org/help/html/memory.html for more details.\n"
+                     + "-jvmmemmax=MAXMEMORY\tOnly available with standalone executable jar or jalview.bin.Launcher. Limit maximum heap size (memory) to MAXMEMORY. MAXMEMORY can be specified in bytes, kilobytes(k), megabytes(m), gigabytes(g) or if you're lucky enough, terabytes(t). This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected. See https://www.jalview.org/help/html/memory.html for more details.\n"
+                     + "\n~Read documentation in Application or visit https://www.jalview.org for description of Features and Annotations file~\n\n");
    }
  
    private static void startUsageStats(final Desktop desktop)
      /**
       * start a User Config prompt asking if we can log usage statistics.
       */
 -    PromptUserConfig prompter = new PromptUserConfig(Desktop.desktop,
 +    PromptUserConfig prompter = new PromptUserConfig(Desktop.getDesktopPane(),
              "USAGESTATS", "Jalview Usage Statistics",
              "Do you want to help make Jalview better by enabling "
                      + "the collection of usage statistics with Google Analytics ?"
    }
  
    /**
--   * 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()
    {
  
    public static AlignFrame getCurrentAlignFrame()
    {
 -    return Jalview.currentAlignFrame;
 +    return Jalview.getInstance().currentAlignFrame;
    }
  
    public static void setCurrentAlignFrame(AlignFrame currentAlignFrame)
    {
 -    Jalview.currentAlignFrame = currentAlignFrame;
 +    Jalview.getInstance().currentAlignFrame = currentAlignFrame;
 +  }
 +  
 +  public void notifyWorker(AlignCalcWorkerI worker, String status)
 +  {
 +    // System.out.println("Jalview worker " + worker.getClass().getSimpleName()
 +    // + " " + status);
 +  }
 +
 +
 +  private static boolean isInteractive = true;
 +
 +  public static boolean isInteractive()
 +  {
 +    return isInteractive;
 +  }
 +
 +  public static void setInteractive(boolean tf)
 +  {
 +    isInteractive = tf;
    }
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.bin;
  
+ import java.util.Locale;
  import jalview.analysis.AlignmentUtils;
  import jalview.api.StructureSelectionManagerProvider;
  import jalview.appletgui.AlignFrame;
@@@ -45,12 -47,10 +47,12 @@@ import jalview.io.IdentifyFile
  import jalview.io.JPredFile;
  import jalview.io.JnetAnnotationMaker;
  import jalview.io.NewickFile;
 -import jalview.javascript.JSFunctionExec;
 -import jalview.javascript.JalviewLiteJsApi;
 -import jalview.javascript.JsCallBack;
 -import jalview.javascript.MouseOverStructureListener;
 +import jalview.appletgui.js.JSFunctionExec;
 +import jalview.appletgui.js.JalviewLiteJsApi;
 +import jalview.appletgui.js.JsCallBack;
 +import jalview.appletgui.js.JsSelectionSender;
 +import jalview.appletgui.js.MouseOverListener;
 +import jalview.appletgui.js.MouseOverStructureListener;
  import jalview.structure.SelectionListener;
  import jalview.structure.StructureSelectionManager;
  import jalview.util.ColorUtils;
@@@ -210,7 -210,7 +212,7 @@@ public class JalviewLite extends Apple
        final int pos = apos;
        // use vamsas listener to broadcast to all listeners in scope
        if (alignedPosition != null && (alignedPosition.trim().length() == 0
-               || alignedPosition.toLowerCase().indexOf("false") > -1))
+               || alignedPosition.toLowerCase(Locale.ROOT).indexOf("false") > -1))
        {
          java.awt.EventQueue.invokeLater(new Runnable()
          {
              r--;
            } catch (NumberFormatException ex)
            {
-             if (cl.toLowerCase().equals("sequence"))
+             if (cl.toLowerCase(Locale.ROOT).equals("sequence"))
              {
                // we are in the dataset sequence's coordinate frame.
                inseqpos = true;
      setMouseoverListener(currentAlignFrame, listener);
    }
  
 -  private Vector<jalview.javascript.JSFunctionExec> javascriptListeners = new Vector<>();
 +  private Vector<JSFunctionExec> javascriptListeners = new Vector<>();
  
    /*
     * (non-Javadoc)
          return;
        }
      }
 -    jalview.javascript.MouseOverListener mol = new jalview.javascript.MouseOverListener(
 +    MouseOverListener mol = new MouseOverListener(
              this, af, listener);
      javascriptListeners.addElement(mol);
      StructureSelectionManager.getStructureSelectionManager(this)
          return;
        }
      }
 -    jalview.javascript.JsSelectionSender mol = new jalview.javascript.JsSelectionSender(
 +    JsSelectionSender mol = new JsSelectionSender(
              this, af, listener);
      javascriptListeners.addElement(mol);
      StructureSelectionManager.getStructureSelectionManager(this)
      {
        while (javascriptListeners.size() > 0)
        {
 -        jalview.javascript.JSFunctionExec mol = javascriptListeners
 +        JSFunctionExec mol = javascriptListeners
                  .elementAt(0);
          javascriptListeners.removeElement(mol);
          if (mol instanceof SelectionListener)
      StructureSelectionManager.release(this);
    }
  
 -  private jalview.javascript.JSFunctionExec jsFunctionExec;
 +  private JSFunctionExec jsFunctionExec;
  
    /*
     * (non-Javadoc)
     * (non-Javadoc)
     * 
     * @see
 -   * jalview.javascript.JalviewLiteJsApi#scrollViewToRowIn(jalview.appletgui
 +   * JalviewLiteJsApi#scrollViewToRowIn(jalview.appletgui
     * .AlignFrame, java.lang.String)
     */
    @Override
     * (non-Javadoc)
     * 
     * @see
 -   * jalview.javascript.JalviewLiteJsApi#scrollViewToColumnIn(jalview.appletgui
 +   * JalviewLiteJsApi#scrollViewToColumnIn(jalview.appletgui
     * .AlignFrame, java.lang.String)
     */
    @Override
      String externalsviewer = getParameter("externalstructureviewer");
      if (externalsviewer != null)
      {
-       useXtrnalSviewer = externalsviewer.trim().toLowerCase().equals(TRUE);
+       useXtrnalSviewer = externalsviewer.trim().toLowerCase(Locale.ROOT).equals(TRUE);
      }
      /**
       * if true disable the check for jmol
            final String groups, boolean state)
    {
      final boolean st = state;// !(state==null || state.equals("") ||
-     // state.toLowerCase().equals("false"));
+     // state.toLowerCase(Locale.ROOT).equals("false"));
      java.awt.EventQueue.invokeLater(new Runnable()
      {
        @Override
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.datamodel;
  
+ import java.util.Locale;
  import jalview.analysis.Rna;
  import jalview.analysis.SecStrConsensus.SimpleBP;
  import jalview.analysis.WUSSParseException;
@@@ -94,10 -96,6 +96,10 @@@ public class AlignmentAnnotatio
     */
    private long invalidrnastruc = -2;
  
 +  private double bitScore;
 +
 +  private double eValue;
 +
    /**
     * Updates the _rnasecstr field Determines the positions that base pair and
     * the positions of helices based on secondary structure from a Stockholm file
      }
      return null;
    }
 -
 +  
    /**
     * Creates a new AlignmentAnnotation object.
     * 
        }
      }
    }
 -
 +  
    /**
     * Copy constructor creates a new independent annotation row with the same
     * associated sequenceRef
    public AlignmentAnnotation(AlignmentAnnotation annotation)
    {
      setAnnotationId();
 +    updateAlignmentAnnotationFrom(annotation);
 +  }
 +
 +  /**
 +   * copy attributes and annotation from an existing annotation (used by copy
 +   * constructor). This method does not update the unique annotationId
 +   * 
 +   * @param annotation
 +   */
 +  public void updateAlignmentAnnotationFrom(AlignmentAnnotation annotation)
 +  {
      this.label = new String(annotation.label);
      if (annotation.description != null)
      {
      this.scaleColLabel = annotation.scaleColLabel;
      this.showAllColLabels = annotation.showAllColLabels;
      this.calcId = annotation.calcId;
 +    this.bitScore = annotation.bitScore;
 +    this.eValue = annotation.eValue;
 +
      if (annotation.properties != null)
      {
        properties = new HashMap<>();
     * @param seqRef
     * @param startRes
     * @param alreadyMapped
 +   *          - annotation are at aligned columns
     */
    public void createSequenceMapping(SequenceI seqRef, int startRes,
            boolean alreadyMapped)
    {
      return hasScore || !Double.isNaN(score);
    }
 -
 +  
    /**
     * Score only annotation
     * 
      makeVisibleAnnotation(hidden);
    }
  
 +
    public void setPadGaps(boolean padgaps, char gapchar)
    {
      this.padGaps = padgaps;
    {
      if (seqname && this.sequenceRef != null)
      {
-       int i = description.toLowerCase().indexOf("<html>");
+       int i = description.toLowerCase(Locale.ROOT).indexOf("<html>");
        if (i > -1)
        {
          // move the html tag to before the sequence reference.
            Iterable<AlignmentAnnotation> list, SequenceI seq, String calcId,
            String label)
    {
 -
      ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
      for (AlignmentAnnotation ann : list)
      {
    public static Iterable<AlignmentAnnotation> findAnnotation(
            List<AlignmentAnnotation> list, String calcId)
    {
 -
      List<AlignmentAnnotation> aa = new ArrayList<>();
      if (calcId == null)
      {
      return aa;
    }
  
 +  public double getBitScore()
 +  {
 +    return bitScore;
 +  }
 +
 +  public void setBitScore(double bitScore)
 +  {
 +    this.bitScore = bitScore;
 +  }
 +
 +  public double getEValue()
 +  {
 +    return eValue;
 +  }
 +
 +  public void setEValue(double eValue)
 +  {
 +    this.eValue = eValue;
 +  }
 +
 +  public static AlignmentAnnotation findFirstAnnotation(
 +          Iterable<AlignmentAnnotation> alignmentAnnotation, String name,
 +          String calcId, boolean autoCalc, SequenceI seqRef,
 +          SequenceGroup groupRef)
 +  {
 +
 +    for (AlignmentAnnotation annot : alignmentAnnotation)
 +    {
 +      if (annot.autoCalculated == autoCalc && (name.equals(annot.label))
 +              && (calcId == null || annot.getCalcId().equals(calcId))
 +              && annot.sequenceRef == seqRef && annot.groupRef == groupRef)
 +      {
 +        return annot;
 +      }
 +    }
 +    return null;
 +  }
 +
  }
   */
  package jalview.datamodel;
  
 -import jalview.util.CaseInsensitiveString;
 -
  import java.util.Collections;
  import java.util.Enumeration;
  import java.util.Hashtable;
  
 +import jalview.util.CaseInsensitiveString;
 +
  public class PDBEntry
  {
  
@@@ -38,6 -38,8 +38,8 @@@
  
    private static final int PDB_ID_LENGTH = 4;
  
+   private static final String FAKED_ID = "faked_pdbid";
    private String file;
  
    private String type;
@@@ -48,7 -50,7 +50,7 @@@
    {
      // TODO is FILE needed; if not is this enum needed, or can we
      // use FileFormatI for PDB, MMCIF?
-     PDB("pdb", "pdb"), MMCIF("mmcif", "cif"), FILE("?", "?");
+     PDB("pdb", "pdb"), MMCIF("mmcif", "cif"), BCIF("bcif","bcif"),FILE("?", "?");
  
      /*
       * file extension for cached structure file; must be one that
    {
    }
  
 +  /**
 +   * Entry point when file is not known and fileType may be string
 +   * @param pdbId
 +   * @param chain may be null
 +   * @param fileType "pdb", "mmcif", or "bcif"; null defaults to mmcif
 +   */
 +  public PDBEntry(String pdbId, String chain, String fileType) {
 +    this.id = pdbId.toLowerCase();
 +    setChainCode(chain); // I note that PDB Chains ARE case-sensitive now
 +    if (fileType == null)
 +      fileType = "mmcif";
 +    switch (fileType.toLowerCase()) {
 +    case "pdb":
 +      this.type = Type.PDB.toString();
 +      break;
 +    case "mmcif":
 +      this.type = Type.MMCIF.toString();
 +      break;
 +    default:
 +    case "bcif":
 +      System.out.println("format " + fileType + " has not been implemented; using mmCIF");
 +      this.type = Type.MMCIF.toString();
 +      break;
 +    }
 +  }
 +  
    public PDBEntry(String pdbId, String chain, PDBEntry.Type type,
            String filePath)
    {
      return id;
    }
  
 +  /**
 +   * TODO 
 +   * 
 +   * @param key  "protocol" 
 +   * @param value
 +   */
    public void setProperty(String key, Object value)
    {
      if (this.properties == null)
        return false; // shouldn't happen
      }
  
-     /*
-      * id has to match (ignoring case)
-      */
-     if (!getId().equalsIgnoreCase(newId))
-     {
-       return false;
-     }
+     boolean idMatches = getId().equalsIgnoreCase(newId);
  
      /*
       * Don't update if associated with different structure files
       */
      String newFile = newEntry.getFile();
-     if (newFile != null && getFile() != null && !newFile.equals(getFile()))
+     if (newFile != null && getFile() != null)
      {
-       return false;
+       if (!newFile.equals(getFile()))
+       {
+         return false;
+       }
+       else
+       {
+         // files match.
+         if (!idMatches)
+         {
+           // this shouldn't happen, but could do if the id from the
+           // file is not the same as the id from the authority that provided
+           // the file
+           if (!newEntry.fakedPDBId())
+           {
+             return false;
+           } // otherwise we can update
+         }
+       }
+     }
+     else
+     {
+       // one has data, one doesn't ..
+       if (!idMatches)
+       {
+         return false;
+       } // otherwise maybe can update
      }
  
      /*
         */
        String key = newProps.nextElement();
        Object value = newEntry.getProperty(key);
+       if (FAKED_ID.equals(key))
+       {
+         // we never update the fake ID property
+         continue;
+       }
        if (!value.equals(getProperty(key)))
        {
          setProperty(key, value);
      }
      return true;
    }
+   
+   /**
+    * set when Jalview has manufactured the ID using a local filename
+    * @return
+    */
+   public boolean fakedPDBId()
+   {
+     if (_hasProperty(FAKED_ID))
+     {
+       return true;
+     }
+     return false;
+   }
+   public void setFakedPDBId(boolean faked)
+   {
+     if (faked)
+     {
+       setProperty(FAKED_ID, Boolean.TRUE);
+     }
+     else 
+     {
+       if (properties!=null) {
+         properties.remove(FAKED_ID);
+       }
+     }
+   }
+   private boolean _hasProperty(final String key)
+   {
+     return (properties != null && properties.containsKey(key));
+   }
+   private static final String RETRIEVE_FROM = "RETRIEVE_FROM";
+   private static final String PROVIDER = "PROVIDER";
+   private static final String MODELPAGE = "PROVIDERPAGE";
+   /**
+    * Permanent URI for retrieving the original structure data
+    * 
+    * @param urlStr
+    */
+   public void setRetrievalUrl(String urlStr)
+   {
+     setProperty(RETRIEVE_FROM, urlStr);
+   }
+   public boolean hasRetrievalUrl()
+   {
+     return _hasProperty(RETRIEVE_FROM);
+   }
+   /**
+    * get the Permanent URI for retrieving the original structure data
+    */
+   public String getRetrievalUrl()
+   {
+     return (String) getProperty(RETRIEVE_FROM);
+   }
+   /**
+    * Data provider name - from 3D Beacons
+    * 
+    * @param provider
+    */
+   public void setProvider(String provider)
+   {
+     setProperty(PROVIDER, provider);
+   }
+   /**
+    * Get Data provider name - from 3D Beacons
+    * 
+    */
+   public String getProvider()
+   {
+     return (String) getProperty(PROVIDER);
+   }
+   /**
+    * Permanent URI for retrieving the original structure data
+    * 
+    * @param urlStr
+    */
+   public void setProviderPage(String urlStr)
+   {
+     setProperty(MODELPAGE, urlStr);
+   }
+   /**
+    * get the Permanent URI for retrieving the original structure data
+    */
+   public String getProviderPage()
+   {
+     return (String) getProperty(MODELPAGE);
+   }
+   public boolean hasProviderPage()
+   {
+     return _hasProperty(MODELPAGE);
+   }
+   public boolean hasProvider()
+   {
+     return _hasProperty(PROVIDER);
+   }
  }
@@@ -25,8 -25,6 +25,8 @@@ import jalview.util.Format
  import jalview.util.QuickSort;
  import jalview.util.SparseCount;
  
 +import java.util.List;
 +
  /**
   * A class to count occurrences of residues in a profile, optimised for speed
   * and memory footprint.
@@@ -72,7 -70,7 +72,7 @@@ public class ResidueCoun
     */
    private static final String AAS = "ACDEFGHIKLMNPQRSTUVWXY";
  
-   private static final int GAP_COUNT = 0;
+   static final int GAP_COUNT = 0;
  
    /*
     * fast lookup tables holding the index into our count
    }
  
    /**
 +   * A constructor that counts frequency of all symbols (including gaps) in the
 +   * sequences (not case-sensitive)
 +   * 
 +   * @param sequences
 +   */
 +  public ResidueCount(List<SequenceI> sequences)
 +  {
 +    this();
 +    for (SequenceI seq : sequences)
 +    {
 +      for (int i = 0; i < seq.getLength(); i++)
 +      {
 +        add(seq.getCharAt(i));
 +      }
 +    }
 +  }
 +
 +  /**
     * Increments the count for the given character. The supplied character may be
     * upper or lower case but counts are for the upper case only. Gap characters
     * (space, ., -) are all counted together.
          counts[offset] = (short) ++newValue;
        }
      }
-     maxCount = Math.max(maxCount, newValue);
+     if (offset!=GAP_COUNT)
+     {
+       // update modal residue count
+       maxCount = Math.max(maxCount, newValue);
+     }
      return newValue;
    }
  
     */
    public int addGap()
    {
-     int newValue;
-     if (useIntCounts)
-     {
-       newValue = ++intCounts[GAP_COUNT];
-     }
-     else
-     {
-       newValue = ++counts[GAP_COUNT];
-     }
+     int newValue = increment(GAP_COUNT);
      return newValue;
    }
  
      sb.append("]");
      return sb.toString();
    }
 +
 +  /**
 +   * Answers the total count for all symbols (excluding gaps)
 +   * 
 +   * @return
 +   */
 +  public int getTotalResidueCount()
 +  {
 +    int total = 0;
 +    for (char symbol : this.getSymbolCounts().symbols)
 +    {
 +      total += getCount(symbol);
 +    }
 +    return total;
 +  }
  }
@@@ -27,7 -27,6 +27,7 @@@ import jalview.util.Comparison
  import jalview.util.DBRefUtils;
  import jalview.util.MapList;
  import jalview.util.StringUtils;
 +import jalview.workers.InformationThread;
  
  import java.util.ArrayList;
  import java.util.Arrays;
@@@ -83,10 -82,6 +83,9 @@@ public class Sequence extends ASequenc
  
    private String vamsasId;
  
 +  HiddenMarkovModel hmm;
 +
 +  boolean isHMMConsensusSequence = false;
    private DBModList<DBRefEntry> dbrefs; // controlled access
  
    /**
          this.addPDBId(new PDBEntry(pdb));
        }
      }
 +    if (seq.getHMM() != null)
 +    {
 +      this.hmm = new HiddenMarkovModel(seq.getHMM(), this);
 +    }
    }
  
    @Override
    @Override
    public ContiguousI findPositions(int fromColumn, int toColumn)
    {
 -    if (toColumn < fromColumn || fromColumn < 1)
 +    fromColumn = Math.max(fromColumn, 1);
 +    if (toColumn < fromColumn)
      {
        return null;
      }
    public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
            String label)
    {
+     return getAlignmentAnnotations(calcId, label, null, true);
+   }
+   @Override
+   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
+           String label, String description)
+   {
+     return getAlignmentAnnotations(calcId, label, description, false);
+   }
+   private List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
+           String label, String description, boolean ignoreDescription)
+   {
      List<AlignmentAnnotation> result = new ArrayList<>();
      if (this.annotation != null)
      {
        for (AlignmentAnnotation ann : annotation)
        {
 -        if ((ann.calcId != null && ann.calcId.equals(calcId))
 +        String id = ann.getCalcId();
-         if (id != null && id.equals(calcId)
-                 && ann.label != null && ann.label.equals(label))
++        if ((id != null && id.equals(calcId))
+                 && (ann.label != null && ann.label.equals(label))
+                 && ((ignoreDescription && description == null)
+                         || (ann.description != null
+                                 && ann.description.equals(description))))
 -
          {
            result.add(ann);
          }
      }
    }
  
 +  @Override
 +  public HiddenMarkovModel getHMM()
 +  {
 +    return hmm;
 +  }
 +
 +  @Override
 +  public void setHMM(HiddenMarkovModel hmm)
 +  {
 +    this.hmm = hmm;
 +  }
 +
 +  @Override
 +  public boolean hasHMMAnnotation()
 +  {
 +    if (this.annotation == null) {
 +      return false;
 +    }
 +    for (AlignmentAnnotation ann : annotation)
 +    {
 +      if (InformationThread.HMM_CALC_ID.equals(ann.getCalcId()))
 +      {
 +        return true;
 +      }
 +    }
 +    return false;
 +  }
    /**
     * {@inheritDoc}
     */
      // otherwise, sequence was completely hidden
      return 0;
    }
 +
 +  @Override
 +  public boolean hasHMMProfile()
 +  {
 +    return hmm != null;
 +  }
  }
@@@ -48,10 -48,6 +48,10 @@@ public interface SequenceI extends ASeq
     */
    public void setName(String name);
  
 +  public HiddenMarkovModel getHMM();
 +
 +  public void setHMM(HiddenMarkovModel hmm);
 +
    /**
     * Get the display name
     */
     * from 1), or null if no residues are included in the range
     * 
     * @param fromColum
 -   *          - first column base 1
 +   *          - first column base 1. (0 and negative positions are rounded up)
     * @param toColumn
     *          - last column, base 1
 -   * @return
 +   * @return null if fromColum>toColumn
     */
    public ContiguousI findPositions(int fromColum, int toColumn);
  
            String label);
  
    /**
+    * Returns a (possibly empty) list of any annotations that match on given
+    * calcId (source), label (type) and description (observation instance).
+    * Null values do not match.
+    * 
+    * @param calcId
+    * @param label
+    * @param description
+    */
+   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
+           String label, String description);
+   /**
     * create a new dataset sequence (if necessary) for this sequence and sets
     * this sequence to refer to it. This call will move any features or
     * references on the sequence onto the dataset. It will also make a duplicate
    public List<DBRefEntry> getPrimaryDBRefs();
  
    /**
 +   * Answers true if the sequence has annotation for Hidden Markov Model
 +   * information content, else false
 +   */
 +  boolean hasHMMAnnotation();
 +
 +  /**
     * Returns a (possibly empty) list of sequence features that overlap the given
     * alignment column range, optionally restricted to one or more specified
     * feature types. If the range is all gaps, then features which enclose it are
     * @param c1
     * @param c2
     */
 -  public int replace(char c1, char c2);
 +  int replace(char c1, char c2);
  
    /**
     * Answers the GeneLociI, or null if not known
     *          the iterator to use
     * @return a String corresponding to the sequence
     */
 -  public String getSequenceStringFromIterator(Iterator<int[]> it);
 +  String getSequenceStringFromIterator(Iterator<int[]> it);
  
    /**
     * Locate the first position in this sequence which is not contained in an
     *          iterator over regions
     * @return first residue not contained in regions
     */
 +
    public int firstResidueOutsideIterator(Iterator<int[]> it);
  
  
 +  /**
 +   * Answers true if this sequence has an associated Hidden Markov Model
 +   * 
 +   * @return
 +   */
 +  boolean hasHMMProfile();
  }
  
@@@ -55,7 -55,7 +55,7 @@@ public class EnsemblGene extends Ensemb
     * accepts anything as we will attempt lookup of gene or 
     * transcript id or gene name
     */
 -  private static final Regex ACCESSION_REGEX = new Regex(".*");
 +  private static final Regex ACCESSION_REGEX = Platform.newRegex(".*");
  
    private static final EnsemblFeatureType[] FEATURES_TO_FETCH = {
        EnsemblFeatureType.gene, EnsemblFeatureType.transcript,
    @Override
    protected boolean retainFeature(SequenceFeature sf, String accessionId)
    {
 -    SequenceOntologyI so = SequenceOntologyFactory.getInstance();
 +    SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
      String type = sf.getType();
      if (so.isA(type, SequenceOntologyI.GENE))
      {
    {
      return new FeatureSettingsAdapter()
      {
 -      SequenceOntologyI so = SequenceOntologyFactory.getInstance();
 +      SequenceOntologyI so = SequenceOntologyFactory.getSequenceOntology();
  
        @Override
-       public boolean isFeatureDisplayed(String type)
+       public boolean isFeatureHidden(String type)
        {
-         return (so.isA(type, SequenceOntologyI.EXON)
-                 || so.isA(type, SequenceOntologyI.SEQUENCE_VARIANT));
+         return (!so.isA(type, SequenceOntologyI.EXON)
+                 && !so.isA(type, SequenceOntologyI.SEQUENCE_VARIANT));
        }
  
        @Override
   */
  package jalview.ext.jmol;
  
- import jalview.api.AlignmentViewPanel;
- import jalview.api.FeatureRenderer;
- import jalview.api.SequenceRenderer;
- import jalview.datamodel.AlignmentI;
- import jalview.datamodel.HiddenColumns;
- import jalview.datamodel.PDBEntry;
- import jalview.datamodel.SequenceI;
- import jalview.gui.IProgressIndicator;
- import jalview.io.DataSourceType;
- import jalview.io.StructureFile;
- import jalview.schemes.ColourSchemeI;
- import jalview.schemes.ResidueProperties;
- import jalview.structure.AtomSpec;
- import jalview.structure.StructureMappingcommandSet;
- import jalview.structure.StructureSelectionManager;
- import jalview.structures.models.AAStructureBindingModel;
- import jalview.util.MessageManager;
- import java.awt.Color;
  import java.awt.Container;
  import java.awt.event.ComponentEvent;
  import java.awt.event.ComponentListener;
  import java.io.File;
  import java.net.URL;
  import java.util.ArrayList;
- import java.util.BitSet;
- import java.util.Hashtable;
  import java.util.List;
  import java.util.Map;
  import java.util.StringTokenizer;
  import java.util.Vector;
  
+ import javax.swing.SwingUtilities;
 -
  import org.jmol.adapter.smarter.SmarterJmolAdapter;
  import org.jmol.api.JmolAppConsoleInterface;
  import org.jmol.api.JmolSelectionListener;
  import org.jmol.api.JmolStatusListener;
  import org.jmol.api.JmolViewer;
  import org.jmol.c.CBK;
- import org.jmol.script.T;
  import org.jmol.viewer.Viewer;
  
+ import jalview.api.AlignmentViewPanel;
+ import jalview.api.FeatureRenderer;
+ import jalview.api.FeatureSettingsModelI;
+ import jalview.api.SequenceRenderer;
+ import jalview.bin.Cache;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.SequenceI;
+ import jalview.gui.AppJmol;
+ import jalview.gui.IProgressIndicator;
+ import jalview.gui.StructureViewer.ViewerType;
+ import jalview.io.DataSourceType;
+ import jalview.io.StructureFile;
+ import jalview.structure.AtomSpec;
+ import jalview.structure.StructureCommand;
+ import jalview.structure.StructureCommandI;
+ import jalview.structure.StructureSelectionManager;
+ import jalview.structures.models.AAStructureBindingModel;
+ import jalview.ws.dbsources.Pdb;
+ import javajs.util.BS;
 -
  public abstract class JalviewJmolBinding extends AAStructureBindingModel
          implements JmolStatusListener, JmolSelectionListener,
          ComponentListener
  {
    private String lastMessage;
  
-   boolean allChainsSelected = false;
 +
    /*
     * when true, try to search the associated datamodel for sequences that are
     * associated with any unknown structures in the Jmol view.
     */
    private boolean associateNewStructs = false;
  
-   Vector<String> atomsPicked = new Vector<>();
-   private List<String> chainNames;
-   Hashtable<String, String> chainFile;
+   private Vector<String> atomsPicked = new Vector<>();
  
-   /*
-    * the default or current model displayed if the model cannot be identified
-    * from the selection message
-    */
-   int frameNo = 0;
-   // protected JmolGenericPopup jmolpopup; // not used - remove?
+   private String lastCommand;
  
-   String lastCommand;
+   private boolean loadedInline;
  
-   boolean loadedInline;
+   private StringBuffer resetLastRes = new StringBuffer();
  
-   StringBuffer resetLastRes = new StringBuffer();
-   public Viewer viewer;
+   public Viewer jmolViewer;
  
    public JalviewJmolBinding(StructureSelectionManager ssm,
            PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
            DataSourceType protocol)
    {
      super(ssm, pdbentry, sequenceIs, protocol);
+     setStructureCommands(new JmolCommands());
      /*
       * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
-      * "jalviewJmol", ap.av.applet .getDocumentBase(),
-      * ap.av.applet.getCodeBase(), "", this);
+      * "jalviewJmol", ap.av.applet .getDocumentBase(), ap.av.applet.getCodeBase(),
+      * "", this);
       * 
       * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
       */
    {
      super(ssm, seqs);
  
-     viewer = theViewer;
-     viewer.setJmolStatusListener(this);
-     viewer.addSelectionListener(this);
+     jmolViewer = theViewer;
+     jmolViewer.setJmolStatusListener(this);
+     jmolViewer.addSelectionListener(this);
+     setStructureCommands(new JmolCommands());
    }
  
    /**
      return getViewerTitle("Jmol", true);
    }
  
-   /**
-    * prepare the view for a given set of models/chains. chainList contains
-    * strings of the form 'pdbfilename:Chaincode'
-    * 
-    * @param chainList
-    *          list of chains to make visible
-    */
-   public void centerViewer(Vector<String> chainList)
-   {
-     StringBuilder cmd = new StringBuilder(128);
-     int mlength, p;
-     for (String lbl : chainList)
-     {
-       mlength = 0;
-       do
-       {
-         p = mlength;
-         mlength = lbl.indexOf(":", p);
-       } while (p < mlength && mlength < (lbl.length() - 2));
-       // TODO: lookup each pdb id and recover proper model number for it.
-       cmd.append(":" + lbl.substring(mlength + 1) + " /"
-               + (1 + getModelNum(chainFile.get(lbl))) + " or ");
-     }
-     if (cmd.length() > 0)
-     {
-       cmd.setLength(cmd.length() - 4);
-     }
-     evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
-   }
-   public void closeViewer()
-   {
-     // remove listeners for all structures in viewer
-     getSsm().removeStructureViewerListener(this, this.getStructureFiles());
-     if (viewer != null)
-     {
-       viewer.dispose();
-     }
-     lastCommand = null;
-     viewer = null;
-     releaseUIResources();
-   }
-   @Override
-   public void colourByChain()
-   {
-     colourBySequence = false;
-     // TODO: colour by chain should colour each chain distinctly across all
-     // visible models
-     // TODO: http://issues.jalview.org/browse/JAL-628
-     evalStateCommand("select *;color chain");
-   }
-   @Override
-   public void colourByCharge()
-   {
-     colourBySequence = false;
-     evalStateCommand("select *;color white;select ASP,GLU;color red;"
-             + "select LYS,ARG;color blue;select CYS;color yellow");
-   }
-   /**
-    * superpose the structures associated with sequences in the alignment
-    * according to their corresponding positions.
-    */
-   public void superposeStructures(AlignmentI alignment)
-   {
-     superposeStructures(alignment, -1, null);
-   }
-   /**
-    * superpose the structures associated with sequences in the alignment
-    * according to their corresponding positions. ded)
-    * 
-    * @param refStructure
-    *          - select which pdb file to use as reference (default is -1 - the
-    *          first structure in the alignment)
-    */
-   public void superposeStructures(AlignmentI alignment, int refStructure)
+   private String jmolScript(String script)
    {
-     superposeStructures(alignment, refStructure, null);
-   }
+     Cache.log.debug(">>Jmol>> " + script);
+     String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
+     Cache.log.debug("<<Jmol<< " + s);
  
-   /**
-    * superpose the structures associated with sequences in the alignment
-    * according to their corresponding positions. ded)
-    * 
-    * @param refStructure
-    *          - select which pdb file to use as reference (default is -1 - the
-    *          first structure in the alignment)
-    * @param hiddenCols
-    *          TODO
-    */
-   public void superposeStructures(AlignmentI alignment, int refStructure,
-           HiddenColumns hiddenCols)
-   {
-     superposeStructures(new AlignmentI[] { alignment },
-             new int[]
-             { refStructure }, new HiddenColumns[] { hiddenCols });
+     return s;
    }
  
-   /**
-    * {@inheritDoc}
-    */
    @Override
-   public String superposeStructures(AlignmentI[] _alignment,
-           int[] _refStructure, HiddenColumns[] _hiddenCols)
+   public List<String> executeCommand(StructureCommandI command,
+           boolean getReply)
    {
-     while (viewer.isScriptExecuting())
-     {
-       try
-       {
-         Thread.sleep(10);
-       } catch (InterruptedException i)
-       {
-       }
-     }
-     /*
-      * get the distinct structure files modelled
-      * (a file with multiple chains may map to multiple sequences)
-      */
-     String[] files = getStructureFiles();
-     if (!waitForFileLoad(files))
+     if (command == null)
      {
        return null;
      }
 +
-     StringBuilder selectioncom = new StringBuilder(256);
-     // In principle - nSeconds specifies the speed of animation for each
-     // superposition - but is seems to behave weirdly, so we don't specify it.
-     String nSeconds = " ";
-     if (files.length > 10)
-     {
-       nSeconds = " 0.005 ";
-     }
-     else
-     {
-       nSeconds = " " + (2.0 / files.length) + " ";
-       // if (nSeconds).substring(0,5)+" ";
-     }
-     // see JAL-1345 - should really automatically turn off the animation for
-     // large numbers of structures, but Jmol doesn't seem to allow that.
-     // nSeconds = " ";
-     // union of all aligned positions are collected together.
-     for (int a = 0; a < _alignment.length; a++)
-     {
-       int refStructure = _refStructure[a];
-       AlignmentI alignment = _alignment[a];
-       HiddenColumns hiddenCols = _hiddenCols[a];
-       if (a > 0 && selectioncom.length() > 0 && !selectioncom
-               .substring(selectioncom.length() - 1).equals("|"))
-       {
-         selectioncom.append("|");
-       }
-       // process this alignment
-       if (refStructure >= files.length)
-       {
-         System.err.println(
-                 "Invalid reference structure value " + refStructure);
-         refStructure = -1;
-       }
-       /*
-        * 'matched' bit j will be set for visible alignment columns j where
-        * all sequences have a residue with a mapping to the PDB structure
-        */
-       BitSet matched = new BitSet();
-       for (int m = 0; m < alignment.getWidth(); m++)
-       {
-         if (hiddenCols == null || hiddenCols.isVisible(m))
-         {
-           matched.set(m);
-         }
-       }
-       SuperposeData[] structures = new SuperposeData[files.length];
-       for (int f = 0; f < files.length; f++)
-       {
-         structures[f] = new SuperposeData(alignment.getWidth());
-       }
-       /*
-        * Calculate the superposable alignment columns ('matched'), and the
-        * corresponding structure residue positions (structures.pdbResNo)
-        */
-       int candidateRefStructure = findSuperposableResidues(alignment,
-               matched, structures);
-       if (refStructure < 0)
-       {
-         /*
-          * If no reference structure was specified, pick the first one that has
-          * a mapping in the alignment
-          */
-         refStructure = candidateRefStructure;
-       }
-       String[] selcom = new String[files.length];
-       int nmatched = matched.cardinality();
-       if (nmatched < 4)
-       {
-         return (MessageManager.formatMessage("label.insufficient_residues",
-                 nmatched));
-       }
-       /*
-        * generate select statements to select regions to superimpose structures
-        */
-       {
-         // TODO extract method to construct selection statements
-         for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-         {
-           String chainCd = ":" + structures[pdbfnum].chain;
-           int lpos = -1;
-           boolean run = false;
-           StringBuilder molsel = new StringBuilder();
-           molsel.append("{");
-           int nextColumnMatch = matched.nextSetBit(0);
-           while (nextColumnMatch != -1)
-           {
-             int pdbResNo = structures[pdbfnum].pdbResNo[nextColumnMatch];
-             if (lpos != pdbResNo - 1)
-             {
-               // discontinuity
-               if (lpos != -1)
-               {
-                 molsel.append(lpos);
-                 molsel.append(chainCd);
-                 molsel.append("|");
-               }
-               run = false;
-             }
-             else
-             {
-               // continuous run - and lpos >-1
-               if (!run)
-               {
-                 // at the beginning, so add dash
-                 molsel.append(lpos);
-                 molsel.append("-");
-               }
-               run = true;
-             }
-             lpos = pdbResNo;
-             nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
-           }
-           /*
-            * add final selection phrase
-            */
-           if (lpos != -1)
-           {
-             molsel.append(lpos);
-             molsel.append(chainCd);
-             molsel.append("}");
-           }
-           if (molsel.length() > 1)
-           {
-             selcom[pdbfnum] = molsel.toString();
-             selectioncom.append("((");
-             selectioncom.append(selcom[pdbfnum].substring(1,
-                     selcom[pdbfnum].length() - 1));
-             selectioncom.append(" )& ");
-             selectioncom.append(pdbfnum + 1);
-             selectioncom.append(".1)");
-             if (pdbfnum < files.length - 1)
-             {
-               selectioncom.append("|");
-             }
-           }
-           else
-           {
-             selcom[pdbfnum] = null;
-           }
-         }
-       }
-       StringBuilder command = new StringBuilder(256);
-       // command.append("set spinFps 10;\n");
-       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-       {
-         if (pdbfnum == refStructure || selcom[pdbfnum] == null
-                 || selcom[refStructure] == null)
-         {
-           continue;
-         }
-         command.append("echo ");
-         command.append("\"Superposing (");
-         command.append(structures[pdbfnum].pdbId);
-         command.append(") against reference (");
-         command.append(structures[refStructure].pdbId);
-         command.append(")\";\ncompare " + nSeconds);
-         command.append("{");
-         command.append(Integer.toString(1 + pdbfnum));
-         command.append(".1} {");
-         command.append(Integer.toString(1 + refStructure));
-         // conformation=1 excludes alternate locations for CA (JAL-1757)
-         command.append(
-                 ".1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS ");
-         // for (int s = 0; s < 2; s++)
-         // {
-         // command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
-         // }
-         command.append(selcom[pdbfnum]);
-         command.append(selcom[refStructure]);
-         command.append(" ROTATE TRANSLATE;\n");
-       }
-       if (selectioncom.length() > 0)
-       {
-         // TODO is performing selectioncom redundant here? is done later on
-         // System.out.println("Select regions:\n" + selectioncom.toString());
-         evalStateCommand("select *; cartoons off; backbone; select ("
-                 + selectioncom.toString() + "); cartoons; ");
-         // selcom.append("; ribbons; ");
-         String cmdString = command.toString();
-         // System.out.println("Superimpose command(s):\n" + cmdString);
-         evalStateCommand(cmdString);
-       }
-     }
-     if (selectioncom.length() > 0)
-     {// finally, mark all regions that were superposed.
-       if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
-       {
-         selectioncom.setLength(selectioncom.length() - 1);
-       }
-       // System.out.println("Select regions:\n" + selectioncom.toString());
-       evalStateCommand("select *; cartoons off; backbone; select ("
-               + selectioncom.toString() + "); cartoons; ");
-       // evalStateCommand("select *; backbone; select "+selcom.toString()+";
-       // cartoons; center "+selcom.toString());
-     }
-     return null;
-   }
-   public void evalStateCommand(String command)
-   {
+     String cmd = command.getCommand();
      jmolHistory(false);
-     if (lastCommand == null || !lastCommand.equals(command))
+     if (lastCommand == null || !lastCommand.equals(cmd))
      {
-       jmolScript(command + "\n");
+       jmolScript(cmd + "\n");
      }
      jmolHistory(true);
-     lastCommand = command;
-   }
-   Thread colourby = null;
-   /**
-    * Sends a set of colour commands to the structure viewer
-    * 
-    * @param colourBySequenceCommands
-    */
-   @Override
-   protected void colourBySequence(
-           final StructureMappingcommandSet[] colourBySequenceCommands)
-   {
-     if (colourby != null)
-     {
-       colourby.interrupt();
-       colourby = null;
-     }
-     Thread colourby = new Thread(new Runnable()
-     {
-       @Override
-       public void run()
-       {
-         for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands)
-         {
-           for (String cbyseq : cpdbbyseq.commands)
-           {
-             executeWhenReady(cbyseq);
-           }
-         }
-       }
-     });
-     colourby.start();
-     this.colourby = colourby;
-   }
-   /**
-    * @param files
-    * @param sr
-    * @param viewPanel
-    * @return
-    */
-   @Override
-   protected StructureMappingcommandSet[] getColourBySequenceCommands(
-           String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
-   {
-     return JmolCommands.getColourBySequenceCommand(getSsm(), files,
-             getSequence(), sr, viewPanel);
-   }
-   /**
-    * @param command
-    */
-   protected void executeWhenReady(String command)
-   {
-     evalStateCommand(command);
+     lastCommand = cmd;
+     return null;
    }
  
    public void createImage(String file, String type, int quality)
      return null;
    }
  
-   public Color getColour(int atomIndex, int pdbResNum, String chain,
-           String pdbfile)
-   {
-     if (getModelNum(pdbfile) < 0)
-     {
-       return null;
-     }
-     // TODO: verify atomIndex is selecting correct model.
-     // return new Color(viewer.getAtomArgb(atomIndex)); Jmol 12.2.4
-     int colour = viewer.ms.at[atomIndex].atomPropertyInt(T.color);
-     return new Color(colour);
-   }
-   /**
-    * instruct the Jalview binding to update the pdbentries vector if necessary
-    * prior to matching the jmol view's contents to the list of structure files
-    * Jalview knows about.
-    */
-   public abstract void refreshPdbEntries();
-   private int getModelNum(String modelFileName)
-   {
-     String[] mfn = getStructureFiles();
-     if (mfn == null)
-     {
-       return -1;
-     }
-     for (int i = 0; i < mfn.length; i++)
-     {
-       if (mfn[i].equalsIgnoreCase(modelFileName))
-       {
-         return i;
-       }
-     }
-     return -1;
-   }
 +
    /**
     * map between index of model filename returned from getPdbFile and the first
     * index of models from this file in the viewer. Note - this is not trimmed -
    @Override
    public synchronized String[] getStructureFiles()
    {
-     List<String> mset = new ArrayList<>();
-     if (viewer == null)
+     if (jmolViewer == null)
      {
        return new String[0];
      }
  
      if (modelFileNames == null)
      {
-       int modelCount = viewer.ms.mc;
+       int modelCount = jmolViewer.ms.mc;
        String filePath = null;
+       List<String> mset = new ArrayList<>();
        for (int i = 0; i < modelCount; ++i)
        {
-         filePath = viewer.ms.getModelFileName(i);
-         if (!mset.contains(filePath))
+         /*
+          * defensive check for null as getModelFileName can return null even when model
+          * count ms.mc is > 0
+          */
+         filePath = jmolViewer.ms.getModelFileName(i);
+         if (filePath != null && !mset.contains(filePath))
          {
            mset.add(filePath);
          }
        }
-       modelFileNames = mset.toArray(new String[mset.size()]);
+       if (!mset.isEmpty())
+       {
+         modelFileNames = mset.toArray(new String[mset.size()]);
+       }
      }
  
      return modelFileNames;
    public void highlightAtom(int atomIndex, int pdbResNum, String chain,
            String pdbfile)
    {
-     if (modelFileNames == null)
-     {
-       return;
-     }
-     // look up file model number for this pdbfile
-     int mdlNum = 0;
-     // may need to adjust for URLencoding here - we don't worry about that yet.
-     while (mdlNum < modelFileNames.length
-             && !pdbfile.equals(modelFileNames[mdlNum]))
-     {
-       mdlNum++;
-     }
-     if (mdlNum == modelFileNames.length)
+     String modelId = getModelIdForFile(pdbfile);
+     if (modelId.isEmpty())
      {
        return;
      }
  
      jmolHistory(false);
  
+     StringBuilder selection = new StringBuilder(32);
      StringBuilder cmd = new StringBuilder(64);
-     cmd.append("select " + pdbResNum); // +modelNum
-     resetLastRes.append("select " + pdbResNum); // +modelNum
-     cmd.append(":");
-     resetLastRes.append(":");
+     selection.append("select ").append(String.valueOf(pdbResNum));
+     selection.append(":");
      if (!chain.equals(" "))
      {
-       cmd.append(chain);
-       resetLastRes.append(chain);
-     }
-     {
-       cmd.append(" /" + (mdlNum + 1));
-       resetLastRes.append("/" + (mdlNum + 1));
+       selection.append(chain);
      }
-     cmd.append(";wireframe 100;" + cmd.toString() + " and not hetero;");
+     selection.append(" /").append(modelId);
  
-     resetLastRes.append(";wireframe 0;" + resetLastRes.toString()
-             + " and not hetero; spacefill 0;");
+     cmd.append(selection).append(";wireframe 100;").append(selection)
+             .append(" and not hetero;").append("spacefill 200;select none");
  
-     cmd.append("spacefill 200;select none");
+     resetLastRes.append(selection).append(";wireframe 0;").append(selection)
+             .append(" and not hetero; spacefill 0;");
  
      jmolScript(cmd.toString());
      jmolHistory(true);
 +
    }
  
-   boolean debug = true;
+   private boolean debug = true;
  
    private void jmolHistory(boolean enable)
    {
      // Then, construct pass a reader for the string to Jmol.
      // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
      // fileName, null, reader, false, null, null, 0);
-     viewer.openStringInline(string);
+     jmolViewer.openStringInline(string);
    }
  
    protected void mouseOverStructure(int atomIndex, final String strInfo)
        chainId = " ";
      }
  
-     String pdbfilename = modelFileNames[frameNo]; // default is first or current
-     // model
+     String pdbfilename = modelFileNames[0]; // default is first model
      if (mdlSep > -1)
      {
        if (chainSeparator1 == -1)
  
            if (pdbfilename == null)
            {
-             pdbfilename = new File(viewer.ms.getModelFileName(mnumber))
+             pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
                      .getAbsolutePath();
            }
          }
      }
  
      /*
-      * highlight position on alignment(s); if some text is returned, 
-      * show this as a second line on the structure hover tooltip
+      * highlight position on alignment(s); if some text is returned, show this as a
+      * second line on the structure hover tooltip
       */
      String label = getSsm().mouseOverStructure(pdbResNum, chainId,
              pdbfilename);
        sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
                .append(toks.nextToken());
        sb.append("|").append(label).append("\"");
-       evalStateCommand(sb.toString());
+       executeCommand(new StructureCommand(sb.toString()), false);
      }
    }
  
  
    /*
     * { if (history != null && strStatus != null &&
-    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus);
-    * } }
+    * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
+    * }
     */
  
    public void notifyAtomPicked(int atomIndex, String strInfo,
      }
      else
      {
-       viewer.evalString("select " + picked + ";label off");
+       jmolViewer.evalString("select " + picked + ";label off");
        atomsPicked.removeElement(picked);
      }
      jmolHistory(true);
    @Override
    public void notifyCallback(CBK type, Object[] data)
    {
+     /*
+      * ensure processed in AWT thread to avoid risk of deadlocks
+      */
+     SwingUtilities.invokeLater(new Runnable()
+     {
+       @Override
+       public void run()
+       {
+         processCallback(type, data);
+       }
+     });
+   }
+   /**
+    * Processes one callback notification from Jmol
+    * 
+    * @param type
+    * @param data
+    */
+   protected void processCallback(CBK type, Object[] data)
+   {
      try
      {
        switch (type)
      fileLoadingError = null;
      String[] oldmodels = modelFileNames;
      modelFileNames = null;
-     chainNames = new ArrayList<>();
-     chainFile = new Hashtable<>();
      boolean notifyLoaded = false;
      String[] modelfilenames = getStructureFiles();
+     if (modelfilenames == null)
+     {
+       // Jmol is still loading files!
+       return;
+     }
      // first check if we've lost any structures
      if (oldmodels != null && oldmodels.length > 0)
      {
          // calculate essential attributes for the pdb data imported inline.
          // prolly need to resolve modelnumber properly - for now just use our
          // 'best guess'
-         pdbfile = viewer.getData(
+         pdbfile = jmolViewer.getData(
                  "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
        }
        // search pdbentries and sequences to find correct pdbentry for this
          }
          if (matches)
          {
-           // add an entry for every chain in the model
-           for (int i = 0; i < pdb.getChains().size(); i++)
-           {
-             String chid = new String(
-                     pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
-             chainFile.put(chid, fileName);
-             chainNames.add(chid);
-           }
+           stashFoundChains(pdb, fileName);
            notifyLoaded = true;
          }
        }
          // this is a foreign pdb file that jalview doesn't know about - add
          // it to the dataset and try to find a home - either on a matching
          // sequence or as a new sequence.
-         String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
+         String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
                  "PDB");
          // parse pdb file into a chain, etc.
          // locate best match for pdb in associated views and add mapping to
        FeatureRenderer fr = getFeatureRenderer(null);
        if (fr != null)
        {
-         fr.featuresAdded();
+         FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
+         ((AppJmol) getViewer()).getAlignmentPanel().av
+                 .applyFeaturesStyle(colours);
        }
        refreshGUI();
        loadNotifiesHandled++;
      setLoadingFromArchive(false);
    }
  
-   @Override
-   public List<String> getChainNames()
-   {
-     return chainNames;
-   }
 +
    protected IProgressIndicator getIProgressIndicator()
    {
      return null;
    public abstract void sendConsoleEcho(String strEcho); /*
                                                           * { showConsole(true);
                                                           * 
-                                                          * history.append("\n" +
-                                                          * strEcho); }
+                                                          * history.append("\n" + strEcho); }
                                                           */
  
    // /End JmolStatusListener
  
    }
  
-   @Override
-   public void setJalviewColourScheme(ColourSchemeI cs)
-   {
-     colourBySequence = false;
-     if (cs == null)
-     {
-       return;
-     }
-     jmolHistory(false);
-     StringBuilder command = new StringBuilder(128);
-     command.append("select *;color white;");
-     List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
-             false);
-     for (String resName : residueSet)
-     {
-       char res = resName.length() == 3
-               ? ResidueProperties.getSingleCharacterCode(resName)
-               : resName.charAt(0);
-       Color col = cs.findColour(res, 0, null, null, 0f);
-       command.append("select " + resName + ";color[" + col.getRed() + ","
-               + col.getGreen() + "," + col.getBlue() + "];");
-     }
-     evalStateCommand(command.toString());
-     jmolHistory(true);
-   }
 +
    public void showHelp()
    {
      showUrl("http://wiki.jmol.org"
    public abstract void showUrl(String url, String target);
  
    /**
-    * called when the binding thinks the UI needs to be refreshed after a Jmol
-    * state change. this could be because structures were loaded, or because an
-    * error has occured.
-    */
-   public abstract void refreshGUI();
-   /**
     * called to show or hide the associated console window container.
     * 
     * @param show
      {
        commandOptions = "";
      }
-     viewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
+     jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
              (jmolfileio ? new SmarterJmolAdapter() : null),
              htmlName + ((Object) this).toString(), documentBase, codeBase,
              commandOptions, this);
  
-     viewer.setJmolStatusListener(this); 
-     
-     // BH how about extending Jmol's status listener? 
+     jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
  
      try
      {
    protected org.jmol.api.JmolAppConsoleInterface console = null;
  
    @Override
-   public void setBackgroundColour(java.awt.Color col)
-   {
-     jmolHistory(false);
-     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)
    {
      // Jalview doesn't honour resize panel requests
      showConsole(false);
    }
  
+   @Override
+   protected String getModelIdForFile(String pdbFile)
+   {
+     if (modelFileNames == null)
+     {
+       return "";
+     }
+     for (int i = 0; i < modelFileNames.length; i++)
+     {
+       if (modelFileNames[i].equalsIgnoreCase(pdbFile))
+       {
+         return String.valueOf(i + 1);
+       }
+     }
+     return "";
+   }
+   @Override
+   protected ViewerType getViewerType()
+   {
+     return ViewerType.JMOL;
+   }
+   @Override
+   protected String getModelId(int pdbfnum, String file)
+   {
+     return String.valueOf(pdbfnum + 1);
+   }
+   /**
+    * Returns ".spt" - the Jmol session file extension
+    * 
+    * @return
+    * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
+    */
+   @Override
+   public String getSessionFileExtension()
+   {
+     return ".spt";
+   }
+   @Override
+   public void selectionChanged(BS arg0)
+   {
+     // TODO Auto-generated method stub
+   }
+   @Override
+   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
+   {
+     return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
+   }
+   @Override
+   public String getHelpURL()
+   {
+     return "http://wiki.jmol.org"; // BH 2018
+   }
  }
   */
  package jalview.fts.service.pdb;
  
+ import java.io.File;
+ import java.io.FileInputStream;
+ import java.io.FileReader;
  import java.net.URI;
+ import java.nio.CharBuffer;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Iterator;
@@@ -37,15 -41,17 +41,19 @@@ import com.sun.jersey.api.client.Client
  import com.sun.jersey.api.client.WebResource;
  import com.sun.jersey.api.client.config.DefaultClientConfig;
  
 +import jalview.bin.ApplicationSingletonProvider;
 +import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.datamodel.SequenceI;
  import jalview.fts.api.FTSData;
  import jalview.fts.api.FTSDataColumnI;
  import jalview.fts.api.FTSRestClientI;
+ import jalview.fts.api.StructureFTSRestClientI;
+ import jalview.fts.core.FTSDataColumnPreferences;
+ import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
  import jalview.fts.core.FTSRestClient;
  import jalview.fts.core.FTSRestRequest;
  import jalview.fts.core.FTSRestResponse;
+ import jalview.fts.service.alphafold.AlphafoldRestClient;
  import jalview.util.JSONUtils;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
   * 
   * @author tcnofoegbu
   */
- public class PDBFTSRestClient extends FTSRestClient implements ApplicationSingletonI
+ public class PDBFTSRestClient extends FTSRestClient
 -        implements StructureFTSRestClientI
++        implements StructureFTSRestClientI,ApplicationSingletonI
  {
+   private static FTSRestClientI instance = null;
    public static final String PDB_SEARCH_ENDPOINT = "https://www.ebi.ac.uk/pdbe/search/pdb/select?";
  
 +  public static FTSRestClientI getInstance()
 +  {
 +    return (FTSRestClientI) ApplicationSingletonProvider
 +            .getInstance(PDBFTSRestClient.class);
 +  }
    protected PDBFTSRestClient()
    {
    }
  
        // Build request parameters for the REST Request
  
-       // 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
+       // 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())
  
        URI uri = webResource.getURI();
  
-       // System.out.println(uri);
-       // Execute the REST request
-       ClientResponse clientResponse = webResource
-               .accept(MediaType.APPLICATION_JSON).get(clientResponseClass );
+       System.out.println(uri);
+       ClientResponse clientResponse = null;
+       int responseStatus = -1;
 +
        // Get the JSON string from the response object or directly from the
        // client (JavaScript)
        Map<String, Object> jsonObj = null;
        String responseString = null;
  
-       // System.out.println("query >>>>>>> " + pdbRestRequest.toString());
+       System.out.println("query >>>>>>> " + pdbRestRequest.toString());
+       if (!isMocked())
+       {
+         // Execute the REST request
+         clientResponse = webResource.accept(MediaType.APPLICATION_JSON)
+                 .get(clientResponseClass);
+         responseStatus = clientResponse.getStatus();
+       }
+       else
+       {
+         // mock response
+         if (mockQueries.containsKey(uri.toString()))
+         {
+           responseStatus = 200;
+         }
+         else
+         {
+           // FIXME - may cause unexpected exceptions for callers when mocked
+           responseStatus = 400;
+         }
+       }
  
        // Check the response status and report exception if one occurs
        switch (responseStatus)
        {
        case 200:
-         if (Platform.isJS())
 -
+         if (isMocked())
          {
-           jsonObj = clientResponse.getEntity(Map.class);
+           responseString = mockQueries.get(uri.toString());
          }
          else
          {
-           responseString = clientResponse.getEntity(String.class);
+           if (Platform.isJS())
+           {
+             jsonObj = clientResponse.getEntity(Map.class);
+           }
+           else
+           {
+             responseString = clientResponse.getEntity(String.class);
+           }
          }
          break;
        case 400:
-         throw new Exception(parseJsonExceptionString(responseString));
+         throw new Exception(isMocked() ? "400 response (Mocked)"
+                 : parseJsonExceptionString(responseString));
        default:
          throw new Exception(
                  getMessageByHTTPStatusCode(responseStatus, "PDB"));
        return parsePDBJsonResponse(responseString, jsonObj, pdbRestRequest);
      } catch (Exception e)
      {
+       if (e.getMessage() == null)
+       {
+         throw (e);
+       }
        String exceptionMsg = e.getMessage();
        if (exceptionMsg.contains("SocketException"))
        {
     * @return the processed error message from the JSON string
     */
    @SuppressWarnings("unchecked")
- public static String parseJsonExceptionString(String jsonErrorResponse)
+   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"}]
- //      }}
- //    
+     // {
+     // "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
      {
-       Map<String, Object> jsonObj = (Map<String, Object>) JSONUtils.parse(jsonErrorResponse);
-       Map<String, Object> errorResponse = (Map<String, Object>) jsonObj.get("error");
+       Map<String, Object> jsonObj = (Map<String, Object>) JSONUtils
+               .parse(jsonErrorResponse);
+       Map<String, Object> errorResponse = (Map<String, Object>) jsonObj
+               .get("error");
  
        Map<String, Object> responseHeader = (Map<String, Object>) jsonObj
                .get("responseHeader");
-       Map<String, Object> paramsObj = (Map<String, Object>) 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();
      {
        if (jsonObj == null)
        {
-         jsonObj = (Map<String, Object>) JSONUtils.parse(pdbJsonResponseString);
+         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();
+       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());
+       List<Object> docs = (List<Object>) pdbResponse.get("docs");
+       result = new ArrayList<FTSData>();
        if (numFound > 0)
        {
-         result = new ArrayList<>();
-         List<Object> docs = (List<Object>) pdbResponse.get("docs");
-         for (Iterator<Object> docIter = docs.iterator(); docIter
-                 .hasNext();)
 -
+         for (Iterator<Object> docIter = docs.iterator(); docIter.hasNext();)
          {
            Map<String, Object> doc = (Map<String, Object>) docIter.next();
            result.add(getFTSData(doc, pdbRestRequest));
          }
-         searchResult.setNumberOfItemsFound(numFound);
-         searchResult.setResponseTime(queryTime);
-         searchResult.setSearchSummary(result);
        }
+       // this is the total number found by the query,
+       // rather than the set returned in SearchSummary
+       searchResult.setNumberOfItemsFound(numFound);
+       searchResult.setResponseTime(queryTime);
+       searchResult.setSearchSummary(result);
 -
      } catch (ParseException e)
      {
        e.printStackTrace();
  
      for (FTSDataColumnI field : diplayFields)
      {
+       // System.out.println("Field " + field);
        String fieldData = (pdbJsonDoc.get(field.getCode()) == null) ? ""
                : pdbJsonDoc.get(field.getCode()).toString();
+       // System.out.println("Field Data : " + fieldData);
        if (field.isPrimaryKeyColumn())
        {
          primaryKey = fieldData;
      return "/fts/pdb_data_columns.txt";
    }
  
 -  public static FTSRestClientI getInstance()
 -  {
 -    if (instance == null)
 -    {
 -      instance = new PDBFTSRestClient();
 -    }
 -    return instance;
 -  }
    private Collection<FTSDataColumnI> allDefaultDisplayedStructureDataColumns;
  
+   @Override
    public Collection<FTSDataColumnI> getAllDefaultDisplayedStructureDataColumns()
    {
      if (allDefaultDisplayedStructureDataColumns == null
      }
      return allDefaultDisplayedStructureDataColumns;
    }
-   
-   
 -
+   @Override
+   public String[] getPreferencesColumnsFor(PreferenceSource source)
+   {
+     String[] columnNames = null;
+     switch (source)
+     {
+     case SEARCH_SUMMARY:
+       columnNames = new String[] { "", "Display", "Group" };
+       break;
+     case STRUCTURE_CHOOSER:
+       columnNames = new String[] { "", "Display", "Group" };
+       break;
+     case PREFERENCES:
+       columnNames = new String[] { "PDB Field", "Show in search summary",
+           "Show in structure summary" };
+       break;
+     default:
+       break;
+     }
+     return columnNames;
+   }
  }
   */
  package jalview.gui;
  
+ import java.util.Locale;
++import java.io.IOException;
++import java.util.HashSet;
++import java.util.Set;
++
++import javax.swing.JFileChooser;
++import javax.swing.JOptionPane;
++
+ import java.awt.BorderLayout;
+ import java.awt.Color;
+ import java.awt.Component;
++import java.awt.Dimension;
+ import java.awt.Rectangle;
+ import java.awt.Toolkit;
+ import java.awt.datatransfer.Clipboard;
+ import java.awt.datatransfer.DataFlavor;
+ import java.awt.datatransfer.StringSelection;
+ import java.awt.datatransfer.Transferable;
+ import java.awt.dnd.DnDConstants;
+ import java.awt.dnd.DropTargetDragEvent;
+ import java.awt.dnd.DropTargetDropEvent;
+ import java.awt.dnd.DropTargetEvent;
+ import java.awt.dnd.DropTargetListener;
+ import java.awt.event.ActionEvent;
+ import java.awt.event.ActionListener;
+ import java.awt.event.FocusAdapter;
+ import java.awt.event.FocusEvent;
+ import java.awt.event.ItemEvent;
+ import java.awt.event.ItemListener;
+ import java.awt.event.KeyAdapter;
+ import java.awt.event.KeyEvent;
+ import java.awt.event.MouseEvent;
+ import java.awt.print.PageFormat;
+ import java.awt.print.PrinterJob;
+ import java.beans.PropertyChangeEvent;
++import java.beans.PropertyChangeListener;
+ import java.io.File;
+ import java.io.FileWriter;
 -import java.io.IOException;
+ import java.io.PrintWriter;
+ import java.net.URL;
+ import java.util.ArrayList;
+ import java.util.Arrays;
++import java.util.Collection;
+ import java.util.Deque;
+ import java.util.Enumeration;
+ import java.util.Hashtable;
+ import java.util.List;
+ 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 javax.swing.event.InternalFrameAdapter;
++import javax.swing.event.InternalFrameEvent;
+ import ext.vamsas.ServiceHandle;
  import jalview.analysis.AlignmentSorter;
  import jalview.analysis.AlignmentUtils;
  import jalview.analysis.CrossRef;
@@@ -64,13 -119,6 +130,13 @@@ import jalview.datamodel.SequenceGroup
  import jalview.datamodel.SequenceI;
  import jalview.gui.ColourMenuHelper.ColourChangeListener;
  import jalview.gui.ViewSelectionMenu.ViewSetProvider;
 +import jalview.hmmer.HMMAlign;
 +import jalview.hmmer.HMMBuild;
 +import jalview.hmmer.HMMERParamStore;
 +import jalview.hmmer.HMMERPreset;
 +import jalview.hmmer.HMMSearch;
 +import jalview.hmmer.HmmerCommand;
 +import jalview.hmmer.JackHMMER;
  import jalview.io.AlignmentProperties;
  import jalview.io.AnnotationFile;
  import jalview.io.BackupFiles;
@@@ -98,6 -146,7 +164,7 @@@ import jalview.schemes.ColourSchemeI
  import jalview.schemes.ColourSchemes;
  import jalview.schemes.ResidueColourScheme;
  import jalview.schemes.TCoffeeColourScheme;
+ import jalview.util.HttpUtils;
  import jalview.util.ImageMaker.TYPE;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
@@@ -105,81 -154,10 +172,81 @@@ import jalview.viewmodel.AlignmentViewp
  import jalview.viewmodel.ViewportRanges;
  import jalview.ws.DBRefFetcher;
  import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
 +import jalview.ws.ServiceChangeListener;
 +import jalview.ws.WSDiscovererI;
 +import jalview.ws.api.ServiceWithParameters;
  import jalview.ws.jws1.Discoverer;
  import jalview.ws.jws2.Jws2Discoverer;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 +import jalview.ws.jws2.PreferredServiceRegistry;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.ParamDatastoreI;
 +import jalview.ws.params.WsParamSetI;
  import jalview.ws.seqfetcher.DbSourceProxy;
 +import jalview.ws.slivkaws.SlivkaWSDiscoverer;
 +import java.io.IOException;
 +import java.util.HashSet;
 +import java.util.Set;
 +
 +import javax.swing.JFileChooser;
 +import javax.swing.JOptionPane;
 +
 +import java.awt.BorderLayout;
 +import java.awt.Color;
 +import java.awt.Component;
 +import java.awt.Dimension;
 +import java.awt.Rectangle;
 +import java.awt.Toolkit;
 +import java.awt.datatransfer.Clipboard;
 +import java.awt.datatransfer.DataFlavor;
 +import java.awt.datatransfer.StringSelection;
 +import java.awt.datatransfer.Transferable;
 +import java.awt.dnd.DnDConstants;
 +import java.awt.dnd.DropTargetDragEvent;
 +import java.awt.dnd.DropTargetDropEvent;
 +import java.awt.dnd.DropTargetEvent;
 +import java.awt.dnd.DropTargetListener;
 +import java.awt.event.ActionEvent;
 +import java.awt.event.ActionListener;
 +import java.awt.event.FocusAdapter;
 +import java.awt.event.FocusEvent;
 +import java.awt.event.ItemEvent;
 +import java.awt.event.ItemListener;
 +import java.awt.event.KeyAdapter;
 +import java.awt.event.KeyEvent;
 +import java.awt.event.MouseEvent;
 +import java.awt.print.PageFormat;
 +import java.awt.print.PrinterJob;
 +import java.beans.PropertyChangeEvent;
 +import java.beans.PropertyChangeListener;
 +import java.io.File;
 +import java.io.FileWriter;
 +import java.io.PrintWriter;
 +import java.net.URL;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collection;
 +import java.util.Deque;
 +import java.util.Enumeration;
 +import java.util.Hashtable;
 +import java.util.List;
 +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 javax.swing.event.InternalFrameAdapter;
 +import javax.swing.event.InternalFrameEvent;
 +
 +import ext.vamsas.ServiceHandle;
  
  /**
   * DOCUMENT ME!
   * @version $Revision$
   */
  @SuppressWarnings("serial")
 -public class AlignFrame extends GAlignFrame implements DropTargetListener,
 -        IProgressIndicator, AlignViewControllerGuiI, ColourChangeListener
 +public class AlignFrame extends GAlignFrame
 +        implements DropTargetListener, IProgressIndicator,
 +        AlignViewControllerGuiI, ColourChangeListener, ServiceChangeListener
  {
  
 +  public static int frameCount;
    public static final int DEFAULT_WIDTH = 700;
  
    public static final int DEFAULT_HEIGHT = 500;
     */
    String fileName = null;
  
 +  /**
 +   * TODO: remove reference to 'FileObject' in AlignFrame - not correct mapping
 +   */
    File fileObject;
  
 +  private int id;
 +
 +  private DataSourceType protocol ;
    /**
     * Creates a new AlignFrame object with specific width and height.
     * 
    public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns, int width,
            int height, String sequenceSetId, String viewId)
    {
 +    id = (++frameCount);
      setSize(width, height);
  
      if (al.getDataset() == null)
  
      viewport = new AlignViewport(al, hiddenColumns, sequenceSetId, viewId);
  
 -    alignPanel = new AlignmentPanel(this, viewport);
 -
 -    addAlignmentPanel(alignPanel, true);
 +    // JalviewJS needs to distinguish a new panel from an old one in init()
 +    // alignPanel = new AlignmentPanel(this, viewport);
 +    // addAlignmentPanel(alignPanel, true);
      init();
    }
  
      {
        viewport.hideSequence(hiddenSeqs);
      }
 -    alignPanel = new AlignmentPanel(this, viewport);
 -    addAlignmentPanel(alignPanel, true);
 +    // alignPanel = new AlignmentPanel(this, viewport);
 +    // addAlignmentPanel(alignPanel, true);
      init();
    }
  
    {
      viewport = ap.av;
      alignPanel = ap;
 -    addAlignmentPanel(ap, false);
 +    // addAlignmentPanel(ap, false);
      init();
    }
  
     * initalise the alignframe from the underlying viewport data and the
     * configurations
     */
    void init()
    {
 -    // setBackground(Color.white); // BH 2019
 +    boolean newPanel = (alignPanel == null);
 +    viewport.setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
 +    if (newPanel)
 +    {
 +      if (Platform.isJS())
 +      {
 +        // need to set this up front if NOANNOTATION is
 +        // used in conjunction with SHOWOVERVIEW.
 +
 +        // I have not determined if this is appropriate for
 +        // Jalview/Java, as it means we are setting this flag
 +        // for all subsequent AlignFrames. For now, at least,
 +        // I am setting it to be JalviewJS-only.
 +
 +        boolean showAnnotation = Jalview.getInstance().getShowAnnotation();
 +        viewport.setShowAnnotation(showAnnotation);
 +      }
 +      alignPanel = new AlignmentPanel(this, viewport);
 +    }
 +    addAlignmentPanel(alignPanel, newPanel);
  
 +    // setBackground(Color.white); // BH 2019
      if (!Jalview.isHeadlessMode())
      {
        progressBar = new ProgressBar(this.statusPanel, this.statusBar);
 +      // JalviewJS options
 +      statusPanel.setVisible(Jalview.getInstance().getShowStatus());
 +      alignFrameMenuBar.setVisible(Jalview.getInstance().getAllowMenuBar());
      }
  
      avc = new jalview.controller.AlignViewController(this, viewport,
        // modifyPID.setEnabled(false);
      }
  
 -    String sortby = jalview.bin.Cache.getDefault("SORT_ALIGNMENT",
 +    String sortby = jalview.bin.Cache.getDefault(Preferences.SORT_ALIGNMENT,
              "No sort");
  
      if (sortby.equals("Id"))
        sortPairwiseMenuItem_actionPerformed(null);
      }
  
 -    this.alignPanel.av
 -            .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
 +    // BH see above
 +    //
 +    // this.alignPanel.av
 +    // .setShowAutocalculatedAbove(isShowAutoCalculatedAbove());
  
      setMenusFromViewport(viewport);
      buildSortByAnnotationScoresMenu();
      });
      buildColourMenu();
  
 -    if (Desktop.desktop != null)
 +    if (Desktop.getDesktopPane() != null)
      {
        this.setDropTarget(new java.awt.dnd.DropTarget(this, this));
 +      addServiceListeners();
        if (!Platform.isJS())
        {
 -        addServiceListeners();
        }
        setGUINucleotide();
      }
        wrapMenuItem_actionPerformed(null);
      }
  
 -    if (jalview.bin.Cache.getDefault("SHOW_OVERVIEW", false))
 +    if (jalview.bin.Cache.getDefault(Preferences.SHOW_OVERVIEW, false))
      {
        this.overviewMenuItem_actionPerformed(null);
      }
  
      addKeyListener();
  
-     final List<AlignmentPanel> selviews = new ArrayList<>();
+     final List<AlignmentViewPanel> selviews = new ArrayList<>();
      final List<AlignmentPanel> origview = new ArrayList<>();
      final String menuLabel = MessageManager
              .getString("label.copy_format_from");
                  }
                }
              });
-     if (Cache.getDefault("VERSION", "DEVELOPMENT").toLowerCase()
+     if (Cache.getDefault("VERSION", "DEVELOPMENT").toLowerCase(Locale.ROOT)
              .indexOf("devel") > -1
-             || Cache.getDefault("VERSION", "DEVELOPMENT").toLowerCase()
+             || Cache.getDefault("VERSION", "DEVELOPMENT").toLowerCase(Locale.ROOT)
                      .indexOf("test") > -1)
      {
        formatMenu.add(vsel);
      }
      addFocusListener(new FocusAdapter()
      {
        @Override
        public void focusGained(FocusEvent e)
        {
     * @param format
     *          format of file
     */
 +  @Deprecated
    public void setFileName(String file, FileFormatI format)
    {
      fileName = file;
    }
  
    /**
 +   * 
 +   * @param fileName
 +   * @param file  from SwingJS; may contain bytes -- for reload
 +   * @param protocol from SwingJS; may be RELATIVE_URL
 +   * @param format
 +   */
 +  public void setFile(String fileName, File file, DataSourceType protocol, FileFormatI format)
 +  {
 +    this.fileName = fileName;
 +    this.fileObject = file;
 +    this.protocol = protocol;
 +    setFileFormat(format);
 +    reload.setEnabled(true);
 +  }
 +
 +  /**
     * 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
     */
    void addKeyListener()
    {
      addKeyListener(new KeyAdapter()
      {
        @Override
        public void keyPressed(KeyEvent evt)
        {
          switch (evt.getKeyCode())
          {
  
 -        case 27: // escape key
 -          deselectAllSequenceMenuItem_actionPerformed(null);
 +        case KeyEvent.VK_ESCAPE: // escape key
 +                                 // alignPanel.deselectAllSequences();
 +          alignPanel.deselectAllSequences();
  
            break;
  
            }
            if (viewport.cursorMode)
            {
-             alignPanel.getSeqPanel().moveCursor(0, 1);
+             alignPanel.getSeqPanel().moveCursor(0, 1, evt.isShiftDown());
            }
            break;
  
            }
            if (viewport.cursorMode)
            {
-             alignPanel.getSeqPanel().moveCursor(0, -1);
+             alignPanel.getSeqPanel().moveCursor(0, -1,evt.isShiftDown());
            }
  
            break;
            }
            else
            {
-             alignPanel.getSeqPanel().moveCursor(-1, 0);
+             alignPanel.getSeqPanel().moveCursor(-1, 0, evt.isShiftDown());
            }
  
            break;
            }
            else
            {
-             alignPanel.getSeqPanel().moveCursor(1, 0);
+             alignPanel.getSeqPanel().moveCursor(1, 0, evt.isShiftDown());
            }
            break;
  
          case KeyEvent.VK_LEFT:
            if (evt.isAltDown() || !viewport.cursorMode)
            {
 -            viewport.firePropertyChange("alignment", null,
 -                    viewport.getAlignment().getSequences());
 +            viewport.notifyAlignment();
            }
            break;
  
          case KeyEvent.VK_RIGHT:
            if (evt.isAltDown() || !viewport.cursorMode)
            {
 -            viewport.firePropertyChange("alignment", null,
 -                    viewport.getAlignment().getSequences());
 +            viewport.notifyAlignment();
            }
            break;
          }
        {
          ap.av.getAlignment().padGaps();
        }
 -      ap.av.updateConservation(ap);
 -      ap.av.updateConsensus(ap);
 -      ap.av.updateStrucConsensus(ap);
 +      if (Jalview.getInstance().getStartCalculations())
 +      {
 +        ap.av.updateConservation(ap);
 +        ap.av.updateConsensus(ap);
 +        ap.av.updateStrucConsensus(ap);
 +        ap.av.initInformationWorker(ap);
 +      }
      }
    }
  
      return viewport;
    }
  
 +  @Override
 +  public void servicesChanged(WSDiscovererI discoverer,
 +          Collection<? extends ServiceWithParameters> services)
 +  {
 +    buildWebServicesMenu();
 +  }
    /* Set up intrinsic listeners for dynamically generated GUI bits. */
    private void addServiceListeners()
    {
 -    final java.beans.PropertyChangeListener thisListener;
 -    Desktop.instance.addJalviewPropertyChangeListener("services",
 -            thisListener = new java.beans.PropertyChangeListener()
 -            {
 -              @Override
 -              public void propertyChange(PropertyChangeEvent evt)
 -              {
 -                // // System.out.println("Discoverer property change.");
 -                // if (evt.getPropertyName().equals("services"))
 -                {
 -                  SwingUtilities.invokeLater(new Runnable()
 -                  {
 -
 -                    @Override
 -                    public void run()
 -                    {
 -                      System.err.println(
 -                              "Rebuild WS Menu for service change");
 -                      BuildWebServiceMenu();
 -                    }
 -
 -                  });
 -                }
 -              }
 -            });
 -    addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
 +    if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
 +    {
 +      WSDiscovererI discoverer = SlivkaWSDiscoverer.getInstance();
 +      discoverer.addServiceChangeListener(this);
 +    }
 +    if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
      {
 +      WSDiscovererI discoverer = Jws2Discoverer.getInstance();
 +      discoverer.addServiceChangeListener(this);
 +    }
 +    // legacy event listener for compatibility with jws1
 +    PropertyChangeListener legacyListener = (changeEvent) -> {
 +      buildWebServicesMenu();
 +    };
 +    Desktop.getInstance().addJalviewPropertyChangeListener("services",legacyListener);
 +    
 +    addInternalFrameListener(new InternalFrameAdapter() {
        @Override
 -      public void internalFrameClosed(
 -              javax.swing.event.InternalFrameEvent evt)
 -      {
 -        // System.out.println("deregistering discoverer listener");
 -        Desktop.instance.removeJalviewPropertyChangeListener("services",
 -                thisListener);
 +      public void internalFrameClosed(InternalFrameEvent e) {
 +        System.out.println("deregistering discoverer listener");
 +        SlivkaWSDiscoverer.getInstance().removeServiceChangeListener(AlignFrame.this);
 +        Jws2Discoverer.getInstance().removeServiceChangeListener(AlignFrame.this);
 +        Desktop.getInstance().removeJalviewPropertyChangeListener("services", legacyListener);
          closeMenuItem_actionPerformed(true);
        }
      });
 -    // Finally, build the menu once to get current service state
 -    new Thread(new Runnable()
 -    {
 -      @Override
 -      public void run()
 -      {
 -        BuildWebServiceMenu();
 -      }
 -    }).start();
 +    buildWebServicesMenu();
    }
  
    /**
     * Configure menu items that vary according to whether the alignment is
     * nucleotide or protein
     */
    public void setGUINucleotide()
    {
      AlignmentI al = getViewport().getAlignment();
     * operation that affects the data in the current view (selection changed,
     * etc) to update the menus to reflect the new state.
     */
    @Override
    public void setMenusForViewport()
    {
     * @param av
     *          AlignViewport
     */
    public void setMenusFromViewport(AlignViewport av)
    {
      padGapsMenuitem.setSelected(av.isPadGaps());
      scaleLeft.setVisible(av.getWrapAlignment());
      scaleRight.setVisible(av.getWrapAlignment());
      annotationPanelMenuItem.setState(av.isShowAnnotation());
 -    /*
 -     * Show/hide annotations only enabled if annotation panel is shown
 -     */
 -    showAllSeqAnnotations.setEnabled(annotationPanelMenuItem.getState());
 -    hideAllSeqAnnotations.setEnabled(annotationPanelMenuItem.getState());
 -    showAllAlAnnotations.setEnabled(annotationPanelMenuItem.getState());
 -    hideAllAlAnnotations.setEnabled(annotationPanelMenuItem.getState());
 +    // Show/hide annotations only enabled if annotation panel is shown
 +    syncAnnotationMenuItems(av.isShowAnnotation());
      viewBoxesMenuItem.setSelected(av.getShowBoxes());
      viewTextMenuItem.setSelected(av.getShowText());
      showNonconservedMenuItem.setSelected(av.getShowUnconserved());
      showConsensusHistogram.setSelected(av.isShowConsensusHistogram());
      showSequenceLogo.setSelected(av.isShowSequenceLogo());
      normaliseSequenceLogo.setSelected(av.isNormaliseSequenceLogo());
 +    showInformationHistogram.setSelected(av.isShowInformationHistogram());
 +    showHMMSequenceLogo.setSelected(av.isShowHMMSequenceLogo());
 +    normaliseHMMSequenceLogo.setSelected(av.isNormaliseHMMSequenceLogo());
  
      ColourMenuHelper.setColourSelected(colourMenu,
              av.getGlobalColourScheme());
      applyToAllGroups.setState(av.getColourAppliesToAllGroups());
      showNpFeatsMenuitem.setSelected(av.isShowNPFeats());
      showDbRefsMenuitem.setSelected(av.isShowDBRefs());
 -    autoCalculate.setSelected(av.autoCalculateConsensus);
 +    autoCalculate
 +            .setSelected(av.getAutoCalculateConsensusAndConservation());
      sortByTree.setSelected(av.sortByTree);
      listenToViewSelections.setSelected(av.followSelection);
  
     * 
     * @param b
     */
    public void setGroovyEnabled(boolean b)
    {
      runGroovy.setEnabled(b);
     * 
     * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
     */
    @Override
    public void setProgressBar(String message, long id)
    {
      progressBar.setProgressBar(message, id);
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    progressBar.removeProgressBar(id);
 +  }
  
    @Override
    public void registerHandler(final long id,
     * 
     * @return true if any progress bars are still active
     */
    @Override
    public boolean operationInProgress()
    {
     * will cause the status bar to be hidden, with possibly undesirable flicker
     * of the screen layout.
     */
    @Override
    public void setStatus(String text)
    {
    /*
     * Added so Castor Mapping file can obtain Jalview Version
     */
    public String getVersion()
    {
      return jalview.bin.Cache.getProperty("VERSION");
    @Override
    public void addFromFile_actionPerformed(ActionEvent e)
    {
 -    Desktop.instance.inputLocalFileMenuItem_actionPerformed(viewport);
 +    Desktop.getInstance().inputLocalFileMenuItem_actionPerformed(viewport);
    }
  
    @Override
 -  public void reload_actionPerformed(ActionEvent e)
 +  public void hmmBuild_actionPerformed(boolean withDefaults)
    {
 -    if (fileName != null)
 +    if (!alignmentIsSufficient(1))
      {
 -      // TODO: JAL-1108 - ensure all associated frames are closed regardless of
 -      // originating file's format
 -      // TODO: work out how to recover feature settings for correct view(s) when
 -      // file is reloaded.
 -      if (FileFormat.Jalview.equals(currentFileFormat))
 -      {
 -        JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 -        for (int i = 0; i < frames.length; i++)
 +      return;
 +    }
 +
 +    /*
 +     * get default parameters, and optionally show a dialog
 +     * to allow them to be modified
 +     */
 +    ParamDatastoreI store = HMMERParamStore.forBuild(viewport);
 +    List<ArgumentI> args = store.getServiceParameters();
 +
 +    if (!withDefaults)
 +    {
 +      WsParamSetI set = new HMMERPreset();
 +      WsJobParameters params = new WsJobParameters(store, set, args);
 +      params.showRunDialog().thenAccept((startJob) -> {
 +        if (startJob)
          {
 -          if (frames[i] instanceof AlignFrame && frames[i] != this
 -                  && ((AlignFrame) frames[i]).fileName != null
 -                  && ((AlignFrame) frames[i]).fileName.equals(fileName))
 -          {
 -            try
 -            {
 -              frames[i].setSelected(true);
 -              Desktop.instance.closeAssociatedWindows();
 -            } catch (java.beans.PropertyVetoException ex)
 -            {
 -            }
 -          }
 +          var args2 = params.getJobParams();
 +          new Thread(new HMMBuild(this, args2)).start();
 +        }
 +      });
 +    }
 +    else
 +    {
 +      new Thread(new HMMBuild(this, args)).start();
 +    }
 +  }
 +
 +  @Override
 +  public void hmmAlign_actionPerformed(boolean withDefaults)
 +  {
 +    if (!(checkForHMM() && alignmentIsSufficient(2)))
 +    {
 +      return;
 +    }
 +
 +    /*
 +     * get default parameters, and optionally show a dialog
 +     * to allow them to be modified
 +     */
 +    ParamDatastoreI store = HMMERParamStore.forAlign(viewport);
 +    List<ArgumentI> args = store.getServiceParameters();
  
 +    if (!withDefaults)
 +    {
 +      WsParamSetI set = new HMMERPreset();
 +      WsJobParameters params = new WsJobParameters(store, set, args);
 +      params.showRunDialog().thenAccept((startJob) -> {
 +        if (startJob)
 +        {
 +          var args2 = params.getJobParams();
 +          new Thread(new HMMAlign(this, args2)).start();
          }
 -        Desktop.instance.closeAssociatedWindows();
 +      });
 +    }
 +    else
 +    {
 +      new Thread(new HMMAlign(this, args)).start();
 +    }
 +  }
 +
 +  @Override
 +  public void hmmSearch_actionPerformed(boolean withDefaults)
 +  {
 +    if (!checkForHMM())
 +    {
 +      return;
 +    }
 +
 +    /*
 +     * get default parameters, and (if requested) show 
 +     * dialog to allow modification
 +     */
 +    ParamDatastoreI store = HMMERParamStore.forSearch(viewport);
 +    List<ArgumentI> args = store.getServiceParameters();
 +
 +    if (!withDefaults)
 +    {
 +      WsParamSetI set = new HMMERPreset();
 +      WsJobParameters params = new WsJobParameters(store, set, args);
 +      params.showRunDialog().thenAccept((startJob) -> {
 +        if (startJob)
 +        {
 +          var args2 = params.getJobParams();
 +          new Thread(new HMMSearch(this, args2)).start();
 +          alignPanel.repaint();
 +        }
 +      });
 +    }
 +    else
 +    {
 +      new Thread(new HMMSearch(this, args)).start();
 +      alignPanel.repaint();
 +    }
 +  }
 +
 +  @Override
 +  public void jackhmmer_actionPerformed(boolean withDefaults)
 +  {
 +
 +    /*
 +     * get default parameters, and (if requested) show 
 +     * dialog to allow modification
 +     */
 +
 +    ParamDatastoreI store = HMMERParamStore.forJackhmmer(viewport);
 +    List<ArgumentI> args = store.getServiceParameters();
 +
 +    if (!withDefaults)
 +    {
 +      WsParamSetI set = new HMMERPreset();
 +      WsJobParameters params = new WsJobParameters(store, set, args);
 +      params.showRunDialog().thenAccept((startJob) -> {
 +        if (startJob)
 +        {
 +          var args2 = params.getJobParams();
 +          new Thread(new JackHMMER(this, args2)).start();
 +          alignPanel.repaint();
 +        }
 +      });
 +    }
 +    else
 +    {
 +      new Thread(new JackHMMER(this, args)).start();
 +      alignPanel.repaint();
 +    }
 +  }
 +
 +  /**
 +   * Checks if the alignment has at least one hidden Markov model, if not shows
 +   * a dialog advising to run hmmbuild or load an HMM profile
 +   * 
 +   * @return
 +   */
 +  private boolean checkForHMM()
 +  {
 +    if (viewport.getAlignment().getHmmSequences().isEmpty())
 +    {
 +      JOptionPane.showMessageDialog(this,
 +              MessageManager.getString("warn.no_hmm"));
 +      return false;
 +    }
 +    return true;
 +  }
 +
 +  @Override
 +  protected void filterByEValue_actionPerformed()
 +  {
 +    viewport.filterByEvalue(inputDouble("Enter E-Value Cutoff"));
 +  }
  
 -        FileLoader loader = new FileLoader();
 -        DataSourceType protocol = HttpUtils.startsWithHttpOrHttps(fileName)
 -                ? DataSourceType.URL
 -                : DataSourceType.FILE;
 -        loader.LoadFile(viewport, fileName, protocol, currentFileFormat);
 +  @Override
 +  protected void filterByScore_actionPerformed()
 +  {
 +    viewport.filterByScore(inputDouble("Enter Bit Score Threshold"));
 +  }
 +
 +  private double inputDouble(String message)
 +  {
 +    String str = null;
 +    Double d = null;
 +    while (d == null || d <= 0)
 +    {
 +      str = JOptionPane.showInputDialog(this.alignPanel, message);
 +      try
 +      {
 +        d = Double.valueOf(str);
 +      } catch (NumberFormatException e)
 +      {
 +      }
 +    }
 +    return d;
 +  }
 +
 +  /**
 +   * Checks if the alignment contains the required number of sequences.
 +   * 
 +   * @param required
 +   * @return
 +   */
 +  public boolean alignmentIsSufficient(int required)
 +  {
 +    if (getViewport().getSequenceSelection().length < required)
 +    {
 +      JOptionPane.showMessageDialog(this,
 +              MessageManager.getString("label.not_enough_sequences"));
 +      return false;
 +    }
 +    return true;
 +  }
 +
 +  /**
 +   * Opens a file browser and adds the selected file, if in Fasta, Stockholm or
 +   * Pfam format, to the list held under preference key "HMMSEARCH_DBS" (as a
 +   * comma-separated list)
 +   */
 +  @Override
 +  public void addDatabase_actionPerformed() throws IOException
 +  {
 +    if (Cache.getProperty(Preferences.HMMSEARCH_DBS) == null)
 +    {
 +      Cache.setProperty(Preferences.HMMSEARCH_DBS, "");
 +    }
 +
 +    String path = openFileChooser(false);
 +    if (path != null && new File(path).exists())
 +    {
 +      IdentifyFile identifier = new IdentifyFile();
 +      FileFormatI format = identifier.identify(path, DataSourceType.FILE);
 +      if (format == FileFormat.Fasta || format == FileFormat.Stockholm
 +              || format == FileFormat.Pfam)
 +      {
 +        String currentDbPaths = Cache
 +                .getProperty(Preferences.HMMSEARCH_DBS);
 +        currentDbPaths += Preferences.COMMA + path;
 +        Cache.setProperty(Preferences.HMMSEARCH_DBS, currentDbPaths);
        }
        else
        {
 -        Rectangle bounds = this.getBounds();
 +        JOptionPane.showMessageDialog(this,
 +                MessageManager.getString("warn.invalid_format"));
 +      }
 +    }
 +  }
  
 -        FileLoader loader = new FileLoader();
 +  /**
 +   * Opens a file chooser, optionally restricted to selecting folders
 +   * (directories) only. Answers the path to the selected file or folder, or
 +   * null if none is chosen.
 +   * 
 +   * @param
 +   * @return
 +   */
 +  protected String openFileChooser(boolean forFolder)
 +  {
 +    // TODO duplicates GPreferences method - relocate to JalviewFileChooser?
 +    String choice = null;
 +    JFileChooser chooser = new JFileChooser();
 +    if (forFolder)
 +    {
 +      chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
 +    }
 +    chooser.setDialogTitle(
 +            MessageManager.getString("label.open_local_file"));
 +    chooser.setToolTipText(MessageManager.getString("action.open"));
  
 -        AlignFrame newframe = null;
 +    int value = chooser.showOpenDialog(this);
  
 -        if (fileObject == null)
 -        {
 +    if (value == JFileChooser.APPROVE_OPTION)
 +    {
 +      choice = chooser.getSelectedFile().getPath();
 +    }
 +    return choice;
 +  }
  
 -          DataSourceType protocol = HttpUtils.startsWithHttpOrHttps(
 -                  fileName) ? DataSourceType.URL : DataSourceType.FILE;
 -          newframe = loader.LoadFileWaitTillLoaded(fileName, protocol,
 -                  currentFileFormat);
 -        }
 -        else
 +  @Override
 +  public void reload_actionPerformed(ActionEvent e)
 +  {
 +    if (fileName == null && fileObject == null)
 +    {
 +      return;
 +    }
 +    // TODO: JAL-1108 - ensure all associated frames are closed regardless of
 +    // originating file's format
 +    // TODO: work out how to recover feature settings for correct view(s) when
 +    // file is reloaded.
 +    if (FileFormat.Jalview.equals(currentFileFormat))
 +    {
 +      JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
 +      for (int i = 0; i < frames.length; i++)
 +      {
 +        if (frames[i] instanceof AlignFrame && frames[i] != this
 +                && ((AlignFrame) frames[i]).fileName != null
 +                && ((AlignFrame) frames[i]).fileName.equals(fileName))
          {
 -          newframe = loader.LoadFileWaitTillLoaded(fileObject,
 -                  DataSourceType.FILE, currentFileFormat);
 +          try
 +          {
 +            frames[i].setSelected(true);
 +            Desktop.getInstance().closeAssociatedWindows();
 +          } catch (java.beans.PropertyVetoException ex)
 +          {
 +          }
          }
  
 -        newframe.setBounds(bounds);
 -        if (featureSettings != null && featureSettings.isShowing())
 +      }
 +      Desktop.getInstance().closeAssociatedWindows();
 +
 +      FileLoader loader = new FileLoader();
- //      DataSourceType protocol = fileName.startsWith("http:")
- //              ? DataSourceType.URL
- //              : DataSourceType.FILE;
-         loader.LoadFile(viewport, (fileObject == null ? fileName : fileObject), protocol, currentFileFormat);
++      loader.LoadFile(viewport, (fileObject == null ? fileName : fileObject), protocol, currentFileFormat);
 +    }
 +    else
 +    {
 +      Rectangle bounds = this.getBounds();
 +
 +      FileLoader loader = new FileLoader();
 +
 +      AlignFrame newframe = null;
 +
 +      if (fileObject == null)
 +      {
 +        newframe = loader.LoadFileWaitTillLoaded(fileName, protocol,
 +                currentFileFormat);
 +      }
 +      else
 +      {
 +        newframe = loader.LoadFileWaitTillLoaded(fileObject,
 +                DataSourceType.FILE, currentFileFormat);
 +      }
 +
 +      newframe.setBounds(bounds);
 +      if (featureSettings != null && featureSettings.isShowing())
 +      {
 +        final Rectangle fspos = featureSettings.frame.getBounds();
 +        // TODO: need a 'show feature settings' function that takes bounds -
 +        // need to refactor Desktop.addFrame
 +        newframe.featureSettings_actionPerformed(null);
 +        final FeatureSettings nfs = newframe.featureSettings;
 +        SwingUtilities.invokeLater(new Runnable()
          {
 -          final Rectangle fspos = featureSettings.frame.getBounds();
 -          // TODO: need a 'show feature settings' function that takes bounds -
 -          // need to refactor Desktop.addFrame
 -          newframe.featureSettings_actionPerformed(null);
 -          final FeatureSettings nfs = newframe.featureSettings;
 -          SwingUtilities.invokeLater(new Runnable()
 +          @Override
 +          public void run()
            {
 -            @Override
 -            public void run()
 -            {
 -              nfs.frame.setBounds(fspos);
 -            }
 -          });
 -          this.featureSettings.close();
 -          this.featureSettings = null;
 -        }
 -        this.closeMenuItem_actionPerformed(true);
 +            nfs.frame.setBounds(fspos);
 +          }
 +        });
 +        this.featureSettings.close();
 +        this.featureSettings = null;
        }
 +      this.closeMenuItem_actionPerformed(true);
      }
    }
  
    @Override
    public void addFromText_actionPerformed(ActionEvent e)
    {
 -    Desktop.instance
 +    Desktop.getInstance()
              .inputTextboxMenuItem_actionPerformed(viewport.getAlignPanel());
    }
  
    @Override
    public void addFromURL_actionPerformed(ActionEvent e)
    {
 -    Desktop.instance.inputURLMenuItem_actionPerformed(viewport);
 +    Desktop.getInstance().inputURLMenuItem_actionPerformed(viewport);
    }
  
    @Override
    public void save_actionPerformed(ActionEvent e)
    {
      if (fileName == null || (currentFileFormat == null)
-             || fileName.startsWith("http"))
+             || HttpUtils.startsWithHttpOrHttps(fileName))
      {
        saveAs_actionPerformed();
      }
     * 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()
    {
      // todo is this (2005) test now obsolete - value is never null?
      while (currentFileFormat == null)
      {
 -      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +      JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                MessageManager
                        .getString("label.select_file_format_before_saving"),
                MessageManager.getString("label.file_format_not_specified"),
     *
     * @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);
+       if (!Platform.isHeadless())
+       {
+         JvOptionPane.showInternalMessageDialog(this, MessageManager
+                 .formatMessage("label.couldnt_save_file", new Object[]
+                 { lastFilenameSaved }),
+                 MessageManager.getString("label.error_saving_file"),
+                 JvOptionPane.WARNING_MESSAGE);
+       }
+       else
+       {
+         Cache.log.error(MessageManager
+                 .formatMessage("label.couldnt_save_file", new Object[]
+                 { lastFilenameSaved }));
+       }
      }
      else
      {
     * @param file
     * @param format
     */
    public void saveAlignment(String file, FileFormatI format)
    {
      lastSaveSuccessful = true;
        }
        lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file,
                shortName);
--
        statusBar.setText(MessageManager.formatMessage(
                "label.successfully_saved_to_file_in_format", new Object[]
 -              { file, format }));
 -
 +              { fileName, format }));
        return;
      }
  
      AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
      Runnable cancelAction = new Runnable()
      {
        @Override
        public void run()
        {
      };
      Runnable outputAction = new Runnable()
      {
        @Override
        public void run()
        {
          {
            // create backupfiles object and get new temp filename destination
            boolean doBackup = BackupFiles.getEnabled();
-           BackupFiles backupfiles = doBackup ? new BackupFiles(file) : null;
+           BackupFiles backupfiles = null;
+           if (doBackup)
+           {
+             Cache.log.trace(
+                     "ALIGNFRAME making backupfiles object for " + file);
+             backupfiles = new BackupFiles(file);
+           }
            try
            {
              String tempFilePath = doBackup ? backupfiles.getTempFilePath()
                      : file;
+             Cache.log.trace("ALIGNFRAME setting PrintWriter");
              PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
  
+             if (backupfiles != null)
+             {
+               Cache.log.trace("ALIGNFRAME about to write to temp file "
+                       + backupfiles.getTempFilePath());
+             }
              out.print(output);
+             Cache.log.trace("ALIGNFRAME about to close file");
              out.close();
+             Cache.log.trace("ALIGNFRAME closed file");
              AlignFrame.this.setTitle(file);
              statusBar.setText(MessageManager.formatMessage(
                      "label.successfully_saved_to_file_in_format",
                      new Object[]
                      { fileName, format.getName() }));
              lastSaveSuccessful = true;
+           } catch (IOException e)
+           {
+             lastSaveSuccessful = false;
+             Cache.log.error(
+                     "ALIGNFRAME Something happened writing the temp file");
+             Cache.log.error(e.getMessage());
+             Cache.log.debug(Cache.getStackTraceString(e));
            } catch (Exception ex)
            {
              lastSaveSuccessful = false;
-             ex.printStackTrace();
+             Cache.log.error(
+                     "ALIGNFRAME Something unexpected happened writing the temp file");
+             Cache.log.error(ex.getMessage());
+             Cache.log.debug(Cache.getStackTraceString(ex));
            }
  
            if (doBackup)
            {
              backupfiles.setWriteSuccess(lastSaveSuccessful);
+             Cache.log.debug("ALIGNFRAME writing temp file was "
+                     + (lastSaveSuccessful ? "" : "NOT ") + "successful");
              // do the backup file roll and rename the temp file to actual file
+             Cache.log.trace(
+                     "ALIGNFRAME about to rollBackupsAndRenameTempFile");
              lastSaveSuccessful = backupfiles.rollBackupsAndRenameTempFile();
+             Cache.log.debug(
+                     "ALIGNFRAME performed rollBackupsAndRenameTempFile "
+                             + (lastSaveSuccessful ? "" : "un")
+                             + "successfully");
            }
          }
        }
     * 
     * @param fileFormatName
     */
    @Override
    protected void outputText_actionPerformed(String fileFormatName)
    {
      AlignExportSettingsI options = new AlignExportSettingsAdapter(false);
      Runnable outputAction = new Runnable()
      {
        @Override
        public void run()
        {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void htmlMenuItem_actionPerformed(ActionEvent e)
    {
      bjs.exportHTML(null);
    }
  
 +  // ??
    public void createImageMap(File file, String image)
    {
      alignPanel.makePNGImageMap(file, image);
     * 
     * @param f
     */
    @Override
    public void createPNG(File f)
    {
     * 
     * @param f
     */
    @Override
    public void createEPS(File f)
    {
     * 
     * @param f
     */
    @Override
    public void createSVG(File f)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void printMenuItem_actionPerformed(ActionEvent e)
    {
  
    @Override
    public void associatedData_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
      final JalviewFileChooser chooser = new JalviewFileChooser(
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
      chooser.setToolTipText(tooltip);
      chooser.setResponseHandler(0, new Runnable()
      {
        @Override
        public void run()
        {
     * 
     * @param closeAllTabs
     */
    @Override
    public void closeMenuItem_actionPerformed(boolean closeAllTabs)
    {
     * 
     * @param panelToClose
     */
    public void closeView(AlignmentPanel panelToClose)
    {
      int index = tabbedPane.getSelectedIndex();
    /**
     * DOCUMENT ME!
     */
    void updateEditMenuBar()
    {
  
     * 
     * @return alignment objects for all views
     */
    AlignmentI[] getViewAlignments()
    {
      if (alignPanels != null)
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void undoMenuItem_actionPerformed(ActionEvent e)
    {
        // && viewport.getColumnSelection().getHiddenColumns() != null &&
        // viewport.getColumnSelection()
        // .getHiddenColumns().size() > 0);
 -      originalSource.firePropertyChange("alignment", null,
 -              originalSource.getAlignment().getSequences());
 +      originalSource.notifyAlignment();
      }
    }
  
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void redoMenuItem_actionPerformed(ActionEvent e)
    {
        // && viewport.getColumnSelection().getHiddenColumns() != null &&
        // viewport.getColumnSelection()
        // .getHiddenColumns().size() > 0);
 -      originalSource.firePropertyChange("alignment", null,
 -              originalSource.getAlignment().getSequences());
 +      originalSource.notifyAlignment();
      }
    }
  
    }
  
    /**
-    * DOCUMENT ME!
+    * Calls AlignmentI.moveSelectedSequencesByOne with current sequence selection
+    * or the sequence under cursor in keyboard mode
     * 
     * @param up
-    *          DOCUMENT ME!
+    *          or down (if !up)
     */
    public void moveSelectedSequences(boolean up)
    {
      SequenceGroup sg = viewport.getSelectionGroup();
  
      if (sg == null)
      {
+       if (viewport.cursorMode)
+       {
+         sg = new SequenceGroup();
+         sg.addSequence(viewport.getAlignment().getSequenceAt(
+                 alignPanel.getSeqPanel().seqCanvas.cursorY), false);
+       }
+       else
+       {
+         return;
+       }
+     }
+     if (sg.getSize() < 1)
+     {
        return;
      }
 -
+     // TODO: JAL-3733 - add an event to the undo buffer for this !
 -
      viewport.getAlignment().moveSelectedSequencesByOne(sg,
              viewport.getHiddenRepSequences(), up);
      alignPanel.paintAlignment(true, false);
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void copy_actionPerformed()
    {
  
      StringSelection ss = new StringSelection(output);
  
 +    Desktop d = Desktop.getInstance();
      try
      {
 -      jalview.gui.Desktop.internalCopy = true;
 +      d.internalCopy = true;
        // Its really worth setting the clipboard contents
        // to empty before setting the large StringSelection!!
        Toolkit.getDefaultToolkit().getSystemClipboard()
                .setContents(new StringSelection(""), null);
  
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss,
-               Desktop.getInstance());
 -              Desktop.instance);
++              d);
      } catch (OutOfMemoryError er)
      {
        new OOMWarning("copying region", er);
                hiddenCutoff, hiddenOffset);
      }
  
 -    Desktop.jalviewClipboard = new Object[] { seqs,
 +    d.jalviewClipboard = new Object[] { seqs,
          viewport.getAlignment().getDataset(), hiddenColumns };
      setStatus(MessageManager.formatMessage(
              "label.copied_sequences_to_clipboard", new Object[]
     * 
     * @param e
     *          DOCUMENT ME!
 +   * @throws InterruptedException
 +   * @throws IOException
     */
    @Override
    protected void pasteNew_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
      paste(true);
    }
     * 
     * @param e
     *          DOCUMENT ME!
 +   * @throws InterruptedException
 +   * @throws IOException
     */
    @Override
    protected void pasteThis_actionPerformed(ActionEvent e)
 +          throws IOException, InterruptedException
    {
      paste(false);
    }
     * 
     * @param newAlignment
     *          true to paste to a new alignment, otherwise add to this.
 +   * @throws InterruptedException
 +   * @throws IOException
     */
 -  void paste(boolean newAlignment)
 +  void paste(boolean newAlignment) throws IOException, InterruptedException
    {
      boolean externalPaste = true;
      try
        boolean annotationAdded = false;
        AlignmentI alignment = null;
  
 -      if (Desktop.jalviewClipboard != null)
 +      Desktop d = Desktop.getInstance();
 +
 +      if (d.jalviewClipboard != null)
        {
          // The clipboard was filled from within Jalview, we must use the
          // sequences
          // And dataset from the copied alignment
 -        SequenceI[] newseq = (SequenceI[]) Desktop.jalviewClipboard[0];
 +        SequenceI[] newseq = (SequenceI[]) d.jalviewClipboard[0];
          // be doubly sure that we create *new* sequence objects.
          sequences = new SequenceI[newseq.length];
          for (int i = 0; i < newseq.length; i++)
        if (newAlignment)
        {
  
 -        if (Desktop.jalviewClipboard != null)
 +        if (d.jalviewClipboard != null)
          {
            // dataset is inherited
 -          alignment.setDataset((Alignment) Desktop.jalviewClipboard[1]);
 +          alignment.setDataset((Alignment) d.jalviewClipboard[1]);
          }
          else
          {
          alignment = viewport.getAlignment();
          alwidth = alignment.getWidth() + 1;
          // decide if we need to import sequences from an existing dataset
 -        boolean importDs = Desktop.jalviewClipboard != null
 -                && Desktop.jalviewClipboard[1] != alignment.getDataset();
 +        boolean importDs = d.jalviewClipboard != null
 +                && d.jalviewClipboard[1] != alignment.getDataset();
          // importDs==true instructs us to copy over new dataset sequences from
          // an existing alignment
          Vector<SequenceI> newDs = (importDs) ? new Vector<>() : null; // used to
            }
            buildSortByAnnotationScoresMenu();
          }
 -        viewport.firePropertyChange("alignment", null,
 -                alignment.getSequences());
 +        viewport.notifyAlignment();
          if (alignPanels != null)
          {
            for (AlignmentPanel ap : alignPanels)
                  DEFAULT_HEIGHT);
          String newtitle = new String("Copied sequences");
  
 -        if (Desktop.jalviewClipboard != null
 -                && Desktop.jalviewClipboard[2] != null)
 +        if (d.jalviewClipboard != null && d.jalviewClipboard[2] != null)
          {
 -          HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
 +          HiddenColumns hc = (HiddenColumns) d.jalviewClipboard[2];
            af.viewport.setHiddenColumns(hc);
          }
  
        System.out.println("Exception whilst pasting: " + ex);
        // could be anything being pasted in here
      }
    }
  
    @Override
        AlignFrame af = new AlignFrame(alignment, DEFAULT_WIDTH,
                DEFAULT_HEIGHT);
        String newtitle = new String("Flanking alignment");
 -      if (Desktop.jalviewClipboard != null
 -              && Desktop.jalviewClipboard[2] != null)
 +      Desktop d = Desktop.getInstance();
 +      if (d.jalviewClipboard != null && d.jalviewClipboard[2] != null)
        {
 -        HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
 +        HiddenColumns hc = (HiddenColumns) d.jalviewClipboard[2];
          af.viewport.setHiddenColumns(hc);
        }
  
    /**
     * Action Cut (delete and copy) the selected region
     */
    @Override
    protected void cut_actionPerformed()
    {
    /**
     * Performs menu option to Delete the currently selected region
     */
    @Override
    protected void delete_actionPerformed()
    {
  
      Runnable okAction = new Runnable()
      {
 +
        @Override
        public void run()
        {
          viewport.sendSelection();
          viewport.getAlignment().deleteGroup(sg);
  
 -        viewport.firePropertyChange("alignment", null,
 -                viewport.getAlignment().getSequences());
 +        viewport.notifyAlignment();
 +
          if (viewport.getAlignment().getHeight() < 1)
          {
            try
            } catch (Exception ex)
            {
            }
 +        } else {
 +          updateAll(null);
          }
        }
      };
              + 1) == viewport.getAlignment().getWidth()) ? true : false;
      if (wholeHeight && wholeWidth)
      {
 -      JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop);
 +      JvOptionPane dialog = JvOptionPane
 +              .newOptionDialog(Desktop.getDesktopPane());
        dialog.setResponseHandler(0, okAction); // 0 = OK_OPTION
        Object[] options = new Object[] {
            MessageManager.getString("action.ok"),
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void deleteGroups_actionPerformed(ActionEvent e)
    {
      if (avc.deleteGroups())
      {
 -      PaintRefresher.Refresh(this, viewport.getSequenceSetId());
 -      alignPanel.updateAnnotation();
 +      updateAll(viewport.getSequenceSetId());
 +    }
 +  }
 +
 +  private void updateAll(String id)
 +  {
 +    if (id == null)
 +    {
 +      // this will force a non-fast repaint of both the IdPanel and SeqPanel
 +      alignPanel.getIdPanel().getIdCanvas().setNoFastPaint();
 +      alignPanel.getSeqPanel().seqCanvas.setNoFastPaint();
 +      alignPanel.repaint();
 +    }
 +    else
 +    {
 +      // original version
 +      PaintRefresher.Refresh(this, id);
        alignPanel.paintAlignment(true, true);
      }
 +    alignPanel.updateAnnotation();
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void selectAllSequenceMenuItem_actionPerformed(ActionEvent e)
    {
 -    SequenceGroup sg = new SequenceGroup(
 -            viewport.getAlignment().getSequences());
 -
 -    sg.setEndRes(viewport.getAlignment().getWidth() - 1);
 -    viewport.setSelectionGroup(sg);
 -    viewport.isSelectionGroupChanged(true);
 -    viewport.sendSelection();
 -    // JAL-2034 - should delegate to
 -    // alignPanel to decide if overview needs
 -    // updating.
 -    alignPanel.paintAlignment(false, false);
 -    PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
 +    alignPanel.selectAllSequences();
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void deselectAllSequenceMenuItem_actionPerformed(ActionEvent e)
    {
 -    if (viewport.cursorMode)
 -    {
 -      alignPanel.getSeqPanel().keyboardNo1 = null;
 -      alignPanel.getSeqPanel().keyboardNo2 = null;
 -    }
 -    viewport.setSelectionGroup(null);
 -    viewport.getColumnSelection().clear();
 -    viewport.setSearchResults(null);
 -    alignPanel.getIdPanel().getIdCanvas().searchResults = null;
 -    // JAL-2034 - should delegate to
 -    // alignPanel to decide if overview needs
 -    // updating.
 -    alignPanel.paintAlignment(false, false);
 -    PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
 -    viewport.sendSelection();
 +    alignPanel.deselectAllSequences();
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void invertSequenceMenuItem_actionPerformed(ActionEvent e)
    {
  
      if (sg == null)
      {
 -      selectAllSequenceMenuItem_actionPerformed(null);
 +      alignPanel.selectAllSequences();
  
        return;
      }
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void remove2LeftMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void remove2RightMenuItem_actionPerformed(ActionEvent e)
    {
          }
        }
  
 -      viewport.firePropertyChange("alignment", null,
 -              viewport.getAlignment().getSequences());
 +      viewport.notifyAlignment();
      }
    }
  
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void removeGappedColumnMenuItem_actionPerformed(ActionEvent e)
    {
      // if (viewport.hasHiddenColumns)
      // viewport.getColumnSelection().compensateForEdits(shifts);
      ranges.setStartRes(seq.findIndex(startRes) - 1);
 -    viewport.firePropertyChange("alignment", null,
 -            viewport.getAlignment().getSequences());
 +    viewport.notifyAlignment();
  
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void removeAllGapsMenuItem_actionPerformed(ActionEvent e)
    {
              viewport.getAlignment()));
  
      viewport.getRanges().setStartRes(seq.findIndex(startRes) - 1);
 -    viewport.firePropertyChange("alignment", null,
 -            viewport.getAlignment().getSequences());
 +    viewport.notifyAlignment();
  
    }
  
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void padGapsMenuitem_actionPerformed(ActionEvent e)
    {
      viewport.setPadGaps(padGapsMenuitem.isSelected());
 -    viewport.firePropertyChange("alignment", null,
 -            viewport.getAlignment().getSequences());
 +    viewport.notifyAlignment();
    }
  
    /**
-    * DOCUMENT ME!
+    * Opens a Finder dialog
     * 
     * @param e
-    *          DOCUMENT ME!
     */
    @Override
    public void findMenuItem_actionPerformed(ActionEvent e)
    {
-     new Finder();
+     new Finder(alignPanel, false, null);
    }
  
    /**
     * Create a new view of the current alignment.
     */
    @Override
    public void newView_actionPerformed(ActionEvent e)
    {
     *          if true then duplicate all annnotation, groups and settings
     * @return new alignment panel, already displayed.
     */
    public AlignmentPanel newView(String viewTitle, boolean copyAnnotation)
    {
      /*
     * @param viewTitle
     * @return
     */
    protected String getNewViewName(String viewTitle)
    {
      int index = Desktop.getViewCount(viewport.getSequenceSetId());
     * @param comps
     * @return
     */
    protected List<String> getExistingViewNames(List<Component> comps)
    {
      List<String> existingNames = new ArrayList<>();
    /**
     * Explode tabbed views into separate windows.
     */
    @Override
    public void expandViews_actionPerformed(ActionEvent e)
    {
    /**
     * Gather views in separate windows back into a tabbed presentation.
     */
    @Override
    public void gatherViews_actionPerformed(ActionEvent e)
    {
 -    Desktop.instance.gatherViews(this);
 +    Desktop.getInstance().gatherViews(this);
    }
  
    /**
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void font_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void seqLimit_actionPerformed(ActionEvent e)
    {
     * 
     * @see jalview.jbgui.GAlignFrame#followHighlight_actionPerformed()
     */
    @Override
    protected void followHighlight_actionPerformed()
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void colourTextMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void wrapMenuItem_actionPerformed(ActionEvent e)
    {
     * @param toggleSeqs
     * @param toggleCols
     */
    protected void toggleHiddenRegions(boolean toggleSeqs, boolean toggleCols)
    {
  
     * jalview.jbgui.GAlignFrame#hideAllButSelection_actionPerformed(java.awt.
     * event.ActionEvent)
     */
    @Override
    public void hideAllButSelection_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#hideAllSelection_actionPerformed(java.awt.event
     * .ActionEvent)
     */
    @Override
    public void hideAllSelection_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showAllhidden_actionPerformed(java.awt.event.
     * ActionEvent)
     */
    @Override
    public void showAllhidden_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void scaleAbove_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void scaleLeft_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void scaleRight_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void viewBoxesMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void viewTextMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void renderGapsMenuItem_actionPerformed(ActionEvent e)
    {
     * @param evt
     *          DOCUMENT ME!
     */
    @Override
    public void showSeqFeatures_actionPerformed(ActionEvent evt)
    {
     * 
     * @param e
     */
    @Override
    public void annotationPanelMenuItem_actionPerformed(ActionEvent e)
    {
      final boolean setVisible = annotationPanelMenuItem.isSelected();
      viewport.setShowAnnotation(setVisible);
 -    this.showAllSeqAnnotations.setEnabled(setVisible);
 -    this.hideAllSeqAnnotations.setEnabled(setVisible);
 -    this.showAllAlAnnotations.setEnabled(setVisible);
 -    this.hideAllAlAnnotations.setEnabled(setVisible);
 +    syncAnnotationMenuItems(setVisible);
      alignPanel.updateLayout();
 +    repaint();
 +    SwingUtilities.invokeLater(new Runnable() {
 +
 +      @Override
 +      public void run()
 +      {
 +        alignPanel.updateScrollBarsFromRanges();
 +      }
 +      
 +    });
 +  }
 +
 +  private void syncAnnotationMenuItems(boolean setVisible)
 +  {
 +    showAllSeqAnnotations.setEnabled(setVisible);
 +    hideAllSeqAnnotations.setEnabled(setVisible);
 +    showAllAlAnnotations.setEnabled(setVisible);
 +    hideAllAlAnnotations.setEnabled(setVisible);
    }
  
    @Override
        JLabel textLabel = new JLabel();
        textLabel.setText(content);
        textLabel.setBackground(Color.WHITE);
--
        pane = new JPanel(new BorderLayout());
        ((JPanel) pane).setOpaque(true);
        pane.setBackground(Color.WHITE);
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void overviewMenuItem_actionPerformed(ActionEvent e)
    {
      }
  
      JInternalFrame frame = new JInternalFrame();
 -    final OverviewPanel overview = new OverviewPanel(alignPanel);
 +    // BH 2019.07.26 we allow for an embedded
 +    // undecorated overview with defined size
 +    frame.setName(Platform.getAppID("overview"));
 +    //
 +    Dimension dim = Platform.getDimIfEmbedded(frame, -1, -1);
 +    if (dim != null && dim.width == 0)
 +    {
 +      dim = null; // hidden, not embedded
 +    }
 +    OverviewPanel overview = new OverviewPanel(alignPanel, dim);
      frame.setContentPane(overview);
 +    if (dim == null)
 +    {
 +      dim = new Dimension();
 +      // was frame.getSize(), but that is 0,0 at this point;
 +    }
 +    else
 +    {
 +      // we are imbedding, and so we have an undecorated frame
 +      // and we can set the the frame dimensions accordingly.
 +    }
 +    // allowing for unresizable option using, style="resize:none"
 +    boolean resizable = (Platform.getEmbeddedAttribute(frame,
 +            "resize") != "none");
      Desktop.addInternalFrame(frame, MessageManager
              .formatMessage("label.overview_params", new Object[]
 -            { this.getTitle() }), true, frame.getWidth(), frame.getHeight(),
 -            true, true);
 +            { this.getTitle() }), Desktop.FRAME_MAKE_VISIBLE, dim.width,
 +            dim.height, resizable, Desktop.FRAME_ALLOW_ANY_SIZE);
      frame.pack();
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
      frame.addInternalFrameListener(
              new javax.swing.event.InternalFrameAdapter()
              {
                @Override
                public void internalFrameClosed(
                        javax.swing.event.InternalFrameEvent evt)
     * CovariationColourScheme(viewport.getAlignment().getAlignmentAnnotation
     * ()[0])); }
     */
    @Override
    public void annotationColour_actionPerformed()
    {
     * 
     * @param selected
     */
    @Override
    public void applyToAllGroups_actionPerformed(boolean selected)
    {
     * @param name
     *          the name (not the menu item label!) of the colour scheme
     */
    @Override
    public void changeColour_actionPerformed(String name)
    {
     * 
     * @param cs
     */
    @Override
    public void changeColour(ColourSchemeI cs)
    {
    /**
     * Show the PID threshold slider panel
     */
    @Override
    protected void modifyPID_actionPerformed()
    {
    /**
     * Show the Conservation slider panel
     */
    @Override
    protected void modifyConservation_actionPerformed()
    {
    /**
     * Action on selecting or deselecting (Colour) By Conservation
     */
    @Override
    public void conservationMenuItem_actionPerformed(boolean selected)
    {
    /**
     * Action on selecting or deselecting (Colour) Above PID Threshold
     */
    @Override
    public void abovePIDThreshold_actionPerformed(boolean selected)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void sortPairwiseMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void sortIDMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void sortLengthMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void sortGroupMenuItem_actionPerformed(ActionEvent e)
    {
      alignPanel.paintAlignment(true, false);
    }
  
 +  @Override
 +  public void sortEValueMenuItem_actionPerformed(ActionEvent e)
 +  {
 +    SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
 +    AlignmentSorter.sortByEValue(viewport.getAlignment());
 +    addHistoryItem(new OrderCommand("Group Sort", oldOrder,
 +            viewport.getAlignment()));
 +    alignPanel.paintAlignment(true, false);
 +
 +  }
 +
 +  @Override
 +  public void sortBitScoreMenuItem_actionPerformed(ActionEvent e)
 +  {
 +    SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
 +    AlignmentSorter.sortByBitScore(viewport.getAlignment());
 +    addHistoryItem(new OrderCommand("Group Sort", oldOrder,
 +            viewport.getAlignment()));
 +    alignPanel.paintAlignment(true, false);
 +
 +  }
    /**
     * DOCUMENT ME!
     * 
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void removeRedundancyMenuItem_actionPerformed(ActionEvent e)
    {
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    public void pairwiseAlignmentMenuItem_actionPerformed(ActionEvent e)
    {
    @Override
    public void autoCalculate_actionPerformed(ActionEvent e)
    {
 -    viewport.autoCalculateConsensus = autoCalculate.isSelected();
 -    if (viewport.autoCalculateConsensus)
 +    viewport.setAutoCalculateConsensusAndConservation(
 +            autoCalculate.isSelected());
 +    if (viewport.getAutoCalculateConsensusAndConservation())
-     // ??
-     // viewport.autoCalculateConsensus = autoCalculate.isSelected();
-     // if (viewport.autoCalculateConsensus)
      {
 -      viewport.firePropertyChange("alignment", null,
 -              viewport.getAlignment().getSequences());
 +      viewport.notifyAlignment();
      }
    }
  
     * @param options
     *          parameters for the distance or similarity calculation
     */
    void newTreePanel(String type, String modelName,
            SimilarityParamsI options)
    {
        {
          if (_s.getLength() < sg.getEndRes())
          {
 -          JvOptionPane.showMessageDialog(Desktop.desktop,
 +          JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                    MessageManager.getString(
                            "label.selected_region_to_tree_may_only_contain_residues_or_gaps"),
                    MessageManager.getString(
  
      frameTitle += this.title;
  
 -    Desktop.addInternalFrame(tp, frameTitle, 600, 500);
 +    Dimension dim = Platform.getDimIfEmbedded(tp, 600, 500);
 +    Desktop.addInternalFrame(tp, frameTitle, dim.width, dim.height);
    }
  
    /**
     * @param order
     *          DOCUMENT ME!
     */
    public void addSortByOrderMenuItem(String title,
            final AlignmentOrder order)
    {
      sort.add(item);
      item.addActionListener(new java.awt.event.ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
     *          the label used to retrieve scores for each sequence on the
     *          alignment
     */
    public void addSortByAnnotScoreMenuItem(JMenu sort,
            final String scoreLabel)
    {
      sort.add(item);
      item.addActionListener(new java.awt.event.ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
     * rebuilding in subsequence calls.
     * 
     */
    @Override
    public void buildSortByAnnotationScoresMenu()
    {
      }
  
      if (viewport.getAlignment().getAlignmentAnnotation()
 -            .hashCode() != _annotationScoreVectorHash)
 +            .hashCode() == _annotationScoreVectorHash)
      {
 -      sortByAnnotScore.removeAll();
 -      // almost certainly a quicker way to do this - but we keep it simple
 -      Hashtable<String, String> scoreSorts = new Hashtable<>();
 -      AlignmentAnnotation aann[];
 -      for (SequenceI sqa : viewport.getAlignment().getSequences())
 +      return;
 +    }
 +
 +    sortByAnnotScore.removeAll();
 +    Set<String> scoreSorts = new HashSet<>();
 +    for (SequenceI sqa : viewport.getAlignment().getSequences())
 +    {
 +      AlignmentAnnotation[] anns = sqa.getAnnotation();
 +      for (int i = 0; anns != null && i < anns.length; i++)
        {
 -        aann = sqa.getAnnotation();
 -        for (int i = 0; aann != null && i < aann.length; i++)
 +        AlignmentAnnotation aa = anns[i];
 +        if (aa != null && aa.hasScore() && aa.sequenceRef != null)
          {
 -          if (aann[i].hasScore() && aann[i].sequenceRef != null)
 -          {
 -            scoreSorts.put(aann[i].label, aann[i].label);
 -          }
 +          scoreSorts.add(aa.label);
          }
        }
 -      Enumeration<String> labels = scoreSorts.keys();
 -      while (labels.hasMoreElements())
 -      {
 -        addSortByAnnotScoreMenuItem(sortByAnnotScore, labels.nextElement());
 -      }
 -      sortByAnnotScore.setVisible(scoreSorts.size() > 0);
 -      scoreSorts.clear();
 -
 -      _annotationScoreVectorHash = viewport.getAlignment()
 -              .getAlignmentAnnotation().hashCode();
      }
 +    for (String label : scoreSorts)
 +    {
 +      addSortByAnnotScoreMenuItem(sortByAnnotScore, label);
 +    }
 +    sortByAnnotScore.setVisible(!scoreSorts.isEmpty());
 +
 +    _annotationScoreVectorHash = viewport.getAlignment()
 +            .getAlignmentAnnotation().hashCode();
    }
  
    /**
 +   * Enable (or, if desired, make visible) the By Tree 
 +   * submenu only if it has at least one element (or will have).
 +   * 
 +   */
 +  @Override
 +  protected void enableSortMenuOptions()
 +  {
 +    List<TreePanel> treePanels = getTreePanels();
 +    sortByTreeMenu.setEnabled(!treePanels.isEmpty());
 +  }
 +  
 +  /**
     * Maintain the Order by->Displayed Tree menu. Creates a new menu item for a
     * TreePanel with an appropriate <code>jalview.analysis.AlignmentSorter</code>
     * call. Listeners are added to remove the menu item when the treePanel is
     * closed, and adjust the tree leaf to sequence mapping when the alignment is
     * modified.
     */
    @Override
    public void buildTreeSortMenu()
    {
      sortByTreeMenu.removeAll();
  
 -    List<Component> comps = PaintRefresher.components
 -            .get(viewport.getSequenceSetId());
 -    List<TreePanel> treePanels = new ArrayList<>();
 -    for (Component comp : comps)
 -    {
 -      if (comp instanceof TreePanel)
 -      {
 -        treePanels.add((TreePanel) comp);
 -      }
 -    }
 -
 -    if (treePanels.size() < 1)
 -    {
 -      sortByTreeMenu.setVisible(false);
 -      return;
 -    }
 -
 -    sortByTreeMenu.setVisible(true);
 +    List<TreePanel> treePanels = getTreePanels();
  
      for (final TreePanel tp : treePanels)
      {
        final JMenuItem item = new JMenuItem(tp.getTitle());
        item.addActionListener(new java.awt.event.ActionListener()
        {
          @Override
          public void actionPerformed(ActionEvent e)
          {
        sortByTreeMenu.add(item);
      }
    }
 -
 +
 +  private List<TreePanel> getTreePanels()
 +  {
 +    List<Component> comps = PaintRefresher.components
 +            .get(viewport.getSequenceSetId());
 +    List<TreePanel> treePanels = new ArrayList<>();
 +    for (Component comp : comps)
 +    {
 +      if (comp instanceof TreePanel)
 +      {
 +        treePanels.add((TreePanel) comp);
 +      }
 +    }
 +    return treePanels;
 +  }
    public boolean sortBy(AlignmentOrder alorder, String undoname)
    {
      SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
     * be submitted for multiple alignment.
     * 
     */
    public jalview.datamodel.AlignmentView gatherSequencesForAlignment()
    {
      // Now, check we have enough sequences
     * region or the whole alignment. (where the first sequence in the set is the
     * one that the prediction will be for).
     */
    public AlignmentView gatherSeqOrMsaForSecStrPrediction()
    {
      AlignmentView seqs = null;
     * @param e
     *          DOCUMENT ME!
     */
    @Override
    protected void loadTreeMenuItem_actionPerformed(ActionEvent e)
    {
  
      chooser.setResponseHandler(0, new Runnable()
      {
        @Override
        public void run()
        {
            viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
          } catch (Exception ex)
          {
 -          JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
 +          JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
 +                  ex.getMessage(),
                    MessageManager
                            .getString("label.problem_reading_tree_file"),
                    JvOptionPane.WARNING_MESSAGE);
          }
          if (fin != null && fin.hasWarningMessage())
          {
 -          JvOptionPane.showMessageDialog(Desktop.desktop,
 +          JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                    fin.getWarningMessage(),
                    MessageManager.getString(
                            "label.possible_problem_with_tree_file"),
     *          position
     * @return TreePanel handle
     */
    public TreePanel showNewickTree(NewickFile nf, String treeTitle,
            AlignmentView input, int w, int h, int x, int y)
    {
        if (nf.getTree() != null)
        {
          tp = new TreePanel(alignPanel, nf, treeTitle, input);
 -        tp.setSize(w, h);
 +        Dimension dim = Platform.getDimIfEmbedded(tp, -1, -1);
 +        if (dim == null)
 +        {
 +          dim = new Dimension(w, h);
 +        }
 +        else
 +        {
 +          // no offset, either
 +          x = 0;
 +        }
 +        tp.setSize(dim.width, dim.height);
  
          if (x > 0 && y > 0)
          {
            tp.setLocation(x, y);
          }
  
 -        Desktop.addInternalFrame(tp, treeTitle, w, h);
 +        Desktop.addInternalFrame(tp, treeTitle, dim.width, dim.height);
        }
      } catch (Exception ex)
      {
      return tp;
    }
  
 -  private boolean buildingMenu = false;
    /**
 -   * Generates menu items and listener event actions for web service clients
 -   * 
 +   * Schedule the web services menu rebuild to the event dispatch thread.
     */
 -  public void BuildWebServiceMenu()
 +  public void buildWebServicesMenu()
    {
 -    while (buildingMenu)
 -    {
 -      try
 +    SwingUtilities.invokeLater(() -> {
 +      Cache.log.info("Rebuiling WS menu");
 +      webService.removeAll();
 +      if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
        {
 -        System.err.println("Waiting for building menu to finish.");
 -        Thread.sleep(10);
 -      } catch (Exception e)
 +        Cache.log.info("Building web service menu for slivka");
 +        SlivkaWSDiscoverer discoverer = SlivkaWSDiscoverer.getInstance();
 +        JMenu submenu = new JMenu("Slivka");
 +        buildWebServicesMenu(discoverer, submenu);
 +        webService.add(submenu);
 +      }
 +      if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
        {
 +        WSDiscovererI jws2servs = Jws2Discoverer.getInstance();
 +        JMenu submenu = new JMenu("JABAWS");
 +        buildLegacyWebServicesMenu(submenu);
 +        buildWebServicesMenu(jws2servs, submenu);
 +        webService.add(submenu);
        }
 -    }
 -    final AlignFrame me = this;
 -    buildingMenu = true;
 -    new Thread(new Runnable()
 +    });
 +  }
 +
 +  private void buildLegacyWebServicesMenu(JMenu menu)
 +  {
 +    JMenu secstrmenu = new JMenu("Secondary Structure Prediction");
 +    if (Discoverer.getServices() != null && Discoverer.getServices().size() > 0) 
      {
 -      @Override
 -      public void run()
 +      var secstrpred = Discoverer.getServices().get("SecStrPred");
 +      if (secstrpred != null) 
        {
 -        final List<JMenuItem> legacyItems = new ArrayList<>();
 -        try
 -        {
 -          // System.err.println("Building ws menu again "
 -          // + Thread.currentThread());
 -          // TODO: add support for context dependent disabling of services based
 -          // on
 -          // alignment and current selection
 -          // TODO: add additional serviceHandle parameter to specify abstract
 -          // handler
 -          // class independently of AbstractName
 -          // TODO: add in rediscovery GUI function to restart discoverer
 -          // TODO: group services by location as well as function and/or
 -          // introduce
 -          // object broker mechanism.
 -          final Vector<JMenu> wsmenu = new Vector<>();
 -          final IProgressIndicator af = me;
 -
 -          /*
 -           * do not i18n these strings - they are hard-coded in class
 -           * compbio.data.msa.Category, Jws2Discoverer.isRecalculable() and
 -           * SequenceAnnotationWSClient.initSequenceAnnotationWSClient()
 -           */
 -          final JMenu msawsmenu = new JMenu("Alignment");
 -          final JMenu secstrmenu = new JMenu(
 -                  "Secondary Structure Prediction");
 -          final JMenu seqsrchmenu = new JMenu("Sequence Database Search");
 -          final JMenu analymenu = new JMenu("Analysis");
 -          final JMenu dismenu = new JMenu("Protein Disorder");
 -          // JAL-940 - only show secondary structure prediction services from
 -          // the legacy server
 -          if (// Cache.getDefault("SHOW_JWS1_SERVICES", true)
 -              // &&
 -          Discoverer.services != null && (Discoverer.services.size() > 0))
 -          {
 -            // TODO: refactor to allow list of AbstractName/Handler bindings to
 -            // be
 -            // stored or retrieved from elsewhere
 -            // No MSAWS used any more:
 -            // Vector msaws = null; // (Vector)
 -            // Discoverer.services.get("MsaWS");
 -            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 = secstrpr.get(i);
 -                jalview.ws.WSMenuEntryProviderI impl = jalview.ws.jws1.Discoverer
 -                        .getServiceClient(sh);
 -                int p = secstrmenu.getItemCount();
 -                impl.attachWSMenuEntry(secstrmenu, me);
 -                int q = secstrmenu.getItemCount();
 -                for (int litm = p; litm < q; litm++)
 -                {
 -                  legacyItems.add(secstrmenu.getItem(litm));
 -                }
 -              }
 -            }
 -          }
 -
 -          // Add all submenus in the order they should appear on the web
 -          // services menu
 -          wsmenu.add(msawsmenu);
 -          wsmenu.add(secstrmenu);
 -          wsmenu.add(dismenu);
 -          wsmenu.add(analymenu);
 -          // No search services yet
 -          // wsmenu.add(seqsrchmenu);
 -
 -          javax.swing.SwingUtilities.invokeLater(new Runnable()
 -          {
 -            @Override
 -            public void run()
 -            {
 -              try
 -              {
 -                webService.removeAll();
 -                // first, add discovered services onto the webservices menu
 -                if (wsmenu.size() > 0)
 -                {
 -                  for (int i = 0, j = wsmenu.size(); i < j; i++)
 -                  {
 -                    webService.add(wsmenu.get(i));
 -                  }
 -                }
 -                else
 -                {
 -                  webService.add(me.webServiceNoServices);
 -                }
 -                // TODO: move into separate menu builder class.
 -                boolean new_sspred = false;
 -                if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
 -                {
 -                  Jws2Discoverer jws2servs = Jws2Discoverer.getDiscoverer();
 -                  if (jws2servs != null)
 -                  {
 -                    if (jws2servs.hasServices())
 -                    {
 -                      jws2servs.attachWSMenuEntry(webService, me);
 -                      for (Jws2Instance sv : jws2servs.getServices())
 -                      {
 -                        if (sv.description.toLowerCase(Locale.ROOT).contains("jpred"))
 -                        {
 -                          for (JMenuItem jmi : legacyItems)
 -                          {
 -                            jmi.setVisible(false);
 -                          }
 -                        }
 -                      }
 -
 -                    }
 -                    if (jws2servs.isRunning())
 -                    {
 -                      JMenuItem tm = new JMenuItem(
 -                              "Still discovering JABA Services");
 -                      tm.setEnabled(false);
 -                      webService.add(tm);
 -                    }
 -                  }
 -                }
 -                build_urlServiceMenu(me.webService);
 -                build_fetchdbmenu(webService);
 -                for (JMenu item : wsmenu)
 -                {
 -                  if (item.getItemCount() == 0)
 -                  {
 -                    item.setEnabled(false);
 -                  }
 -                  else
 -                  {
 -                    item.setEnabled(true);
 -                  }
 -                }
 -              } catch (Exception e)
 -              {
 -                Cache.log.debug(
 -                        "Exception during web service menu building process.",
 -                        e);
 -              }
 -            }
 -          });
 -        } catch (Exception e)
 +        for (ext.vamsas.ServiceHandle sh : secstrpred) 
          {
 +          var menuProvider = Discoverer.getServiceClient(sh);
 +          menuProvider.attachWSMenuEntry(secstrmenu, this);
          }
 -        buildingMenu = false;
        }
 -    }).start();
 +    }
 +    menu.add(secstrmenu);
 +  }
  
 +  /**
 +   * Constructs the web services menu for the given discoverer under the
 +   * specified menu. This method must be called on the EDT
 +   * 
 +   * @param discoverer
 +   *          the discoverer used to build the menu
 +   * @param menu
 +   *          parent component which the elements will be attached to
 +   */
 +  private void buildWebServicesMenu(WSDiscovererI discoverer, JMenu menu)
 +  {
 +    if (discoverer.hasServices())
 +    {
 +      PreferredServiceRegistry.getRegistry().populateWSMenuEntry(
 +              discoverer.getServices(), sv -> buildWebServicesMenu(), menu,
 +              this, null);
 +    }
 +    if (discoverer.isRunning())
 +    {
 +      JMenuItem item = new JMenuItem("Service discovery in progress.");
 +      item.setEnabled(false);
 +      menu.add(item);
 +    }
 +    else if (!discoverer.hasServices())
 +    {
 +      JMenuItem item = new JMenuItem("No services available.");
 +      item.setEnabled(false);
 +      menu.add(item);
 +    }
    }
  
    /**
     * 
     * @param webService
     */
    protected void build_urlServiceMenu(JMenu webService)
    {
      // TODO: remove this code when 2.7 is released
       * JMenuItem testAlView = new JMenuItem("Test AlignmentView"); final
       * AlignFrame af = this; testAlView.addActionListener(new ActionListener() {
       * 
 -     * @Override public void actionPerformed(ActionEvent e) {
 +     *  public void actionPerformed(ActionEvent e) {
       * jalview.datamodel.AlignmentView
       * .testSelectionViews(af.viewport.getAlignment(),
       * af.viewport.getColumnSelection(), af.viewport.selectionGroup); }
     * 
     * @return true if Show Cross-references menu should be enabled
     */
    public boolean canShowProducts()
    {
      SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
          JMenuItem xtype = new JMenuItem(source);
          xtype.addActionListener(new ActionListener()
          {
            @Override
            public void actionPerformed(ActionEvent e)
            {
     * @param source
     *          the database to show cross-references for
     */
    protected void showProductsFor(final SequenceI[] sel, final boolean _odna,
            final String source)
    {
     * Construct and display a new frame containing the translation of this
     * frame's DNA sequences to their aligned protein (amino acid) equivalents.
     */
    @Override
    public void showTranslation_actionPerformed(GeneticCodeI codeTable)
    {
        final String errorTitle = MessageManager
                .getString("label.implementation_error")
                + MessageManager.getString("label.translation_failed");
 -      JvOptionPane.showMessageDialog(Desktop.desktop, msg, errorTitle,
 -              JvOptionPane.ERROR_MESSAGE);
 +      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), msg,
 +              errorTitle, JvOptionPane.ERROR_MESSAGE);
        return;
      }
      if (al == null || al.getHeight() == 0)
                "label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation");
        final String errorTitle = MessageManager
                .getString("label.translation_failed");
 -      JvOptionPane.showMessageDialog(Desktop.desktop, msg, errorTitle,
 -              JvOptionPane.WARNING_MESSAGE);
 +      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), msg,
 +              errorTitle, JvOptionPane.WARNING_MESSAGE);
      }
      else
      {
        if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
        {
          final SequenceI[] seqs = viewport.getSelectionAsNewSequence();
 -        viewport.openSplitFrame(af, new Alignment(seqs));
 +        AlignViewport.openSplitFrame(this, af, new Alignment(seqs));
        }
        else
        {
     * 
     * @param format
     */
    public void setFileFormat(FileFormatI format)
    {
      this.currentFileFormat = format;
     *          access mode of file (see jalview.io.AlignFile)
     * @return true if features file was parsed correctly.
     */
    public boolean parseFeaturesFile(Object file, DataSourceType sourceType)
    {
      // BH 2018
      return avc.parseFeaturesFile(file, sourceType,
 -            Cache.getDefault("RELAXEDSEQIDMATCHING", false));
 +            Cache.getDefault(Preferences.RELAXEDSEQIDMATCHING, false));
  
    }
  
      evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
      Transferable t = evt.getTransferable();
  
 -    final AlignFrame thisaf = this;
      final List<Object> files = new ArrayList<>();
      List<DataSourceType> protocols = new ArrayList<>();
  
      try
      {
        Desktop.transferFromDropTarget(files, protocols, evt, t);
 +      if (files.size() > 0)
 +      {
 +        new Thread(new Runnable()
 +        {
 +
 +          @Override
 +          public void run()
 +          {
 +            loadDroppedFiles(files, protocols, evt, t);
 +          }
 +        }).start();
 +      }
      } catch (Exception e)
      {
        e.printStackTrace();
      }
 -    if (files != null)
 +  }
 +
 +  protected void loadDroppedFiles(List<Object> files,
 +          List<DataSourceType> protocols, DropTargetDropEvent evt,
 +          Transferable t)
 +  {
 +    try
      {
 -      new Thread(new Runnable()
 +      // check to see if any of these files have names matching sequences
 +      // in
 +      // the alignment
 +      SequenceIdMatcher idm = new SequenceIdMatcher(
 +              viewport.getAlignment().getSequencesArray());
 +      /**
 +       * Object[] { String,SequenceI}
 +       */
 +      ArrayList<Object[]> filesmatched = new ArrayList<>();
 +      ArrayList<Object> filesnotmatched = new ArrayList<>();
 +      for (int i = 0; i < files.size(); i++)
        {
 -        @Override
 -        public void run()
 +        // BH 2018
 +        Object fileObj = files.get(i);
 +        String fileName = fileObj.toString();
 +        String pdbfn = "";
 +        DataSourceType protocol = (fileObj instanceof File
 +                ? DataSourceType.FILE
 +                : FormatAdapter.checkProtocol(fileName));
 +        if (protocol == DataSourceType.FILE)
          {
 -          try
 +          File file;
 +          if (fileObj instanceof File)
 +          {
 +            file = (File) fileObj;
 +            Platform.cacheFileData(file);
 +          }
 +          else
 +          {
 +            file = new File(fileName);
 +          }
 +          pdbfn = file.getName();
 +        }
 +        else if (protocol == DataSourceType.URL)
 +        {
 +          URL url = new URL(fileName);
 +          pdbfn = url.getFile();
 +        }
 +        if (pdbfn.length() > 0)
 +        {
 +          // attempt to find a match in the alignment
 +          SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
 +          int l = 0, c = pdbfn.indexOf(".");
 +          while (mtch == null && c != -1)
            {
 -            // check to see if any of these files have names matching sequences
 -            // in
 -            // the alignment
 -            SequenceIdMatcher idm = new SequenceIdMatcher(
 -                    viewport.getAlignment().getSequencesArray());
 -            /**
 -             * Object[] { String,SequenceI}
 -             */
 -            ArrayList<Object[]> filesmatched = new ArrayList<>();
 -            ArrayList<Object> filesnotmatched = new ArrayList<>();
 -            for (int i = 0; i < files.size(); i++)
 +            do
              {
 -              // BH 2018
 -              Object file = files.get(i);
 -              String fileName = file.toString();
 -              String pdbfn = "";
 -              DataSourceType protocol = (file instanceof File
 -                      ? DataSourceType.FILE
 -                      : FormatAdapter.checkProtocol(fileName));
 -              if (protocol == DataSourceType.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(fileName);
 -                pdbfn = url.getFile();
 -              }
 -              if (pdbfn.length() > 0)
 -              {
 -                // attempt to find a match in the alignment
 -                SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
 -                int l = 0, c = pdbfn.indexOf(".");
 -                while (mtch == null && c != -1)
 -                {
 -                  do
 -                  {
 -                    l = c;
 -                  } while ((c = pdbfn.indexOf(".", l)) > l);
 -                  if (l > -1)
 -                  {
 -                    pdbfn = pdbfn.substring(0, l);
 -                  }
 -                  mtch = idm.findAllIdMatches(pdbfn);
 -                }
 -                if (mtch != null)
 -                {
 -                  FileFormatI type;
 -                  try
 -                  {
 -                    type = new IdentifyFile().identify(file, protocol);
 -                  } catch (Exception ex)
 -                  {
 -                    type = null;
 -                  }
 -                  if (type != null && type.isStructureFile())
 -                  {
 -                    filesmatched.add(new Object[] { file, protocol, mtch });
 -                    continue;
 -                  }
 -                }
 -                // File wasn't named like one of the sequences or wasn't a PDB
 -                // file.
 -                filesnotmatched.add(file);
 -              }
 +              l = c;
 +            } while ((c = pdbfn.indexOf(".", l)) > l);
 +            if (l > -1)
 +            {
 +              pdbfn = pdbfn.substring(0, l);
              }
 -            int assocfiles = 0;
 -            if (filesmatched.size() > 0)
 +            mtch = idm.findAllIdMatches(pdbfn);
 +          }
 +          if (mtch != null)
 +          {
 +            FileFormatI type;
 +            try
              {
 -              boolean autoAssociate = Cache
 -                      .getDefault("AUTOASSOCIATE_PDBANDSEQS", false);
 -              if (!autoAssociate)
 -              {
 -                String msg = MessageManager.formatMessage(
 -                        "label.automatically_associate_structure_files_with_sequences_same_name",
 -                        new Object[]
 -                        { Integer.valueOf(filesmatched.size())
 -                                .toString() });
 -                String ttl = MessageManager.getString(
 -                        "label.automatically_associate_structure_files_by_name");
 -                int choice = JvOptionPane.showConfirmDialog(thisaf, msg,
 -                        ttl, JvOptionPane.YES_NO_OPTION);
 -                autoAssociate = choice == JvOptionPane.YES_OPTION;
 -              }
 -              if (autoAssociate)
 -              {
 -                for (Object[] fm : filesmatched)
 -                {
 -                  // try and associate
 -                  // TODO: may want to set a standard ID naming formalism for
 -                  // associating PDB files which have no IDs.
 -                  for (SequenceI toassoc : (SequenceI[]) fm[2])
 -                  {
 -                    PDBEntry pe = new AssociatePdbFileWithSeq()
 -                            .associatePdbWithSeq(fm[0].toString(),
 -                                    (DataSourceType) fm[1], toassoc, false,
 -                                    Desktop.instance);
 -                    if (pe != null)
 -                    {
 -                      System.err.println("Associated file : "
 -                              + (fm[0].toString()) + " with "
 -                              + toassoc.getDisplayId(true));
 -                      assocfiles++;
 -                    }
 -                  }
 -                  // TODO: do we need to update overview ? only if features are
 -                  // shown I guess
 -                  alignPanel.paintAlignment(true, false);
 -                }
 -              }
 -              else
 -              {
 -                /*
 -                 * add declined structures as sequences
 -                 */
 -                for (Object[] o : filesmatched)
 -                {
 -                  filesnotmatched.add(o[0]);
 -                }
 -              }
 +              type = new IdentifyFile().identify(fileObj, protocol);
 +            } catch (Exception ex)
 +            {
 +              type = null;
              }
 -            if (filesnotmatched.size() > 0)
 +            if (type != null && type.isStructureFile())
              {
 -              if (assocfiles > 0 && (Cache.getDefault(
 -                      "AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
 -                      || JvOptionPane.showConfirmDialog(thisaf,
 -                              "<html>" + MessageManager.formatMessage(
 -                                      "label.ignore_unmatched_dropped_files_info",
 -                                      new Object[]
 -                                      { Integer.valueOf(
 -                                              filesnotmatched.size())
 -                                              .toString() })
 -                                      + "</html>",
 -                              MessageManager.getString(
 -                                      "label.ignore_unmatched_dropped_files"),
 -                              JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
 -              {
 -                return;
 -              }
 -              for (Object fn : filesnotmatched)
 +              filesmatched.add(new Object[] { fileObj, protocol, mtch });
 +              continue;
 +            }
 +          }
 +          // File wasn't named like one of the sequences or wasn't a PDB
 +          // file.
 +          filesnotmatched.add(fileObj);
 +        }
 +      }
 +      int assocfiles = 0;
 +      if (filesmatched.size() > 0)
 +      {
 +        boolean autoAssociate = Cache
 +                .getDefault(Preferences.AUTOASSOCIATE_PDBANDSEQS, false);
 +        if (!autoAssociate)
 +        {
 +          String msg = MessageManager.formatMessage(
 +                  "label.automatically_associate_structure_files_with_sequences_same_name",
 +                  new Object[]
 +                  { Integer.valueOf(filesmatched.size()).toString() });
 +          String ttl = MessageManager.getString(
 +                  "label.automatically_associate_structure_files_by_name");
 +          int choice = JvOptionPane.showConfirmDialog(this, msg, ttl,
 +                  JvOptionPane.YES_NO_OPTION);
 +          autoAssociate = choice == JvOptionPane.YES_OPTION;
 +        }
 +        if (autoAssociate)
 +        {
 +          for (Object[] fm : filesmatched)
 +          {
 +            // try and associate
 +            // TODO: may want to set a standard ID naming formalism for
 +            // associating PDB files which have no IDs.
 +            for (SequenceI toassoc : (SequenceI[]) fm[2])
 +            {
 +              PDBEntry pe = AssociatePdbFileWithSeq.associatePdbWithSeq(
 +                      fm[0].toString(), (DataSourceType) fm[1], toassoc,
 +                      false);
 +              if (pe != null)
                {
 -                loadJalviewDataFile(fn, null, null, null);
 +                System.err.println("Associated file : " + (fm[0].toString())
 +                        + " with " + toassoc.getDisplayId(true));
 +                assocfiles++;
                }
              }
 -          } catch (Exception ex)
 +            // TODO: do we need to update overview ? only if features are
 +            // shown I guess
 +            alignPanel.paintAlignment(true, false);
 +          }
 +        }
 +        else
 +        {
 +          /*
 +           * add declined structures as sequences
 +           */
 +          for (Object[] o : filesmatched)
            {
 -            ex.printStackTrace();
 +            filesnotmatched.add(o[0]);
            }
          }
 -      }).start();
 +      }
 +      if (filesnotmatched.size() > 0)
 +      {
 +        if (assocfiles > 0 && (Cache
 +                .getDefault("AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
 +                || JvOptionPane.showConfirmDialog(this,
 +                        "<html>" + MessageManager.formatMessage(
 +                                "label.ignore_unmatched_dropped_files_info",
 +                                new Object[]
 +                                { Integer.valueOf(filesnotmatched.size())
 +                                        .toString() })
 +                                + "</html>",
 +                        MessageManager.getString(
 +                                "label.ignore_unmatched_dropped_files"),
 +                        JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
 +        {
 +          return;
 +        }
 +        for (Object fn : filesnotmatched)
 +        {
 +          loadJalviewDataFile(fn, null, null, null);
 +        }
 +
 +      }
 +    } catch (Exception ex)
 +    {
 +      ex.printStackTrace();
      }
    }
  
     * 
     * @param file
     *          either a filename or a URL string.
 +   * @throws InterruptedException
 +   * @throws IOException
     */
    public void loadJalviewDataFile(Object file, DataSourceType sourceType,
            FileFormatI format, SequenceI assocSeq)
    {
              {
                // some problem - if no warning its probable that the ID matching
                // process didn't work
 -              JvOptionPane.showMessageDialog(Desktop.desktop,
 +              JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                        tcf.getWarningMessage() == null
                                ? MessageManager.getString(
                                        "label.check_file_matches_sequence_ids_alignment")
        }
        if (isAnnotation)
        {
 -        alignPanel.adjustAnnotationHeight();
 -        viewport.updateSequenceIdColours();
 -        buildSortByAnnotationScoresMenu();
 -        alignPanel.paintAlignment(true, true);
 +        updateForAnnotations();
        }
      } catch (Exception ex)
      {
                        + (format != null
                                ? "(parsing as '" + format + "' file)"
                                : ""),
 -              oom, Desktop.desktop);
 +              oom, Desktop.getDesktopPane());
 +    }
 +  }
 +
 +  /**
 +   * Do all updates necessary after an annotation file such as jnet. Also called
 +   * from Jalview.loadAppletParams for "annotations", "jnetFile"
 +   */
 +
 +  public void updateForAnnotations()
 +  {
 +    alignPanel.adjustAnnotationHeight();
 +    viewport.updateSequenceIdColours();
 +    buildSortByAnnotationScoresMenu();
 +    alignPanel.paintAlignment(true, true);
 +  }
 +
 +  /**
 +   * Change the display state for the given feature groups -- Added by BH from
 +   * JalviewLite
 +   * 
 +   * @param groups
 +   *          list of group strings
 +   * @param state
 +   *          visible or invisible
 +   */
 +
 +  public void setFeatureGroupState(String[] groups, boolean state)
 +  {
 +    jalview.api.FeatureRenderer fr = null;
 +    viewport.setShowSequenceFeatures(true);
 +    if (alignPanel != null
 +            && (fr = alignPanel.getFeatureRenderer()) != null)
 +    {
 +
 +      fr.setGroupVisibility(Arrays.asList(groups), state);
 +      alignPanel.getSeqPanel().seqCanvas.repaint();
 +      if (alignPanel.overviewPanel != null)
 +      {
 +        alignPanel.overviewPanel.updateOverviewImage();
 +      }
      }
    }
  
     * Method invoked by the ChangeListener on the tabbed pane, in other words
     * when a different tabbed pane is selected by the user or programmatically.
     */
    @Override
    public void tabSelectionChanged(int index)
    {
    /**
     * On right mouse click on view tab, prompt for and set new view name.
     */
    @Override
    public void tabbedPane_mousePressed(MouseEvent e)
    {
    /**
     * Open the dialog for regex description parsing.
     */
    @Override
    protected void extractScores_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showDbRefs_actionPerformed(java.awt.event.ActionEvent
     * )
     */
    @Override
    protected void showDbRefs_actionPerformed(ActionEvent e)
    {
     * @seejalview.jbgui.GAlignFrame#showNpFeats_actionPerformed(java.awt.event.
     * ActionEvent)
     */
    @Override
    protected void showNpFeats_actionPerformed(ActionEvent e)
    {
     * 
     * @param av
     */
    public boolean closeView(AlignViewportI av)
    {
      if (viewport == av)
              Cache.getDefault(DBRefFetcher.TRIM_RETRIEVED_SEQUENCES, true));
      trimrs.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
        {
          new Thread(new Runnable()
          {
            @Override
            public void run()
            {
                      alignPanel.alignFrame.featureSettings, isNucleotide);
              dbRefFetcher.addListener(new FetchFinishedListenerI()
              {
                @Override
                public void finished()
                {
      rfetch.add(fetchr);
      new Thread(new Runnable()
      {
        @Override
        public void run()
        {
 -        final jalview.ws.SequenceFetcher sf = jalview.gui.SequenceFetcher
 -                .getSequenceFetcherSingleton();
 +        // ??
 +        // final jalview.ws.SequenceFetcher sf = jalview.gui.SequenceFetcher
 +        // .getSequenceFetcherSingleton();
          javax.swing.SwingUtilities.invokeLater(new Runnable()
          {
            @Override
            public void run()
            {
 +            jalview.ws.SequenceFetcher sf = jalview.ws.SequenceFetcher
 +                    .getInstance();
              String[] dbclasses = sf.getNonAlignmentSources();
              List<DbSourceProxy> otherdb;
              JMenu dfetch = new JMenu();
                }
                if (otherdb.size() == 1)
                {
 -                final DbSourceProxy[] dassource = otherdb
 -                        .toArray(new DbSourceProxy[0]);
                  DbSourceProxy src = otherdb.get(0);
 +                DbSourceProxy[] dassource = new DbSourceProxy[] { src };
                  fetchr = new JMenuItem(src.getDbSource());
                  fetchr.addActionListener(new ActionListener()
                  {
                          dbRefFetcher
                                  .addListener(new FetchFinishedListenerI()
                                  {
                                    @Override
                                    public void finished()
                                    {
                          { src.getDbSource() }));
                  fetchr.addActionListener(new ActionListener()
                  {
                    @Override
                    public void actionPerformed(ActionEvent e)
                    {
                          dbRefFetcher
                                  .addListener(new FetchFinishedListenerI()
                                  {
                                    @Override
                                    public void finished()
                                    {
                            dbRefFetcher
                                    .addListener(new FetchFinishedListenerI()
                                    {
                                      @Override
                                      public void finished()
                                      {
    /**
     * Left justify the whole alignment.
     */
    @Override
    protected void justifyLeftMenuItem_actionPerformed(ActionEvent e)
    {
 -    AlignmentI al = viewport.getAlignment();
 -    al.justify(false);
 -    viewport.firePropertyChange("alignment", null, al);
 +    viewport.getAlignment().justify(false);
 +    viewport.notifyAlignment();
    }
  
    /**
     * Right justify the whole alignment.
     */
    @Override
    protected void justifyRightMenuItem_actionPerformed(ActionEvent e)
    {
 -    AlignmentI al = viewport.getAlignment();
 -    al.justify(true);
 -    viewport.firePropertyChange("alignment", null, al);
 +    viewport.getAlignment().justify(true);
 +    viewport.notifyAlignment();
    }
  
    @Override
     * jalview.jbgui.GAlignFrame#showUnconservedMenuItem_actionPerformed(java.
     * awt.event.ActionEvent)
     */
    @Override
    protected void showUnconservedMenuItem_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showGroupConsensus_actionPerformed(java.awt.event
     * .ActionEvent)
     */
    @Override
    protected void showGroupConsensus_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showGroupConservation_actionPerformed(java.awt
     * .event.ActionEvent)
     */
    @Override
    protected void showGroupConservation_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showConsensusHistogram_actionPerformed(java.awt
     * .event.ActionEvent)
     */
    @Override
    protected void showConsensusHistogram_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#showConsensusProfile_actionPerformed(java.awt
     * .event.ActionEvent)
     */
    @Override
    protected void showSequenceLogo_actionPerformed(ActionEvent e)
    {
     * jalview.jbgui.GAlignFrame#makeGrpsFromSelection_actionPerformed(java.awt
     * .event.ActionEvent)
     */
    @Override
    protected void makeGrpsFromSelection_actionPerformed(ActionEvent e)
    {
     * 
     * @param alignmentPanel
     */
    public void setDisplayedView(AlignmentPanel alignmentPanel)
    {
      if (!viewport.getSequenceSetId()
     * @param forAlignment
     *          update non-sequence-related annotations
     */
    @Override
    protected void setAnnotationsVisibility(boolean visible,
            boolean forSequences, boolean forAlignment)
    /**
     * Store selected annotation sort order for the view and repaint.
     */
    @Override
    protected void sortAnnotations_actionPerformed()
    {
     * 
     * @return alignment panels in this alignment frame
     */
    public List<? extends AlignmentViewPanel> getAlignPanels()
    {
      // alignPanels is never null
     * Open a new alignment window, with the cDNA associated with this (protein)
     * alignment, aligned as is the protein.
     */
    protected void viewAsCdna_actionPerformed()
    {
      // TODO no longer a menu action - refactor as required
     * 
     * @param show
     */
    @Override
    protected void showComplement_actionPerformed(boolean show)
    {
     * Generate the reverse (optionally complemented) of the selected sequences,
     * and add them to the alignment
     */
    @Override
    protected void showReverse_actionPerformed(boolean complement)
    {
     * AlignFrame is set as currentAlignFrame in Desktop, to allow the script to
     * be targeted at this alignment.
     */
    @Override
    protected void runGroovy_actionPerformed()
    {
        } catch (Exception ex)
        {
          System.err.println((ex.toString()));
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                  MessageManager.getString("label.couldnt_run_groovy_script"),
                  MessageManager.getString("label.groovy_support_failed"),
                  JvOptionPane.ERROR_MESSAGE);
     * @param columnsContaining
     * @return
     */
    public boolean hideFeatureColumns(String featureType,
            boolean columnsContaining)
    {
     * Rebuilds the Colour menu, including any user-defined colours which have
     * been loaded either on startup or during the session
     */
    public void buildColourMenu()
    {
      colourMenu.removeAll();
     * Open a dialog (if not already open) that allows the user to select and
     * calculate PCA or Tree analysis
     */
    protected void openTreePcaDialog()
    {
      if (alignPanel.getCalculationDialog() == null)
      }
    }
  
 +  /**
 +   * Sets the status of the HMMER menu
 +   */
 +  public void updateHMMERStatus()
 +  {
 +    hmmerMenu.setEnabled(HmmerCommand.isHmmerAvailable());
 +  }
    @Override
    protected void loadVcf_actionPerformed()
    {
      final AlignFrame us = this;
      chooser.setResponseHandler(0, new Runnable()
      {
        @Override
        public void run()
        {
    }
  
    private Rectangle lastFeatureSettingsBounds = null;
--
    @Override
    public void setFeatureSettingsGeometry(Rectangle bounds)
    {
    {
      return lastFeatureSettingsBounds;
    }
 -}
  
 -class PrintThread extends Thread
 -{
 -  AlignmentPanel ap;
 +  public void scrollTo(int row, int column)
 +  {
 +    alignPanel.getSeqPanel().scrollTo(row, column);
 +  }
  
 -  public PrintThread(AlignmentPanel ap)
 +  public void scrollToRow(int row)
    {
 -    this.ap = ap;
 +    alignPanel.getSeqPanel().scrollToRow(row);
    }
  
 -  static PageFormat pf;
 +  public void scrollToColumn(int column)
 +  {
 +    alignPanel.getSeqPanel().scrollToColumn(column);
 +  }
  
 -  @Override
 -  public void run()
 +  /**
 +   * BH 2019 from JalviewLite
 +   * 
 +   * get sequence feature groups that are hidden or shown
 +   * 
 +   * @param visible
 +   *          true is visible
 +   * @return list
 +   */
 +
 +  public String[] getFeatureGroupsOfState(boolean visible)
    {
 -    PrinterJob printJob = PrinterJob.getPrinterJob();
 +    jalview.api.FeatureRenderer fr = null;
 +    if (alignPanel != null
 +            && (fr = alignPanel.getFeatureRenderer()) != null)
 +    {
 +      List<String> gps = fr.getGroups(visible);
 +      String[] _gps = gps.toArray(new String[gps.size()]);
 +      return _gps;
 +    }
 +    return null;
 +  }
 +
 +  /**
 +   * 
 +   * @return list of feature groups on the view
 +   */
  
 -    if (pf != null)
 +  public String[] getFeatureGroups()
 +  {
 +    jalview.api.FeatureRenderer fr = null;
 +    if (alignPanel != null
 +            && (fr = alignPanel.getFeatureRenderer()) != null)
      {
 -      printJob.setPrintable(ap, pf);
 +      List<String> gps = fr.getFeatureGroups();
 +      String[] _gps = gps.toArray(new String[gps.size()]);
 +      return _gps;
      }
 -    else
 +    return null;
 +  }
 +
 +  public void select(SequenceGroup sel, ColumnSelection csel,
 +          HiddenColumns hidden)
 +  {
 +    alignPanel.getSeqPanel().selection(sel, csel, hidden, null);
 +  }
 +
 +  public int getID()
 +  {
 +    return id;
 +  }
 +
 +  static class PrintThread extends Thread
 +  {
 +    AlignmentPanel ap;
 +
 +    public PrintThread(AlignmentPanel ap)
      {
 -      printJob.setPrintable(ap);
 +      this.ap = ap;
      }
  
 -    if (printJob.printDialog())
 +    static PageFormat pf;
 +
 +    @Override
 +    public void run()
      {
 -      try
 +      PrinterJob printJob = PrinterJob.getPrinterJob();
 +
 +      if (pf != null)
 +      {
 +        printJob.setPrintable(ap, pf);
 +      }
 +      else
        {
 -        printJob.print();
 -      } catch (Exception PrintException)
 +        printJob.setPrintable(ap);
 +      }
 +
 +      if (printJob.printDialog())
        {
 -        PrintException.printStackTrace();
 +        try
 +        {
 +          printJob.print();
 +        } catch (Exception PrintException)
 +        {
 +          PrintException.printStackTrace();
 +        }
        }
      }
    }
  }
@@@ -39,7 -39,6 +39,7 @@@ import jalview.datamodel.SearchResults
  import jalview.datamodel.SearchResultsI;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
 +import jalview.datamodel.features.FeatureMatcherSetI;
  import jalview.renderer.ResidueShader;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ColourSchemeProperty;
@@@ -60,7 -59,6 +60,7 @@@ import java.awt.FontMetrics
  import java.awt.Rectangle;
  import java.util.ArrayList;
  import java.util.Hashtable;
 +import java.util.Iterator;
  import java.util.List;
  
  import javax.swing.JInternalFrame;
  public class AlignViewport extends AlignmentViewport
          implements SelectionSource
  {
 +  public final static int NO_SPLIT = 0;
 +
 +  public final static int SPLIT_FRAME = 1;
 +
 +  public final static int NEW_WINDOW = 2;
    Font font;
  
    boolean cursorMode = false;
     * @param hiddenColumns
     * @param seqsetid
     *          (may be null)
- f   */
+    */
    public AlignViewport(AlignmentI al, HiddenColumns hiddenColumns,
            String seqsetid)
    {
  
      setRightAlignIds(Cache.getDefault("RIGHT_ALIGN_IDS", false));
      setCentreColumnLabels(Cache.getDefault("CENTRE_COLUMN_LABELS", false));
 -    autoCalculateConsensus = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
 +    autoCalculateConsensusAndConservation = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
  
      setPadGaps(Cache.getDefault("PAD_GAPS", true));
      setShowNPFeats(Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true));
  
      setFont(new Font(fontName, style, Integer.parseInt(fontSize)), true);
  
 -    alignment
 -            .setGapCharacter(Cache.getDefault("GAP_SYMBOL", "-").charAt(0));
 +              alignment.setGapCharacter(Cache.getDefault("GAP_SYMBOL", "-").charAt(0));
  
      // We must set conservation and consensus before setting colour,
      // as Blosum and Clustal require this to be done
 -    if (hconsensus == null && !isDataset)
 +              if (hconsensus == null && !isDataset)
      {
 -      if (!alignment.isNucleotide())
 +                      if (!alignment.isNucleotide())
        {
          showConservation = Cache.getDefault("SHOW_CONSERVATION", true);
          showQuality = Cache.getDefault("SHOW_QUALITY", true);
        showSequenceLogo = Cache.getDefault("SHOW_CONSENSUS_LOGO", false);
        normaliseSequenceLogo = Cache.getDefault("NORMALISE_CONSENSUS_LOGO",
                false);
 +      // for now, use consensus options for Information till it gets its own
 +      setShowHMMSequenceLogo(showSequenceLogo);
 +      setNormaliseHMMSequenceLogo(normaliseSequenceLogo);
 +      setShowInformationHistogram(showConsensusHistogram);
        showGroupConsensus = Cache.getDefault("SHOW_GROUP_CONSENSUS", false);
        showConsensus = Cache.getDefault("SHOW_IDENTITY", true);
  
        showOccupancy = Cache.getDefault(Preferences.SHOW_OCCUPANCY, true);
      }
      initAutoAnnotation();
 -    String colourProperty = alignment.isNucleotide()
 +    // initInformation();
 +
 +              String colourProperty = alignment.isNucleotide()
              ? Preferences.DEFAULT_COLOUR_NUC
              : Preferences.DEFAULT_COLOUR_PROT;
      String schemeName = Cache.getProperty(colourProperty);
  
      if (residueShading != null)
      {
 -      residueShading.setConsensus(hconsensus);
 +                      residueShading.setConsensus(hconsensus);
      }
      setColourAppliesToAllGroups(true);
    }
 +  
    boolean validCharWidth;
  
    /**
      if (align != null)
      {
        StructureSelectionManager ssm = StructureSelectionManager
 -              .getStructureSelectionManager(Desktop.instance);
 +              .getStructureSelectionManager(Desktop.getInstance());
        ssm.registerMappings(align.getCodonFrames());
      }
  
      /*
       * replace mappings on our alignment
       */
 -    if (alignment != null && align != null)
 +              if (alignment != null && align != null)
      {
        alignment.setCodonFrames(align.getCodonFrames());
      }
        if (mappings != null)
        {
          StructureSelectionManager ssm = StructureSelectionManager
 -                .getStructureSelectionManager(Desktop.instance);
 +                .getStructureSelectionManager(Desktop.getInstance());
          for (AlignedCodonFrame acf : mappings)
          {
            if (noReferencesTo(acf))
    }
  
    /**
-    * Returns an iterator over the visible column regions of the alignment
-    * 
-    * @param selectedRegionOnly
-    *          true to just return the contigs intersecting with the selected
-    *          area
-    * @return
-    */
-   public Iterator<int[]> getViewAsVisibleContigs(boolean selectedRegionOnly)
-   {
-     int start = 0;
-     int end = 0;
-     if (selectedRegionOnly && selectionGroup != null)
-     {
-       start = selectionGroup.getStartRes();
-       end = selectionGroup.getEndRes() + 1;
-     }
-     else
-     {
-       end = alignment.getWidth();
-     }
-     return (alignment.getHiddenColumns().getVisContigsIterator(start,
-             end, false));
-   }
-   /**
     * get hash of undo and redo list for the alignment
     * 
     * @return long[] { historyList.hashCode, redoList.hashCode };
    }
  
    public boolean followSelection = true;
-   
    /**
     * @return true if view selection should always follow the selections
     *         broadcast by other selection sources
    public void sendSelection()
    {
      jalview.structure.StructureSelectionManager
 -            .getStructureSelectionManager(Desktop.instance)
 +            .getStructureSelectionManager(Desktop.getInstance())
              .sendSelection(new SequenceGroup(getSelectionGroup()),
                      new ColumnSelection(getColumnSelection()),
                      new HiddenColumns(getAlignment().getHiddenColumns()),
    public StructureSelectionManager getStructureSelectionManager()
    {
      return StructureSelectionManager
 -            .getStructureSelectionManager(Desktop.instance);
 +            .getStructureSelectionManager(Desktop.getInstance());
    }
 +  
    @Override
    public boolean isNormaliseSequenceLogo()
    {
      return normaliseSequenceLogo;
    }
  
 -  public void setNormaliseSequenceLogo(boolean state)
 +  @Override
 +public void setNormaliseSequenceLogo(boolean state)
    {
      normaliseSequenceLogo = state;
    }
  
    /**
     * 
     * @return true if alignment characters should be displayed
    {
      return validCharWidth;
    }
 -
 +  
    private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<>();
  
    public AutoCalcSetting getCalcIdSettingsFor(String calcId)
      }
  
      ranges.setEndSeq(getAlignment().getHeight() - 1); // BH 2019.04.18
 -    firePropertyChange("alignment", null, getAlignment().getSequences());
 +    notifyAlignment();
    }
  
    /**
       * dialog responses 0, 1, 2 (even though JOptionPane shows them
       * in reverse order)
       */
 -    JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.desktop)
 -            .setResponseHandler(0, new Runnable()
 +    JvOptionPane dialog = JvOptionPane.newOptionDialog(Desktop.getDesktopPane())
 +            .setResponseHandler(NO_SPLIT, new Runnable()
              {
                @Override
                public void run()
                {
                    addDataToAlignment(al);
                }
 -            }).setResponseHandler(1, new Runnable()
 +            }).setResponseHandler(SPLIT_FRAME, new Runnable()
              {
                @Override
                public void run()
                {
 -                us.openLinkedAlignmentAs(al, title, true);
++                // Make a copy of this one to open it in a splitframe
 +                openLinkedAlignmentAs(getAlignPanel().alignFrame,
 +                        new Alignment(getAlignment()), al, title,
 +                        SPLIT_FRAME);
- //                us.openLinkedAlignmentAs(al, title, true);
                }
 -            }).setResponseHandler(2, new Runnable()
 +            }).setResponseHandler(NEW_WINDOW, new Runnable()
              {
                @Override
                public void run()
                {
 -                us.openLinkedAlignmentAs(al, title, false);
 +                openLinkedAlignmentAs(null, getAlignment(), al, title,
 +                        NEW_WINDOW);
                }
              });
 -      dialog.showDialog(question,
 +      dialog.showDialog(question,
              MessageManager.getString("label.open_split_window"),
              JvOptionPane.DEFAULT_OPTION, JvOptionPane.PLAIN_MESSAGE, null,
              options, options[0]);
    }
 -  protected void openLinkedAlignmentAs(AlignmentI al, String title,
 -          boolean newWindowOrSplitPane)
 -    {
 +  /**
 +   * Open a split frame or a new window
 +   * 
 +   * @param al
 +   * @param title
 +   * @param mode
 +   *          SPLIT_FRAME or NEW_WINDOW
 +   */
 +  public static void openLinkedAlignmentAs(AlignFrame thisFrame,
 +          AlignmentI thisAlignment, AlignmentI al, String title, int mode)
 +  {
      /*
--     * Identify protein and dna alignments. Make a copy of this one if opening
--     * in a new split pane.
++     * Identify protein and dna alignments. 
       */
 -    AlignmentI thisAlignment = newWindowOrSplitPane
 -            ? new Alignment(getAlignment())
 -            : getAlignment();
      AlignmentI protein = al.isNucleotide() ? thisAlignment : al;
 -    final AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
 +    AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
      /*
       * Map sequences. At least one should get mapped as we have already passed
       * the test for 'mappability'. Any mappings made will be added to the
      // alignFrame.setFileName(file, format);
      // }
  
 -    if (!newWindowOrSplitPane)
 +    if (mode == NEW_WINDOW)
      {
        Desktop.addInternalFrame(newAlignFrame, title,
                AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
      {
      }
  
 -    if (newWindowOrSplitPane)
 +    if (mode == SPLIT_FRAME)
      {
        al.alignAs(thisAlignment);
 -      protein = openSplitFrame(newAlignFrame, thisAlignment);
 +      openSplitFrame(thisFrame, newAlignFrame, thisAlignment);
      }
    }
  
     *          cdna/protein complement alignment to show in the other split half
     * @return the protein alignment in the split frame
     */
 -  protected AlignmentI openSplitFrame(AlignFrame newAlignFrame,
 -          AlignmentI complement)
 +  static protected AlignmentI openSplitFrame(AlignFrame thisFrame,
 +          AlignFrame newAlignFrame, AlignmentI complement)
    {
      /*
       * Make a new frame with a copy of the alignment we are adding to. If this
       */
      AlignFrame copyMe = new AlignFrame(complement, AlignFrame.DEFAULT_WIDTH,
              AlignFrame.DEFAULT_HEIGHT);
 -    copyMe.setTitle(getAlignPanel().alignFrame.getTitle());
 +    copyMe.setTitle(thisFrame.getTitle());
  
      AlignmentI al = newAlignFrame.viewport.getAlignment();
      final AlignFrame proteinFrame = al.isNucleotide() ? copyMe
              : newAlignFrame;
      
      FeatureRenderer fr = getAlignPanel().getSeqPanel().seqCanvas
              .getFeatureRenderer();
 -    List<String> origRenderOrder = new ArrayList<>();
 -    List<String> origGroups = new ArrayList<>();
 +    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());
      if (!mergeOnly)
      {
        // only clear displayed features if we are mergeing
 -      // displayed.clear();
 +      displayed.clear();
      }
      // TODO this clears displayed.featuresRegistered - do we care?
      //
      {
        FeatureColourI preferredColour = featureSettings
                .getFeatureColour(type);
 +      FeatureMatcherSetI preferredFilters = featureSettings
 +              .getFeatureFilters(type);
        FeatureColourI origColour = fr.getFeatureStyle(type);
        if (!mergeOnly || (!origRenderOrder.contains(type)
                || origColour == null
          {
            fr.setColour(type, preferredColour);
          }
 +        if (preferredFilters != null
 +                && (!mergeOnly || fr.getFeatureFilter(type) != null))
 +        {
 +          fr.setFeatureFilter(type, preferredFilters);
 +        }
          if (featureSettings.isFeatureDisplayed(type))
          {
            displayed.setVisible(type);
          }
+         else if (featureSettings.isFeatureHidden(type))
+         {
+           displayed.setHidden(type);
+         }
        }
      }
  
        fr.orderFeatures(featureSettings);
      }
      fr.setTransparency(featureSettings.getTransparency());
 -
+     fr.notifyFeaturesChanged();
    }
  
    public String getViewName()
    {
      this.viewName = viewName;
    }
  }
@@@ -53,8 -53,6 +53,6 @@@ import net.miginfocom.swing.MigLayout
  @SuppressWarnings("serial")
  public class AnnotationColourChooser extends AnnotationRowFilter
  {
-   private static final int ONETHOUSAND = 1000;
    private ColourSchemeI oldcs;
  
    private JButton defColours;
                  "error.implementation_error_dont_know_about_threshold_setting"));
        }
        thresholdIsMin.setSelected(acg.isThresholdIsMinMax());
-       thresholdValue.setText("" + acg.getAnnotationThreshold());
+       thresholdValue
+               .setText(String.valueOf(acg.getAnnotationThreshold()));
      }
  
      jbInit();
          updateView();
        }
      };
 -    JalviewColourChooser.showColourChooser(Desktop.getDesktop(), ttl,
 +    JalviewColourChooser.showColourChooser(Desktop.getDesktopPane(), ttl,
              colourPanel.getBackground(), listener);
    }
  
        {
          updateView();
        }
-       getCurrentAnnotation().threshold.value = slider.getValue() / 1000f;
+       getCurrentAnnotation().threshold.value = getSliderValue();
        propagateSeqAssociatedThreshold(updateAllAnnotation,
                getCurrentAnnotation());
        ap.paintAlignment(false, false);
      thresholdValue.setEnabled(true);
      thresholdIsMin.setEnabled(!useOriginalColours.isSelected());
  
+     final AlignmentAnnotation currentAnnotation = getCurrentAnnotation();
      if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
      {
        slider.setEnabled(false);
        thresholdIsMin.setEnabled(false);
      }
      else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD
-             && getCurrentAnnotation().threshold == null)
+             && currentAnnotation.threshold == null)
      {
-       getCurrentAnnotation().setThreshold(new GraphLine(
-               (getCurrentAnnotation().graphMax
-                       - getCurrentAnnotation().graphMin) / 2f,
+       currentAnnotation.setThreshold(new GraphLine(
+               (currentAnnotation.graphMax - currentAnnotation.graphMin)
+                       / 2f,
                "Threshold", Color.black));
      }
  
      if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
      {
        adjusting = true;
-       float range = getCurrentAnnotation().graphMax * ONETHOUSAND
-               - getCurrentAnnotation().graphMin * ONETHOUSAND;
-       slider.setMinimum(
-               (int) (getCurrentAnnotation().graphMin * ONETHOUSAND));
-       slider.setMaximum(
-               (int) (getCurrentAnnotation().graphMax * ONETHOUSAND));
-       slider.setValue(
-               (int) (getCurrentAnnotation().threshold.value * ONETHOUSAND));
-       thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
-       slider.setMajorTickSpacing((int) (range / 10f));
+       setSliderModel(currentAnnotation.graphMin, currentAnnotation.graphMax,
+               currentAnnotation.threshold.value);
        slider.setEnabled(true);
+       setThresholdValueText();
        thresholdValue.setEnabled(true);
        adjusting = false;
      }
-     colorAlignmentContaining(getCurrentAnnotation(), selectedThresholdItem);
+     colorAlignmentContaining(currentAnnotation, selectedThresholdItem);
  
      ap.alignmentChanged();
    }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.gui;
  
+ import java.util.Locale;
  import jalview.api.FeatureRenderer;
  import jalview.bin.Cache;
  import jalview.datamodel.AlignmentAnnotation;
@@@ -109,8 -111,8 +111,8 @@@ public class AnnotationExporter extend
      frame.setContentPane(this);
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
      Dimension preferredSize = frame.getPreferredSize();
 -    Desktop.addInternalFrame(frame, "", true, preferredSize.width,
 -            preferredSize.height, true, true);
 +    Desktop.addInternalFrame(frame, "", Desktop.FRAME_MAKE_VISIBLE, preferredSize.width,
 +            preferredSize.height, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_ALLOW_ANY_SIZE);
    }
  
    /**
  
      boolean nucleotide = ap.av.isNucleotide();
      String complement = nucleotide
-             ? MessageManager.getString("label.protein").toLowerCase()
+             ? MessageManager.getString("label.protein").toLowerCase(Locale.ROOT)
              : "CDS";
      JLabel label = new JLabel(
              MessageManager.formatMessage("label.include_linked_features",
   */
  package jalview.gui;
  
+ import java.util.Locale;
+ import jalview.analysis.AlignSeq;
+ import jalview.analysis.AlignmentUtils;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.Annotation;
+ import jalview.datamodel.HiddenColumns;
+ import jalview.datamodel.Sequence;
+ import jalview.datamodel.SequenceGroup;
+ import jalview.datamodel.SequenceI;
+ import jalview.io.FileFormat;
+ import jalview.io.FormatAdapter;
+ import jalview.util.Comparison;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
++import jalview.workers.InformationThread;
  import java.awt.Color;
  import java.awt.Cursor;
  import java.awt.Dimension;
@@@ -47,22 -64,6 +65,6 @@@ import javax.swing.JPopupMenu
  import javax.swing.SwingUtilities;
  import javax.swing.ToolTipManager;
  
- import jalview.analysis.AlignSeq;
- import jalview.analysis.AlignmentUtils;
- import jalview.datamodel.Alignment;
- import jalview.datamodel.AlignmentAnnotation;
- import jalview.datamodel.Annotation;
- import jalview.datamodel.HiddenColumns;
- import jalview.datamodel.Sequence;
- import jalview.datamodel.SequenceGroup;
- import jalview.datamodel.SequenceI;
- import jalview.io.FileFormat;
- import jalview.io.FormatAdapter;
- import jalview.util.Comparison;
- import jalview.util.MessageManager;
- import jalview.util.Platform;
- import jalview.workers.InformationThread;
  /**
   * The panel that holds the labels for alignment annotations, providing
   * tooltips, context menus, drag to reorder rows, and drag to adjust panel
@@@ -353,10 -354,6 +355,8 @@@ public class AnnotationLabels extends J
        pop.show(this, evt.getX(), evt.getY());
        return;
      }
 +    final AlignmentAnnotation ann = aa[selectedRow];
 +    final boolean isSequenceAnnotation = ann.sequenceRef != null;
      item = new JMenuItem(EDITNAME);
      item.addActionListener(this);
      pop.add(item);
      if (selectedRow < aa.length)
      {
        final String label = aa[selectedRow].label;
 -      if (!aa[selectedRow].autoCalculated)
 +      if (!(aa[selectedRow].autoCalculated)
 +              && !(InformationThread.HMM_CALC_ID.equals(ann.getCalcId())))
        {
          if (aa[selectedRow].graph == AlignmentAnnotation.NO_GRAPH)
          {
            pop.addSeparator();
            // av and sequencegroup need to implement same interface for
            item = new JCheckBoxMenuItem(TOGGLE_LABELSCALE,
 -                  aa[selectedRow].scaleColLabel);
 +                        aa[selectedRow].scaleColLabel);
            item.addActionListener(this);
            pop.add(item);
          }
          consclipbrd.addActionListener(this);
          pop.add(consclipbrd);
        }
 +      else if (InformationThread.HMM_CALC_ID.equals(ann.getCalcId()))
 +      {
 +        addHmmerMenu(pop, ann);
 +      }
      }
      pop.show(this, evt.getX(), evt.getY());
    }
  
    /**
 +   * Adds context menu options for (alignment or group) Hmmer annotation
 +   * 
 +   * @param pop
 +   * @param ann
 +   */
 +  protected void addHmmerMenu(JPopupMenu pop, final AlignmentAnnotation ann)
 +  {
 +    final boolean isGroupAnnotation = ann.groupRef != null;
 +    pop.addSeparator();
 +    final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(
 +            MessageManager.getString(
 +                    "label.ignore_below_background_frequency"),
 +            isGroupAnnotation
 +                    ? ann.groupRef
 +                            .isIgnoreBelowBackground()
 +                    : ap.av.isIgnoreBelowBackground());
 +    cbmi.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        if (isGroupAnnotation)
 +        {
 +          if (!ann.groupRef.isUseInfoLetterHeight())
 +          {
 +            ann.groupRef.setIgnoreBelowBackground(cbmi.getState());
 +            // todo and recompute group annotation
 +          }
 +        }
 +        else if (!ap.av.isInfoLetterHeight())
 +        {
 +          ap.av.setIgnoreBelowBackground(cbmi.getState(), ap);
 +          // todo and recompute annotation
 +        }
 +        ap.alignmentChanged(); // todo not like this
 +      }
 +    });
 +    pop.add(cbmi);
 +    final JCheckBoxMenuItem letterHeight = new JCheckBoxMenuItem(
 +            MessageManager.getString("label.use_info_for_height"),
 +            isGroupAnnotation ? ann.groupRef.isUseInfoLetterHeight()
 +                    : ap.av.isInfoLetterHeight());
 +    letterHeight.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        if (isGroupAnnotation)
 +        {
 +          ann.groupRef.setInfoLetterHeight((letterHeight.getState()));
 +          ann.groupRef.setIgnoreBelowBackground(true);
 +          // todo and recompute group annotation
 +        }
 +        else
 +        {
 +          ap.av.setInfoLetterHeight(letterHeight.getState(), ap);
 +          ap.av.setIgnoreBelowBackground(true, ap);
 +          // todo and recompute annotation
 +        }
 +        ap.alignmentChanged();
 +      }
 +    });
 +    pop.add(letterHeight);
 +    if (isGroupAnnotation)
 +    {
 +      final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.show_group_histogram"),
 +              ann.groupRef.isShowInformationHistogram());
 +      chist.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          ann.groupRef.setShowInformationHistogram(chist.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(chist);
 +      final JCheckBoxMenuItem cprofl = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.show_group_logo"),
 +              ann.groupRef.isShowHMMSequenceLogo());
 +      cprofl.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          ann.groupRef.setShowHMMSequenceLogo(cprofl.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(cprofl);
 +      final JCheckBoxMenuItem cproflnorm = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.normalise_group_logo"),
 +              ann.groupRef.isNormaliseHMMSequenceLogo());
 +      cproflnorm.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          ann.groupRef
 +                  .setNormaliseHMMSequenceLogo(cproflnorm.getState());
 +          // automatically enable logo display if we're clicked
 +          ann.groupRef.setShowHMMSequenceLogo(true);
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(cproflnorm);
 +    }
 +    else
 +    {
 +      final JCheckBoxMenuItem chist = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.show_histogram"),
 +              av.isShowInformationHistogram());
 +      chist.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          av.setShowInformationHistogram(chist.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(chist);
 +      final JCheckBoxMenuItem cprof = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.show_logo"),
 +              av.isShowHMMSequenceLogo());
 +      cprof.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          av.setShowHMMSequenceLogo(cprof.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(cprof);
 +      final JCheckBoxMenuItem cprofnorm = new JCheckBoxMenuItem(
 +              MessageManager.getString("label.normalise_logo"),
 +              av.isNormaliseHMMSequenceLogo());
 +      cprofnorm.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          av.setShowHMMSequenceLogo(true);
 +          av.setNormaliseHMMSequenceLogo(cprofnorm.getState());
 +          ap.repaint();
 +        }
 +      });
 +      pop.add(cprofnorm);
 +    }
 +  }
 +
 +  /**
     * A helper method that adds menu options for calculation and visualisation of
     * group and/or alignment consensus annotation to a popup menu. This is
     * designed to be reusable for either unwrapped mode (popup menu is shown on
        // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
        // tooltips
        String desc = aa.getDescription(true).trim();
-       if (!desc.toLowerCase().startsWith(HTML_START_TAG))
+       if (!desc.toLowerCase(Locale.ROOT).startsWith(HTML_START_TAG))
        {
          tooltip.append(HTML_START_TAG);
          desc = desc.replace("<", "&lt;");
        }
-       else if (desc.toLowerCase().endsWith(HTML_END_TAG))
+       else if (desc.toLowerCase(Locale.ROOT).endsWith(HTML_END_TAG))
        {
          desc = desc.substring(0, desc.length() - HTML_END_TAG.length());
        }
              PaintRefresher.Refresh(ap, ap.av.getSequenceSetId());
              ap.av.sendSelection();
            }
          }
        }
        return;
              seqs, omitHidden, alignmentStartEnd);
  
      Toolkit.getDefaultToolkit().getSystemClipboard()
 -            .setContents(new StringSelection(output), Desktop.instance);
 +            .setContents(new StringSelection(output), Desktop.getInstance());
  
      HiddenColumns hiddenColumns = null;
  
                av.getAlignment().getHiddenColumns());
      }
  
 -    Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
 -                                                        // of a consensus
 -                                                        // sequence ? need to
 -                                                        // flag
 -        // sequence as special.
 +    // what is the dataset of a consensus sequence? 
 +    // need to flag sequence as special.
 +    Desktop.getInstance().jalviewClipboard = new Object[] { seqs, ds, 
          hiddenColumns };
    }
  
    @Override
    public void paintComponent(Graphics g)
    {
      int width = getWidth();
      if (width == 0)
      {
      }
  
      drawComponent(g2, true, width);
    }
  
    /**
   */
  package jalview.gui;
  
- 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;
+ import java.util.Locale;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.Graphics;
- import java.awt.Rectangle;
- import java.awt.event.ActionEvent;
  import java.io.File;
- import java.util.ArrayList;
  import java.util.List;
- import java.util.Vector;
+ import java.util.Map;
  
- import javax.swing.JCheckBoxMenuItem;
  import javax.swing.JPanel;
  import javax.swing.JSplitPane;
  import javax.swing.SwingUtilities;
  import javax.swing.event.InternalFrameAdapter;
  import javax.swing.event.InternalFrameEvent;
  
+ import jalview.api.AlignmentViewPanel;
+ import jalview.bin.Cache;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.SequenceI;
+ import jalview.datamodel.StructureViewerModel;
+ import jalview.datamodel.StructureViewerModel.StructureData;
+ import jalview.fts.service.alphafold.AlphafoldRestClient;
+ import jalview.gui.ImageExporter.ImageWriterI;
+ import jalview.gui.StructureViewer.ViewerType;
+ import jalview.structure.StructureCommand;
+ import jalview.structures.models.AAStructureBindingModel;
+ import jalview.util.BrowserLauncher;
+ import jalview.util.ImageMaker;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
 -
  public class AppJmol extends StructureViewerBase
  {
    // ms to wait for Jmol to load files
     * @param bounds
     * @param viewid
     */
-   public AppJmol(String[] files, String[] ids, SequenceI[][] seqs,
-           AlignmentPanel ap, boolean usetoColour, boolean useToAlign,
-           boolean leaveColouringToJmol, String loadStatus, Rectangle bounds,
-           String viewid)
+   public AppJmol(StructureViewerModel viewerModel, AlignmentPanel ap,
+           String sessionFile, String viewid)
    {
-     PDBEntry[] pdbentrys = new PDBEntry[files.length];
-     for (int i = 0; i < pdbentrys.length; i++)
+     Map<File, StructureData> pdbData = viewerModel.getFileData();
+     PDBEntry[] pdbentrys = new PDBEntry[pdbData.size()];
+     SequenceI[][] seqs = new SequenceI[pdbData.size()][];
+     int i = 0;
+     for (StructureData data : pdbData.values())
      {
-       // PDBEntry pdbentry = new PDBEntry(files[i], ids[i]);
-       PDBEntry pdbentry = new PDBEntry(ids[i], null, PDBEntry.Type.PDB,
-               files[i]);
+       PDBEntry pdbentry = new PDBEntry(data.getPdbId(), null,
+               PDBEntry.Type.PDB, data.getFilePath());
        pdbentrys[i] = pdbentry;
+       List<SequenceI> sequencesForPdb = data.getSeqList();
+       seqs[i] = sequencesForPdb
+               .toArray(new SequenceI[sequencesForPdb.size()]);
+       i++;
      }
-     // / TODO: check if protocol is needed to be set, and if chains are
 -
+     // TODO: check if protocol is needed to be set, and if chains are
      // autodiscovered.
      jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
              pdbentrys, seqs, null);
  
      jmb.setLoadingFromArchive(true);
      addAlignmentPanel(ap);
-     if (useToAlign)
+     if (viewerModel.isAlignWithPanel())
      {
        useAlignmentPanelForSuperposition(ap);
      }
      initMenus();
-     if (leaveColouringToJmol || !usetoColour)
+     boolean useToColour = viewerModel.isColourWithAlignPanel();
+     boolean leaveColouringToJmol = viewerModel.isColourByViewer();
+     if (leaveColouringToJmol || !useToColour)
      {
        jmb.setColourBySequence(false);
        seqColour.setSelected(false);
        viewerColour.setSelected(true);
      }
-     else if (usetoColour)
+     else if (useToColour)
      {
        useAlignmentPanelForColourbyseq(ap);
        jmb.setColourBySequence(true);
        seqColour.setSelected(true);
        viewerColour.setSelected(false);
      }
-     this.setBounds(bounds);
 -
+     this.setBounds(viewerModel.getX(), viewerModel.getY(),
+             viewerModel.getWidth(), viewerModel.getHeight());
      setViewId(viewid);
-     // jalview.gui.Desktop.addInternalFrame(this, "Loading File",
-     // bounds.width,bounds.height);
  
      this.addInternalFrameListener(new InternalFrameAdapter()
      {
          closeViewer(false);
        }
      });
-     initJmol(loadStatus); // pdbentry, seq, JBPCHECK!
+     StringBuilder cmd = new StringBuilder();
+     cmd.append("load FILES ").append(QUOTE)
+             .append(Platform.escapeBackslashes(sessionFile)).append(QUOTE);
+     initJmol(cmd.toString());
    }
  
    @Override
    {
      super.initMenus();
  
-     viewerActionMenu.setText(MessageManager.getString("label.jmol"));
 +
      viewerColour
              .setText(MessageManager.getString("label.colour_with_jmol"));
      viewerColour.setToolTipText(MessageManager
              .getString("label.let_jmol_manage_structure_colours"));
    }
  
-   IProgressIndicator progressBar = null;
-   @Override
-   protected IProgressIndicator getIProgressIndicator()
-   {
-     return progressBar;
-   }
-   
    /**
     * display a single PDB structure in a new Jmol view
     * 
    public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
            final AlignmentPanel ap)
    {
-     progressBar = ap.alignFrame;
+     setProgressIndicator(ap.alignFrame);
  
      openNewJmol(ap, alignAddedStructures, new PDBEntry[] { pdbentry },
              new SequenceI[][]
            PDBEntry[] pdbentrys,
            SequenceI[][] seqs)
    {
-     progressBar = ap.alignFrame;
+     setProgressIndicator(ap.alignFrame);
      jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
              pdbentrys, seqs, null);
      addAlignmentPanel(ap);
      useAlignmentPanelForColourbyseq(ap);
  
      alignAddedStructures = alignAdded;
-     useAlignmentPanelForSuperposition(ap);
+     if (pdbentrys.length > 1)
+     {
+       useAlignmentPanelForSuperposition(ap);
+     }
  
      jmb.setColourBySequence(true);
      setSize(400, 400); // probably should be a configurable/dynamic default here
      {
        command = "";
      }
-     jmb.evalStateCommand(command);
-     jmb.evalStateCommand("set hoverDelay=0.1");
+     jmb.executeCommand(new StructureCommand(command), false);
+     jmb.executeCommand(new StructureCommand("set hoverDelay=0.1"), false);
      jmb.setFinishedInit(true);
    }
  
    @Override
-   void showSelectedChains()
-   {
-     Vector<String> toshow = new Vector<>();
-     for (int i = 0; i < chainMenu.getItemCount(); i++)
-     {
-       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
-       {
-         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
-         if (item.isSelected())
-         {
-           toshow.addElement(item.getText());
-         }
-       }
-     }
-     jmb.centerViewer(toshow);
-   }
-   @Override
-   public void closeViewer(boolean closeExternalViewer)
-   {
-     // Jmol does not use an external viewer
-     if (jmb != null)
-     {
-       jmb.closeViewer();
-     }
-     setAlignmentPanel(null);
-     _aps.clear();
-     _alignwith.clear();
-     _colourwith.clear();
-     // TODO: check for memory leaks where instance isn't finalised because jmb
-     // holds a reference to the window
-     jmb = null;
-   }
-   @Override
    public void run()
    {
      _started = true;
      try
      {
-       List<String> files = fetchPdbFiles();
+       List<String> files = jmb.fetchPdbFiles(this);
        if (files.size() > 0)
        {
          showFilesInViewer(files);
        cmd.append("loadingJalviewdata=true\nload APPEND ");
        cmd.append(filesString);
        cmd.append("\nloadingJalviewdata=null");
-       final String command = cmd.toString();
+       final StructureCommand command = new StructureCommand(cmd.toString());
        lastnotify = jmb.getLoadNotifiesHandled();
  
        try
        {
-         jmb.evalStateCommand(command);
+         jmb.executeCommand(command, false);
        } catch (OutOfMemoryError oomerror)
        {
          new OOMWarning("When trying to add structures to the Jmol viewer!",
        try
        {
          Cache.log.debug("Waiting around for jmb notify.");
-         Thread.sleep(waitFor);
          waitTotal += waitFor;
+         // Thread.sleep() throws an exception in JS
+         Thread.sleep(waitFor);
        } catch (Exception e)
        {
        }
      }
  
      // refresh the sequence colours for the new structure(s)
-     for (AlignmentPanel ap : _colourwith)
+     for (AlignmentViewPanel ap : _colourwith)
      {
        jmb.updateColours(ap);
      }
        @Override
        public void run()
        {
-         if (jmb.viewer.isScriptExecuting())
+         if (jmb.jmolViewer.isScriptExecuting())
          {
            SwingUtilities.invokeLater(this);
            try
          }
          else
          {
-           alignStructs_withAllAlignPanels();
+           alignStructsWithAllAlignPanels();
          }
        }
      });
    }
  
    /**
-    * Retrieves and saves as file any modelled PDB entries for which we do not
-    * already have a file saved. Returns a list of absolute paths to structure
-    * files which were either retrieved, or already stored but not modelled in
-    * the structure viewer (i.e. files to add to the viewer display).
-    * 
-    * @return
-    */
-   List<String> fetchPdbFiles()
-   {
-     // todo - record which pdbids were successfully imported.
-     StringBuilder errormsgs = new StringBuilder();
-     List<String> files = new ArrayList<>();
-     String pdbid = "";
-     try
-     {
-       String[] filesInViewer = jmb.getStructureFiles();
-       // TODO: replace with reference fetching/transfer code (validate PDBentry
-       // as a DBRef?)
-       Pdb pdbclient = new Pdb();
-       for (int pi = 0; pi < jmb.getPdbCount(); pi++)
-       {
-         String file = jmb.getPdbEntry(pi).getFile();
-         if (file == null)
-         {
-           // todo: extract block as method and pull up (also ChimeraViewFrame)
-           // retrieve the pdb and store it locally
-           AlignmentI pdbseq = null;
-           pdbid = jmb.getPdbEntry(pi).getId();
-           long hdl = pdbid.hashCode() - System.currentTimeMillis();
-           if (progressBar != null)
-           {
-             progressBar.setProgressBar(MessageManager
-                     .formatMessage("status.fetching_pdb", new String[]
-                     { pdbid }), hdl);
-           }
-           try
-           {
-             pdbseq = pdbclient.getSequenceRecords(pdbid);
-           } catch (OutOfMemoryError oomerror)
-           {
-             new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
-           } catch (Exception ex)
-           {
-             ex.printStackTrace();
-             errormsgs.append("'").append(pdbid).append("'");
-           } finally
-           {
-             if (progressBar != null)
-             {
-               progressBar.setProgressBar(
-                       MessageManager.getString("label.state_completed"),
-                       hdl);
-             }
-           }
-           if (pdbseq != null)
-           {
-             // just transfer the file name from the first sequence's first
-             // PDBEntry
-             file = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
-                     .elementAt(0).getFile()).getAbsolutePath();
-             jmb.getPdbEntry(pi).setFile(file);
-             files.add(file);
-           }
-           else
-           {
-             errormsgs.append("'").append(pdbid).append("' ");
-           }
-         }
-         else
-         {
-           if (filesInViewer != null && filesInViewer.length > 0)
-           {
-             addingStructures = true; // already files loaded.
-             for (int c = 0; c < filesInViewer.length; c++)
-             {
-               if (Platform.pathEquals(filesInViewer[c], file))
-               {
-                 file = null;
-                 break;
-               }
-             }
-           }
-           if (file != null)
-           {
-             files.add(file);
-           }
-         }
-       }
-     } catch (OutOfMemoryError oomerror)
-     {
-       new OOMWarning("Retrieving PDB files: " + pdbid, oomerror);
-     } catch (Exception ex)
-     {
-       ex.printStackTrace();
-       errormsgs.append("When retrieving pdbfiles : current was: '")
-               .append(pdbid).append("'");
-     }
-     if (errormsgs.length() > 0)
-     {
-       JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
-               MessageManager.formatMessage(
-                       "label.pdb_entries_couldnt_be_retrieved", new String[]
-                       { errormsgs.toString() }),
-               MessageManager.getString("label.couldnt_load_file"),
-               JvOptionPane.ERROR_MESSAGE);
-     }
-     return files;
-   }
-   /**
     * 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
     */
+   @Override
    public void makePDBImage(ImageMaker.TYPE type)
    {
      int width = getWidth();
        @Override
        public void exportImage(Graphics g) throws Exception
        {
-         jmb.viewer.renderScreenImage(g, width, height);
+         jmb.jmolViewer.renderScreenImage(g, width, height);
        }
      };
-     String view = MessageManager.getString("action.view").toLowerCase();
+     String view = MessageManager.getString("action.view").toLowerCase(Locale.ROOT);
      ImageExporter exporter = new ImageExporter(writer,
-             jmb.getIProgressIndicator(), type, getTitle());
+             getProgressIndicator(), type, getTitle());
      exporter.doExport(null, this, width, height, view);
    }
  
    @Override
-   public void showHelp_actionPerformed(ActionEvent actionEvent)
+   public void showHelp_actionPerformed()
    {
      try
      {
                .openURL("http://wiki.jmol.org");//http://jmol.sourceforge.net/docs/JmolUserGuide/");
      } catch (Exception ex)
      {
+       System.err.println("Show Jmol help failed with: " + ex.getMessage());
      }
    }
  
+   @Override
    public void showConsole(boolean showConsole)
    {
 +
      if (showConsole)
      {
        if (splitPane == null)
            }
          }
        }
-       else if (jmb == null || jmb.viewer == null || !jmb.isFinishedInit())
+       else if (jmb == null || jmb.jmolViewer == null || !jmb.isFinishedInit())
        {
          g.setColor(Color.black);
          g.fillRect(0, 0, currentSize.width, currentSize.height);
        }
        else
        {
-         jmb.viewer.renderScreenImage(g, currentSize.width,
+         jmb.jmolViewer.renderScreenImage(g, currentSize.width,
                  currentSize.height);
        }
      }
    }
  
    @Override
-   public String getStateInfo()
-   {
-     return jmb == null ? null : jmb.viewer.getStateInfo();
-   }
-   @Override
    public ViewerType getViewerType()
    {
      return ViewerType.JMOL;
   */
  package jalview.gui;
  
- import jalview.api.FeatureRenderer;
- import jalview.bin.Cache;
- import jalview.datamodel.AlignmentI;
- import jalview.datamodel.PDBEntry;
- import jalview.datamodel.SequenceI;
- import jalview.ext.rbvi.chimera.ChimeraCommands;
- import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
- import jalview.gui.StructureViewer.ViewerType;
- 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;
 +
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
- import java.util.Random;
+ import java.util.Map;
  
- import javax.swing.JCheckBoxMenuItem;
  import javax.swing.JInternalFrame;
  import javax.swing.JMenu;
  import javax.swing.JMenuItem;
  import javax.swing.event.InternalFrameAdapter;
  import javax.swing.event.InternalFrameEvent;
  
+ import jalview.api.AlignmentViewPanel;
+ import jalview.api.FeatureRenderer;
+ import jalview.bin.Cache;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.SequenceI;
+ import jalview.datamodel.StructureViewerModel;
+ import jalview.datamodel.StructureViewerModel.StructureData;
+ import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
+ import jalview.gui.StructureViewer.ViewerType;
+ import jalview.io.DataSourceType;
+ import jalview.io.StructureFile;
+ import jalview.structures.models.AAStructureBindingModel;
+ import jalview.util.ImageMaker.TYPE;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
 -
  /**
   * GUI elements for handling an external chimera display
   * 
@@@ -67,8 -62,6 +62,7 @@@ public class ChimeraViewFrame extends S
  {
    private JalviewChimeraBinding jmb;
  
-   private IProgressIndicator progressBar = null;
 +
    /*
     * Path to Chimera session file. This is set when an open Jalview/Chimera
     * session is saved, or on restore from a Jalview project (if it holds the
     */
    private String chimeraSessionFile = null;
  
-   private Random random = new Random();
 +
    private int myWidth = 500;
  
    private int myHeight = 150;
  
-   /**
+   private JMenuItem writeFeatures=null;
+   private JMenu fetchAttributes=null;
+ /**
     * Initialise menu options.
     */
    @Override
    {
      super.initMenus();
  
      savemenu.setVisible(false); // not yet implemented
      viewMenu.add(fitToWindow);
  
-     JMenuItem writeFeatures = new JMenuItem(
-             MessageManager.getString("label.create_chimera_attributes"));
+     writeFeatures = new JMenuItem(
+             MessageManager.getString("label.create_viewer_attributes"));
      writeFeatures.setToolTipText(MessageManager
-             .getString("label.create_chimera_attributes_tip"));
+             .getString("label.create_viewer_attributes_tip"));
      writeFeatures.addActionListener(new ActionListener()
      {
        @Override
      });
      viewerActionMenu.add(writeFeatures);
  
-     final JMenu fetchAttributes = new JMenu(
+     fetchAttributes = new JMenu(
              MessageManager.getString("label.fetch_chimera_attributes"));
      fetchAttributes.setToolTipText(
              MessageManager.getString("label.fetch_chimera_attributes_tip"));
      });
      viewerActionMenu.add(fetchAttributes);
    }
 +
+   @Override
+   protected void buildActionMenu()
+   {
+     super.buildActionMenu();
+     // add these back in after menu is refreshed
+     viewerActionMenu.add(writeFeatures);
+     viewerActionMenu.add(fetchAttributes);
+     
+   };
    /**
-    * Query Chimera for its residue attribute names and add them as items off the
-    * attributes menu
+    * Query the structure viewer for its residue attribute names and add them as
+    * items off the attributes menu
     * 
     * @param attributesMenu
     */
    protected void buildAttributesMenu(JMenu attributesMenu)
    {
-     List<String> atts = jmb.sendChimeraCommand("list resattr", true);
-     if (atts == null)
-     {
-       return;
-     }
+     List<String> atts = jmb.getChimeraAttributes();
      attributesMenu.removeAll();
      Collections.sort(atts);
-     for (String att : atts)
+     for (String attName : atts)
      {
-       final String attName = att.split(" ")[1];
-       /*
-        * ignore 'jv_*' attributes, as these are Jalview features that have
-        * been transferred to residue attributes in Chimera!
-        */
-       if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX))
+       JMenuItem menuItem = new JMenuItem(attName);
+       menuItem.addActionListener(new ActionListener()
        {
-         JMenuItem menuItem = new JMenuItem(attName);
-         menuItem.addActionListener(new ActionListener()
+         @Override
+         public void actionPerformed(ActionEvent e)
          {
-           @Override
-           public void actionPerformed(ActionEvent e)
+           if (getBinding().copyStructureAttributesToFeatures(attName,
+                   getAlignmentPanel()) > 0)
            {
-             getChimeraAttributes(attName);
+             getAlignmentPanel().getFeatureRenderer().featuresAdded();
            }
-         });
-         attributesMenu.add(menuItem);
-       }
+         }
+       });
+       attributesMenu.add(menuItem);
      }
    }
  
    /**
-    * Read residues in Chimera with the given attribute name, and set as features
-    * on the corresponding sequence positions (if any)
-    * 
-    * @param attName
-    */
-   protected void getChimeraAttributes(String attName)
-   {
-     jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
-   }
-   /**
-    * Send a command to Chimera to create residue attributes for Jalview features
-    * <p>
-    * The syntax is: setattr r <attName> <attValue> <atomSpec>
-    * <p>
-    * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A
+    * Sends command(s) to the structure viewer to create residue attributes for
+    * visible Jalview features
     */
    protected void sendFeaturesToChimera()
    {
+     // todo pull up?
      int count = jmb.sendFeaturesToViewer(getAlignmentPanel());
      statusBar.setText(
              MessageManager.formatMessage("label.attributes_set", count));
     */
    protected void createProgressBar()
    {
-     if (progressBar == null)
+     if (getProgressIndicator() == null)
      {
-       progressBar = new ProgressBar(statusPanel, statusBar);
+       setProgressIndicator(new ProgressBar(statusPanel, statusBar));
      }
    }
  
            SequenceI[][] seqs)
    {
      createProgressBar();
-     jmb = new JalviewChimeraBindingModel(this,
-             ap.getStructureSelectionManager(), pdbentrys, seqs, null);
+     jmb = newBindingModel(ap, pdbentrys, seqs);
      addAlignmentPanel(ap);
      useAlignmentPanelForColourbyseq(ap);
  
  
    }
  
+   protected JalviewChimeraBindingModel newBindingModel(AlignmentPanel ap,
+           PDBEntry[] pdbentrys, SequenceI[][] seqs)
+   {
+     return new JalviewChimeraBindingModel(this,
+             ap.getStructureSelectionManager(), pdbentrys, seqs, null);
+   }
 -
    /**
     * Create a new viewer from saved session state data including Chimera session
     * file
     * @param colourBySequence
     * @param newViewId
     */
-   public ChimeraViewFrame(String chimeraSessionFile,
-           AlignmentPanel alignPanel, PDBEntry[] pdbArray,
-           SequenceI[][] seqsArray, boolean colourByChimera,
-           boolean colourBySequence, String newViewId)
+   public ChimeraViewFrame(StructureViewerModel viewerData,
+           AlignmentPanel alignPanel, String sessionFile, String vid)
    {
      this();
-     setViewId(newViewId);
-     this.chimeraSessionFile = chimeraSessionFile;
+     setViewId(vid);
+     this.chimeraSessionFile = sessionFile;
+     Map<File, StructureData> pdbData = viewerData.getFileData();
+     PDBEntry[] pdbArray = new PDBEntry[pdbData.size()];
+     SequenceI[][] seqsArray = new SequenceI[pdbData.size()][];
+     int i = 0;
+     for (StructureData data : pdbData.values())
+     {
+       PDBEntry pdbentry = new PDBEntry(data.getPdbId(), null,
+               PDBEntry.Type.PDB, data.getFilePath());
+       pdbArray[i] = pdbentry;
+       List<SequenceI> sequencesForPdb = data.getSeqList();
+       seqsArray[i] = sequencesForPdb
+               .toArray(new SequenceI[sequencesForPdb.size()]);
+       i++;
+     }
      openNewChimera(alignPanel, pdbArray, seqsArray);
-     if (colourByChimera)
+     if (viewerData.isColourByViewer())
      {
        jmb.setColourBySequence(false);
        seqColour.setSelected(false);
        viewerColour.setSelected(true);
      }
-     else if (colourBySequence)
+     else if (viewerData.isColourWithAlignPanel())
      {
        jmb.setColourBySequence(true);
        seqColour.setSelected(true);
  
      if (!jmb.launchChimera())
      {
 -      JvOptionPane.showMessageDialog(Desktop.desktop,
 +      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
-               MessageManager.getString("label.chimera_failed"),
+               MessageManager.formatMessage("label.open_viewer_failed",
+                       getViewerName()),
                MessageManager.getString("label.error_loading_file"),
                JvOptionPane.ERROR_MESSAGE);
        this.dispose();
    }
  
    /**
-    * Show only the selected chain(s) in the viewer
-    */
-   @Override
-   void showSelectedChains()
-   {
-     List<String> toshow = new ArrayList<>();
-     for (int i = 0; i < chainMenu.getItemCount(); i++)
-     {
-       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
-       {
-         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
-         if (item.isSelected())
-         {
-           toshow.add(item.getText());
-         }
-       }
-     }
-     jmb.showChains(toshow);
-   }
-   /**
-    * Close down this instance of Jalview's Chimera viewer, giving the user the
-    * option to close the associated Chimera window (process). They may wish to
-    * keep it open until they have had an opportunity to save any work.
-    * 
-    * @param closeChimera
-    *          if true, close any linked Chimera process; if false, prompt first
-    */
-   @Override
-   public void closeViewer(boolean closeChimera)
-   {
-     if (jmb != null && jmb.isChimeraRunning())
-     {
-       if (!closeChimera)
-       {
-         String prompt = MessageManager
-                 .formatMessage("label.confirm_close_chimera", new Object[]
-                 { jmb.getViewerTitle(getViewerName(), false) });
-         prompt = JvSwingUtils.wrapTooltip(true, prompt);
-         int confirm = JvOptionPane.showConfirmDialog(this, prompt,
-                 MessageManager.getString("label.close_viewer"),
-                 JvOptionPane.YES_NO_CANCEL_OPTION);
-         /*
-          * abort closure if user hits escape or Cancel
-          */
-         if (confirm == JvOptionPane.CANCEL_OPTION
-                 || confirm == JvOptionPane.CLOSED_OPTION)
-         {
-           return;
-         }
-         closeChimera = confirm == JvOptionPane.YES_OPTION;
-       }
-       jmb.closeViewer(closeChimera);
-     }
-     setAlignmentPanel(null);
-     _aps.clear();
-     _alignwith.clear();
-     _colourwith.clear();
-     // TODO: check for memory leaks where instance isn't finalised because jmb
-     // holds a reference to the window
-     jmb = null;
-     dispose();
-   }
-   /**
     * Open any newly added PDB structures in Chimera, having first fetched data
     * from PDB (if not already saved).
     */
      if (errormsgs.length() > 0)
      {
  
 -      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +      JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                MessageManager.formatMessage(
                        "label.pdb_entries_couldnt_be_retrieved", new Object[]
                        { errormsgs.toString() }),
  
              pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
                      jmb.getChains()[pos], pe.getFile(), protocol,
-                     progressBar);
-             stashFoundChains(pdb, pe.getFile());
+                     getProgressIndicator());
+             jmb.stashFoundChains(pdb, pe.getFile());
  
            } catch (OutOfMemoryError oomerror)
            {
  
        /*
         * ensure that any newly discovered features (e.g. RESNUM)
-        * are added to any open feature settings dialog
+        * are notified to the FeatureRenderer (and added to any 
+        * open feature settings dialog)
         */
        FeatureRenderer fr = getBinding().getFeatureRenderer(null);
        if (fr != null)
        }
  
        // refresh the sequence colours for the new structure(s)
-       for (AlignmentPanel ap : _colourwith)
+       for (AlignmentViewPanel ap : _colourwith)
        {
          jmb.updateColours(ap);
        }
            @Override
            public void run()
            {
-             alignStructs_withAllAlignPanels();
+             alignStructsWithAllAlignPanels();
            }
          }).start();
        }
      worker = null;
    }
  
-   /**
-    * Fetch PDB data and save to a local file. Returns the full path to the file,
-    * or null if fetch fails. TODO: refactor to common with Jmol ? duplication
-    * 
-    * @param processingEntry
-    * @return
-    * @throws Exception
-    */
-   private void stashFoundChains(StructureFile pdb, String file)
-   {
-     for (int i = 0; i < pdb.getChains().size(); i++)
-     {
-       String chid = new String(
-               pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
-       jmb.getChainNames().add(chid);
-       jmb.getChainFile().put(chid, file);
-     }
-   }
-   private String fetchPdbFile(PDBEntry processingEntry) throws Exception
-   {
-     String filePath = null;
-     Pdb pdbclient = new Pdb();
-     AlignmentI pdbseq = null;
-     String pdbid = processingEntry.getId();
-     long handle = System.currentTimeMillis()
-             + Thread.currentThread().hashCode();
-     /*
-      * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
-      */
-     String msg = MessageManager.formatMessage("status.fetching_pdb",
-             new Object[]
-             { pdbid });
-     getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
-     // long hdl = startProgressBar(MessageManager.formatMessage(
-     // "status.fetching_pdb", new Object[]
-     // { pdbid }));
-     try
-     {
-       pdbseq = pdbclient.getSequenceRecords(pdbid);
-     } catch (OutOfMemoryError oomerror)
-     {
-       new OOMWarning("Retrieving PDB id " + pdbid, oomerror);
-     } finally
-     {
-       msg = pdbid + " " + MessageManager.getString("label.state_completed");
-       getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
-       // stopProgressBar(msg, hdl);
-     }
-     /*
-      * If PDB data were saved and are not invalid (empty alignment), return the
-      * file path.
-      */
-     if (pdbseq != null && pdbseq.getHeight() > 0)
-     {
-       // just use the file name from the first sequence's first PDBEntry
-       filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
-               .elementAt(0).getFile()).getAbsolutePath();
-       processingEntry.setFile(filePath);
-     }
-     return filePath;
-   }
-   /**
-    * Convenience method to update the progress bar if there is one. Be sure to
-    * call stopProgressBar with the returned handle to remove the message.
-    * 
-    * @param msg
-    * @param handle
-    */
-   public long startProgressBar(String msg)
-   {
-     // TODO would rather have startProgress/stopProgress as the
-     // IProgressIndicator interface
-     long tm = random.nextLong();
-     if (progressBar != null)
-     {
-       progressBar.setProgressBar(msg, tm);
-     }
-     return tm;
-   }
-   /**
-    * End the progress bar with the specified handle, leaving a message (if not
-    * null) on the status bar
-    * 
-    * @param msg
-    * @param handle
-    */
-   public void stopProgressBar(String msg, long handle)
-   {
-     if (progressBar != null)
-     {
-       progressBar.setProgressBar(msg, handle);
-     }
-   }
 +
    @Override
    public void makePDBImage(TYPE imageType)
    {
    }
  
    @Override
-   public void showHelp_actionPerformed(ActionEvent actionEvent)
-   {
-     try
-     {
-       BrowserLauncher
-               .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
-     } catch (IOException ex)
-     {
-     }
-   }
-   @Override
    public AAStructureBindingModel getBinding()
    {
      return jmb;
    }
  
-   /**
-    * Ask Chimera to save its session to the designated file path, or to a
-    * temporary file if the path is null. Returns the file path if successful,
-    * else null.
-    * 
-    * @param filepath
-    * @see getStateInfo
-    */
-   protected String saveSession(String filepath)
-   {
-     String pathUsed = filepath;
-     try
-     {
-       if (pathUsed == null)
-       {
-         File tempFile = File.createTempFile("chimera", ".py");
-         tempFile.deleteOnExit();
-         pathUsed = tempFile.getPath();
-       }
-       boolean result = jmb.saveSession(pathUsed);
-       if (result)
-       {
-         this.chimeraSessionFile = pathUsed;
-         return pathUsed;
-       }
-     } catch (IOException e)
-     {
-     }
-     return null;
-   }
-   /**
-    * Returns a string representing the state of the Chimera session. This is
-    * done by requesting Chimera to save its session to a temporary file, then
-    * reading the file contents. Returns an empty string on any error.
-    */
-   @Override
-   public String getStateInfo()
-   {
-     String sessionFile = saveSession(null);
-     if (sessionFile == null)
-     {
-       return "";
-     }
-     InputStream is = null;
-     try
-     {
-       File f = new File(sessionFile);
-       byte[] bytes = new byte[(int) f.length()];
-       is = new FileInputStream(sessionFile);
-       is.read(bytes);
-       return new String(bytes);
-     } catch (IOException e)
-     {
-       return "";
-     } finally
-     {
-       if (is != null)
-       {
-         try
-         {
-           is.close();
-         } catch (IOException e)
-         {
-           // ignore
-         }
-       }
-     }
-   }
-   @Override
-   protected void fitToWindow_actionPerformed()
-   {
-     jmb.focusView();
-   }
 +
    @Override
    public ViewerType getViewerType()
    {
    {
      return "Chimera";
    }
-   /**
-    * Sends commands to align structures according to associated alignment(s).
-    * 
-    * @return
-    */
-   @Override
-   protected String alignStructs_withAllAlignPanels()
-   {
-     String reply = super.alignStructs_withAllAlignPanels();
-     if (reply != null)
-     {
-       statusBar.setText("Superposition failed: " + reply);
-     }
-     return reply;
-   }
-   @Override
-   protected IProgressIndicator getIProgressIndicator()
-   {
-     return progressBar;
-   }
  }
@@@ -234,7 -234,7 +234,7 @@@ public class CutAndPasteTransfer extend
                .println(MessageManager.getString("label.couldnt_read_data"));
        if (!Jalview.isHeadlessMode())
        {
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                  AppletFormatAdapter.getSupportedFormats(),
                  MessageManager.getString("label.couldnt_read_data"),
                  JvOptionPane.WARNING_MESSAGE);
  
      } catch (IOException ex)
      {
 -      JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
 +      JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(), MessageManager
                .formatMessage("label.couldnt_read_pasted_text", new String[]
                { ex.toString() }),
                MessageManager.getString("label.error_parsing_text"),
         */
        if (viewport != null && viewport.getAlignment() != null)
        {
-         if (proxyColourScheme != null)
-         {
-           viewport.applyFeaturesStyle(proxyColourScheme);
-         }
          ((AlignViewport) viewport).addAlignment(al, title);
+         viewport.applyFeaturesStyle(proxyColourScheme);
        }
        else
        {
                .println(MessageManager.getString("label.couldnt_read_data"));
        if (!Jalview.isHeadlessMode())
        {
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                  AppletFormatAdapter.getSupportedFormats(),
                  MessageManager.getString("label.couldnt_read_data"),
                  JvOptionPane.WARNING_MESSAGE);
   */
  package jalview.gui;
  
+ import java.util.Locale;
 -
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.FontMetrics;
  import java.awt.Graphics;
+ import java.awt.Graphics2D;
  import java.awt.GridLayout;
  import java.awt.Point;
  import java.awt.Rectangle;
@@@ -47,23 -50,23 +49,26 @@@ import java.awt.event.MouseAdapter
  import java.awt.event.MouseEvent;
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
+ import java.awt.geom.AffineTransform;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.io.File;
  import java.io.FileWriter;
  import java.io.IOException;
+ import java.lang.reflect.Field;
  import java.net.URL;
  import java.util.ArrayList;
+ import java.util.Arrays;
  import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.List;
  import java.util.ListIterator;
  import java.util.Vector;
 +import java.util.concurrent.ExecutionException;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
 +import java.util.concurrent.Future;
 +import java.util.concurrent.FutureTask;
  import java.util.concurrent.Semaphore;
  
  import javax.swing.AbstractAction;
@@@ -97,9 -100,6 +102,9 @@@ import org.stackoverflowusers.file.Wind
  
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
 +import jalview.api.StructureSelectionManagerProvider;
 +import jalview.bin.ApplicationSingletonProvider;
 +import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
  import jalview.gui.ImageExporter.ImageWriterI;
@@@ -114,19 -114,19 +119,21 @@@ import jalview.io.FormatAdapter
  import jalview.io.IdentifyFile;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
 +import jalview.jbgui.GDesktop;
  import jalview.jbgui.GSplitFrame;
  import jalview.jbgui.GStructureViewer;
  import jalview.project.Jalview2XML;
  import jalview.structure.StructureSelectionManager;
  import jalview.urls.IdOrgSettings;
  import jalview.util.BrowserLauncher;
+ import jalview.util.ChannelProperties;
  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.WSDiscovererI;
  import jalview.ws.params.ParamManager;
  import jalview.ws.utils.UrlDownloadClient;
  
   * @author $author$
   * @version $Revision: 1.155 $
   */
 -public class Desktop extends jalview.jbgui.GDesktop
 -    implements DropTargetListener, ClipboardOwner, IProgressIndicator, jalview.api.StructureSelectionManagerProvider {
 +@SuppressWarnings("serial")
 +public class Desktop extends GDesktop
 +        implements DropTargetListener, ClipboardOwner, IProgressIndicator,
 +        StructureSelectionManagerProvider, ApplicationSingletonI
 +
 +{
-   private static final String CITATION = "<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.<br>"
-           + "<br><br>For help, see the FAQ at <a href=\"http://www.jalview.org/faq\">www.jalview.org/faq</a> and/or join the jalview-discuss@jalview.org mailing list"
-           + "<br><br>If  you use Jalview, please cite:"
-           + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
-           + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
-           + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033";
+   private static final String CITATION;
+   static {
+     URL bg_logo_url = ChannelProperties.getImageURL("bg_logo." + String.valueOf(SplashScreen.logoSize));
+     URL uod_logo_url = ChannelProperties.getImageURL("uod_banner." + String.valueOf(SplashScreen.logoSize));
+     boolean logo = (bg_logo_url != null || uod_logo_url != null);
+     StringBuilder sb = new StringBuilder();
+     sb.append("<br><br>Development managed by The Barton Group, University of Dundee, Scotland, UK.");
+     if (logo) {
+       sb.append("<br>");
+     }
+     sb.append(bg_logo_url == null ? "" : "<img alt=\"Barton Group logo\" src=\"" + bg_logo_url.toString() + "\">");
+     sb.append(uod_logo_url == null ? ""
+         : "&nbsp;<img alt=\"University of Dundee shield\" src=\"" + uod_logo_url.toString() + "\">");
+     sb.append(
+         "<br><br>For help, see the FAQ at <a href=\"https://www.jalview.org/faq\">www.jalview.org/faq</a> and/or join the jalview-discuss@jalview.org mailing list");
+     sb.append("<br><br>If  you use Jalview, please cite:"
+         + "<br>Waterhouse, A.M., Procter, J.B., Martin, D.M.A, Clamp, M. and Barton, G. J. (2009)"
+         + "<br>Jalview Version 2 - a multiple sequence alignment editor and analysis workbench"
+         + "<br>Bioinformatics doi: 10.1093/bioinformatics/btp033");
+     CITATION = sb.toString();
+   }
  
    private static final String DEFAULT_AUTHORS = "The Jalview Authors (See AUTHORS file for current list)";
  
  
    public static HashMap<String, FileWriter> savingFiles = new HashMap<String, FileWriter>();
  
 +  @SuppressWarnings("deprecation")
    private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
  
+   public static boolean nosplash = false;
 -
    /**
     * news reader - null if it was never started.
     */
     * @param listener
     * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
     */
 -  public void addJalviewPropertyChangeListener(PropertyChangeListener listener) {
 +  @Deprecated
 +  public void addJalviewPropertyChangeListener(
 +          PropertyChangeListener listener)
 +  {
      changeSupport.addJalviewPropertyChangeListener(listener);
    }
  
     * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.lang.String,
     *      java.beans.PropertyChangeListener)
     */
 -  public void addJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
 +  @Deprecated
 +  public void addJalviewPropertyChangeListener(String propertyName,
 +          PropertyChangeListener listener)
 +  {
      changeSupport.addJalviewPropertyChangeListener(propertyName, listener);
    }
  
     * @see jalview.gui.JalviewChangeSupport#removeJalviewPropertyChangeListener(java.lang.String,
     *      java.beans.PropertyChangeListener)
     */
 -  public void removeJalviewPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
 -    changeSupport.removeJalviewPropertyChangeListener(propertyName, listener);
 +  @Deprecated
 +  public void removeJalviewPropertyChangeListener(String propertyName,
 +          PropertyChangeListener listener)
 +  {
 +    changeSupport.removeJalviewPropertyChangeListener(propertyName,
 +            listener);
    }
  
 -  /** Singleton Desktop instance */
 -  public static Desktop instance;
 +  private MyDesktopPane desktopPane;
  
 -  public static MyDesktopPane desktop;
 +  public static MyDesktopPane getDesktopPane()
 +  {
 +    Desktop desktop = getInstance();
 +    return desktop == null ? null : desktop.desktopPane;
 +  }
  
 -  public static MyDesktopPane getDesktop() {
 -    // BH 2018 could use currentThread() here as a reference to a
 -    // Hashtable<Thread, MyDesktopPane> in JavaScript
 -    return desktop;
 +  /**
 +   * Answers an 'application scope' singleton instance of this class. Separate
 +   * SwingJS 'applets' running in the same browser page will each have a
 +   * distinct instance of Desktop.
 +   * 
 +   * @return
 +   */
 +  public static Desktop getInstance()
 +  {
 +    return Jalview.isHeadlessMode() ? null
 +            : (Desktop) ApplicationSingletonProvider
 +                    .getInstance(Desktop.class);
    }
  
 -  static int openFrameCount = 0;
 +  public static StructureSelectionManager getStructureSelectionManager()
 +  {
 +    return StructureSelectionManager
 +            .getStructureSelectionManager(getInstance());
 +  }
 +
 +  int openFrameCount = 0;
  
 -  static final int xOffset = 30;
 +  final int xOffset = 30;
  
 -  static final int yOffset = 30;
 +  final int yOffset = 30;
  
 -  public static jalview.ws.jws1.Discoverer discoverer;
 +  public jalview.ws.jws1.Discoverer discoverer;
  
 -  public static Object[] jalviewClipboard;
 +  public Object[] jalviewClipboard;
  
 -  public static boolean internalCopy = false;
 +  public boolean internalCopy = false;
  
 -  static int fileLoadingCount = 0;
 +  int fileLoadingCount = 0;
  
 -  class MyDesktopManager implements DesktopManager {
 +  class MyDesktopManager implements DesktopManager
 +  {
  
      private DesktopManager delegate;
  
 -    public MyDesktopManager(DesktopManager delegate) {
 +    public MyDesktopManager(DesktopManager delegate)
 +    {
        this.delegate = delegate;
      }
  
      @Override
 -    public void activateFrame(JInternalFrame f) {
 -      try {
 +    public void activateFrame(JInternalFrame f)
 +    {
 +      try
 +      {
          delegate.activateFrame(f);
 -      } catch (NullPointerException npe) {
 +      } catch (NullPointerException npe)
 +      {
          Point p = getMousePosition();
 -        instance.showPasteMenu(p.x, p.y);
 +        showPasteMenu(p.x, p.y);
        }
      }
  
      @Override
 -    public void beginDraggingFrame(JComponent f) {
 +    public void beginDraggingFrame(JComponent f)
 +    {
        delegate.beginDraggingFrame(f);
      }
  
      @Override
 -    public void beginResizingFrame(JComponent f, int direction) {
 +    public void beginResizingFrame(JComponent f, int direction)
 +    {
        delegate.beginResizingFrame(f, direction);
      }
  
      @Override
 -    public void closeFrame(JInternalFrame f) {
 +    public void closeFrame(JInternalFrame f)
 +    {
        delegate.closeFrame(f);
      }
  
      @Override
 -    public void deactivateFrame(JInternalFrame f) {
 +    public void deactivateFrame(JInternalFrame f)
 +    {
        delegate.deactivateFrame(f);
      }
  
      @Override
 -    public void deiconifyFrame(JInternalFrame f) {
 +    public void deiconifyFrame(JInternalFrame f)
 +    {
        delegate.deiconifyFrame(f);
      }
  
      @Override
 -    public void dragFrame(JComponent f, int newX, int newY) {
 -      if (newY < 0) {
 +    public void dragFrame(JComponent f, int newX, int newY)
 +    {
 +      if (newY < 0)
 +      {
          newY = 0;
        }
        delegate.dragFrame(f, newX, newY);
      }
  
      @Override
 -    public void endDraggingFrame(JComponent f) {
 +    public void endDraggingFrame(JComponent f)
 +    {
        delegate.endDraggingFrame(f);
 -      desktop.repaint();
 +      desktopPane.repaint();
      }
  
      @Override
 -    public void endResizingFrame(JComponent f) {
 +    public void endResizingFrame(JComponent f)
 +    {
        delegate.endResizingFrame(f);
 -      desktop.repaint();
 +      desktopPane.repaint();
      }
  
      @Override
 -    public void iconifyFrame(JInternalFrame f) {
 +    public void iconifyFrame(JInternalFrame f)
 +    {
        delegate.iconifyFrame(f);
      }
  
      @Override
 -    public void maximizeFrame(JInternalFrame f) {
 +    public void maximizeFrame(JInternalFrame f)
 +    {
        delegate.maximizeFrame(f);
      }
  
      @Override
 -    public void minimizeFrame(JInternalFrame f) {
 +    public void minimizeFrame(JInternalFrame f)
 +    {
        delegate.minimizeFrame(f);
      }
  
      @Override
 -    public void openFrame(JInternalFrame f) {
 +    public void openFrame(JInternalFrame f)
 +    {
        delegate.openFrame(f);
      }
  
      @Override
 -    public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
 -      if (newY < 0) {
 +    public void resizeFrame(JComponent f, int newX, int newY, int newWidth,
 +            int newHeight)
 +    {
 +      if (newY < 0)
 +      {
          newY = 0;
        }
        delegate.resizeFrame(f, newX, newY, newWidth, newHeight);
      }
  
      @Override
 -    public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) {
 +    public void setBoundsForFrame(JComponent f, int newX, int newY,
 +            int newWidth, int newHeight)
 +    {
        delegate.setBoundsForFrame(f, newX, newY, newWidth, newHeight);
      }
  
    }
  
    /**
 -   * Creates a new Desktop object.
 +   * Private constructor enforces singleton pattern. It is called by reflection
 +   * from ApplicationSingletonProvider.getInstance().
     */
 -  public Desktop() {
 +  private 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;
 +    Cache.initLogger();
 +    try
 +    {
 +      /**
 +       * 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.
 +       */
  
 -    doConfigureStructurePrefs();
 +      doConfigureStructurePrefs();
-       setTitle("Jalview " + Cache.getProperty("VERSION"));
-       /*
-       if (!Platform.isAMac())
-       {
-       // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
-       }
-       else
-       {
-        this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
-       }
-       */
+     setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
  
-       try
-       {
-         APQHandlers.setAPQHandlers(this);
-       } catch (Throwable t)
-       {
-         System.out.println("Error setting APQHandlers: " + t.toString());
-         // t.printStackTrace();
+     /**
+      * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
+      * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
+      * documented or guaranteed to exist, so we access it via reflection. There
+      * appear to be unfathomable criteria about what this string can contain, and it
+      * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
+      * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
+      * but "Jalview non-release" does not. The reflection access may generate a
+      * warning: WARNING: An illegal reflective access operation has occurred
+      * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
+      * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
+      */
+     if (Platform.isLinux()) {
+       try {
+         Toolkit xToolkit = Toolkit.getDefaultToolkit();
+         Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
+         Field awtAppClassNameField = null;
+         if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
+           awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
+         }
+         String title = ChannelProperties.getProperty("app_name");
+         if (awtAppClassNameField != null) {
+           awtAppClassNameField.setAccessible(true);
+           awtAppClassNameField.set(xToolkit, title);
+         } else {
+           Cache.log.debug("XToolkit: awtAppClassName not found");
+         }
+       } catch (Exception e) {
+         Cache.debug("Error setting awtAppClassName");
+         Cache.trace(Cache.getStackTraceString(e));
        }
+     }
  
-       addWindowListener(new WindowAdapter()
-       {
+     /**
+      * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
+      * macOS's application menu. APQHandlers will check to see if a handler is
+      * supported before setting it.
+      */
+     try {
+       APQHandlers.setAPQHandlers(this);
+     } catch (Exception e) {
+       System.out.println("Cannot set APQHandlers");
+       // e.printStackTrace();
+     } catch (Throwable t) {
+       Cache.warn("Error setting APQHandlers: " + t.toString());
+       Cache.trace(Cache.getStackTraceString(t));
+     }
+     setIconImages(ChannelProperties.getIconList());
  
-         @Override
-         public void windowClosing(WindowEvent ev)
-         {
-           quit();
-         }
-       });
+     addWindowListener(new WindowAdapter() {
+       @Override
+       public void windowClosing(WindowEvent ev) {
+         quit();
+       }
+     });
  
 -    boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
 +      boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
  
 -    boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
 -    desktop = new MyDesktopPane(selmemusage);
 +      boolean showjconsole = Cache.getDefault("SHOW_JAVA_CONSOLE", false);
 +      desktopPane = new MyDesktopPane(selmemusage);
  
 -    showMemusage.setSelected(selmemusage);
 -    desktop.setBackground(Color.white);
 +      showMemusage.setSelected(selmemusage);
 +      desktopPane.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);
 +      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");
 -    }
 +      // 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);
 +      getContentPane().add(desktopPane, BorderLayout.CENTER);
 +      desktopPane.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.isWindowsAndNotJS() ? new DefaultDesktopManager()
 -        : Platform.isAMacAndNotJS() ? new AquaInternalFrameManager(desktop.getDesktopManager())
 -            : desktop.getDesktopManager())));
 -    Rectangle dims = getLastKnownDimensions("");
 -    if (dims != null) {
 -      setBounds(dims);
 -    } else {
 -      Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 -      int xPos = Math.max(5, (screenSize.width - 900) / 2);
 -      int yPos = Math.max(5, (screenSize.height - 650) / 2);
 -      setBounds(xPos, yPos, 900, 650);
 -    }
 +      // This line prevents Windows Look&Feel resizing all new windows to
 +      // maximum
 +      // if previous window was maximised
 +      desktopPane.setDesktopManager(new MyDesktopManager(
 +              (Platform.isWindowsAndNotJS() ? new DefaultDesktopManager()
 +                      : Platform.isAMacAndNotJS()
 +                              ? new AquaInternalFrameManager(
 +                                      desktopPane.getDesktopManager())
 +                              : desktopPane.getDesktopManager())));
  
 -    if (!Platform.isJS())
 -    /**
 -     * Java only
 -     * 
 -     * @j2sIgnore
 -     */
 -    {
 -      jconsole = new Console(this, showjconsole);
 -      jconsole.setHeader(Cache.getVersionDetailsForConsole());
 -      showConsole(showjconsole);
 +      Rectangle dims = getLastKnownDimensions("");
 +      if (dims != null)
 +      {
 +        setBounds(dims);
 +      }
 +      else
 +      {
 +        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 +        int xPos = Math.max(5, (screenSize.width - 900) / 2);
 +        int yPos = Math.max(5, (screenSize.height - 650) / 2);
 +        setBounds(xPos, yPos, 900, 650);
 +      }
  
-       getIdentifiersOrgData();
 -      showNews.setVisible(false);
 +      if (!Platform.isJS())
 +      /**
 +       * Java only
 +       * 
 +       * @j2sIgnore
 +       */
 +      {
 +        jconsole = new Console(this, showjconsole);
 +        jconsole.setHeader(Cache.getVersionDetailsForConsole());
 +        showConsole(showjconsole);
  
-         showNews.setVisible(false);
 -      experimentalFeatures.setSelected(showExperimental());
++        showNews.setVisible(false); // not sure if we should only do this for interactive session?
  
 -      getIdentifiersOrgData();
 +        experimentalFeatures.setSelected(showExperimental());
  
 -      checkURLLinks();
++        getIdentifiersOrgData();
 -      // Spawn a thread that shows the splashscreen
 -      if (!nosplash) {
 -        SwingUtilities.invokeLater(new Runnable() {
 -          @Override
 -          public void run() {
 -            new SplashScreen(true);
 +        if (Jalview.isInteractive())
 +        {
 +          // disabled for SeqCanvasTest
 +          checkURLLinks();
 +
 +          // Spawn a thread that shows the splashscreen
++          if (!nosplash) {
 +          SwingUtilities.invokeLater(new Runnable()
-           {
-             @Override
-             public void run()
-             {
-               new SplashScreen(true);
-             }
-           });
++           {
++             @Override
++             public void run()
++             {
++               new SplashScreen(true);
++             }
++           });
+           }
 -        });
 -      }
  
 -      // 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.");
 +          // 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);
 +                    }
 +                  });
          }
 -      }).start();
 -      // Add the service change listener
 -      changeSupport.addJalviewPropertyChangeListener("services", new PropertyChangeListener() {
 +      }
 +      this.setDropTarget(new java.awt.dnd.DropTarget(desktopPane, this));
 +
 +      this.addWindowListener(new WindowAdapter()
 +      {
          @Override
 -        public void propertyChange(PropertyChangeEvent evt) {
 -          Cache.log.debug("Firing service changed event for " + evt.getNewValue());
 -          JalviewServicesChanged(evt);
 +        public void windowClosing(WindowEvent evt)
 +        {
 +          quit();
          }
        });
 -    }
 -
 -    this.setDropTarget(new java.awt.dnd.DropTarget(desktop, this));
  
 -    this.addWindowListener(new WindowAdapter() {
 -      @Override
 -      public void windowClosing(WindowEvent evt) {
 -        quit();
 -      }
 -    });
 -
 -    MouseAdapter ma;
 -    this.addMouseListener(ma = new MouseAdapter() {
 -      @Override
 -      public void mousePressed(MouseEvent evt) {
 -        if (evt.isPopupTrigger()) // Mac
 +      MouseAdapter ma;
 +      this.addMouseListener(ma = new MouseAdapter()
 +      {
 +        @Override
 +        public void mousePressed(MouseEvent evt)
          {
 -          showPasteMenu(evt.getX(), evt.getY());
 +          if (evt.isPopupTrigger()) // Mac
 +          {
 +            showPasteMenu(evt.getX(), evt.getY());
 +          }
          }
 -      }
 -
 -      @Override
 -      public void mouseReleased(MouseEvent evt) {
 -        if (evt.isPopupTrigger()) // Windows
 +        @Override
 +        public void mouseReleased(MouseEvent evt)
          {
 -          showPasteMenu(evt.getX(), evt.getY());
 +          if (evt.isPopupTrigger()) // Windows
 +          {
 +            showPasteMenu(evt.getX(), evt.getY());
 +          }
          }
 -      }
 -    });
 -    desktop.addMouseListener(ma);
 +      });
 +      desktopPane.addMouseListener(ma);
 +    } catch (Throwable t)
 +    {
 +      t.printStackTrace();
 +    }
 +
    }
  
    /**
     * 
     * @return
     */
 -  public boolean showExperimental() {
 -    String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES, Boolean.FALSE.toString());
 +  public boolean showExperimental()
 +  {
 +    String experimental = Cache.getDefault(EXPERIMENTAL_FEATURES,
 +            Boolean.FALSE.toString());
      return Boolean.valueOf(experimental).booleanValue();
    }
  
 -  public void doConfigureStructurePrefs() {
 +  public void doConfigureStructurePrefs()
 +  {
      // configure services
-     StructureSelectionManager ssm = StructureSelectionManager
-             .getStructureSelectionManager(this);
-     if (Cache.getDefault(Preferences.ADD_SS_ANN, true))
-     {
-       ssm.setAddTempFacAnnot(
-               Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
-       ssm.setProcessSecondaryStructure(
-               Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
-       ssm.setSecStructServices(
-               Cache.getDefault(Preferences.USE_RNAVIEW, true));
-     }
-     else
-     {
+     StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
+     if (Cache.getDefault(Preferences.ADD_SS_ANN, true)) {
+       ssm.setAddTempFacAnnot(Cache.getDefault(Preferences.ADD_TEMPFACT_ANN, true));
+       ssm.setProcessSecondaryStructure(Cache.getDefault(Preferences.STRUCT_FROM_PDB, true));
+       // JAL-3915 - RNAView is no longer an option so this has no effect
+       ssm.setSecStructServices(Cache.getDefault(Preferences.USE_RNAVIEW, false));
+     } else {
        ssm.setAddTempFacAnnot(false);
        ssm.setProcessSecondaryStructure(false);
        ssm.setSecStructServices(false);
      }
    }
  
-   public void checkForNews()
-   {
+   public void checkForNews() {
      final Desktop me = this;
      // Thread off the news reader, in case there are connection problems.
-     new Thread(new Runnable()
-     {
+     new Thread(new Runnable() {
        @Override
-       public void run()
-       {
+       public void run() {
          Cache.log.debug("Starting news thread.");
          jvnews = new BlogReader(me);
          showNews.setVisible(true);
      }).start();
    }
  
-   public void getIdentifiersOrgData()
-   {
-     // Thread off the identifiers fetcher
-     new Thread(new Runnable()
-     {
-       @Override
-       public void run()
-       {
-         Cache.log.debug("Downloading data from identifiers.org");
-         try
-         {
-           UrlDownloadClient.download(IdOrgSettings.getUrl(),
-                   IdOrgSettings.getDownloadLocation());
-         } catch (IOException e)
-         {
-           Cache.log.debug("Exception downloading identifiers.org data"
-                   + e.getMessage());
+   public void getIdentifiersOrgData() {
+     if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
+       // Thread off the identifiers fetcher
+       new Thread(new Runnable() {
+         @Override
+         public void run() {
+           Cache.log.debug("Downloading data from identifiers.org");
+           try {
+             UrlDownloadClient.download(IdOrgSettings.getUrl(), IdOrgSettings.getDownloadLocation());
+           } catch (IOException e) {
+             Cache.log.debug("Exception downloading identifiers.org data" + e.getMessage());
+           }
          }
-       }
-     }).start();
+       }).start();
+     }
    }
  
    @Override
-   protected void showNews_actionPerformed(ActionEvent e)
-   {
+   protected void showNews_actionPerformed(ActionEvent e) {
      showNews(showNews.isSelected());
    }
  
 -  void showNews(boolean visible) {
 +  void showNews(boolean visible)
 +  {
      Cache.log.debug((visible ? "Showing" : "Hiding") + " news.");
      showNews.setSelected(visible);
 -    if (visible && !jvnews.isVisible()) {
 -      new Thread(new Runnable() {
 +    if (visible && !jvnews.isVisible())
 +    {
 +      new Thread(new Runnable()
 +      {
          @Override
 -        public void run() {
 +        public void run()
 +        {
            long now = System.currentTimeMillis();
 -          Desktop.instance.setProgressBar(MessageManager.getString("status.refreshing_news"), now);
 +          setProgressBar(MessageManager.getString("status.refreshing_news"),
 +                  now);
            jvnews.refreshNews();
 -          Desktop.instance.setProgressBar(null, now);
 +          setProgressBar(null, now);
            jvnews.showNews();
          }
        }).start();
    /**
     * recover the last known dimensions for a jalview window
     * 
 -   * @param windowName - empty string is desktop, all other windows have unique
 -   *                   prefix
 +   * @param windowName
 +   *          - empty string is desktop, all other windows have unique prefix
     * @return null or last known dimensions scaled to current geometry (if last
     *         window geom was known)
     */
 -  Rectangle getLastKnownDimensions(String windowName) {
 +  Rectangle getLastKnownDimensions(String windowName)
 +  {
      // TODO: lock aspect ratio for scaling desktop Bug #0058199
      Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
      String x = Cache.getProperty(windowName + "SCREEN_X");
      String y = Cache.getProperty(windowName + "SCREEN_Y");
      String width = Cache.getProperty(windowName + "SCREEN_WIDTH");
      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 (Cache.getProperty("SCREENGEOMETRY_WIDTH") != null)
-       {
+     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 (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(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
-         double sh = ((1f * screenSize.height) / (1f * Integer
-                 .parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
+         double sw = ((1f * screenSize.width) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_WIDTH"))));
+         double sh = ((1f * screenSize.height) / (1f * Integer.parseInt(Cache.getProperty("SCREENGEOMETRY_HEIGHT"))));
          // rescale the bounds depending upon the current screen geometry.
          ix = (int) (ix * sw);
          iw = (int) (iw * sw);
          iy = (int) (iy * sh);
          ih = (int) (ih * sh);
-         while (ix >= screenSize.width)
-         {
-           Cache.log.debug(
-                   "Window geometry location recall error: shifting horizontal to within screenbounds.");
+         while (ix >= screenSize.width) {
+           Cache.log.debug("Window geometry location recall error: shifting horizontal to within screenbounds.");
            ix -= screenSize.width;
          }
-         while (iy >= screenSize.height)
-         {
-           Cache.log.debug(
-                   "Window geometry location recall error: shifting vertical to within screenbounds.");
+         while (iy >= screenSize.height) {
+           Cache.log.debug("Window geometry location recall error: shifting vertical to within screenbounds.");
            iy -= screenSize.height;
          }
-         Cache.log.debug(
-                 "Got last known dimensions for " + windowName + ": x:" + ix
-                         + " y:" + iy + " width:" + iw + " height:" + ih);
+         Cache.log.debug("Got last known dimensions for " + windowName + ": x:" + ix + " y:" + iy + " width:" + iw
+             + " height:" + ih);
        }
        // return dimensions for new instance
        return new Rectangle(ix, iy, iw, ih);
      return null;
    }
  
 -  void showPasteMenu(int x, int y) {
 +  void showPasteMenu(int x, int y)
 +  {
      JPopupMenu popup = new JPopupMenu();
 -    JMenuItem item = new JMenuItem(MessageManager.getString("label.paste_new_window"));
 -    item.addActionListener(new ActionListener() {
 +    JMenuItem item = new JMenuItem(
 +            MessageManager.getString("label.paste_new_window"));
 +    item.addActionListener(new ActionListener()
 +    {
        @Override
 -      public void actionPerformed(ActionEvent evt) {
 +      public void actionPerformed(ActionEvent evt)
 +      {
          paste();
        }
      });
      popup.show(this, x, y);
    }
  
 -  public void paste() {
 -    try {
 +  public void paste()
 +  {
 +    try
 +    {
        Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable contents = c.getContents(this);
  
 -      if (contents != null) {
 -        String file = (String) contents.getTransferData(DataFlavor.stringFlavor);
 +      if (contents != null)
 +      {
 +        String file = (String) contents
 +                .getTransferData(DataFlavor.stringFlavor);
  
 -        FileFormatI format = new IdentifyFile().identify(file, DataSourceType.PASTE);
 +        FileFormatI format = new IdentifyFile().identify(file,
 +                DataSourceType.PASTE);
  
          new FileLoader().LoadFile(file, DataSourceType.PASTE, format);
  
        }
 -    } catch (Exception ex) {
 -      System.out.println("Unable to paste alignment from system clipboard:\n" + ex);
 +    } catch (Exception ex)
 +    {
 +      System.out.println(
 +              "Unable to paste alignment from system clipboard:\n" + ex);
      }
    }
  
- //  /**
- //   * Add an internal frame to the Jalview desktop that is allowed to be resized,
- //   * has a minimum size of 300px and might or might not be visible
- //   * 
- //   * @param frame
- //   *          Frame to show
- //   * @param title
- //   *          Visible Title
- //   * @param makeVisible
- //   *          When true, display frame immediately, otherwise, caller must call
- //   *          setVisible themselves.
- //   * @param w
- //   *          width
- //   * @param h
- //   *          height
- //   */
- //  @Deprecated
- //  public static synchronized void addInternalFrame(
- //          final JInternalFrame frame, String title, boolean makeVisible,
- //          int w, int h)
- //  {
- //    // textbox, web services, sequenceFetcher, featureSettings
- //    getInstance().addFrame(frame, title, makeVisible, w, h,
- //            FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
- //  }
- //
- //  /**
- //   * Add an internal frame to the Jalview desktop that is visible, has a minimum
- //   * size of 300px, and may or may not be resizable
- //   * 
- //   * @param frame
- //   *          Frame to show
- //   * @param title
- //   *          Visible Title
- //   * @param w
- //   *          width
- //   * @param h
- //   *          height
- //   * @param resizable
- //   *          Allow resize
- //   */
- //  @Deprecated
- //  public static synchronized void addInternalFrame(
- //          final JInternalFrame frame, String title, int w, int h,
- //          boolean resizable)
- //  {
- //    // annotation, font, calculation, user-defined colors
- //    getInstance().addFrame(frame, title, FRAME_MAKE_VISIBLE, w, h,
- //            resizable, FRAME_SET_MIN_SIZE_300);
- //  }
 -  /**
 -   * Adds and opens the given frame to the desktop
 -   * 
 -   * @param frame Frame to show
 -   * @param title Visible Title
 -   * @param w     width
 -   * @param h     height
 -   */
 -  public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h) {
 -    addInternalFrame(frame, title, true, w, h, true, false);
 -  }
 -  /**
 -   * Add an internal frame to the Jalview desktop
 -   * 
 -   * @param frame       Frame to show
 -   * @param title       Visible Title
 -   * @param makeVisible When true, display frame immediately, otherwise, caller
 -   *                    must call setVisible themselves.
 -   * @param w           width
 -   * @param h           height
 -   */
 -  public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
 -      int h) {
 -    addInternalFrame(frame, title, makeVisible, w, h, true, false);
 -  }
  
    /**
 -   * Add an internal frame to the Jalview desktop and make it visible
 +   * Adds and opens the given frame to the desktop that is visible, allowed to
 +   * resize, and has a 300px minimum width.
     * 
 -   * @param frame     Frame to show
 -   * @param title     Visible Title
 -   * @param w         width
 -   * @param h         height
 -   * @param resizable Allow resize
 -   */
 -  public static synchronized void addInternalFrame(final JInternalFrame frame, String title, int w, int h,
 -      boolean resizable) {
 -    addInternalFrame(frame, title, true, w, h, resizable, false);
 +   * @param frame
 +   *          Frame to show
 +   * @param title
 +   *          Visible Title
 +   * @param w
 +   *          width
 +   * @param h
 +   *          height
 +   */
 +  public static synchronized void addInternalFrame(
 +          final JInternalFrame frame, String title, int w, int h)
 +  {
 +    // 58 classes
 +    
 +    addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, w, h, 
 +            FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
    }
  
    /**
 -   * Add an internal frame to the Jalview desktop
 +   * Add an internal frame to the Jalview desktop that may optionally be
 +   * visible, resizable, and allowed to be any size
     * 
 -   * @param frame         Frame to show
 -   * @param title         Visible Title
 -   * @param makeVisible   When true, display frame immediately, otherwise, caller
 -   *                      must call setVisible themselves.
 -   * @param w             width
 -   * @param h             height
 -   * @param resizable     Allow resize
 -   * @param ignoreMinSize Do not set the default minimum size for frame
 -   */
 -  public static synchronized void addInternalFrame(final JInternalFrame frame, String title, boolean makeVisible, int w,
 -      int h, boolean resizable, boolean ignoreMinSize) {
 +   * @param frame
 +   *          Frame to show
 +   * @param title
 +   *          Visible Title
 +   * @param makeVisible
 +   *          When true, display frame immediately, otherwise, caller must call
 +   *          setVisible themselves.
 +   * @param w
 +   *          width
 +   * @param h
 +   *          height
 +   * @param resizable
 +   *          Allow resize
 +   * @param ignoreMinSize
 +   *          Do not set the default minimum size for frame
 +   */
 +  public static synchronized void addInternalFrame(
 +          final JInternalFrame frame, String title, boolean makeVisible,
 +          int w, int h, boolean resizable, boolean ignoreMinSize)
 +  {
 +    // 15 classes call this method directly.
 +    
      // TODO: allow callers to determine X and Y position of frame (eg. via
      // bounds object).
      // TODO: consider fixing method to update entries in the window submenu with
      // the current window title
  
      frame.setTitle(title);
 -    if (frame.getWidth() < 1 || frame.getHeight() < 1) {
 +    if (frame.getWidth() < 1 || frame.getHeight() < 1)
 +    {
        frame.setSize(w, h);
      }
 -    // THIS IS A PUBLIC STATIC METHOD, SO IT MAY BE CALLED EVEN IN
 -    // A HEADLESS STATE WHEN NO DESKTOP EXISTS. MUST RETURN
 -    // IF JALVIEW IS RUNNING HEADLESS
 -    // ///////////////////////////////////////////////
 -    if (instance == null || (System.getProperty("java.awt.headless") != null
 -        && System.getProperty("java.awt.headless").equals("true"))) {
 -      return;
 -    }
 +    if (getInstance() != null)
 +      getInstance().addFrame(frame, makeVisible, resizable,
 +            ignoreMinSize);
 +  }
 +
 +  // These can now by put into a single int flag, if desired:
 +  
 +  public final static boolean FRAME_MAKE_VISIBLE = true;
 +
 +  public final static boolean FRAME_NOT_VISIBLE = false;
 +
 +  public final static boolean FRAME_ALLOW_RESIZE = true;
 +
 +  public final static boolean FRAME_NOT_RESIZABLE = false;
 +
 +  public final static boolean FRAME_ALLOW_ANY_SIZE = true;
 +
 +  public final static boolean FRAME_SET_MIN_SIZE_300 = false;
 +  
 +  private void addFrame(JInternalFrame frame,
 +          boolean makeVisible, boolean resizable,
 +          boolean ignoreMinSize)
 +  {
  
      openFrameCount++;
 -    if (!ignoreMinSize) {
 -      frame.setMinimumSize(new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
 +    
 +    boolean isEmbedded = (Platform.getEmbeddedAttribute(frame, "id") != null);
 +    boolean hasEmbeddedSize = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
 +    // Web page embedding allows us to ignore minimum size
 +    ignoreMinSize |= hasEmbeddedSize;
 +    
 +    if (!ignoreMinSize)
 +    {
        // Set default dimension for Alignment Frame window.
        // The Alignment Frame window could be added from a number of places,
        // hence,
        // I did this here in order not to miss out on any Alignment frame.
 -      if (frame instanceof AlignFrame) {
 -        frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH, ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
 +      if (frame instanceof AlignFrame)
 +      {
 +        frame.setMinimumSize(new Dimension(ALIGN_FRAME_DEFAULT_MIN_WIDTH,
 +                ALIGN_FRAME_DEFAULT_MIN_HEIGHT));
 +      } else {
 +        frame.setMinimumSize(
 +                new Dimension(DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT));
 +        
        }
      }
  
      frame.setMaximizable(resizable);
      frame.setIconifiable(resizable);
      frame.setOpaque(Platform.isJS());
 -    if (frame.getX() < 1 && frame.getY() < 1) {
 -      frame.setLocation(xOffset * openFrameCount, yOffset * ((openFrameCount - 1) % 10) + yOffset);
 +    if (!isEmbedded && frame.getX() < 1 && frame.getY() < 1)
 +    {
 +      frame.setLocation(xOffset * openFrameCount,
 +              yOffset * ((openFrameCount - 1) % 10) + yOffset);
      }
  
      /*
 -     * add an entry for the new frame in the Window menu (and remove it when the
 -     * frame is closed)
 +     * add an entry for the new frame in the Window menu 
 +     * (and remove it when the frame is closed)
       */
 -    final JMenuItem menuItem = new JMenuItem(title);
 -    frame.addInternalFrameListener(new InternalFrameAdapter() {
 +    final JMenuItem menuItem = new JMenuItem(frame.getTitle());
 +    frame.addInternalFrameListener(new InternalFrameAdapter()
 +    {
        @Override
 -      public void internalFrameActivated(InternalFrameEvent evt) {
 -        JInternalFrame itf = desktop.getSelectedFrame();
 -        if (itf != null) {
 -          if (itf instanceof AlignFrame) {
 +      public void internalFrameActivated(InternalFrameEvent evt)
 +      {
 +        JInternalFrame itf = getDesktopPane().getSelectedFrame();
 +        if (itf != null)
 +        {
 +          if (itf instanceof AlignFrame)
 +          {
              Jalview.setCurrentAlignFrame((AlignFrame) itf);
            }
            itf.requestFocus();
        }
  
        @Override
 -      public void internalFrameClosed(InternalFrameEvent evt) {
 +      public void internalFrameClosed(InternalFrameEvent evt)
 +      {
          PaintRefresher.RemoveComponent(frame);
  
          /*
 -         * defensive check to prevent frames being added half off the window
 +         * defensive check to prevent frames being
 +         * added half off the window
           */
 -        if (openFrameCount > 0) {
 +        if (openFrameCount > 0)
 +        {
            openFrameCount--;
          }
  
          /*
           * ensure no reference to alignFrame retained by menu item listener
           */
 -        if (menuItem.getActionListeners().length > 0) {
 +        if (menuItem.getActionListeners().length > 0)
 +        {
            menuItem.removeActionListener(menuItem.getActionListeners()[0]);
          }
 -        windowMenu.remove(menuItem);
 +        getInstance().windowMenu.remove(menuItem);
        }
      });
  
 -    menuItem.addActionListener(new ActionListener() {
 +    menuItem.addActionListener(new ActionListener()
 +    {
        @Override
 -      public void actionPerformed(ActionEvent e) {
 -        try {
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        try
 +        {
            frame.setSelected(true);
            frame.setIcon(false);
 -        } catch (java.beans.PropertyVetoException ex) {
 -
 +        } catch (java.beans.PropertyVetoException ex)
 +        {
 +          // System.err.println(ex.toString());
          }
        }
      });
  
      setKeyBindings(frame);
  
 -    desktop.add(frame);
 +    getDesktopPane().add(frame);
  
 -    windowMenu.add(menuItem);
 +    getInstance().windowMenu.add(menuItem);
  
      frame.toFront();
 -    try {
 +    try
 +    {
        frame.setSelected(true);
        frame.requestFocus();
 -    } catch (java.beans.PropertyVetoException ve) {
 -    } catch (java.lang.ClassCastException cex) {
 +    } catch (java.beans.PropertyVetoException ve)
 +    {
 +    } catch (java.lang.ClassCastException cex)
 +    {
        Cache.log.warn(
 -          "Squashed a possible GUI implementation error. If you can recreate this, please look at https://issues.jalview.org/browse/JAL-869",
 -          cex);
 +              "Squashed a possible GUI implementation error. If you can recreate this, please look at http://issues.jalview.org/browse/JAL-869",
 +              cex);
      }
    }
  
    /**
 -   * 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
     */
 -  private static void setKeyBindings(JInternalFrame frame) {
 -    @SuppressWarnings("serial")
 -    final Action closeAction = new AbstractAction() {
 +  private static void setKeyBindings(JInternalFrame frame)
 +  {
 +    final Action closeAction = new AbstractAction()
 +    {
        @Override
 -      public void actionPerformed(ActionEvent e) {
 +      public void actionPerformed(ActionEvent e)
 +      {
          frame.dispose();
        }
      };
      /*
       * set up key bindings for Ctrl-W and Cmd-W, with the same (Close) action
       */
-     KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-             InputEvent.CTRL_DOWN_MASK);
-     KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W,
-             Platform.SHORTCUT_KEY_MASK);
+     KeyStroke ctrlWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK);
+     KeyStroke cmdWKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx());
  
 -    InputMap inputMap = frame.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
 +    InputMap inputMap = frame
 +            .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
      String ctrlW = ctrlWKey.toString();
      inputMap.put(ctrlWKey, ctrlW);
      inputMap.put(cmdWKey, ctrlW);
    }
  
    @Override
 -  public void lostOwnership(Clipboard clipboard, Transferable contents) {
 -    if (!internalCopy) {
 -      Desktop.jalviewClipboard = null;
 +  public void lostOwnership(Clipboard clipboard, Transferable contents)
 +  {
 +    if (!internalCopy)
 +    {
 +      jalviewClipboard = null;
      }
  
      internalCopy = false;
    }
  
    @Override
 -  public void dragEnter(DropTargetDragEvent evt) {
 +  public void dragEnter(DropTargetDragEvent evt)
 +  {
    }
  
    @Override
 -  public void dragExit(DropTargetEvent evt) {
 +  public void dragExit(DropTargetEvent evt)
 +  {
    }
  
    @Override
 -  public void dragOver(DropTargetDragEvent evt) {
 +  public void dragOver(DropTargetDragEvent evt)
 +  {
    }
  
    @Override
 -  public void dropActionChanged(DropTargetDragEvent evt) {
 +  public void dropActionChanged(DropTargetDragEvent evt)
 +  {
    }
  
    /**
     * DOCUMENT ME!
     * 
 -   * @param evt DOCUMENT ME!
 +   * @param evt
 +   *          DOCUMENT ME!
     */
    @Override
 -  public void drop(DropTargetDropEvent evt) {
 +  public void drop(DropTargetDropEvent evt)
 +  {
      boolean success = true;
      // JAL-1552 - acceptDrop required before getTransferable call for
      // Java's Transferable for native dnd
      List<Object> files = new ArrayList<>();
      List<DataSourceType> protocols = new ArrayList<>();
  
 -    try {
 -      Desktop.transferFromDropTarget(files, protocols, evt, t);
 -    } catch (Exception e) {
 +    try
 +    {
 +      transferFromDropTarget(files, protocols, evt, t);
 +    } catch (Exception e)
 +    {
        e.printStackTrace();
        success = false;
      }
  
 -    if (files != null) {
 -      try {
 -        for (int i = 0; i < files.size(); i++) {
 +    if (files != null)
 +    {
 +      try
 +      {
 +        for (int i = 0; i < files.size(); i++)
 +        {
            // BH 2018 File or String
            Object file = files.get(i);
            String fileName = file.toString();
 -          DataSourceType protocol = (protocols == null) ? DataSourceType.FILE : protocols.get(i);
 +          DataSourceType protocol = (protocols == null)
 +                  ? DataSourceType.FILE
 +                  : protocols.get(i);
            FileFormatI format = null;
  
 -          if (fileName.endsWith(".jar")) {
 +          if (fileName.endsWith(".jar"))
 +          {
              format = FileFormat.Jalview;
  
 -          } else {
 +          }
 +          else
 +          {
              format = new IdentifyFile().identify(file, protocol);
            }
 -          if (file instanceof File) {
 +          if (file instanceof File)
 +          {
              Platform.cacheFileData((File) file);
            }
            new FileLoader().LoadFile(null, file, protocol, format);
  
          }
 -      } catch (Exception ex) {
 +      } catch (Exception ex)
 +      {
          success = false;
        }
      }
    /**
     * DOCUMENT ME!
     * 
 -   * @param e DOCUMENT ME!
 +   * @param e
 +   *          DOCUMENT ME!
     */
    @Override
 -  public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport) {
 +  public void inputLocalFileMenuItem_actionPerformed(AlignViewport viewport)
 +  {
      String fileFormat = Cache.getProperty("DEFAULT_FILE_FORMAT");
 -    JalviewFileChooser chooser = JalviewFileChooser.forRead(Cache.getProperty("LAST_DIRECTORY"), fileFormat,
 -        BackupFiles.getEnabled());
 +    JalviewFileChooser chooser = JalviewFileChooser.forRead(
 +            Cache.getProperty("LAST_DIRECTORY"), fileFormat,
 +            BackupFiles.getEnabled());
  
      chooser.setFileView(new JalviewFileView());
 -    chooser.setDialogTitle(MessageManager.getString("label.open_local_file"));
 +    chooser.setDialogTitle(
 +            MessageManager.getString("label.open_local_file"));
      chooser.setToolTipText(MessageManager.getString("action.open"));
  
 -    chooser.setResponseHandler(0, new Runnable() {
 +    chooser.setResponseHandler(0, new Runnable()
 +    {
        @Override
 -      public void run() {
 +      public void run()
 +      {
          File selectedFile = chooser.getSelectedFile();
          Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
  
  
          /*
           * 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.
 +         * 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(selectedFile, DataSourceType.FILE);
 -          } catch (FileFormatException e) {
 +        if (FileFormats.getInstance().isIdentifiable(format))
 +        {
 +          try
 +          {
 +            format = new IdentifyFile().identify(selectedFile,
 +                    DataSourceType.FILE);
 +          } catch (FileFormatException e)
 +          {
              // format = null; //??
            }
          }
  
 -        new FileLoader().LoadFile(viewport, selectedFile, DataSourceType.FILE, format);
 +        new FileLoader().LoadFile(viewport, selectedFile,
 +                DataSourceType.FILE, format);
        }
      });
      chooser.showOpenDialog(this);
     * @param viewport
     */
    @Override
 -  public void inputURLMenuItem_actionPerformed(AlignViewport viewport) {
 +  public void inputURLMenuItem_actionPerformed(AlignViewport viewport)
 +  {
      // This construct allows us to have a wider textfield
      // for viewing
 -    JLabel label = new JLabel(MessageManager.getString("label.input_file_url"));
 +    JLabel label = new JLabel(
 +            MessageManager.getString("label.input_file_url"));
  
      JPanel panel = new JPanel(new GridLayout(2, 1));
      panel.add(label);
  
      /*
 -     * the URL to fetch is input in Java: an editable combobox with history JS:
 -     * (pending JAL-3038) a plain text field
 +     * the URL to fetch is
 +     * Java: an editable combobox with history
 +     * JS: (pending JAL-3038) a plain text field
       */
      JComponent history;
 -    String urlBase = "https://www.";
 -    if (Platform.isJS()) {
 +    String urlBase = "http://www.";
 +    if (Platform.isJS())
 +    {
        history = new JTextField(urlBase, 35);
 -    } else
 +    }
 +    else
      /**
       * Java only
       * 
        asCombo.setEditable(true);
        asCombo.addItem(urlBase);
        String historyItems = Cache.getProperty("RECENT_URL");
 -      if (historyItems != null) {
 -        for (String token : historyItems.split("\\t")) {
 +      if (historyItems != null)
 +      {
 +        for (String token : historyItems.split("\\t"))
 +        {
            asCombo.addItem(token);
          }
        }
  
      Object[] options = new Object[] { MessageManager.getString("action.ok"),
          MessageManager.getString("action.cancel") };
 -    Runnable action = new Runnable() {
 +    Runnable action = new Runnable()
 +    {
        @Override
 -      public void run() {
 +      public void run()
 +      {
          @SuppressWarnings("unchecked")
-         String url = (history instanceof JTextField
-                 ? ((JTextField) history).getText()
-                 : ((JComboBox<String>) history).getSelectedItem()
-                         .toString());
-         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);
+         String url = (history instanceof JTextField ? ((JTextField) history).getText()
+             : ((JComboBox<String>) history).getEditor().getItem().toString().trim());
+         if (url.toLowerCase(Locale.ROOT).endsWith(".jar")) {
+           if (viewport != null) {
+             new FileLoader().LoadFile(viewport, url, DataSourceType.URL, FileFormat.Jalview);
+           } else {
+             new FileLoader().LoadFile(url, DataSourceType.URL, FileFormat.Jalview);
            }
-         }
-         else
-         {
+         } else {
            FileFormatI format = null;
 -          try {
 +          try
 +          {
              format = new IdentifyFile().identify(url, DataSourceType.URL);
 -          } catch (FileFormatException e) {
 +          } catch (FileFormatException e)
 +          {
              // TODO revise error handling, distinguish between
              // URL not found and response not valid
            }
  
 -          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 (format == null)
 +          {
 +            String msg = MessageManager
 +                    .formatMessage("label.couldnt_locate", url);
 +            JvOptionPane.showInternalMessageDialog(getDesktopPane(), msg,
 +                    MessageManager.getString("label.url_not_found"),
 +                    JvOptionPane.WARNING_MESSAGE);
  
              return;
            }
  
 -          if (viewport != null) {
 -            new FileLoader().LoadFile(viewport, url, DataSourceType.URL, format);
 -          } else {
 +          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"));
 +    String dialogOption = MessageManager
 +            .getString("label.input_alignment_from_url");
 +    JvOptionPane.newOptionDialog(desktopPane).setResponseHandler(0, action)
 +            .showInternalDialog(panel, dialogOption,
 +                    JvOptionPane.YES_NO_CANCEL_OPTION,
 +                    JvOptionPane.PLAIN_MESSAGE, null, options,
 +                    MessageManager.getString("action.ok"));
    }
  
    /**
     * Opens the CutAndPaste window for the user to paste an alignment in to
     * 
 -   * @param viewPanel - if not null, the pasted alignment is added to the current
 -   *                  alignment; if null, to a new alignment window
 +   * @param viewPanel
 +   *          - if not null, the pasted alignment is added to the current
 +   *          alignment; if null, to a new alignment window
     */
    @Override
 -  public void inputTextboxMenuItem_actionPerformed(AlignmentViewPanel viewPanel) {
 +  public void inputTextboxMenuItem_actionPerformed(
 +          AlignmentViewPanel viewPanel)
 +  {
      CutAndPasteTransfer cap = new CutAndPasteTransfer();
      cap.setForInput(viewPanel);
 -    Desktop.addInternalFrame(cap, MessageManager.getString("label.cut_paste_alignmen_file"), true, 600, 500);
 +    addInternalFrame(cap,
 +            MessageManager.getString("label.cut_paste_alignmen_file"),
 +            FRAME_MAKE_VISIBLE, 600, 500, FRAME_ALLOW_RESIZE,
 +            FRAME_SET_MIN_SIZE_300);
    }
  
    /*
     * Exit the program
     */
    @Override
 -  public void quit() {
 +  public void quit()
 +  {
      Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
      Cache.setProperty("SCREENGEOMETRY_WIDTH", screen.width + "");
      Cache.setProperty("SCREENGEOMETRY_HEIGHT", screen.height + "");
 -    storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y, getWidth(), getHeight()));
 +    storeLastKnownDimensions("", new Rectangle(getBounds().x, getBounds().y,
 +            getWidth(), getHeight()));
  
 -    if (jconsole != null) {
 +    if (jconsole != null)
 +    {
        storeLastKnownDimensions("JAVA_CONSOLE_", jconsole.getBounds());
        jconsole.stopConsole();
      }
 -    if (jvnews != null) {
 +    if (jvnews != null)
 +    {
        storeLastKnownDimensions("JALVIEW_RSS_WINDOW_", jvnews.getBounds());
  
      }
 -    if (dialogExecutor != null) {
 +    if (dialogExecutor != null)
 +    {
        dialogExecutor.shutdownNow();
      }
      closeAll_actionPerformed(null);
  
 -    if (groovyConsole != null) {
 +    if (groovyConsole != null)
 +    {
        // suppress a possible repeat prompt to save script
        groovyConsole.setDirty(false);
        groovyConsole.exit();
      System.exit(0);
    }
  
 -  private void storeLastKnownDimensions(String string, Rectangle jc) {
 -    Cache.log.debug("Storing last known dimensions for " + string + ": x:" + jc.x + " y:" + jc.y + " width:" + jc.width
 -        + " height:" + jc.height);
 +  private void storeLastKnownDimensions(String string, Rectangle jc)
 +  {
 +    Cache.log.debug("Storing last known dimensions for " + string + ": x:"
 +            + jc.x + " y:" + jc.y + " width:" + jc.width + " height:"
 +            + jc.height);
  
      Cache.setProperty(string + "SCREEN_X", jc.x + "");
      Cache.setProperty(string + "SCREEN_Y", jc.y + "");
    /**
     * DOCUMENT ME!
     * 
 -   * @param e DOCUMENT ME!
 +   * @param e
 +   *          DOCUMENT ME!
     */
    @Override
 -  public void aboutMenuItem_actionPerformed(ActionEvent e) {
 -    new Thread(new Runnable() {
 +  public void aboutMenuItem_actionPerformed(ActionEvent e)
 +  {
 +    new Thread(new Runnable()
 +    {
        @Override
 -      public void run() {
 +      public void run()
 +      {
          new SplashScreen(false);
        }
      }).start();
  
    /**
     * Returns the html text for the About screen, including any available version
 -   * number, build details, author details and citation reference, but without the
 -   * enclosing {@code html} tags
 +   * number, build details, author details and citation reference, but without
 +   * the enclosing {@code html} tags
     * 
     * @return
     */
 -  public String getAboutMessage() {
 +  public String getAboutMessage()
 +  {
      StringBuilder message = new StringBuilder(1024);
-     message.append("<h1><strong>Version: ")
-             .append(Cache.getProperty("VERSION")).append("</strong></h1>")
-             .append("<strong>Built: <em>")
-             .append(Cache.getDefault("BUILD_DATE", "unknown"))
-             .append("</em> from ").append(Cache.getBuildDetailsForSplash())
-             .append("</strong>");
+     message.append("<div style=\"font-family: sans-serif;\">").append("<h1><strong>Version: ")
+         .append(Cache.getProperty("VERSION")).append("</strong></h1>").append("<strong>Built: <em>")
+         .append(Cache.getDefault("BUILD_DATE", "unknown")).append("</em> from ")
+         .append(Cache.getBuildDetailsForSplash()).append("</strong>");
  
      String latestVersion = Cache.getDefault("LATEST_VERSION", "Checking");
-     if (latestVersion.equals("Checking"))
-     {
+     if (latestVersion.equals("Checking")) {
        // JBP removed this message for 2.11: May be reinstated in future version
        // message.append("<br>...Checking latest version...</br>");
-     }
-     else if (!latestVersion.equals(Cache.getProperty("VERSION")))
-     {
+     } else if (!latestVersion.equals(Cache.getProperty("VERSION"))) {
        boolean red = false;
-       if (Cache.getProperty("VERSION").toLowerCase()
-               .indexOf("automated build") == -1)
-       {
+       if (Cache.getProperty("VERSION").toLowerCase(Locale.ROOT).indexOf("automated build") == -1) {
          red = true;
          // Displayed when code version and jnlp version do not match and code
          // version is not a development build
          message.append("<div style=\"color: #FF0000;font-style: bold;\">");
        }
  
-       message.append("<br>!! Version ")
-               .append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
-               .append(" is available for download from ")
-               .append(Cache.getDefault("www.jalview.org",
-                       "http://www.jalview.org"))
-               .append(" !!");
-       if (red)
-       {
+       message.append("<br>!! Version ").append(Cache.getDefault("LATEST_VERSION", "..Checking.."))
+           .append(" is available for download from ")
+           .append(Cache.getDefault("www.jalview.org", "https://www.jalview.org")).append(" !!");
+       if (red) {
          message.append("</div>");
        }
      }
      message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
      message.append(CITATION);
  
+     message.append("</div>");
 -
      return message.toString();
    }
  
     * Action on requesting Help documentation
     */
    @Override
-   public void documentationMenuItem_actionPerformed()
-   {
-     try
-     {
-       if (Platform.isJS())
-       {
-         BrowserLauncher.openURL("http://www.jalview.org/help.html");
-       }
-       else
+   public void documentationMenuItem_actionPerformed() {
+     try {
+       if (Platform.isJS()) {
+         BrowserLauncher.openURL("https://www.jalview.org/help.html");
+       } else
        /**
         * Java only
         * 
        {
          Help.showHelpWindow();
        }
-     } catch (Exception ex)
-     {
+     } catch (Exception ex) {
        System.err.println("Error opening help: " + ex.getMessage());
      }
    }
  
    @Override
 -  public void closeAll_actionPerformed(ActionEvent e) {
 +  public void closeAll_actionPerformed(ActionEvent e)
 +  {
      // TODO show a progress bar while closing?
 -    JInternalFrame[] frames = desktop.getAllFrames();
 -    for (int i = 0; i < frames.length; i++) {
 -      try {
 +    JInternalFrame[] frames = desktopPane.getAllFrames();
 +    for (int i = 0; i < frames.length; i++)
 +    {
 +      try
 +      {
          frames[i].setClosed(true);
 -      } catch (java.beans.PropertyVetoException ex) {
 +      } catch (java.beans.PropertyVetoException ex)
 +      {
        }
      }
      Jalview.setCurrentAlignFrame(null);
       * reset state of singleton objects as appropriate (clear down session state
       * when all windows are closed)
       */
 -    StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(this);
 -    if (ssm != null) {
 +    StructureSelectionManager ssm = StructureSelectionManager
 +            .getStructureSelectionManager(this);
 +    if (ssm != null)
 +    {
        ssm.resetAll();
      }
    }
  
    @Override
 -  public void raiseRelated_actionPerformed(ActionEvent e) {
 +  public void raiseRelated_actionPerformed(ActionEvent e)
 +  {
      reorderAssociatedWindows(false, false);
    }
  
    @Override
 -  public void minimizeAssociated_actionPerformed(ActionEvent e) {
 +  public void minimizeAssociated_actionPerformed(ActionEvent e)
 +  {
      reorderAssociatedWindows(true, false);
    }
  
 -  void closeAssociatedWindows() {
 +  void closeAssociatedWindows()
 +  {
      reorderAssociatedWindows(false, true);
    }
  
     * ActionEvent)
     */
    @Override
 -  protected void garbageCollect_actionPerformed(ActionEvent e) {
 +  protected void garbageCollect_actionPerformed(ActionEvent e)
 +  {
      // We simply collect the garbage
      Cache.log.debug("Collecting garbage...");
      System.gc();
    /*
     * (non-Javadoc)
     * 
 -   * @see jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.
 -   * ActionEvent )
 +   * @see
 +   * jalview.jbgui.GDesktop#showMemusage_actionPerformed(java.awt.event.ActionEvent
 +   * )
     */
    @Override
 -  protected void showMemusage_actionPerformed(ActionEvent e) {
 -    desktop.showMemoryUsage(showMemusage.isSelected());
 +  protected void showMemusage_actionPerformed(ActionEvent e)
 +  {
 +    desktopPane.showMemoryUsage(showMemusage.isSelected());
    }
  
    /*
     * )
     */
    @Override
 -  protected void showConsole_actionPerformed(ActionEvent e) {
 +  protected void showConsole_actionPerformed(ActionEvent e)
 +  {
      showConsole(showConsole.isSelected());
    }
  
     * 
     * @param selected
     */
 -  void showConsole(boolean selected) {
 +  void showConsole(boolean selected)
 +  {
      // TODO: decide if we should update properties file
      if (jconsole != null) // BH 2018
      {
        showConsole.setSelected(selected);
 -      Cache.setProperty("SHOW_JAVA_CONSOLE", Boolean.valueOf(selected).toString());
 +      Cache.setProperty("SHOW_JAVA_CONSOLE",
 +              Boolean.valueOf(selected).toString());
        jconsole.setVisible(selected);
      }
    }
  
 -  void reorderAssociatedWindows(boolean minimize, boolean close) {
 -    JInternalFrame[] frames = desktop.getAllFrames();
 -    if (frames == null || frames.length < 1) {
 +  void reorderAssociatedWindows(boolean minimize, boolean close)
 +  {
 +    JInternalFrame[] frames = desktopPane.getAllFrames();
 +    if (frames == null || frames.length < 1)
 +    {
        return;
      }
  
 -    AlignmentViewport source = null, target = null;
 -    if (frames[0] instanceof AlignFrame) {
 +    AlignViewportI source = null;
 +    AlignViewportI target = null;
 +    if (frames[0] instanceof AlignFrame)
 +    {
        source = ((AlignFrame) frames[0]).getCurrentView();
 -    } else if (frames[0] instanceof TreePanel) {
 +    }
 +    else if (frames[0] instanceof TreePanel)
 +    {
        source = ((TreePanel) frames[0]).getViewPort();
 -    } else if (frames[0] instanceof PCAPanel) {
 +    }
 +    else if (frames[0] instanceof PCAPanel)
 +    {
        source = ((PCAPanel) frames[0]).av;
 -    } else if (frames[0].getContentPane() instanceof PairwiseAlignPanel) {
 +    }
 +    else if (frames[0].getContentPane() instanceof PairwiseAlignPanel)
 +    {
        source = ((PairwiseAlignPanel) frames[0].getContentPane()).av;
      }
  
 -    if (source != null) {
 -      for (int i = 0; i < frames.length; i++) {
 +    if (source != null)
 +    {
 +      for (int i = 0; i < frames.length; i++)
 +      {
          target = null;
 -        if (frames[i] == null) {
 +        if (frames[i] == null)
 +        {
            continue;
          }
 -        if (frames[i] instanceof AlignFrame) {
 +        if (frames[i] instanceof AlignFrame)
 +        {
            target = ((AlignFrame) frames[i]).getCurrentView();
 -        } else if (frames[i] instanceof TreePanel) {
 +        }
 +        else if (frames[i] instanceof TreePanel)
 +        {
            target = ((TreePanel) frames[i]).getViewPort();
 -        } else if (frames[i] instanceof PCAPanel) {
 +        }
 +        else if (frames[i] instanceof PCAPanel)
 +        {
            target = ((PCAPanel) frames[i]).av;
 -        } else if (frames[i].getContentPane() instanceof PairwiseAlignPanel) {
 +        }
 +        else if (frames[i].getContentPane() instanceof PairwiseAlignPanel)
 +        {
            target = ((PairwiseAlignPanel) frames[i].getContentPane()).av;
          }
  
 -        if (source == target) {
 -          try {
 -            if (close) {
 +        if (source == target)
 +        {
 +          try
 +          {
 +            if (close)
 +            {
                frames[i].setClosed(true);
 -            } else {
 +            }
 +            else
 +            {
                frames[i].setIcon(minimize);
 -              if (!minimize) {
 +              if (!minimize)
 +              {
                  frames[i].toFront();
                }
              }
  
 -          } catch (java.beans.PropertyVetoException ex) {
 +          } catch (java.beans.PropertyVetoException ex)
 +          {
            }
          }
        }
    /**
     * DOCUMENT ME!
     * 
 -   * @param e DOCUMENT ME!
 +   * @param e
 +   *          DOCUMENT ME!
     */
    @Override
-   protected void preferences_actionPerformed(ActionEvent e)
-   {
-     new Preferences();
+   protected void preferences_actionPerformed(ActionEvent e) {
+     Preferences.openPreferences();
    }
  
    /**
     * Jalview project file
     */
    @Override
 -  public void saveState_actionPerformed() {
 +  public void saveState_actionPerformed()
 +  {
      saveState_actionPerformed(false);
    }
  
 -  public void saveState_actionPerformed(boolean saveAs) {
 +  public void saveState_actionPerformed(boolean saveAs)
 +  {
      java.io.File projectFile = getProjectFile();
      // autoSave indicates we already have a file and don't need to ask
 -    boolean autoSave = projectFile != null && !saveAs && BackupFiles.getEnabled();
 +    boolean autoSave = projectFile != null && !saveAs
 +            && BackupFiles.getEnabled();
  
      // System.out.println("autoSave="+autoSave+", projectFile='"+projectFile+"',
      // saveAs="+saveAs+", Backups
      // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
  
      boolean approveSave = false;
 -    if (!autoSave) {
 -      JalviewFileChooser chooser = new JalviewFileChooser("jvp", "Jalview Project");
 +    if (!autoSave)
 +    {
 +      JalviewFileChooser chooser = new JalviewFileChooser("jvp",
 +              "Jalview Project");
  
        chooser.setFileView(new JalviewFileView());
        chooser.setDialogTitle(MessageManager.getString("label.save_state"));
  
        int value = chooser.showSaveDialog(this);
  
 -      if (value == JalviewFileChooser.APPROVE_OPTION) {
 +      if (value == JalviewFileChooser.APPROVE_OPTION)
 +      {
          projectFile = chooser.getSelectedFile();
          setProjectFile(projectFile);
          approveSave = true;
        }
      }
--
-     if (approveSave || autoSave)
-     {
+     if (approveSave || autoSave) {
        final Desktop me = this;
        final java.io.File chosenFile = projectFile;
 -      new Thread(new Runnable() {
 +      new Thread(new Runnable()
 +      {
          @Override
 -        public void run() {
 +        public void run()
 +        {
            // TODO: refactor to Jalview desktop session controller action.
 -          setProgressBar(
 -              MessageManager.formatMessage("label.saving_jalview_project", new Object[] { chosenFile.getName() }),
 -              chosenFile.hashCode());
 +          setProgressBar(MessageManager.formatMessage(
 +                  "label.saving_jalview_project", new Object[]
 +                  { chosenFile.getName() }), chosenFile.hashCode());
            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 {
 +          try
 +          {
              boolean doBackup = BackupFiles.getEnabled();
 -            BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile) : null;
 +            BackupFiles backupfiles = doBackup ? new BackupFiles(chosenFile)
 +                    : null;
  
 -            new Jalview2XML().saveState(doBackup ? backupfiles.getTempFile() : chosenFile);
 +            new Jalview2XML().saveState(
 +                    doBackup ? backupfiles.getTempFile() : chosenFile);
  
 -            if (doBackup) {
 +            if (doBackup)
 +            {
                backupfiles.setWriteSuccess(true);
                backupfiles.rollBackupsAndRenameTempFile();
              }
 -          } catch (OutOfMemoryError oom) {
 -            new OOMWarning("Whilst saving current state to " + chosenFile.getName(), oom);
 -          } catch (Exception ex) {
 -            Cache.log.error("Problems whilst trying to save to " + chosenFile.getName(), ex);
 +          } catch (OutOfMemoryError oom)
 +          {
 +            new OOMWarning("Whilst saving current state to "
 +                    + chosenFile.getName(), oom);
 +          } catch (Exception ex)
 +          {
 +            Cache.log.error("Problems whilst trying to save to "
 +                    + chosenFile.getName(), ex);
              JvOptionPane.showMessageDialog(me,
 -                MessageManager.formatMessage("label.error_whilst_saving_current_state_to",
 -                    new Object[] { chosenFile.getName() }),
 -                MessageManager.getString("label.couldnt_save_project"), JvOptionPane.WARNING_MESSAGE);
 +                    MessageManager.formatMessage(
 +                            "label.error_whilst_saving_current_state_to",
 +                            new Object[]
 +                            { chosenFile.getName() }),
 +                    MessageManager.getString("label.couldnt_save_project"),
 +                    JvOptionPane.WARNING_MESSAGE);
            }
            setProgressBar(null, chosenFile.hashCode());
          }
    }
  
    @Override
 -  public void saveAsState_actionPerformed(ActionEvent e) {
 +  public void saveAsState_actionPerformed(ActionEvent e)
 +  {
      saveState_actionPerformed(true);
    }
  
 -  private void setProjectFile(File choice) {
 +  private void setProjectFile(File choice)
 +  {
      this.projectFile = choice;
    }
  
 -  public File getProjectFile() {
 +  public File getProjectFile()
 +  {
      return this.projectFile;
    }
  
     * Jalview project
     */
    @Override
 -  public void loadState_actionPerformed() {
 +  public void loadState_actionPerformed()
 +  {
      final String[] suffix = new String[] { "jvp", "jar" };
 -    final String[] desc = new String[] { "Jalview Project", "Jalview Project (old)" };
 -    JalviewFileChooser chooser = new JalviewFileChooser(Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
 -        "Jalview Project", true, BackupFiles.getEnabled()); // last two
 -                                                            // booleans:
 -                                                            // allFiles,
 +    final String[] desc = new String[] { "Jalview Project",
 +        "Jalview Project (old)" };
 +    JalviewFileChooser chooser = new JalviewFileChooser(
 +            Cache.getProperty("LAST_DIRECTORY"), suffix, desc,
 +            "Jalview Project", true, BackupFiles.getEnabled()); // last two
 +                                                                // booleans:
 +                                                                // allFiles,
      // allowBackupFiles
      chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(MessageManager.getString("label.restore_state"));
 -    chooser.setResponseHandler(0, new Runnable() {
 +    chooser.setResponseHandler(0, new Runnable()
 +    {
        @Override
 -      public void run() {
 +      public void run()
 +      {
          File selectedFile = chooser.getSelectedFile();
          setProjectFile(selectedFile);
          String choice = selectedFile.getAbsolutePath();
          Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
 -        new Thread(new Runnable() {
 +        new Thread(new Runnable()
 +        {
            @Override
 -          public void run() {
 -            try {
 +          public void run()
 +          {
 +            try
 +            {
                new Jalview2XML().loadJalviewAlign(selectedFile);
 -            } catch (OutOfMemoryError oom) {
 +            } 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);
 +            } catch (Exception ex)
 +            {
 +              Cache.log.error(
 +                      "Problems whilst loading project from " + choice, ex);
 +              JvOptionPane.showMessageDialog(getDesktopPane(),
 +                      MessageManager.formatMessage(
 +                              "label.error_whilst_loading_project_from",
 +                              new Object[]
 +                              { choice }),
 +                      MessageManager
 +                              .getString("label.couldnt_load_project"),
 +                      JvOptionPane.WARNING_MESSAGE);
              }
            }
-         }).start();
+         }, "Project Loader").start();
        }
      });
--
      chooser.showOpenDialog(this);
    }
  
    @Override
 -  public void inputSequence_actionPerformed(ActionEvent e) {
 +  public void inputSequence_actionPerformed(ActionEvent e)
 +  {
      new SequenceFetcher(this);
    }
  
  
    ArrayList<JPanel> fileLoadingPanels = new ArrayList<>();
  
 -  public void startLoading(final Object fileName) {
 -    if (fileLoadingCount == 0) {
 -      fileLoadingPanels
 -          .add(addProgressPanel(MessageManager.formatMessage("label.loading_file", new Object[] { fileName })));
 +  public void startLoading(final Object fileName)
 +  {
 +    if (fileLoadingCount == 0)
 +    {
 +      fileLoadingPanels.add(addProgressPanel(MessageManager
 +              .formatMessage("label.loading_file", new Object[]
 +              { fileName })));
      }
      fileLoadingCount++;
    }
  
 -  private JPanel addProgressPanel(String string) {
 -    if (progressPanel == null) {
 +  private JPanel addProgressPanel(String string)
 +  {
 +    if (progressPanel == null)
 +    {
        progressPanel = new JPanel(new GridLayout(1, 1));
        totalProgressCount = 0;
 -      instance.getContentPane().add(progressPanel, BorderLayout.SOUTH);
 +      getContentPane().add(progressPanel, BorderLayout.SOUTH);
      }
      JPanel thisprogress = new JPanel(new BorderLayout(10, 5));
      JProgressBar progressBar = new JProgressBar();
  
      thisprogress.add(progressBar, BorderLayout.CENTER);
      progressPanel.add(thisprogress);
 -    ((GridLayout) progressPanel.getLayout()).setRows(((GridLayout) progressPanel.getLayout()).getRows() + 1);
 +    ((GridLayout) progressPanel.getLayout()).setRows(
 +            ((GridLayout) progressPanel.getLayout()).getRows() + 1);
      ++totalProgressCount;
 -    instance.validate();
 +    validate();
      return thisprogress;
    }
  
    int totalProgressCount = 0;
  
 -  private void removeProgressPanel(JPanel progbar) {
 -    if (progressPanel != null) {
 -      synchronized (progressPanel) {
 +  private void removeProgressPanel(JPanel progbar)
 +  {
 +    if (progressPanel != null)
 +    {
 +      synchronized (progressPanel)
 +      {
          progressPanel.remove(progbar);
          GridLayout gl = (GridLayout) progressPanel.getLayout();
          gl.setRows(gl.getRows() - 1);
 -        if (--totalProgressCount < 1) {
 +        if (--totalProgressCount < 1)
 +        {
            this.getContentPane().remove(progressPanel);
            progressPanel = null;
          }
      validate();
    }
  
 -  public void stopLoading() {
 +  public void stopLoading()
 +  {
      fileLoadingCount--;
 -    if (fileLoadingCount < 1) {
 -      while (fileLoadingPanels.size() > 0) {
 +    if (fileLoadingCount < 1)
 +    {
 +      while (fileLoadingPanels.size() > 0)
 +      {
          removeProgressPanel(fileLoadingPanels.remove(0));
        }
        fileLoadingPanels.clear();
      validate();
    }
  
 -  public static int getViewCount(String alignmentId) {
 +  public static int getViewCount(String alignmentId)
 +  {
      AlignmentViewport[] aps = getViewports(alignmentId);
      return (aps == null) ? 0 : aps.length;
    }
  
    /**
     * 
 -   * @param alignmentId - if null, all sets are returned
 +   * @param alignmentId
 +   *          - if null, all sets are returned
     * @return all AlignmentPanels concerning the alignmentId sequence set
     */
 -  public static AlignmentPanel[] getAlignmentPanels(String alignmentId) {
 -    if (Desktop.desktop == null) {
 +  public static AlignmentPanel[] getAlignmentPanels(String alignmentId)
 +  {
 +    if (getDesktopPane() == null)
 +    {
        // no frames created and in headless mode
        // TODO: verify that frames are recoverable when in headless mode
        return null;
      }
      List<AlignmentPanel> aps = new ArrayList<>();
      AlignFrame[] frames = getAlignFrames();
 -    if (frames == null) {
 +    if (frames == null)
 +    {
        return null;
      }
 -    for (AlignFrame af : frames) {
 -      for (AlignmentPanel ap : af.alignPanels) {
 -        if (alignmentId == null || alignmentId.equals(ap.av.getSequenceSetId())) {
 +    for (AlignFrame af : frames)
 +    {
 +      for (AlignmentPanel ap : af.alignPanels)
 +      {
 +        if (alignmentId == null
 +                || alignmentId.equals(ap.av.getSequenceSetId()))
 +        {
            aps.add(ap);
          }
        }
      }
 -    if (aps.size() == 0) {
 +    if (aps.size() == 0)
 +    {
        return null;
      }
      AlignmentPanel[] vap = aps.toArray(new AlignmentPanel[aps.size()]);
    /**
     * get all the viewports on an alignment.
     * 
 -   * @param sequenceSetId unique alignment id (may be null - all viewports
 -   *                      returned in that case)
 +   * @param sequenceSetId
 +   *          unique alignment id (may be null - all viewports returned in that
 +   *          case)
     * @return all viewports on the alignment bound to sequenceSetId
     */
 -  public static AlignmentViewport[] getViewports(String sequenceSetId) {
 +  public static AlignmentViewport[] getViewports(String sequenceSetId)
 +  {
      List<AlignmentViewport> viewp = new ArrayList<>();
 -    if (desktop != null) {
 -      AlignFrame[] frames = Desktop.getAlignFrames();
 -
 -      for (AlignFrame afr : frames) {
 -        if (sequenceSetId == null || afr.getViewport().getSequenceSetId().equals(sequenceSetId)) {
 -          if (afr.alignPanels != null) {
 -            for (AlignmentPanel ap : afr.alignPanels) {
 -              if (sequenceSetId == null || sequenceSetId.equals(ap.av.getSequenceSetId())) {
 +    if (getDesktopPane() != null)
 +    {
 +      AlignFrame[] frames = getAlignFrames();
 +
 +      for (AlignFrame afr : frames)
 +      {
 +        if (sequenceSetId == null || afr.getViewport().getSequenceSetId()
 +                .equals(sequenceSetId))
 +        {
 +          if (afr.alignPanels != null)
 +          {
 +            for (AlignmentPanel ap : afr.alignPanels)
 +            {
 +              if (sequenceSetId == null
 +                      || sequenceSetId.equals(ap.av.getSequenceSetId()))
 +              {
                  viewp.add(ap.av);
                }
              }
 -          } else {
 +          }
 +          else
 +          {
              viewp.add(afr.getViewport());
            }
          }
        }
 -      if (viewp.size() > 0) {
 +      if (viewp.size() > 0)
 +      {
          return viewp.toArray(new AlignmentViewport[viewp.size()]);
        }
      }
     * 
     * @param af
     */
 -  public static void explodeViews(AlignFrame af) {
 +  public static void explodeViews(AlignFrame af)
 +  {
      int size = af.alignPanels.size();
 -    if (size < 2) {
 +    if (size < 2)
 +    {
        return;
      }
  
      // FIXME: ideally should use UI interface API
 -    FeatureSettings viewFeatureSettings = (af.featureSettings != null && af.featureSettings.isOpen())
 -        ? af.featureSettings
 -        : null;
 +    FeatureSettings viewFeatureSettings = (af.featureSettings != null
 +            && af.featureSettings.isOpen()) ? af.featureSettings : null;
      Rectangle fsBounds = af.getFeatureSettingsGeometry();
 -    for (int i = 0; i < size; i++) {
 +    for (int i = 0; i < size; i++)
 +    {
        AlignmentPanel ap = af.alignPanels.get(i);
  
        AlignFrame newaf = new AlignFrame(ap);
  
        // transfer reference for existing feature settings to new alignFrame
 -      if (ap == af.alignPanel) {
 -        if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap) {
 +      if (ap == af.alignPanel)
 +      {
 +        if (viewFeatureSettings != null && viewFeatureSettings.fr.ap == ap)
 +        {
            newaf.featureSettings = viewFeatureSettings;
          }
          newaf.setFeatureSettingsGeometry(fsBounds);
        }
  
        /*
 -       * Restore the view's last exploded frame geometry if known. Multiple views from
 -       * one exploded frame share and restore the same (frame) position and size.
 +       * Restore the view's last exploded frame geometry if known. Multiple
 +       * views from one exploded frame share and restore the same (frame)
 +       * position and size.
         */
        Rectangle geometry = ap.av.getExplodedGeometry();
 -      if (geometry != null) {
 +      if (geometry != null)
 +      {
          newaf.setBounds(geometry);
        }
  
        ap.av.setGatherViewsHere(false);
  
 -      addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
 +      addInternalFrame(newaf, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
 +              AlignFrame.DEFAULT_HEIGHT);
        // and materialise a new feature settings dialog instance for the new
        // alignframe
        // (closes the old as if 'OK' was pressed)
 -      if (ap == af.alignPanel && newaf.featureSettings != null && newaf.featureSettings.isOpen()
 -          && af.alignPanel.getAlignViewport().isShowSequenceFeatures()) {
 +      if (ap == af.alignPanel && newaf.featureSettings != null
 +              && newaf.featureSettings.isOpen()
 +              && af.alignPanel.getAlignViewport().isShowSequenceFeatures())
 +      {
          newaf.showFeatureSettingsUI();
        }
      }
  
    /**
     * 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
     */
 -  public void gatherViews(AlignFrame source) {
 +  public void gatherViews(AlignFrame source)
 +  {
      source.viewport.setGatherViewsHere(true);
      source.viewport.setExplodedGeometry(source.getBounds());
 -    JInternalFrame[] frames = desktop.getAllFrames();
 +    JInternalFrame[] frames = desktopPane.getAllFrames();
      String viewId = source.viewport.getSequenceSetId();
 -    for (int t = 0; t < frames.length; t++) {
 -      if (frames[t] instanceof AlignFrame && frames[t] != source) {
 +    for (int t = 0; t < frames.length; t++)
 +    {
 +      if (frames[t] instanceof AlignFrame && frames[t] != source)
 +      {
          AlignFrame af = (AlignFrame) frames[t];
          boolean gatherThis = false;
 -        for (int a = 0; a < af.alignPanels.size(); a++) {
 +        for (int a = 0; a < af.alignPanels.size(); a++)
 +        {
            AlignmentPanel ap = af.alignPanels.get(a);
 -          if (viewId.equals(ap.av.getSequenceSetId())) {
 +          if (viewId.equals(ap.av.getSequenceSetId()))
 +          {
              gatherThis = true;
              ap.av.setGatherViewsHere(false);
              ap.av.setExplodedGeometry(af.getBounds());
            }
          }
  
 -        if (gatherThis) {
 -          if (af.featureSettings != null && af.featureSettings.isOpen()) {
 -            if (source.featureSettings == null) {
 +        if (gatherThis)
 +        {
 +          if (af.featureSettings != null && af.featureSettings.isOpen())
 +          {
 +            if (source.featureSettings == null)
 +            {
                // preserve the feature settings geometry for this frame
                source.featureSettings = af.featureSettings;
 -              source.setFeatureSettingsGeometry(af.getFeatureSettingsGeometry());
 -            } else {
 +              source.setFeatureSettingsGeometry(
 +                      af.getFeatureSettingsGeometry());
 +            }
 +            else
 +            {
                // close it and forget
                af.featureSettings.close();
              }
      }
  
      // refresh the feature setting UI for the source frame if it exists
 -    if (source.featureSettings != null && source.featureSettings.isOpen()) {
 +    if (source.featureSettings != null && source.featureSettings.isOpen())
 +    {
        source.showFeatureSettingsUI();
      }
 -
    }
  
 -  public JInternalFrame[] getAllFrames() {
 -    return desktop.getAllFrames();
 +  public JInternalFrame[] getAllFrames()
 +  {
 +    return desktopPane.getAllFrames();
    }
  
    /**
     * 
     * @param url
     */
 -  public void checkForQuestionnaire(String url) {
 +  public void checkForQuestionnaire(String url)
 +  {
      UserQuestionnaireCheck jvq = new UserQuestionnaireCheck(url);
      // javax.swing.SwingUtilities.invokeLater(jvq);
      new Thread(jvq).start();
    }
  
 -  public void checkURLLinks() {
 +  public void checkURLLinks()
 +  {
      // Thread off the URL link checker
 -    addDialogThread(new Runnable() {
 +    addDialogThread(new Runnable()
 +    {
        @Override
 -      public void run() {
 -        if (Cache.getDefault("CHECKURLLINKS", true)) {
 +      public void run()
 +      {
 +        if (Cache.getDefault("CHECKURLLINKS", true))
 +        {
            // check what the actual links are - if it's just the default don't
            // bother with the warning
 -          List<String> links = Preferences.sequenceUrlLinks.getLinksForMenu();
 +          List<String> links = Preferences.sequenceUrlLinks
 +                  .getLinksForMenu();
  
            // only need to check links if there is one with a
            // SEQUENCE_ID which is not the default EMBL_EBI link
            ListIterator<String> li = links.listIterator();
            boolean check = false;
            List<JLabel> urls = new ArrayList<>();
 -          while (li.hasNext()) {
 +          while (li.hasNext())
 +          {
              String link = li.next();
 -            if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID) && !UrlConstants.isDefaultString(link)) {
 +            if (link.contains(jalview.util.UrlConstants.SEQUENCE_ID)
 +                    && !UrlConstants.isDefaultString(link))
 +            {
                check = true;
                int barPos = link.indexOf("|");
 -              String urlMsg = barPos == -1 ? link : link.substring(0, barPos) + ": " + link.substring(barPos + 1);
 +              String urlMsg = barPos == -1 ? link
 +                      : link.substring(0, barPos) + ": "
 +                              + link.substring(barPos + 1);
                urls.add(new JLabel(urlMsg));
              }
            }
 -          if (!check) {
 +          if (!check)
 +          {
              return;
            }
  
            JPanel msgPanel = new JPanel();
            msgPanel.setLayout(new BoxLayout(msgPanel, BoxLayout.PAGE_AXIS));
            msgPanel.add(Box.createVerticalGlue());
 -          JLabel msg = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
 -          JLabel msg2 = new JLabel(MessageManager.getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
 +          JLabel msg = new JLabel(MessageManager
 +                  .getString("label.SEQUENCE_ID_for_DB_ACCESSION1"));
 +          JLabel msg2 = new JLabel(MessageManager
 +                  .getString("label.SEQUENCE_ID_for_DB_ACCESSION2"));
            msgPanel.add(msg);
 -          for (JLabel url : urls) {
 +          for (JLabel url : urls)
 +          {
              msgPanel.add(url);
            }
            msgPanel.add(msg2);
  
 -          final JCheckBox jcb = new JCheckBox(MessageManager.getString("label.do_not_display_again"));
 -          jcb.addActionListener(new ActionListener() {
 +          final JCheckBox jcb = new JCheckBox(
 +                  MessageManager.getString("label.do_not_display_again"));
 +          jcb.addActionListener(new ActionListener()
 +          {
              @Override
 -            public void actionPerformed(ActionEvent e) {
 +            public void actionPerformed(ActionEvent e)
 +            {
                // update Cache settings for "don't show this again"
                boolean showWarningAgain = !jcb.isSelected();
 -              Cache.setProperty("CHECKURLLINKS", Boolean.valueOf(showWarningAgain).toString());
 +              Cache.setProperty("CHECKURLLINKS",
 +                      Boolean.valueOf(showWarningAgain).toString());
              }
            });
            msgPanel.add(jcb);
  
 -          JvOptionPane.showMessageDialog(Desktop.desktop, msgPanel,
 -              MessageManager.getString("label.SEQUENCE_ID_no_longer_used"), JvOptionPane.WARNING_MESSAGE);
 +          JvOptionPane.showMessageDialog(desktopPane, msgPanel,
 +                  MessageManager
 +                          .getString("label.SEQUENCE_ID_no_longer_used"),
 +                  JvOptionPane.WARNING_MESSAGE);
          }
        }
      });
  
    /**
     * 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;
  
      java.text.NumberFormat df;
  
 -    float maxMemory, allocatedMemory, freeMemory, totalFreeMemory, percentUsage;
 +    float maxMemory, allocatedMemory, freeMemory, totalFreeMemory,
 +            percentUsage;
  
 -    public MyDesktopPane(boolean showMemoryUsage) {
 +    public MyDesktopPane(boolean showMemoryUsage)
 +    {
        showMemoryUsage(showMemoryUsage);
      }
  
 -    public void showMemoryUsage(boolean showMemory) {
 +    public void showMemoryUsage(boolean showMemory)
 +    {
        this.showMemoryUsage = showMemory;
 -      if (showMemory) {
 +      if (showMemory)
 +      {
          Thread worker = new Thread(this);
          worker.start();
        }
        repaint();
      }
  
 -    public boolean isShowMemoryUsage() {
 +    public boolean isShowMemoryUsage()
 +    {
        return showMemoryUsage;
      }
  
      @Override
 -    public void run() {
 +    public void run()
 +    {
        df = java.text.NumberFormat.getNumberInstance();
        df.setMaximumFractionDigits(2);
        runtime = Runtime.getRuntime();
  
 -      while (showMemoryUsage) {
 -        try {
 +      while (showMemoryUsage)
 +      {
 +        try
 +        {
            maxMemory = runtime.maxMemory() / ONE_MB;
            allocatedMemory = runtime.totalMemory() / ONE_MB;
            freeMemory = runtime.freeMemory() / ONE_MB;
            repaint();
            // sleep after showing usage
            Thread.sleep(3000);
 -        } catch (Exception ex) {
 +        } catch (Exception ex)
 +        {
            ex.printStackTrace();
          }
        }
      }
  
      @Override
 -    public void paintComponent(Graphics g) {
 -      if (showMemoryUsage && g != null && df != null) {
 -        if (percentUsage < 20) {
 +    public void paintComponent(Graphics g)
 +    {
 +      if (showMemoryUsage && g != null && df != null)
 +      {
 +        if (percentUsage < 20)
 +        {
            g.setColor(Color.red);
          }
          FontMetrics fm = g.getFontMetrics();
 -        if (fm != null) {
 -          g.drawString(
 -              MessageManager.formatMessage("label.memory_stats",
 -                  new Object[] { df.format(totalFreeMemory), df.format(maxMemory), df.format(percentUsage) }),
 -              10, getHeight() - fm.getHeight());
 +        if (fm != null)
 +        {
 +          g.drawString(MessageManager.formatMessage("label.memory_stats",
 +                  new Object[]
 +                  { df.format(totalFreeMemory), df.format(maxMemory),
 +                      df.format(percentUsage) }),
 +                  10, getHeight() - fm.getHeight());
          }
        }
 -
+       // output debug scale message. Important for jalview.bin.HiDPISettingTest2
+       Desktop.debugScaleMessage(Desktop.getDesktop().getGraphics());
      }
    }
  
     * 
     * @return an array of AlignFrame, or null if none found
     */
 -  public static AlignFrame[] getAlignFrames() {
 -    if (Jalview.isHeadlessMode()) {
 -      // Desktop.desktop is null in headless mode
 -      return new AlignFrame[] { Jalview.currentAlignFrame };
 +  public static AlignFrame[] getAlignFrames()
 +  {
 +    if (Jalview.isHeadlessMode())
 +    {
 +      return new AlignFrame[] { Jalview.getInstance().currentAlignFrame };
      }
  
 -    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 +    JInternalFrame[] frames = getDesktopPane().getAllFrames();
  
 -    if (frames == null) {
 +    if (frames == null)
 +    {
        return null;
      }
      List<AlignFrame> avp = new ArrayList<>();
      // REVERSE ORDER
 -    for (int i = frames.length - 1; i > -1; i--) {
 -      if (frames[i] instanceof AlignFrame) {
 +    for (int i = frames.length - 1; i > -1; i--)
 +    {
 +      if (frames[i] instanceof AlignFrame)
 +      {
          avp.add((AlignFrame) frames[i]);
 -      } else if (frames[i] instanceof SplitFrame) {
 +      }
 +      else if (frames[i] instanceof SplitFrame)
 +      {
          /*
           * Also check for a split frame containing an AlignFrame
           */
          GSplitFrame sf = (GSplitFrame) frames[i];
 -        if (sf.getTopFrame() instanceof AlignFrame) {
 +        if (sf.getTopFrame() instanceof AlignFrame)
 +        {
            avp.add((AlignFrame) sf.getTopFrame());
          }
 -        if (sf.getBottomFrame() instanceof AlignFrame) {
 +        if (sf.getBottomFrame() instanceof AlignFrame)
 +        {
            avp.add((AlignFrame) sf.getBottomFrame());
          }
        }
      }
 -    if (avp.size() == 0) {
 +    if (avp.size() == 0)
 +    {
        return null;
      }
      AlignFrame afs[] = avp.toArray(new AlignFrame[avp.size()]);
     * 
     * @return
     */
 -  public GStructureViewer[] getJmols() {
 -    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 +  public GStructureViewer[] getJmols()
 +  {
 +    JInternalFrame[] frames = desktopPane.getAllFrames();
  
 -    if (frames == null) {
 +    if (frames == null)
 +    {
        return null;
      }
      List<GStructureViewer> avp = new ArrayList<>();
      // REVERSE ORDER
 -    for (int i = frames.length - 1; i > -1; i--) {
 -      if (frames[i] instanceof AppJmol) {
 +    for (int i = frames.length - 1; i > -1; i--)
 +    {
 +      if (frames[i] instanceof AppJmol)
 +      {
          GStructureViewer af = (GStructureViewer) frames[i];
          avp.add(af);
        }
      }
 -    if (avp.size() == 0) {
 +    if (avp.size() == 0)
 +    {
        return null;
      }
      GStructureViewer afs[] = avp.toArray(new GStructureViewer[avp.size()]);
     * Add Groovy Support to Jalview
     */
    @Override
 -  public void groovyShell_actionPerformed() {
 -    try {
 +  public void groovyShell_actionPerformed()
 +  {
 +    try
 +    {
        openGroovyConsole();
 -    } catch (Exception ex) {
 +    } catch (Exception ex)
 +    {
        Cache.log.error("Groovy Shell Creation failed.", ex);
 -      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +      JvOptionPane.showInternalMessageDialog(desktopPane,
  
 -          MessageManager.getString("label.couldnt_create_groovy_shell"),
 -          MessageManager.getString("label.groovy_support_failed"), JvOptionPane.ERROR_MESSAGE);
 +              MessageManager.getString("label.couldnt_create_groovy_shell"),
 +              MessageManager.getString("label.groovy_support_failed"),
 +              JvOptionPane.ERROR_MESSAGE);
      }
    }
  
    /**
     * Open the Groovy console
     */
 -  void openGroovyConsole() {
 -    if (groovyConsole == null) {
 +  void openGroovyConsole()
 +  {
 +    if (groovyConsole == null)
 +    {
        groovyConsole = new groovy.ui.Console();
        groovyConsole.setVariable("Jalview", this);
        groovyConsole.run();
  
        /*
         * We allow only one console at a time, so that AlignFrame menu option
 -       * 'Calculate | Run Groovy script' is unambiguous. Disable 'Groovy Console', and
 -       * enable 'Run script', when the console is opened, and the reverse when it is
 -       * closed
 +       * 'Calculate | Run Groovy script' is unambiguous.
 +       * Disable 'Groovy Console', and enable 'Run script', when the console is 
 +       * opened, and the reverse when it is closed
         */
        Window window = (Window) groovyConsole.getFrame();
 -      window.addWindowListener(new WindowAdapter() {
 +      window.addWindowListener(new WindowAdapter()
 +      {
          @Override
 -        public void windowClosed(WindowEvent e) {
 +        public void windowClosed(WindowEvent e)
 +        {
            /*
             * rebind CMD-Q from Groovy Console to Jalview Quit
             */
      ((Window) groovyConsole.getFrame()).setVisible(true);
  
      /*
 -     * if we got this far, enable 'Run Groovy' in AlignFrame menus and disable
 -     * opening a second console
 +     * if we got this far, enable 'Run Groovy' in AlignFrame menus
 +     * and disable opening a second console
       */
      enableExecuteGroovy(true);
    }
  
    /**
 -   * 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, jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()),
 -        "Quit");
 -    getRootPane().getActionMap().put("Quit", new AbstractAction() {
 +  protected void addQuitHandler()
 +  {
 +
 +    getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
 +            .put(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
 +                    Platform.SHORTCUT_KEY_MASK),
 +                    "Quit");
 +    getRootPane().getActionMap().put("Quit", new AbstractAction()
 +    {
        @Override
 -      public void actionPerformed(ActionEvent e) {
 +      public void actionPerformed(ActionEvent e)
 +      {
          quit();
        }
      });
    /**
     * Enable or disable 'Run Groovy script' in AlignFrame calculate menus
     * 
 -   * @param enabled true if Groovy console is open
 +   * @param enabled
 +   *          true if Groovy console is open
     */
 -  public void enableExecuteGroovy(boolean enabled) {
 +  public void enableExecuteGroovy(boolean enabled)
 +  {
      /*
 -     * disable opening a second Groovy console (or re-enable when the console is
 -     * closed)
 +     * disable opening a second Groovy console
 +     * (or re-enable when the console is closed)
       */
      groovyShell.setEnabled(!enabled);
  
      AlignFrame[] alignFrames = getAlignFrames();
 -    if (alignFrames != null) {
 -      for (AlignFrame af : alignFrames) {
 +    if (alignFrames != null)
 +    {
 +      for (AlignFrame af : alignFrames)
 +      {
          af.setGroovyEnabled(enabled);
        }
      }
     * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
     */
    @Override
 -  public void setProgressBar(String message, long id) {
 -    if (progressBars == null) {
 +  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(Long.valueOf(id)) != null) {
 +    if (progressBars.get(Long.valueOf(id)) != null)
 +    {
        JPanel panel = progressBars.remove(Long.valueOf(id));
 -      if (progressBarHandlers.contains(Long.valueOf(id))) {
 +      if (progressBarHandlers.contains(Long.valueOf(id)))
 +      {
          progressBarHandlers.remove(Long.valueOf(id));
        }
        removeProgressPanel(panel);
 -    } else {
 +    }
 +    else
 +    {
        progressBars.put(Long.valueOf(id), addProgressPanel(message));
      }
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    //TODO
 +    throw new UnsupportedOperationException("not implemented");
 +  }
  
    /*
     * (non-Javadoc)
     * jalview.gui.IProgressIndicatorHandler)
     */
    @Override
 -  public void registerHandler(final long id, final IProgressIndicatorHandler handler) {
 -    if (progressBarHandlers == null || !progressBars.containsKey(Long.valueOf(id))) {
 -      throw new Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
 +  public void registerHandler(final long id,
 +          final IProgressIndicatorHandler handler)
 +  {
 +    if (progressBarHandlers == null
 +            || !progressBars.containsKey(Long.valueOf(id)))
 +    {
 +      throw new Error(MessageManager.getString(
 +              "error.call_setprogressbar_before_registering_handler"));
      }
      progressBarHandlers.put(Long.valueOf(id), handler);
      final JPanel progressPanel = progressBars.get(Long.valueOf(id));
 -    if (handler.canCancel()) {
 -      JButton cancel = new JButton(MessageManager.getString("action.cancel"));
 +    if (handler.canCancel())
 +    {
 +      JButton cancel = new JButton(
 +              MessageManager.getString("action.cancel"));
        final IProgressIndicator us = this;
 -      cancel.addActionListener(new ActionListener() {
 +      cancel.addActionListener(new ActionListener()
 +      {
  
          @Override
 -        public void actionPerformed(ActionEvent e) {
 +        public void actionPerformed(ActionEvent e)
 +        {
            handler.cancelActivity(id);
 -          us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
 -              new Object[] { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
 +          us.setProgressBar(MessageManager
 +                  .formatMessage("label.cancelled_params", new Object[]
 +                  { ((JLabel) progressPanel.getComponent(0)).getText() }),
 +                  id);
          }
        });
        progressPanel.add(cancel, BorderLayout.EAST);
     * @return true if any progress bars are still active
     */
    @Override
 -  public boolean operationInProgress() {
 -    if (progressBars != null && progressBars.size() > 0) {
 +  public boolean operationInProgress()
 +  {
 +    if (progressBars != null && progressBars.size() > 0)
 +    {
        return true;
      }
      return false;
    }
  
    /**
 -   * 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 static AlignFrame getAlignFrameFor(AlignViewportI viewport) {
 -    if (desktop != null) {
 -      AlignmentPanel[] aps = getAlignmentPanels(viewport.getSequenceSetId());
 -      for (int panel = 0; aps != null && panel < aps.length; panel++) {
 -        if (aps[panel] != null && aps[panel].av == viewport) {
 +  public static AlignFrame getAlignFrameFor(AlignViewportI viewport)
 +  {
 +    if (getDesktopPane() != null)
 +    {
 +      AlignmentPanel[] aps = getAlignmentPanels(
 +              viewport.getSequenceSetId());
 +      for (int panel = 0; aps != null && panel < aps.length; panel++)
 +      {
 +        if (aps[panel] != null && aps[panel].av == viewport)
 +        {
            return aps[panel].alignFrame;
          }
        }
      return null;
    }
  
 -  public VamsasApplication getVamsasApplication() {
 +  public VamsasApplication getVamsasApplication()
 +  {
      // TODO: JAL-3311 remove remaining code from Jalview relating to VAMSAS
      return null;
  
     * 
     * @return inBatchMode
     */
 -  public boolean isInBatchMode() {
 +  public boolean isInBatchMode()
 +  {
      return inBatchMode;
    }
  
     * 
     * @param inBatchMode
     */
 -  public void setInBatchMode(boolean inBatchMode) {
 +  public void setInBatchMode(boolean inBatchMode)
 +  {
      this.inBatchMode = inBatchMode;
    }
  
 -  /**
 -   * start service discovery and wait till it is done
 -   */
 -  public void startServiceDiscovery() {
 +  public void startServiceDiscovery()
 +  {
      startServiceDiscovery(false);
    }
  
 -  /**
 -   * start service discovery threads - blocking or non-blocking
 -   * 
 -   * @param blocking
 -   */
 -  public void startServiceDiscovery(boolean blocking) {
 -    startServiceDiscovery(blocking, false);
 -  }
 -
 -  /**
 -   * start service discovery threads
 -   * 
 -   * @param blocking                             - false means call returns
 -   *                                             immediately
 -   * @param ignore_SHOW_JWS2_SERVICES_preference - when true JABA services are
 -   *                                             discovered regardless of user's
 -   *                                             JWS2 discovery preference setting
 -   */
 -  public void startServiceDiscovery(boolean blocking, boolean ignore_SHOW_JWS2_SERVICES_preference) {
 -    boolean alive = true;
 -    Thread t0 = null, t1 = null, t2 = null;
 +  public void startServiceDiscovery(boolean blocking)
 +  {
 +    System.out.println("Starting service discovery");
 +    var tasks = new ArrayList<Future<?>>();
      // JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
 -    if (true) {
 +
 +    System.out.println("loading services");
 +    
 +    /** @j2sIgnore */
 +    {
        // todo: changesupport handlers need to be transferred
 -      if (discoverer == null) {
 -        discoverer = new jalview.ws.jws1.Discoverer();
 +      if (discoverer == null)
 +      {
 +        discoverer = jalview.ws.jws1.Discoverer.getInstance();
          // register PCS handler for desktop.
          discoverer.addPropertyChangeListener(changeSupport);
        }
        // JAL-940 - disabled JWS1 service configuration - always start discoverer
        // until we phase out completely
 -      (t0 = new Thread(discoverer)).start();
 +      var f = new FutureTask<Void>(discoverer, null);
 +      new Thread(f).start();
 +      tasks.add(f);
      }
  
 -    if (ignore_SHOW_JWS2_SERVICES_preference || Cache.getDefault("SHOW_JWS2_SERVICES", true)) {
 -      t2 = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().startDiscoverer(changeSupport);
 +    if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
 +    {
 +      tasks.add(jalview.ws.jws2.Jws2Discoverer.getInstance().startDiscoverer());
      }
 -    Thread t3 = null;
 +    if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
      {
 -      // TODO: do rest service discovery
 +      tasks.add(jalview.ws.slivkaws.SlivkaWSDiscoverer.getInstance().startDiscoverer());
      }
 -    if (blocking) {
 -      while (alive) {
 -        try {
 -          Thread.sleep(15);
 -        } catch (Exception e) {
 +    if (blocking)
 +    {
 +      for (Future<?> task : tasks) {
 +        try
 +        {
 +          // block until all discovery tasks are done
 +          task.get();
 +        } catch (Exception e)
 +        {
 +          e.printStackTrace();
          }
 -        alive = (t1 != null && t1.isAlive()) || (t2 != null && t2.isAlive()) || (t3 != null && t3.isAlive())
 -            || (t0 != null && t0.isAlive());
        }
      }
    }
     * 
     * @param evt
     */
 -  protected void JalviewServicesChanged(PropertyChangeEvent evt) {
 -    if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector) {
 -      final String ermsg = jalview.ws.jws2.Jws2Discoverer.getDiscoverer().getErrorMessages();
 -      if (ermsg != null) {
 -        if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true)) {
 -          if (serviceChangedDialog == null) {
 +  protected void JalviewServicesChanged(PropertyChangeEvent evt)
 +  {
 +    if (evt.getNewValue() == null || evt.getNewValue() instanceof Vector)
 +    {
 +      final WSDiscovererI discoverer = jalview.ws.jws2.Jws2Discoverer
 +          .getInstance();
 +      final String ermsg = discoverer.getErrorMessages();
 +      // CONFLICT:ALT:?     final String ermsg = jalview.ws.jws2.Jws2Discoverer.getInstance()
 +      if (ermsg != null)
 +      {
 +        if (Cache.getDefault("SHOW_WSDISCOVERY_ERRORS", true))
 +        {
 +          if (serviceChangedDialog == null)
 +          {
              // only run if we aren't already displaying one of these.
 -            addDialogThread(serviceChangedDialog = new Runnable() {
 +            addDialogThread(serviceChangedDialog = new Runnable()
 +            {
                @Override
 -              public void run() {
 +              public void run()
 +              {
  
                  /*
                   * JalviewDialog jd =new JalviewDialog() {
                   * 
 -                 * @Override protected void cancelPressed() { // TODO Auto-generated method stub
 +                 * @Override protected void cancelPressed() { // TODO
 +                 * Auto-generated method stub
                   * 
 -                 * }@Override protected void okPressed() { // TODO Auto-generated method stub
 +                 * }@Override protected void okPressed() { // TODO
 +                 * Auto-generated method stub
                   * 
 -                 * }@Override protected void raiseClosed() { // TODO Auto-generated method stub
 +                 * }@Override protected void raiseClosed() { // TODO
 +                 * Auto-generated method stub
                   * 
 -                 * } }; jd.initDialogFrame(new JLabel("<html><table width=\"450\"><tr><td>" +
 -                 * ermsg +
 +                 * } }; jd.initDialogFrame(new
 +                 * JLabel("<html><table width=\"450\"><tr><td>" + ermsg +
                   * "<br/>It may be that you have invalid JABA URLs in your web service preferences,"
                   * + " or mis-configured HTTP proxy settings.<br/>" +
 -                 * "Check the <em>Connections</em> and <em>Web services</em> tab of the" +
 -                 * " Tools->Preferences dialog box to change them.</td></tr></table></html>" ),
 -                 * true, true, "Web Service Configuration Problem", 450, 400);
 +                 * "Check the <em>Connections</em> and <em>Web services</em> tab of the"
 +                 * +
 +                 * " Tools->Preferences dialog box to change them.</td></tr></table></html>"
 +                 * ), true, true, "Web Service Configuration Problem", 450,
 +                 * 400);
                   * 
                   * jd.waitForInput();
                   */
 -                JvOptionPane.showConfirmDialog(Desktop.desktop,
 -                    new JLabel("<html><table width=\"450\"><tr><td>" + ermsg + "</td></tr></table>"
 -                        + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
 -                        + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
 -                        + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
 -                        + " Tools->Preferences dialog box to change them.</p></html>"),
 -                    "Web Service Configuration Problem", JvOptionPane.DEFAULT_OPTION, JvOptionPane.ERROR_MESSAGE);
 +                JvOptionPane.showConfirmDialog(desktopPane,
 +                        new JLabel("<html><table width=\"450\"><tr><td>"
 +                                + ermsg + "</td></tr></table>"
 +                                + "<p>It may be that you have invalid JABA URLs<br/>in your web service preferences,"
 +                                + "<br>or as a command-line argument, or mis-configured HTTP proxy settings.</p>"
 +                                + "<p>Check the <em>Connections</em> and <em>Web services</em> tab<br/>of the"
 +                                + " Tools->Preferences dialog box to change them.</p></html>"),
 +                        "Web Service Configuration Problem",
 +                        JvOptionPane.DEFAULT_OPTION,
 +                        JvOptionPane.ERROR_MESSAGE);
                  serviceChangedDialog = null;
  
                }
              });
            }
 -        } else {
 -          Cache.log.error("Errors reported by JABA discovery service. Check web services preferences.\n" + ermsg);
 +        }
 +        else
 +        {
 +          Cache.log.error(
 +                  "Errors reported by JABA discovery service. Check web services preferences.\n"
 +                          + ermsg);
          }
        }
      }
     * 
     * @param url
     */
 -  public static void showUrl(final String url) {
 -    showUrl(url, Desktop.instance);
 +  public static void showUrl(final String url)
 +  {
 +    showUrl(url, getInstance());
    }
  
    /**
     * Like showUrl but allows progress handler to be specified
     * 
     * @param url
 -   * @param progress (null) or object implementing IProgressIndicator
 +   * @param progress
 +   *          (null) or object implementing IProgressIndicator
     */
 -  public static void showUrl(final String url, final IProgressIndicator progress) {
 -    new Thread(new Runnable() {
 +  public static void showUrl(final String url,
 +          final IProgressIndicator progress)
 +  {
 +    new Thread(new Runnable()
 +    {
        @Override
 -      public void run() {
 -        try {
 -          if (progress != null) {
 -            progress.setProgressBar(MessageManager.formatMessage("status.opening_params", new Object[] { url }),
 -                this.hashCode());
 +      public void run()
 +      {
 +        try
 +        {
 +          if (progress != null)
 +          {
 +            progress.setProgressBar(MessageManager
 +                    .formatMessage("status.opening_params", new Object[]
 +                    { url }), this.hashCode());
            }
            jalview.util.BrowserLauncher.openURL(url);
 -        } catch (Exception ex) {
 -          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 -              MessageManager.getString("label.web_browser_not_found_unix"),
 -              MessageManager.getString("label.web_browser_not_found"), JvOptionPane.WARNING_MESSAGE);
 +        } catch (Exception ex)
 +        {
 +          JvOptionPane.showInternalMessageDialog(getDesktopPane(),
 +                  MessageManager
 +                          .getString("label.web_browser_not_found_unix"),
 +                  MessageManager.getString("label.web_browser_not_found"),
 +                  JvOptionPane.WARNING_MESSAGE);
  
            ex.printStackTrace();
          }
 -        if (progress != null) {
 +        if (progress != null)
 +        {
            progress.setProgressBar(null, this.hashCode());
          }
        }
  
    public static WsParamSetManager wsparamManager = null;
  
 -  public static ParamManager getUserParameterStore() {
 -    if (wsparamManager == null) {
 +  public static ParamManager getUserParameterStore()
 +  {
 +    if (wsparamManager == null)
 +    {
        wsparamManager = new WsParamSetManager();
      }
      return wsparamManager;
     * 
     * @param e
     */
 -  public static void hyperlinkUpdate(HyperlinkEvent e) {
 -    if (e.getEventType() == EventType.ACTIVATED) {
 +  public static void hyperlinkUpdate(HyperlinkEvent e)
 +  {
 +    if (e.getEventType() == EventType.ACTIVATED)
 +    {
        String url = null;
 -      try {
 +      try
 +      {
          url = e.getURL().toString();
 -        Desktop.showUrl(url);
 -      } catch (Exception x) {
 -        if (url != null) {
 -          if (Cache.log != null) {
 +        showUrl(url);
 +      } catch (Exception x)
 +      {
 +        if (url != null)
 +        {
 +          if (Cache.log != null)
 +          {
              Cache.log.error("Couldn't handle string " + url + " as a URL.");
 -          } else {
 -            System.err.println("Couldn't handle string " + url + " as a URL.");
 +          }
 +          else
 +          {
 +            System.err.println(
 +                    "Couldn't handle string " + url + " as a URL.");
            }
          }
          // ignore any exceptions due to dud links.
     * 
     * @param prompter
     */
 -  public void addDialogThread(final Runnable prompter) {
 -    dialogExecutor.submit(new Runnable() {
 +  public void addDialogThread(final Runnable prompter)
 +  {
 +    dialogExecutor.submit(new Runnable()
 +    {
        @Override
 -      public void run() {
 -        if (dialogPause) {
 -          try {
 +      public void run()
 +      {
 +        if (dialogPause)
 +        {
 +          try
 +          {
              block.acquire();
 -          } catch (InterruptedException x) {
 +          } catch (InterruptedException x)
 +          {
            }
          }
 -        if (instance == null) {
 +        if (Jalview.isHeadlessMode())
 +        {
            return;
          }
 -        try {
 +        try
 +        {
            SwingUtilities.invokeAndWait(prompter);
 -        } catch (Exception q) {
 +        } catch (Exception q)
 +        {
            Cache.log.warn("Unexpected Exception in dialog thread.", q);
          }
        }
      });
    }
  
 -  public void startDialogQueue() {
 +  public void startDialogQueue()
 +  {
      // set the flag so we don't pause waiting for another permit and semaphore
      // the current task to begin
      dialogPause = false;
     * </pre>
     */
    @Override
 -  protected void snapShotWindow_actionPerformed(ActionEvent e) {
 +  protected void snapShotWindow_actionPerformed(ActionEvent e)
 +  {
      // currently the menu option to do this is not shown
      invalidate();
  
      int width = getWidth();
      int height = getHeight();
 -    File of = new File("Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
 -    ImageWriterI writer = new ImageWriterI() {
 +    File of = new File(
 +            "Jalview_snapshot_" + System.currentTimeMillis() + ".eps");
 +    ImageWriterI writer = new ImageWriterI()
 +    {
        @Override
 -      public void exportImage(Graphics g) throws Exception {
 +      public void exportImage(Graphics g) throws Exception
 +      {
          paintAll(g);
 -        Cache.log.info("Successfully written snapshot to file " + of.getAbsolutePath());
 +        Cache.log.info("Successfully written snapshot to file "
 +                + of.getAbsolutePath());
        }
      };
      String title = "View of desktop";
 -    ImageExporter exporter = new ImageExporter(writer, null, TYPE.EPS, title);
 +    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.
     * 
     * @param sf
     */
 -  public void explodeViews(SplitFrame sf) {
 +  public void explodeViews(SplitFrame sf)
 +  {
      AlignFrame oldTopFrame = (AlignFrame) sf.getTopFrame();
      AlignFrame oldBottomFrame = (AlignFrame) sf.getBottomFrame();
 -    List<? extends AlignmentViewPanel> topPanels = oldTopFrame.getAlignPanels();
 -    List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame.getAlignPanels();
 +    List<? extends AlignmentViewPanel> topPanels = oldTopFrame
 +            .getAlignPanels();
 +    List<? extends AlignmentViewPanel> bottomPanels = oldBottomFrame
 +            .getAlignPanels();
      int viewCount = topPanels.size();
 -    if (viewCount < 2) {
 +    if (viewCount < 2)
 +    {
        return;
      }
  
      /*
 -     * Processing in reverse order works, forwards order leaves the first panels not
 -     * visible. I don't know why!
 +     * Processing in reverse order works, forwards order leaves the first panels
 +     * not visible. I don't know why!
       */
 -    for (int i = viewCount - 1; i >= 0; i--) {
 +    for (int i = viewCount - 1; i >= 0; i--)
 +    {
        /*
 -       * Make new top and bottom frames. These take over the respective AlignmentPanel
 -       * objects, including their AlignmentViewports, so the cdna/protein
 -       * relationships between the viewports is carried over to the new split frames.
 +       * Make new top and bottom frames. These take over the respective
 +       * AlignmentPanel objects, including their AlignmentViewports, so the
 +       * cdna/protein relationships between the viewports is carried over to the
 +       * new split frames.
         * 
         * explodedGeometry holds the (x, y) position of the previously exploded
         * SplitFrame, and the (width, height) of the AlignFrame component
        AlignFrame newTopFrame = new AlignFrame(topPanel);
        newTopFrame.setSize(oldTopFrame.getSize());
        newTopFrame.setVisible(true);
 -      Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport()).getExplodedGeometry();
 -      if (geometry != null) {
 +      Rectangle geometry = ((AlignViewport) topPanel.getAlignViewport())
 +              .getExplodedGeometry();
 +      if (geometry != null)
 +      {
          newTopFrame.setSize(geometry.getSize());
        }
  
        AlignFrame newBottomFrame = new AlignFrame(bottomPanel);
        newBottomFrame.setSize(oldBottomFrame.getSize());
        newBottomFrame.setVisible(true);
 -      geometry = ((AlignViewport) bottomPanel.getAlignViewport()).getExplodedGeometry();
 -      if (geometry != null) {
 +      geometry = ((AlignViewport) bottomPanel.getAlignViewport())
 +              .getExplodedGeometry();
 +      if (geometry != null)
 +      {
          newBottomFrame.setSize(geometry.getSize());
        }
  
        topPanel.av.setGatherViewsHere(false);
        bottomPanel.av.setGatherViewsHere(false);
 -      JInternalFrame splitFrame = new SplitFrame(newTopFrame, newBottomFrame);
 -      if (geometry != null) {
 +      JInternalFrame splitFrame = new SplitFrame(newTopFrame,
 +              newBottomFrame);
 +      if (geometry != null)
 +      {
          splitFrame.setLocation(geometry.getLocation());
        }
 -      Desktop.addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
 +      addInternalFrame(splitFrame, sf.getTitle(), -1, -1);
      }
  
      /*
 -     * Clear references to the panels (now relocated in the new SplitFrames) before
 -     * closing the old SplitFrame.
 +     * Clear references to the panels (now relocated in the new SplitFrames)
 +     * before closing the old SplitFrame.
       */
      topPanels.clear();
      bottomPanels.clear();
     * 
     * @param source
     */
 -  public void gatherViews(GSplitFrame source) {
 +  public void gatherViews(GSplitFrame source)
 +  {
      /*
       * special handling of explodedGeometry for a view within a SplitFrame: - it
       * holds the (x, y) position of the enclosing SplitFrame, and the (width,
       */
      AlignFrame myTopFrame = (AlignFrame) source.getTopFrame();
      AlignFrame myBottomFrame = (AlignFrame) source.getBottomFrame();
 -    myTopFrame.viewport.setExplodedGeometry(
 -        new Rectangle(source.getX(), source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
 -    myBottomFrame.viewport.setExplodedGeometry(
 -        new Rectangle(source.getX(), source.getY(), myBottomFrame.getWidth(), myBottomFrame.getHeight()));
 +    myTopFrame.viewport.setExplodedGeometry(new Rectangle(source.getX(),
 +            source.getY(), myTopFrame.getWidth(), myTopFrame.getHeight()));
 +    myBottomFrame.viewport
 +            .setExplodedGeometry(new Rectangle(source.getX(), source.getY(),
 +                    myBottomFrame.getWidth(), myBottomFrame.getHeight()));
      myTopFrame.viewport.setGatherViewsHere(true);
      myBottomFrame.viewport.setGatherViewsHere(true);
      String topViewId = myTopFrame.viewport.getSequenceSetId();
      String bottomViewId = myBottomFrame.viewport.getSequenceSetId();
  
 -    JInternalFrame[] frames = desktop.getAllFrames();
 -    for (JInternalFrame frame : frames) {
 -      if (frame instanceof SplitFrame && frame != source) {
 +    JInternalFrame[] frames = desktopPane.getAllFrames();
 +    for (JInternalFrame frame : frames)
 +    {
 +      if (frame instanceof SplitFrame && frame != source)
 +      {
          SplitFrame sf = (SplitFrame) frame;
          AlignFrame topFrame = (AlignFrame) sf.getTopFrame();
          AlignFrame bottomFrame = (AlignFrame) sf.getBottomFrame();
          boolean gatherThis = false;
 -        for (int a = 0; a < topFrame.alignPanels.size(); a++) {
 +        for (int a = 0; a < topFrame.alignPanels.size(); a++)
 +        {
            AlignmentPanel topPanel = topFrame.alignPanels.get(a);
            AlignmentPanel bottomPanel = bottomFrame.alignPanels.get(a);
            if (topViewId.equals(topPanel.av.getSequenceSetId())
 -              && bottomViewId.equals(bottomPanel.av.getSequenceSetId())) {
 +                  && bottomViewId.equals(bottomPanel.av.getSequenceSetId()))
 +          {
              gatherThis = true;
              topPanel.av.setGatherViewsHere(false);
              bottomPanel.av.setGatherViewsHere(false);
 -            topPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), topFrame.getSize()));
 -            bottomPanel.av.setExplodedGeometry(new Rectangle(sf.getLocation(), bottomFrame.getSize()));
 +            topPanel.av.setExplodedGeometry(
 +                    new Rectangle(sf.getLocation(), topFrame.getSize()));
 +            bottomPanel.av.setExplodedGeometry(
 +                    new Rectangle(sf.getLocation(), bottomFrame.getSize()));
              myTopFrame.addAlignmentPanel(topPanel, false);
              myBottomFrame.addAlignmentPanel(bottomPanel, false);
            }
          }
  
 -        if (gatherThis) {
 +        if (gatherThis)
 +        {
            topFrame.getAlignPanels().clear();
            bottomFrame.getAlignPanels().clear();
            sf.close();
      myTopFrame.setDisplayedView(myTopFrame.alignPanel);
    }
  
 -  public static groovy.ui.Console getGroovyConsole() {
 +  public static groovy.ui.Console getGroovyConsole()
 +  {
      return groovyConsole;
    }
  
     * 
     * TODO refactor to desktop utilities class
     * 
 -   * @param files     - Data source strings extracted from the drop event
 -   * @param protocols - protocol for each data source extracted from the drop
 -   *                  event
 -   * @param evt       - the drop event
 -   * @param t         - the payload from the drop event
 +   * @param files
 +   *          - Data source strings extracted from the drop event
 +   * @param protocols
 +   *          - protocol for each data source extracted from the drop event
 +   * @param evt
 +   *          - the drop event
 +   * @param t
 +   *          - the payload from the drop event
     * @throws Exception
     */
 -  public static void transferFromDropTarget(List<Object> files, List<DataSourceType> protocols, DropTargetDropEvent evt,
 -      Transferable t) throws Exception {
 -
 -    DataFlavor uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"), urlFlavour = null;
 -    try {
 -      urlFlavour = new DataFlavor("application/x-java-url; class=java.net.URL");
 -    } catch (ClassNotFoundException cfe) {
 +  @SuppressWarnings("unchecked")
 +  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
 +    {
 +      urlFlavour = new DataFlavor(
 +              "application/x-java-url; class=java.net.URL");
 +    } catch (ClassNotFoundException cfe)
 +    {
        Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
      }
  
 -    if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour)) {
 +    if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
 +    {
  
 -      try {
 +      try
 +      {
          java.net.URL url = (URL) t.getTransferData(urlFlavour);
          // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
          // means url may be null.
 -        if (url != null) {
 +        if (url != null)
 +        {
            protocols.add(DataSourceType.URL);
            files.add(url.toString());
 -          Cache.log.debug("Drop handled as URL dataflavor " + files.get(files.size() - 1));
 +          Cache.log.debug("Drop handled as URL dataflavor "
 +                  + files.get(files.size() - 1));
            return;
 -        } else {
 -          if (Platform.isAMacAndNotJS()) {
 -            System.err.println("Please ignore plist error - occurs due to problem with java 8 on OSX");
 +        }
 +        else
 +        {
 +          if (Platform.isAMacAndNotJS())
 +          {
 +            System.err.println(
 +                    "Please ignore plist error - occurs due to problem with java 8 on OSX");
            }
          }
 -      } catch (Throwable ex) {
 +      } catch (Throwable ex)
 +      {
          Cache.log.debug("URL drop handler failed.", ex);
        }
      }
 -    if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
 +    if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
 +    {
        // Works on Windows and MacOSX
        Cache.log.debug("Drop handled as javaFileListFlavor");
 -      for (Object file : (List) t.getTransferData(DataFlavor.javaFileListFlavor)) {
 +      for (File file : (List<File>) t
 +              .getTransferData(DataFlavor.javaFileListFlavor))
 +      {
          files.add(file);
          protocols.add(DataSourceType.FILE);
        }
 -    } else {
 +    }
 +    else
 +    {
        // Unix like behaviour
        boolean added = false;
        String data = null;
 -      if (t.isDataFlavorSupported(uriListFlavor)) {
 +      if (t.isDataFlavorSupported(uriListFlavor))
 +      {
          Cache.log.debug("Drop handled as uriListFlavor");
          // This is used by Unix drag system
          data = (String) t.getTransferData(uriListFlavor);
        }
 -      if (data == null) {
 +      if (data == null)
 +      {
          // fallback to text: workaround - on OSX where there's a JVM bug
          Cache.log.debug("standard URIListFlavor failed. Trying text");
          // try text fallback
 -        DataFlavor textDf = new DataFlavor("text/plain;class=java.lang.String");
 -        if (t.isDataFlavorSupported(textDf)) {
 +        DataFlavor textDf = new DataFlavor(
 +                "text/plain;class=java.lang.String");
 +        if (t.isDataFlavorSupported(textDf))
 +        {
            data = (String) t.getTransferData(textDf);
          }
  
 -        Cache.log.debug("Plain text drop content returned " + (data == null ? "Null - failed" : data));
 +        Cache.log.debug("Plain text drop content returned "
 +                + (data == null ? "Null - failed" : data));
  
        }
 -      if (data != null) {
 -        while (protocols.size() < files.size()) {
 -          Cache.log.debug("Adding missing FILE protocol for " + files.get(protocols.size()));
 +      if (data != null)
 +      {
 +        while (protocols.size() < files.size())
 +        {
 +          Cache.log.debug("Adding missing FILE protocol for "
 +                  + files.get(protocols.size()));
            protocols.add(DataSourceType.FILE);
          }
 -        for (java.util.StringTokenizer st = new java.util.StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
 +        for (java.util.StringTokenizer st = new java.util.StringTokenizer(
 +                data, "\r\n"); st.hasMoreTokens();)
 +        {
            added = true;
            String s = st.nextToken();
 -          if (s.startsWith("#")) {
 +          if (s.startsWith("#"))
 +          {
              // the line is a comment (as per the RFC 2483)
              continue;
            }
            java.net.URI uri = new java.net.URI(s);
 -          if (uri.getScheme().toLowerCase(Locale.ROOT).startsWith("http")) {
 +          if (uri.getScheme().toLowerCase().startsWith("http"))
 +          {
              protocols.add(DataSourceType.URL);
              files.add(uri.toString());
 -          } else {
 +          }
 +          else
 +          {
              // otherwise preserve old behaviour: catch all for file objects
              java.io.File file = new java.io.File(uri);
              protocols.add(DataSourceType.FILE);
          }
        }
  
 -      if (Cache.log.isDebugEnabled()) {
 -        if (data == null || !added) {
 +      if (Cache.log.isDebugEnabled())
 +      {
 +        if (data == null || !added)
 +        {
  
 -          if (t.getTransferDataFlavors() != null && t.getTransferDataFlavors().length > 0) {
 -            Cache.log.debug("Couldn't resolve drop data. Here are the supported flavors:");
 -            for (DataFlavor fl : t.getTransferDataFlavors()) {
 -              Cache.log.debug("Supported transfer dataflavor: " + fl.toString());
 +          if (t.getTransferDataFlavors() != null
 +                  && t.getTransferDataFlavors().length > 0)
 +          {
 +            Cache.log.debug(
 +                    "Couldn't resolve drop data. Here are the supported flavors:");
 +            for (DataFlavor fl : t.getTransferDataFlavors())
 +            {
 +              Cache.log.debug(
 +                      "Supported transfer dataflavor: " + fl.toString());
                Object df = t.getTransferData(fl);
 -              if (df != null) {
 +              if (df != null)
 +              {
                  Cache.log.debug("Retrieves: " + df);
 -              } else {
 +              }
 +              else
 +              {
                  Cache.log.debug("Retrieved nothing");
                }
              }
 -          } else {
 -            Cache.log.debug("Couldn't resolve dataflavor for drop: " + t.toString());
 +          }
 +          else
 +          {
 +            Cache.log.debug("Couldn't resolve dataflavor for drop: "
 +                    + t.toString());
            }
          }
        }
      }
 -    if (Platform.isWindowsAndNotJS()) {
 +    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).toString().toLowerCase(Locale.ROOT);
 +      for (int f = 0; f < files.size(); f++)
 +      {
 +        String source = files.get(f).toString().toLowerCase();
          if (protocols.get(f).equals(DataSourceType.FILE)
 -            && (source.endsWith(".lnk") || source.endsWith(".url") || source.endsWith(".site"))) {
 -          try {
 +                && (source.endsWith(".lnk") || source.endsWith(".url")
 +                        || source.endsWith(".site")))
 +        {
 +          try
 +          {
              Object obj = files.get(f);
 -            File lf = (obj instanceof File ? (File) obj : new File((String) obj));
 +            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);
              String fullname = wscfile.getRealFilename();
              protocols.set(f, FormatAdapter.checkProtocol(fullname));
              files.set(f, fullname);
 -            Cache.log.debug("Parsed real filename " + fullname + " to extract protocol: " + protocols.get(f));
 -          } catch (Exception ex) {
 -            Cache.log.error("Couldn't parse " + files.get(f) + " as a link file.", ex);
 +            Cache.log.debug("Parsed real filename " + fullname
 +                    + " to extract protocol: " + protocols.get(f));
 +          } catch (Exception ex)
 +          {
 +            Cache.log.error(
 +                    "Couldn't parse " + files.get(f) + " as a link file.",
 +                    ex);
            }
          }
        }
     * depending on the state of the controlling menu item
     */
    @Override
 -  protected void showExperimental_actionPerformed(boolean selected) {
 +  protected void showExperimental_actionPerformed(boolean selected)
 +  {
      Cache.setProperty(EXPERIMENTAL_FEATURES, Boolean.toString(selected));
    }
  
    /**
 -   * 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
 -   * @param structureViewerClass if not null, only return viewers of this class
 +   * @param apanel
 +   *          if not null, only return viewers linked to this panel
 +   * @param structureViewerClass
 +   *          if not null, only return viewers of this class
     * @return
     */
 -  public List<StructureViewerBase> getStructureViewers(AlignmentPanel apanel,
 -      Class<? extends StructureViewerBase> structureViewerClass) {
 +  public List<StructureViewerBase> getStructureViewers(
 +          AlignmentPanel apanel,
 +          Class<? extends StructureViewerBase> structureViewerClass)
 +  {
      List<StructureViewerBase> result = new ArrayList<>();
 -    JInternalFrame[] frames = Desktop.instance.getAllFrames();
 +    JInternalFrame[] frames = getAllFrames();
  
 -    for (JInternalFrame frame : frames) {
 -      if (frame instanceof StructureViewerBase) {
 -        if (structureViewerClass == null || structureViewerClass.isInstance(frame)) {
 -          if (apanel == null || ((StructureViewerBase) frame).isLinkedWith(apanel)) {
 +    for (JInternalFrame frame : frames)
 +    {
 +      if (frame instanceof StructureViewerBase)
 +      {
 +        if (structureViewerClass == null
 +                || structureViewerClass.isInstance(frame))
 +        {
 +          if (apanel == null
 +                  || ((StructureViewerBase) frame).isLinkedWith(apanel))
 +          {
              result.add((StructureViewerBase) frame);
            }
          }
      return result;
    }
  
+   public static final String debugScaleMessage = "Desktop graphics transform scale=";
+   private static boolean debugScaleMessageDone = false;
+   public static void debugScaleMessage(Graphics g) {
+     if (debugScaleMessageDone) {
+       return;
+     }
+     // output used by tests to check HiDPI scaling settings in action
+     try {
+       Graphics2D gg = (Graphics2D) g;
+       if (gg != null) {
+         AffineTransform t = gg.getTransform();
+         double scaleX = t.getScaleX();
+         double scaleY = t.getScaleY();
+         Cache.debug(debugScaleMessage + scaleX + " (X)");
+         Cache.debug(debugScaleMessage + scaleY + " (Y)");
+         debugScaleMessageDone = true;
+       } else {
+         Cache.debug("Desktop graphics null");
+       }
+     } catch (Exception e) {
+       Cache.debug(Cache.getStackTraceString(e));
+     }
+   }
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.gui;
  
+ import java.util.Locale;
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
@@@ -150,7 -152,7 +152,7 @@@ public class FeatureSettings extends JP
     */
    Object[][] originalData;
  
-   float originalTransparency;
+   private float originalTransparency;
  
    private ViewStyleI originalViewStyle;
  
    /*
     * true when Feature Settings are updating from feature renderer
     */
-   boolean handlingUpdate = false;
+   private boolean handlingUpdate = false;
  
    /*
     * a change listener to ensure the dialog is updated if
        }
        else
        {
 -        Desktop.addInternalFrame(frame, title, false, bounds.width,
 -                bounds.height);
 +        Desktop.addInternalFrame(frame, title, Desktop.FRAME_NOT_VISIBLE, bounds.width,
 +                bounds.height, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_SET_MIN_SIZE_300);
          frame.setBounds(bounds);
          frame.setVisible(true);
        }
      String text = MessageManager.formatMessage("label.show_linked_features",
              nucleotide
                      ? MessageManager.getString("label.protein")
-                             .toLowerCase()
+                             .toLowerCase(Locale.ROOT)
                      : "CDS");
      showComplement = new JCheckBox(text);
      showComplement.addActionListener(new ActionListener()
   */
  package jalview.gui;
  
- import jalview.api.AlignViewportI;
- import jalview.api.FinderI;
- import jalview.datamodel.SearchResultMatchI;
- import jalview.datamodel.SearchResultsI;
- import jalview.datamodel.SequenceFeature;
- import jalview.datamodel.SequenceI;
- import jalview.jbgui.GFinder;
- import jalview.util.MessageManager;
- import jalview.viewmodel.AlignmentViewport;
+ import java.util.Locale;
  
+ import java.awt.Dimension;
  import java.awt.event.ActionEvent;
+ import java.awt.event.FocusAdapter;
+ import java.awt.event.FocusEvent;
  import java.awt.event.KeyEvent;
  import java.util.ArrayList;
  import java.util.HashMap;
@@@ -47,6 -42,15 +42,15 @@@ import javax.swing.KeyStroke
  import javax.swing.event.InternalFrameAdapter;
  import javax.swing.event.InternalFrameEvent;
  
+ import jalview.api.AlignViewportI;
+ import jalview.api.FinderI;
+ import jalview.datamodel.SearchResultMatchI;
+ import jalview.datamodel.SearchResultsI;
+ import jalview.datamodel.SequenceFeature;
+ import jalview.datamodel.SequenceI;
+ import jalview.jbgui.GFinder;
+ import jalview.util.MessageManager;
  /**
   * Performs the menu option for searching the alignment, for the next or all
   * matches. If matches are found, they are highlighted, and the user has the
@@@ -64,7 -68,7 +68,7 @@@ public class Finder extends GFinde
  
    private static final int MIN_HEIGHT = 120;
  
-   private static final int MY_HEIGHT = 120;
+   private static final int MY_HEIGHT = 150;
  
    private static final int MY_WIDTH = 400;
  
    private SearchResultsI searchResults;
  
    /*
-    * true if we only search a given alignment view
+    * true if Finder always acts on the same alignment,
+    * false if it acts on the alignment with focus
     */
-   private boolean focusfixed;
+   private boolean focusFixed;
  
    /**
-    * Creates a new Finder object with no associated viewport or panel. Each Find
-    * or Find Next action will act on whichever viewport has focus at the time.
-    */
-   public Finder()
-   {
-     this(null, null);
-   }
-   /**
-    * Constructor given an associated viewport and alignment panel. Constructs
-    * and displays an internal frame where the user can enter a search string.
+    * Constructor given an associated alignment panel. Constructs and displays an
+    * internal frame where the user can enter a search string. The Finder may
+    * have 'fixed focus' (always act the panel for which it is constructed), or
+    * not (acts on the alignment that has focus). An optional 'scope' may be
+    * added to be shown in the title of the Finder frame.
     * 
-    * @param viewport
     * @param alignPanel
+    * @param fixedFocus
+    * @param scope
     */
-   public Finder(AlignmentViewport viewport, AlignmentPanel alignPanel)
+   public Finder(AlignmentPanel alignPanel, boolean fixedFocus, String scope)
    {
-     av = viewport;
+     av = alignPanel.getAlignViewport();
      ap = alignPanel;
+     focusFixed = fixedFocus;
      finders = new HashMap<>();
      frame = new JInternalFrame();
      frame.setContentPane(this);
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
-     frame.addInternalFrameListener(
-             new InternalFrameAdapter()
-             {
-               @Override
-               public void internalFrameClosing(InternalFrameEvent e)
-               {
-                 closeAction();
-               }
-             });
+     frame.addInternalFrameListener(new InternalFrameAdapter()
+     {
+       @Override
+       public void internalFrameClosing(InternalFrameEvent e)
+       {
+         closeAction();
+       }
+     });
+     frame.addFocusListener(new FocusAdapter()
+     {
+       @Override
+       public void focusGained(FocusEvent e)
+       {
+         /*
+          * ensure 'ignore hidden columns' is only enabled
+          * if the alignment with focus has hidden columns
+          */
+         getFocusedViewport();
+       }
+     });
      addEscapeHandler();
-     Desktop.addInternalFrame(frame, MessageManager.getString("label.find"), 
-             Desktop.FRAME_MAKE_VISIBLE, MY_WIDTH, MY_HEIGHT, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_ALLOW_ANY_SIZE);
+     String title = MessageManager.getString("label.find");
+     if (scope != null)
+     {
+       title += " " + scope;
+     }
 -    Desktop.addInternalFrame(frame, title, MY_WIDTH, MY_HEIGHT);
++    Desktop.addInternalFrame(frame, title, Desktop.FRAME_MAKE_VISIBLE, MY_WIDTH, MY_HEIGHT, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_ALLOW_ANY_SIZE);
+     frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
      searchBox.getComponent().requestFocus();
    }
  
    /**
     * if !focusfixed and not in a desktop environment, checks that av and ap are
     * valid. Otherwise, gets the topmost alignment window and sets av and ap
-    * accordingly
+    * accordingly. Also sets the 'ignore hidden' checkbox disabled if the
+    * viewport has no hidden columns.
     * 
     * @return false if no alignment window was found
     */
    boolean getFocusedViewport()
    {
 -    if (focusFixed || Desktop.desktop == null)
 +    if (focusfixed || Desktop.getDesktopPane() == null)
      {
        if (ap != null && av != null)
        {
+         ignoreHidden.setEnabled(av.hasHiddenColumns());
          return true;
        }
        // we aren't in a desktop environment, so give up now.
      }
      // now checks further down the window stack to fix bug
      // https://mantis.lifesci.dundee.ac.uk/view.php?id=36008
 -    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 +    JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
      for (int f = 0; f < frames.length; f++)
      {
        JInternalFrame alignFrame = frames[f];
        {
          av = ((AlignFrame) alignFrame).viewport;
          ap = ((AlignFrame) alignFrame).alignPanel;
+         ignoreHidden.setEnabled(av.hasHiddenColumns());
          return true;
        }
      }
  
      boolean isCaseSensitive = caseSensitive.isSelected();
      boolean doSearchDescription = searchDescription.isSelected();
+     boolean skipHidden = ignoreHidden.isSelected();
      if (doFindAll)
      {
-       finder.findAll(searchString, isCaseSensitive, doSearchDescription);
+       finder.findAll(searchString, isCaseSensitive, doSearchDescription,
+               skipHidden);
      }
      else
      {
-       finder.findNext(searchString, isCaseSensitive, doSearchDescription);
+       finder.findNext(searchString, isCaseSensitive, doSearchDescription,
+               skipHidden);
      }
  
      searchResults = finder.getSearchResults();
        if (doFindAll)
        {
          // then we report the matches that were found
-         String message = (idMatch.size() > 0) ? "" + idMatch.size() + " IDs"
-                 : "";
+         StringBuilder message = new StringBuilder();
+         if (idMatch.size() > 0)
+         {
+           message.append(idMatch.size()).append(" IDs");
+         }
          if (searchResults != null)
          {
-           if (idMatch.size() > 0 && searchResults.getSize() > 0)
+           if (idMatch.size() > 0 && searchResults.getCount() > 0)
            {
-             message += " and ";
+             message.append(" ").append(
+                     MessageManager.getString("label.and").toLowerCase(Locale.ROOT))
+                     .append(" ");
            }
-           message += searchResults.getSize()
-                   + " subsequence matches found.";
+           message.append(MessageManager.formatMessage(
+                   "label.subsequence_matches_found",
+                   searchResults.getCount()));
          }
-         JvOptionPane.showInternalMessageDialog(this, message, null,
-                 JvOptionPane.PLAIN_MESSAGE);
+         JvOptionPane.showInternalMessageDialog(this, message.toString(),
+                 null, JvOptionPane.INFORMATION_MESSAGE);
        }
      }
    }
   */
  package jalview.gui;
  
 +import jalview.datamodel.SequenceI;
 +import jalview.viewmodel.ViewportListenerI;
 +import jalview.viewmodel.ViewportRanges;
 +
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Font;
@@@ -37,6 -33,10 +37,6 @@@ import java.util.List
  
  import javax.swing.JPanel;
  
 -import jalview.datamodel.SequenceI;
 -import jalview.viewmodel.ViewportListenerI;
 -import jalview.viewmodel.ViewportRanges;
 -
  /**
   * DOCUMENT ME!
   * 
@@@ -59,7 -59,7 +59,7 @@@ public class IdCanvas extends JPanel im
  
    int imgHeight = 0;
  
 -  boolean fastPaint = false;
 +  private boolean fastPaint = false;
  
    List<SequenceI> searchResults;
  
@@@ -67,8 -67,6 +67,8 @@@
  
    private Font idfont;
  
 +  private boolean allowFastPaint;
 +
    /**
     * Creates a new IdCanvas object.
     * 
      g.drawString(s.getDisplayId(av.getShowJVSuffix()), xPos,
              (((i - starty + 1) * charHeight) + ypos) - (charHeight / 5));
  
 +    // JAL-3253-applet was just hiddenRows here. ccfc48e (gmungoc) added getShowHiddenMarkers test
      if (hiddenRows && av.getShowHiddenMarkers())
      {
        drawMarker(g, av, i, starty, ypos);
    @Override
    public void paintComponent(Graphics g)
    {
 +    if (av.getAlignPanel().getHoldRepaint())
 +    {
 +      return;
 +    }
 +
      g.setColor(Color.white);
      g.fillRect(0, 0, getWidth(), getHeight());
      
 -    if (fastPaint)
 +    if (allowFastPaint && fastPaint)
      {
        fastPaint = false;
        g.drawImage(image, 0, 0, this);
      
      if (oldHeight != imgHeight || image.getWidth(this) != getWidth())
      {
 -      image = new BufferedImage(getWidth(), imgHeight,
 +      image = new BufferedImage(getWidth(), imgHeight,
                  BufferedImage.TYPE_INT_RGB);
      }
      
        if (hasHiddenRows || alignViewport.isDisplayReferenceSeq())
        {
          g.setFont(getHiddenFont(sequence, alignViewport));
+         fm = g.getFontMetrics();
        }
  
        // Selected sequence colours
      int alignmentWidth = alignViewport.getAlignment().getWidth();
      final int alheight = alignViewport.getAlignment().getHeight();
  
 -    /*
 +    
 +//    int annotationHeight = 0;
 +
 +    /* (former)
       * assumption: SeqCanvas.calculateWrappedGeometry has been called
 +     * 
 +     * was based on the fact that SeqCanvas was added as a child prior to IdCanvas, 
 +     * and children are processed in order of addition.
 +     * 
 +     * It's probably fine. But...
 +     * 
       */
      SeqCanvas seqCanvas = alignViewport.getAlignPanel()
              .getSeqPanel().seqCanvas;
 +    // ...better: let's call it now
 +    seqCanvas.calculateWrappedGeometry();
  
      final int charHeight = alignViewport.getCharHeight();
  
      AnnotationLabels labels = null;
      if (alignViewport.isShowAnnotation())
      {
 +      // BH when was ap == null?
 +      if (ap == null)
 +      {
 +        ap = new AnnotationPanel(alignViewport);
 +      }
 +//      annotationHeight = ap.adjustPanelHeight();
        labels = new AnnotationLabels(alignViewport);
      }
  
 +//    int hgap = charHeight;
 +//    if (alignViewport.getScaleAboveWrapped())
 +//    {
 +//      hgap += charHeight;
 +//    }
 +//
 +//    /*
 +//     * height of alignment + gap + annotations (if shown)
 +//     */
 +//    int cHeight = alheight * charHeight + hgap
 +//            + annotationHeight;
 +//
      ViewportRanges ranges = alignViewport.getRanges();
  
      int rowSize = ranges.getViewportWidth();
    public void propertyChange(PropertyChangeEvent evt)
    {
      String propertyName = evt.getPropertyName();
 -    if (propertyName.equals(ViewportRanges.STARTSEQ)
 -            || (av.getWrapAlignment()
 -                    && propertyName.equals(ViewportRanges.STARTRES)))
 +    switch (propertyName)
      {
 +    case ViewportRanges.STARTSEQ:
        fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
 -    }
 -    else if (propertyName.equals(ViewportRanges.STARTRESANDSEQ))
 -    {
 +      break;
 +    case ViewportRanges.STARTRES:
 +      if (av.getWrapAlignment())
 +      {
 +        fastPaint((int) evt.getNewValue() - (int) evt.getOldValue());
 +      }
 +      break;
 +    case ViewportRanges.STARTRESANDSEQ:
        fastPaint(((int[]) evt.getNewValue())[1]
                - ((int[]) evt.getOldValue())[1]);
 -    }
 -    else if (propertyName.equals(ViewportRanges.MOVE_VIEWPORT))
 -    {
 +      break;
 +    case ViewportRanges.MOVE_VIEWPORT:
        repaint();
 +      break;
 +    default:
      }
    }
 +
 +  /**
 +   * Clears the flag that allows a 'fast paint' on the next repaint, so
 +   * requiring a full repaint
 +   */
 +  public void setNoFastPaint()
 +  {
 +    allowFastPaint = false;
 +  }
  }
   */
  package jalview.gui;
  
 +import jalview.bin.Cache;
 +import jalview.io.DataSourceType;
 +import jalview.io.FileLoader;
 +import jalview.io.JalviewFileChooser;
 +import jalview.io.JalviewFileView;
 +import jalview.util.MessageManager;
 +import jalview.ws.jws2.dm.JabaOption;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.OptionI;
 +import jalview.ws.params.ParameterI;
 +import jalview.ws.params.ValueConstrainI;
 +import jalview.ws.params.ValueConstrainI.ValueType;
 +import jalview.ws.params.simple.FileParameter;
 +import jalview.ws.params.simple.LogarithmicParameter;
 +import jalview.ws.params.simple.RadioChoiceParameter;
 +import jalview.ws.params.simple.StringParameter;
 +
  import java.awt.BorderLayout;
 +import java.awt.Color;
  import java.awt.Component;
 +import java.awt.Container;
  import java.awt.Dimension;
 +import java.awt.FlowLayout;
  import java.awt.Font;
  import java.awt.Rectangle;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 -import java.awt.event.FocusAdapter;
 -import java.awt.event.FocusEvent;
 +import java.awt.event.KeyAdapter;
  import java.awt.event.KeyEvent;
 -import java.awt.event.KeyListener;
 +import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.awt.event.MouseListener;
 +import java.io.File;
  import java.net.URL;
  import java.util.ArrayList;
 +import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  
 +import javax.swing.ButtonGroup;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
@@@ -68,15 -47,19 +68,15 @@@ import javax.swing.JLabel
  import javax.swing.JMenuItem;
  import javax.swing.JPanel;
  import javax.swing.JPopupMenu;
 +import javax.swing.JRadioButton;
  import javax.swing.JScrollPane;
 +import javax.swing.JSlider;
  import javax.swing.JTextArea;
  import javax.swing.JTextField;
  import javax.swing.border.TitledBorder;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
  
 -import jalview.util.MessageManager;
 -import jalview.ws.params.ArgumentI;
 -import jalview.ws.params.OptionI;
 -import jalview.ws.params.ParameterI;
 -import jalview.ws.params.ValueConstrainI;
 -import jalview.ws.params.ValueConstrainI.ValueType;
  import net.miginfocom.swing.MigLayout;
  
  /**
   */
  public class OptsAndParamsPage
  {
 -  /**
 +  public static final int PARAM_WIDTH = 340;
 +
 +  public static final int PARAM_HEIGHT = 150;
 +
 +  public static final int PARAM_CLOSEDHEIGHT = 80;
 +
 +  URL linkImageURL = getClass().getResource("/images/link.gif");
 +
 +  Map<String, OptionBox> optSet = new LinkedHashMap<>();
 +
 +  Map<String, ParamBox> paramSet = new LinkedHashMap<>();
 +
 +  /*
     * compact or verbose style parameters
     */
    boolean compact = false;
  
 +  OptsParametersContainerI poparent;
 +
 +  /**
 +   * A class that models a panel rendering a single option (checkbox or choice
 +   * list)
 +   */
    public class OptionBox extends JPanel
            implements MouseListener, ActionListener
    {
 -    JCheckBox enabled = new JCheckBox();
 +    JCheckBox enabled;
  
      final URL finfo;
  
  
      OptionI option;
  
 -    JLabel optlabel = new JLabel();
 +    JComboBox<Object> val;
  
 -    JComboBox<String> val = new JComboBox<>();
 +    /**
 +     * Constructs and adds labels and controls to the panel for one Option
 +     * 
 +     * @param opt
 +     */
      public OptionBox(OptionI opt)
      {
        option = opt;
 -      setLayout(new BorderLayout());
 -      enabled.setSelected(opt.isRequired()); // TODO: lock required options
 +      setLayout(new FlowLayout(FlowLayout.LEFT));
 +      enabled = new JCheckBox(opt.getLabel());
 +      enabled.setSelected(opt.isRequired());
 +
 +      /*
 +       * If option is required, show a label, if optional a checkbox
 +       * (but not for Jabaws pending JWS-126 resolution)
 +       */
 +      if (opt.isRequired() && !(opt instanceof JabaOption))
 +      {
 +        finfo = null;
 +        add(new JLabel(opt.getLabel()));
 +      }
 +      else
 +      {
 +        finfo = option.getFurtherDetails();
 +        configureCheckbox(opt);
 +        add(enabled);
 +      }
 +
 +      /*
 +       * construct the choice box with possible values, 
 +       * or their display names if provided
 +       */
 +      val = buildComboBox(opt);
 +      val.setSelectedItem(opt.getValue());
 +
 +      /*
 +       * only show the choicebox if there is more than one option,
 +       * or the option is mandatory
 +       */
 +      if (opt.getPossibleValues().size() > 1 || opt.isRequired())
 +      {
 +        val.addActionListener(this);
 +        add(val);
 +      }
 +
 +      setInitialValue();
 +    }
 +
 +    /**
 +     * Configures the checkbox that controls whether or not the option is
 +     * selected
 +     * 
 +     * @param opt
 +     */
 +    protected void configureCheckbox(OptionI opt)
 +    {
        enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
 -      enabled.setText("");
 -      enabled.setText(opt.getName());
        enabled.addActionListener(this);
 -      finfo = option.getFurtherDetails();
 -      String desc = opt.getDescription();
 +      final String desc = opt.getDescription();
        if (finfo != null)
        {
          hasLink = true;
 -        enabled.setToolTipText(JvSwingUtils.wrapTooltip(true,
 -                ((desc == null || desc.trim().length() == 0)
 -                        ? MessageManager.getString(
 -                                "label.opt_and_params_further_details")
 -                        : desc) + "<br><img src=\"" + linkImageURL
 -                        + "\"/>"));
 -        enabled.addMouseListener(this);
 +        String description = desc;
 +        if (desc == null || desc.trim().isEmpty())
 +        {
 +          description = MessageManager
 +                  .getString("label.opt_and_params_further_details");
 +        }
 +        description = description + "<br><img src=\"" + linkImageURL
 +                + "\"/>";
 +        String text = JvSwingUtils.wrapTooltip(true, description);
 +        enabled.setToolTipText(text);
 +        enabled.addMouseListener(this); // for popup menu to show link
        }
        else
        {
          if (desc != null && desc.trim().length() > 0)
          {
 -          enabled.setToolTipText(
 -                  JvSwingUtils.wrapTooltip(true, opt.getDescription()));
 +          enabled.setToolTipText(JvSwingUtils.wrapTooltip(true, desc));
          }
        }
 -      add(enabled, BorderLayout.NORTH);
 -      for (String str : opt.getPossibleValues())
 -      {
 -        val.addItem(str);
 -      }
 -      val.setSelectedItem(opt.getValue());
 -      if (opt.getPossibleValues().size() > 1)
 -      {
 -        setLayout(new GridLayout(1, 2));
 -        val.addActionListener(this);
 -        add(val, BorderLayout.SOUTH);
 -      }
 -      // TODO: add actionListeners for popup (to open further info),
 -      // and to update list of parameters if an option is enabled
 -      // that takes a value. JBPNote: is this TODO still valid ?
 -      setInitialValue();
      }
  
      @Override
        poparent.argSetModified(this, !notmod);
      }
  
 -    public OptionI getOptionIfEnabled()
 +    /**
 +     * Answers null if the option is not selected, else a new Option holding the
 +     * selected value
 +     * 
 +     * @return
 +     */
 +    public ArgumentI getSelectedOption()
      {
        if (!enabled.isSelected())
        {
          return null;
        }
 +      String value = getSelectedValue(option, val.getSelectedIndex());
        OptionI opt = option.copy();
 -      if (opt.getPossibleValues() != null
 -              && opt.getPossibleValues().size() == 1)
 -      {
 -        // Hack to make sure the default value for an enabled option with only
 -        // one value is actually returned
 -        opt.setValue(opt.getPossibleValues().get(0));
 -      }
 -      if (val.getSelectedItem() != null)
 -      {
 -        opt.setValue((String) val.getSelectedItem());
 -      }
 -      else
 -      {
 -        if (option.getValue() != null)
 -        {
 -          opt.setValue(option.getValue());
 -        }
 -      }
 +      opt.setValue(value);
        return opt;
      }
  
      @Override
      public void mouseEntered(MouseEvent e)
      {
 -      // TODO Auto-generated method stub
      }
  
      @Override
      public void mouseExited(MouseEvent e)
      {
 -      // TODO Auto-generated method stub
      }
  
      @Override
        }
      }
  
 +    /**
 +     * toString representation for identification in the debugger only
 +     */
 +    @Override
 +    public String toString()
 +    {
 +      return option == null ? super.toString() : option.toString();
 +    }
    }
  
 +  /**
 +   * A class that models a panel rendering a single parameter
 +   */
    public class ParamBox extends JPanel
            implements ChangeListener, ActionListener, MouseListener
    {
 -    boolean adjusting = false;
 +    /*
 +     * parameter values (or their logs) are multiplied by this
 +     * scaling factor to ensure an integer range for the slider
 +     */
 +    private int sliderScaleFactor = 1;
 +
 +    boolean isLogarithmicParameter;
 +
 +    boolean isChoiceParameter;
  
 -    boolean choice = false;
 +    boolean isIntegerParameter;
  
 -    JComboBox<String> choicebox;
 +    boolean isStringParameter;
  
 -    JPanel controlPanel = new JPanel();
 +    boolean adjusting;
  
 -    boolean descisvisible = false;
 +    /*
 +     * drop-down list of choice options (if applicable)
 +     */
 +    JComboBox<Object> choicebox;
 +
 +    /*
 +     * radio buttons as an alternative to combo box
 +     */
 +    ButtonGroup buttonGroup;
 +
 +    JPanel controlsPanel = new JPanel();
 +
 +    boolean descriptionIsVisible = false;
  
      JScrollPane descPanel = new JScrollPane();
  
      final URL finfo;
  
 -    boolean integ = false;
 -
 -    String lastVal;
 +    Object lastVal;
  
      ParameterI parameter;
  
  
      JPanel settingPanel = new JPanel();
  
 -    JButton showDesc = new JButton();
 +    JSlider slider;
  
 -    Slider slider = null;
 +    JTextArea descriptionText = new JTextArea();
  
 -    JTextArea string = new JTextArea();
 +    ValueConstrainI validator;
  
 -    ValueConstrainI validator = null;
 +    JTextField valueField;
  
 -    JTextField valueField = null;
 +    private String descTooltip;
  
 -    public ParamBox(final OptsParametersContainerI pmlayout,
 +    public ParamBox(final OptsParametersContainerI paramContainer,
              ParameterI parm)
      {
 -      pmdialogbox = pmlayout;
 +      pmdialogbox = paramContainer;
        finfo = parm.getFurtherDetails();
        validator = parm.getValidValue();
        parameter = parm;
 +      isLogarithmicParameter = parm instanceof LogarithmicParameter;
        if (validator != null)
        {
 -        integ = validator.getType() == ValueType.Integer;
 -      }
 -      else
 -      {
 -        if (parameter.getPossibleValues() != null)
 +        ValueType type = validator.getType();
 +        isIntegerParameter = type == ValueType.Integer;
 +        isStringParameter = type == ValueType.String
 +                || type == ValueType.File;
 +
 +        /*
 +         * ensure slider has an integer range corresponding to
 +         * the min-max range of the parameter
 +         */
 +        if (validator.getMin() != null && validator.getMax() != null
 +        // && !isIntegerParameter
 +                && !isStringParameter)
          {
 -          choice = true;
 +          double min = validator.getMin().doubleValue();
 +          double max = validator.getMax().doubleValue();
 +          if (isLogarithmicParameter)
 +          {
 +            min = Math.log(min);
 +            max = Math.log(max);
 +          }
 +          sliderScaleFactor = (int) (1000000 / (max - min));
 +          // todo scaleMin, scaleMax could also be final fields
          }
        }
  
 -      if (!compact)
 +      List<String> possibleValues = parameter.getPossibleValues();
 +      isChoiceParameter = possibleValues != null
 +              && !possibleValues.isEmpty();
 +
 +      if (compact)
        {
 -        makeExpanderParam(parm);
 +        addCompactParameter(parm);
        }
        else
        {
 -        makeCompactParam(parm);
 +        addExpandableParam(parm);
        }
      }
  
 -    private void makeCompactParam(ParameterI parm)
 +    /**
 +     * Adds a 'compact' format parameter, with any help text shown as a tooltip
 +     * 
 +     * @param parm
 +     */
 +    private void addCompactParameter(ParameterI parm)
      {
        setLayout(new MigLayout("", "[][grow]"));
        String ttipText = null;
  
 -      controlPanel.setLayout(new BorderLayout());
 +      controlsPanel.setLayout(new BorderLayout());
  
        if (parm.getDescription() != null
                && parm.getDescription().trim().length() > 0)
        {
 -        // Only create description boxes if there actually is a description.
          ttipText = (JvSwingUtils.wrapTooltip(true,
                  parm.getDescription() + (finfo != null ? "<br><img src=\""
                          + linkImageURL + "\"/>"
                          : "")));
        }
  
 -      JvSwingUtils.mgAddtoLayout(this, ttipText, new JLabel(parm.getName()),
 -              controlPanel, "");
 +      JvSwingUtils.addtoLayout(this, ttipText, new JLabel(parm.getName()),
 +              controlsPanel, "");
        updateControls(parm);
        validate();
      }
  
 -    private void makeExpanderParam(final ParameterI parm)
 +    /**
 +     * Adds an 'expanded' format parameter, with any help shown in a panel that
 +     * may be shown or hidden
 +     * 
 +     * @param parm
 +     */
 +    private void addExpandableParam(ParameterI parm)
      {
        setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
        setBorder(new TitledBorder(parm.getName()));
        setLayout(null);
 -      showDesc.setFont(new Font("Verdana", Font.PLAIN, 6));
 -      showDesc.setText("+");
 -      string.setFont(new Font("Verdana", Font.PLAIN, 11));
 -      string.setBackground(getBackground());
 +      descriptionText.setFont(new Font("Verdana", Font.PLAIN, 11));
 +      descriptionText.setBackground(getBackground());
  
 -      string.setEditable(false);
 -      descPanel.getViewport().setView(string);
 +      descriptionText.setEditable(false);
 +      descPanel.getViewport().setView(descriptionText);
  
        descPanel.setVisible(false);
  
        JPanel firstrow = new JPanel();
        firstrow.setLayout(null);
 -      controlPanel.setLayout(new BorderLayout());
 -      controlPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
 +      controlsPanel.setLayout(new BorderLayout());
 +      controlsPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
                PARAM_CLOSEDHEIGHT - 50));
 -      firstrow.add(controlPanel);
 +      firstrow.add(controlsPanel);
        firstrow.setBounds(new Rectangle(10, 20, PARAM_WIDTH - 30,
                PARAM_CLOSEDHEIGHT - 30));
  
 -      final ParamBox me = this;
        if (parm.getDescription() != null
                && parm.getDescription().trim().length() > 0)
        {
 -        // Only create description boxes if there actually is a description.
 -        if (finfo != null)
 -        {
 -          showDesc.setToolTipText(JvSwingUtils.wrapTooltip(true,
 -                  MessageManager.formatMessage(
 -                          "label.opt_and_params_show_brief_desc_image_link",
 -                          new String[]
 -                          { linkImageURL.toExternalForm() })));
 -          showDesc.addMouseListener(this);
 -        }
 -        else
 -        {
 -          showDesc.setToolTipText(
 -                  JvSwingUtils.wrapTooltip(true, MessageManager.getString(
 -                          "label.opt_and_params_show_brief_desc")));
 -        }
 -        showDesc.addActionListener(new ActionListener()
 -        {
 -
 -          @Override
 -          public void actionPerformed(ActionEvent e)
 -          {
 -            descisvisible = !descisvisible;
 -            descPanel.setVisible(descisvisible);
 -            descPanel.getVerticalScrollBar().setValue(0);
 -            me.setPreferredSize(new Dimension(PARAM_WIDTH,
 -                    (descisvisible) ? PARAM_HEIGHT : PARAM_CLOSEDHEIGHT));
 -            me.validate();
 -            pmdialogbox.refreshParamLayout();
 -          }
 -        });
 -        string.setWrapStyleWord(true);
 -        string.setLineWrap(true);
 -        string.setColumns(32);
 -        string.setText(parm.getDescription());
 -        showDesc.setBounds(new Rectangle(10, 10, 16, 16));
 -        firstrow.add(showDesc);
 +        addExpandableHelp(firstrow, parm);
        }
        add(firstrow);
        validator = parm.getValidValue();
        parameter = parm;
        if (validator != null)
        {
 -        integ = validator.getType() == ValueType.Integer;
 +        isIntegerParameter = validator.getType() == ValueType.Integer;
        }
        else
        {
          if (parameter.getPossibleValues() != null)
          {
 -          choice = true;
 +          isChoiceParameter = true;
          }
        }
        updateControls(parm);
      }
  
      /**
 -     * Action on input in text field
 +     * Adds a button which can be clicked to show or hide help text
 +     * 
 +     * @param container
 +     * @param param
       */
 +    protected void addExpandableHelp(JPanel container, ParameterI param)
 +    {
 +      JButton showDescBtn = new JButton("+");
 +      showDescBtn.setFont(new Font("Verdana", Font.PLAIN, 8));
 +      if (finfo != null)
 +      {
 +        descTooltip = JvSwingUtils.wrapTooltip(true,
 +                MessageManager.formatMessage(
 +                        "label.opt_and_params_show_brief_desc_image_link",
 +                        new String[]
 +                        { linkImageURL.toExternalForm() }));
 +        showDescBtn.addMouseListener(this);
 +      }
 +      else
 +      {
 +        descTooltip = JvSwingUtils.wrapTooltip(true, MessageManager
 +                .getString("label.opt_and_params_show_brief_desc"));
 +      }
 +      showDescBtn.setToolTipText(descTooltip);
 +      showDescBtn.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          descriptionIsVisible = !descriptionIsVisible;
 +          showDescBtn.setText(descriptionIsVisible ? "-" : "+");
 +          showDescBtn.setToolTipText(
 +                  descriptionIsVisible ? null : descTooltip);
 +          descPanel.setVisible(descriptionIsVisible);
 +          descPanel.getVerticalScrollBar().setValue(0);
 +          ParamBox.this.setPreferredSize(new Dimension(PARAM_WIDTH,
 +                  (descriptionIsVisible) ? PARAM_HEIGHT
 +                          : PARAM_CLOSEDHEIGHT));
 +          ParamBox.this.validate();
 +          pmdialogbox.refreshParamLayout();
 +        }
 +      });
 +      descriptionText.setWrapStyleWord(true);
 +      descriptionText.setLineWrap(true);
 +      descriptionText.setColumns(32);
 +      descriptionText.setText(param.getDescription());
 +      showDescBtn.setBounds(new Rectangle(10, 10, 16, 16));
 +      container.add(showDescBtn);
 +    }
      @Override
      public void actionPerformed(ActionEvent e)
      {
        {
          return;
        }
        checkIfModified();
      }
  
 -    private void checkIfModified()
 -    {
 -      Object cstate = getCurrentValue();
 -      boolean modified = !cstate.equals(lastVal);
 -      pmdialogbox.argSetModified(this, modified);
 -    }
 -
      /**
 -     * Answers the current value of the parameter, as text
 -     * 
 -     * @return
 +     * Checks whether the value of this parameter has been changed and notifies
 +     * the parent page accordingly
       */
 -    private String getCurrentValue()
 +    private void checkIfModified()
      {
 -      return choice ? (String) choicebox.getSelectedItem()
 -              : valueField.getText();
 +      Object newValue = updateSliderFromValueField();
 +      boolean modified = true;
 +      if (newValue.getClass() == lastVal.getClass())
 +      {
 +        modified = !newValue.equals(lastVal);
 +      }
 +      pmdialogbox.argSetModified(this, modified);
      }
  
      @Override
        return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
      }
  
 -    public int getBoxHeight()
 -    {
 -      return (descisvisible ? PARAM_HEIGHT : PARAM_CLOSEDHEIGHT);
 -    }
 -
 -    public ParameterI getParameter()
 +    /**
 +     * Answers an argument holding the value entered or selected in the dialog
 +     * 
 +     * @return
 +     */
 +    public ArgumentI getParameter()
      {
        ParameterI prm = parameter.copy();
 -      if (choice)
 +      String value = null;
 +      if (parameter instanceof RadioChoiceParameter)
        {
 -        prm.setValue((String) choicebox.getSelectedItem());
 +        value = buttonGroup.getSelection().getActionCommand();
 +      }
 +      else if (isChoiceParameter)
 +      {
 +        value = getSelectedValue(this.parameter,
 +                choicebox.getSelectedIndex());
        }
        else
        {
 -        prm.setValue(valueField.getText());
 +        value = valueField.getText();
        }
 +      prm.setValue(value);
        return prm;
      }
  
      @Override
      public void mouseEntered(MouseEvent e)
      {
 -      // TODO Auto-generated method stub
      }
  
      @Override
      public void mouseExited(MouseEvent e)
      {
 -      // TODO Auto-generated method stub
      }
  
      @Override
      @Override
      public void mouseReleased(MouseEvent e)
      {
 -      // TODO Auto-generated method stub
      }
  
      @Override
      public void stateChanged(ChangeEvent e)
      {
 -      if (!adjusting)
 +      if (adjusting)
        {
 -        float value = slider.getSliderValue();
 -        valueField.setText(
 -                integ ? Integer.toString((int) value)
 -                        : Float.toString(value));
 +        return;
 +      }
 +      try
 +      {
 +        adjusting = true;
 +        if (!isLogarithmicParameter)
 +        {
 +          /*
 +           * set (int or float formatted) text field value
 +           */
 +          valueField.setText(isIntegerParameter
 +                  ? String.valueOf(slider.getValue())
 +                  : formatDouble(
 +                          slider.getValue() / (float) sliderScaleFactor));
 +        }
 +        else
 +        {
 +          double value = Math.pow(Math.E,
 +                  slider.getValue() / (double) sliderScaleFactor);
 +          valueField.setText(formatDouble(value));
 +        }
          checkIfModified();
 +      } finally
 +      {
 +        adjusting = false;
        }
      }
  
 -    public void updateControls(ParameterI parm)
 +    /**
 +     * Answers the value formatted as a string to 3 decimal places - in
 +     * scientific notation if the value is less than 0.001
 +     * 
 +     * @param value
 +     * @return
 +     */
 +    String formatDouble(double value)
 +    {
 +      String format = value < 0.001 ? "%3.1E" : "%3.3f";
 +      return String.format(format, value);
 +    }
 +
 +    /**
 +     * Formats a number as integer or float (3dp) or scientific notation (1dp)
 +     * 
 +     * @param n
 +     * @return
 +     */
 +    String formatNumber(Number n)
 +    {
 +      return n instanceof Integer ? String.valueOf(n.intValue())
 +              : formatDouble(n.doubleValue());
 +    }
 +
 +    void updateControls(ParameterI parm)
      {
        adjusting = true;
 -      boolean init = (choicebox == null && valueField == null);
 +      boolean init = (choicebox == null && valueField == null
 +              && buttonGroup == null);
        if (init)
        {
 -        if (choice)
 +        if (parm instanceof RadioChoiceParameter)
 +        {
 +          buttonGroup = addRadioButtons(parameter, controlsPanel);
 +        }
 +        else if (isChoiceParameter)
          {
 -          choicebox = new JComboBox<>();
 +          choicebox = buildComboBox(parm);
            choicebox.addActionListener(this);
 -          controlPanel.add(choicebox, BorderLayout.CENTER);
 +          controlsPanel.add(choicebox, BorderLayout.CENTER);
          }
          else
          {
 -          valueField = new JTextField();
 +          slider = new JSlider();
 +          slider.addChangeListener(this);
 +          int cols = parm instanceof StringParameter ? 20 : 0;
 +          valueField = new JTextField(cols);
            valueField.addActionListener(this);
 -          valueField.addKeyListener(new KeyListener()
 +          valueField.addKeyListener(new KeyAdapter()
            {
 -            @Override
 -            public void keyTyped(KeyEvent e)
 -            {
 -            }
              @Override
              public void keyReleased(KeyEvent e)
              {
 -              if (e.isActionKey())
 +              int keyCode = e.getKeyCode();
 +              if (e.isActionKey() && keyCode != KeyEvent.VK_LEFT
 +                      && keyCode != KeyEvent.VK_RIGHT)
                {
                  if (valueField.getText().trim().length() > 0)
                  {
                  }
                }
              }
 -            @Override
 -            public void keyPressed(KeyEvent e)
 -            {
 -            }
+           });
+           valueField.addFocusListener(new FocusAdapter() {
+             @Override
+             public void focusLost(FocusEvent e)
+             {
+               actionPerformed(null);
+             }
+             
            });
 -          valueField.setPreferredSize(new Dimension(60, 25));
 -          valueField.setText(parm.getValue());
 -          slider = makeSlider(parm.getValidValue());
 -          updateSliderFromValueField();
 -          slider.addChangeListener(this);
 -
 -          controlPanel.add(slider, BorderLayout.WEST);
 -          controlPanel.add(valueField, BorderLayout.EAST);
 +          valueField.setPreferredSize(new Dimension(65, 25));
 +          if (parm instanceof FileParameter)
 +          {
 +            valueField.setToolTipText(MessageManager
 +                    .getString("label.double_click_to_browse"));
 +            valueField.addMouseListener(new MouseAdapter()
 +            {
 +              @Override
 +              public void mouseClicked(MouseEvent e)
 +              {
 +                if (e.getClickCount() == 2)
 +                {
 +                  String dir = Cache.getProperty("LAST_DIRECTORY");
 +                  JalviewFileChooser chooser = new JalviewFileChooser(dir);
 +                  chooser.setFileView(new JalviewFileView());
 +                  chooser.setDialogTitle(
 +                          MessageManager.getString("action.select_ddbb"));
 +
 +                  int val = chooser.showOpenDialog(ParamBox.this);
 +                  if (val == JalviewFileChooser.APPROVE_OPTION)
 +                  {
 +                    File choice = chooser.getSelectedFile();
 +                    String path = choice.getPath();
 +                    valueField.setText(path);
 +                    Cache.setProperty("LAST_DIRECTORY", choice.getParent());
 +                    FileLoader.updateRecentlyOpened(path,
 +                            DataSourceType.FILE);
 +                  }
 +                }
 +              }
 +            });
 +          }
 +          
 +          controlsPanel.add(slider, BorderLayout.WEST);
 +          controlsPanel.add(valueField, BorderLayout.EAST);
          }
        }
  
 -      if (parm != null)
 +      String value = parm.getValue();
 +      if (value != null)
        {
 -        if (choice)
 +        if (isChoiceParameter)
          {
 -          if (init)
 +          if (!(parm instanceof RadioChoiceParameter))
            {
 -            List<String> vals = parm.getPossibleValues();
 -            for (String val : vals)
 -            {
 -              choicebox.addItem(val);
 -            }
 -          }
 -
 -          if (parm.getValue() != null)
 -          {
 -            choicebox.setSelectedItem(parm.getValue());
 +            choicebox.setSelectedItem(value);
            }
          }
          else
          {
 -          valueField.setText(parm.getValue());
 +          valueField.setText(value);
          }
        }
 -      lastVal = getCurrentValue();
 +      lastVal = updateSliderFromValueField();
        adjusting = false;
      }
  
 -    private Slider makeSlider(ValueConstrainI validValue)
 +    /**
 +     * Adds a panel to comp, containing a label and radio buttons for the choice
 +     * of values of the given option. Returns a ButtonGroup whose members are
 +     * the added radio buttons.
 +     * 
 +     * @param option
 +     * @param comp
 +     * 
 +     * @return
 +     */
 +    protected ButtonGroup addRadioButtons(OptionI option, Container comp)
      {
 -      if (validValue != null)
 +      ButtonGroup bg = new ButtonGroup();
 +      JPanel radioPanel = new JPanel();
 +      radioPanel.add(new JLabel(option.getDescription()));
 +
 +      String value = option.getValue();
 +
 +      for (String opt : option.getPossibleValues())
        {
 -        final Number minValue = validValue.getMin();
 -        final Number maxValue = validValue.getMax();
 -        if (minValue != null && maxValue != null)
 -        {
 -          return new Slider(minValue.floatValue(), maxValue.floatValue(),
 -                  minValue.floatValue());
 -        }
 +        JRadioButton btn = new JRadioButton(opt);
 +        btn.setActionCommand(opt);
 +        boolean selected = opt.equals(value);
 +        btn.setSelected(selected);
 +        btn.addActionListener(this);
 +        bg.add(btn);
 +        radioPanel.add(btn);
        }
 +      comp.add(radioPanel);
  
 -      /*
 -       * otherwise, a nominal slider which will not be visible
 -       */
 -      return new Slider(0, 100, 50);
 +      return bg;
      }
  
 -    public void updateSliderFromValueField()
 +    /**
 +     * Action depends on the type of the input parameter:
 +     * <ul>
 +     * <li>if a text input, returns the trimmed value</li>
 +     * <li>if a choice list or radio button, returns the selected value</li>
 +     * <li>if a value slider and input field, sets the value of the slider from
 +     * the value in the text field, limiting it to any defined min-max
 +     * range.</li>
 +     * </ul>
 +     * Answers the (possibly modified) input value, as a String, Integer, Float
 +     * or Double.
 +     * 
 +     * @return
 +     */
 +    Object updateSliderFromValueField()
      {
 -      if (validator != null)
 +      if (validator == null || isStringParameter)
        {
 -        final Number minValue = validator.getMin();
 -        final Number maxValue = validator.getMax();
 -        if (integ)
 +        if (isChoiceParameter)
          {
 -          int iVal = 0;
 -          try
 +          if (parameter instanceof RadioChoiceParameter)
            {
 -            valueField.setText(valueField.getText().trim());
 -            iVal = Integer.valueOf(valueField.getText());
 -            if (minValue != null
 -                    && minValue.intValue() > iVal)
 -            {
 -              iVal = minValue.intValue();
 -              // TODO: provide visual indication that hard limit was reached for
 -              // this parameter
 -            }
 -            if (maxValue != null && maxValue.intValue() < iVal)
 -            {
 -              iVal = maxValue.intValue();
 -            }
 -          } catch (NumberFormatException e)
 -          {
 -            System.err.println(e.toString());
 -          }
 -          if (minValue != null || maxValue != null)
 -          {
 -            valueField.setText(String.valueOf(iVal));
 -            slider.setSliderValue(iVal);
 +            return buttonGroup.getSelection().getActionCommand();
            }
            else
            {
 -            slider.setVisible(false);
 +            return getSelectedValue(this.parameter,
 +                    choicebox.getSelectedIndex());
            }
          }
 +        slider.setVisible(false);
 +        return valueField.getText().trim();
 +      }
 +
 +      if (validator.getMin() == null || validator.getMax() == null)
 +      {
 +        slider.setVisible(false);
 +      }
 +
 +      valueField.setText(valueField.getText().trim());
 +
 +      /*
 +       * ensure not outside min-max range
 +       * TODO: provide some visual indicator if limit reached
 +       */
 +      try
 +      {
 +        valueField.setBackground(Color.WHITE);
 +        double d = Double.parseDouble(valueField.getText());
 +        if (validator.getMin() != null
 +                && validator.getMin().doubleValue() > d)
 +        {
 +          valueField.setText(formatNumber(validator.getMin()));
 +        }
 +        if (validator.getMax() != null
 +                && validator.getMax().doubleValue() < d)
 +        {
 +          valueField.setText(formatNumber(validator.getMax()));
 +        }
 +      } catch (NumberFormatException e)
 +      {
 +        valueField.setBackground(Color.yellow);
 +        return Float.NaN;
 +      }
 +      if (isIntegerParameter)
 +      {
 +        int iVal = 0;
 +        try
 +        {
 +          iVal = Integer.valueOf(valueField.getText());
 +        } catch (Exception e)
 +        {
 +          valueField.setBackground(Color.yellow);
 +          return Integer.valueOf(0);
 +        }
 +
 +        if (validator.getMin() != null && validator.getMax() != null)
 +        {
 +          slider.getModel().setRangeProperties(iVal, 1,
 +                  validator.getMin().intValue(),
 +                  validator.getMax().intValue() + 1, true);
 +        }
          else
          {
 -          float fVal = 0f;
 -          try
 -          {
 -            valueField.setText(valueField.getText().trim());
 -            fVal = Float.valueOf(valueField.getText());
 -            if (minValue != null
 -                    && minValue.floatValue() > fVal)
 -            {
 -              fVal = minValue.floatValue();
 -              // TODO: provide visual indication that hard limit was reached for
 -              // this parameter
 -              // update value field to reflect any bound checking we performed.
 -              valueField.setText("" + fVal);
 -            }
 -            if (maxValue != null
 -                    && maxValue.floatValue() < fVal)
 -            {
 -              fVal = maxValue.floatValue();
 -              // TODO: provide visual indication that hard limit was reached for
 -              // this parameter
 -              // update value field to reflect any bound checking we performed.
 -              valueField.setText("" + fVal);
 -            }
 -          } catch (NumberFormatException e)
 -          {
 -            System.err.println(e.toString());
 -          }
 -          if (minValue != null && maxValue != null)
 -          {
 -            slider.setSliderModel(minValue.floatValue(),
 -                    maxValue.floatValue(), fVal);
 -          }
 -          else
 -          {
 -            slider.setVisible(false);
 -          }
 +          slider.setVisible(false);
          }
 +        return Integer.valueOf(iVal);
        }
 -      else
 +
 +      if (isLogarithmicParameter)
        {
 -        if (!choice)
 +        double dVal = 0d;
 +        try
 +        {
 +          double eValue = Double.valueOf(valueField.getText());
 +          dVal = Math.log(eValue);
 +        } catch (Exception e)
 +        {
 +          // shouldn't be possible here
 +          valueField.setBackground(Color.yellow);
 +          return Double.NaN;
 +        }
 +        if (validator.getMin() != null && validator.getMax() != null)
 +        {
 +          double scaleMin = Math.log(validator.getMin().doubleValue())
 +                  * sliderScaleFactor;
 +          double scaleMax = Math.log(validator.getMax().doubleValue())
 +                  * sliderScaleFactor;
 +          slider.getModel().setRangeProperties(
 +                  (int) (sliderScaleFactor * dVal), 1,
 +                  (int) scaleMin, 1 + (int) scaleMax, true);
 +        }
 +        else
          {
            slider.setVisible(false);
          }
 +        return Double.valueOf(dVal);
        }
 +
 +      float fVal = 0f;
 +      try
 +      {
 +        fVal = Float.valueOf(valueField.getText());
 +      } catch (Exception e)
 +      {
 +        return Float.valueOf(0f); // shouldn't happen
 +      }
 +      if (validator.getMin() != null && validator.getMax() != null)
 +      {
 +        float scaleMin = validator.getMin().floatValue()
 +                * sliderScaleFactor;
 +        float scaleMax = validator.getMax().floatValue()
 +                * sliderScaleFactor;
 +        slider.getModel().setRangeProperties(
 +                (int) (fVal * sliderScaleFactor), 1, (int) scaleMin,
 +                1 + (int) scaleMax, true);
 +      }
 +      else
 +      {
 +        slider.setVisible(false);
 +      }
 +      return Float.valueOf(fVal);
      }
    }
  
 -  public static final int PARAM_WIDTH = 340;
 -
 -  public static final int PARAM_HEIGHT = 150;
 -
 -  public static final int PARAM_CLOSEDHEIGHT = 80;
 -
 -  public OptsAndParamsPage(OptsParametersContainerI paramContainer)
 -  {
 -    this(paramContainer, false);
 -  }
 +  /**
 +   * Constructor with the option to show 'compact' format (parameter description
 +   * as tooltip) or 'expanded' format (parameter description in a textbox which
 +   * may be opened or closed). Use compact for simple description text, expanded
 +   * for more wordy or formatted text.
 +   * 
 +   * @param paramContainer
 +   */
    public OptsAndParamsPage(OptsParametersContainerI paramContainer,
            boolean compact)
    {
      mnu.show(invoker, x, y);
    }
  
 -  URL linkImageURL = getClass().getResource("/images/link.gif");
 -
 -  Map<String, OptionBox> optSet = new java.util.LinkedHashMap<>();
 -
 -  Map<String, ParamBox> paramSet = new java.util.LinkedHashMap<>();
    public Map<String, OptionBox> getOptSet()
    {
      return optSet;
      this.paramSet = paramSet;
    }
  
 -  OptsParametersContainerI poparent;
    OptionBox addOption(OptionI opt)
    {
      OptionBox cb = optSet.get(opt.getName());
        }
        else
        {
-         throw new Error(String.format("Invalid value '%s' for option '%s'",
-                 string, option.getName()));
+         throw new Error(MessageManager.formatMessage(
+                 "error.invalid_value_for_option", new String[]
+                 { string, option.getName() }));
        }
      }
      if (option.isRequired() && !cb.enabled.isSelected())
      {
    }
  
    /**
 -   * recover options and parameters from GUI
 +   * Answers a list of arguments representing all the options and arguments
 +   * selected on the dialog, holding their chosen or input values. Optional
 +   * parameters which were not selected are not included.
     * 
     * @return
     */
      List<ArgumentI> argSet = new ArrayList<>();
      for (OptionBox opts : getOptSet().values())
      {
 -      OptionI opt = opts.getOptionIfEnabled();
 +      ArgumentI opt = opts.getSelectedOption();
        if (opt != null)
        {
          argSet.add(opt);
      }
      for (ParamBox parambox : getParamSet().values())
      {
 -      ParameterI parm = parambox.getParameter();
 +      ArgumentI parm = parambox.getParameter();
        if (parm != null)
        {
          argSet.add(parm);
      return argSet;
    }
  
 +  /**
 +   * A helper method that constructs and returns a CombBox for choice of the
 +   * possible option values. If display names are provided, then these are added
 +   * as options, otherwise the actual values are added.
 +   * 
 +   * @param opt
 +   * @return
 +   */
 +  protected static JComboBox<Object> buildComboBox(OptionI opt)
 +  {
 +    JComboBox<Object> cb = null;
 +    List<String> displayNames = opt.getDisplayNames();
 +    if (displayNames != null)
 +    {
 +      List<Object> displayNamesObjects = new ArrayList<>();
 +      displayNamesObjects.addAll(displayNames);
 +      cb = JvSwingUtils.buildComboWithTooltips(displayNamesObjects,
 +              opt.getPossibleValues());
 +    }
 +    else
 +    {
 +      cb = new JComboBox<>();
 +      for (String v : opt.getPossibleValues())
 +      {
 +        cb.addItem(v);
 +      }
 +    }
 +    return cb;
 +  }
 +
 +  /**
 +   * Answers the value corresponding to the selected item in the choice combo
 +   * box. Note that this returns the underlying value even if a different
 +   * display name is used in the combo box.
 +   * 
 +   * @return
 +   */
 +  protected static String getSelectedValue(OptionI opt, int sel)
 +  {
 +    List<String> possibleValues = opt.getPossibleValues();
 +    String value = null;
 +    if (possibleValues != null && possibleValues.size() == 1)
 +    {
 +      // Hack to make sure the default value for an enabled option with only
 +      // one value is actually returned even if this.val is not displayed
 +      value = possibleValues.get(0);
 +    }
 +    else if (sel >= 0 && sel < possibleValues.size())
 +    {
 +      value = possibleValues.get(sel);
 +    }
 +    return value;
 +  }
  }
@@@ -36,7 -36,6 +36,7 @@@ import jalview.jbgui.GPCAPanel
  import jalview.math.RotatableMatrix.Axis;
  import jalview.util.ImageMaker;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  import jalview.viewmodel.AlignmentViewport;
  import jalview.viewmodel.PCAModel;
  
@@@ -141,6 -140,12 +141,12 @@@ public class PCAPanel extends GPCAPane
    protected void close_actionPerformed()
    {
      setPcaModel(null);
+     if (this.rc != null)
+     {
+       this.rc.sequencePoints = null;
+       this.rc.setAxisEndPoints(null);
+       this.rc = null;
+     }
    }
  
    @Override
      repaint();
      if (getParent() == null)
      {
 -      Desktop.addInternalFrame(this,
 -              MessageManager.formatMessage("label.calc_title", "PCA",
 -                      getPcaModel().getScoreModelName()),
 -              475, 450);
 +      addToDesktop(this, getPcaModel().getScoreModelName());
        this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
      }
      working = false;
      // // setMenusForViewport();
      // validate();
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    progressBar.removeProgressBar(id);
 +  }
  
    @Override
    public void registerHandler(final long id,
      getRotatableCanvas().ap = panel;
      PaintRefresher.Register(PCAPanel.this, panel.av.getSequenceSetId());
    }
 +
 +  public static void addToDesktop(PCAPanel panel, String modelName)
 +  {
 +    Dimension dim = Platform.getDimIfEmbedded(panel, 475, 450);
 +    Desktop.addInternalFrame(panel, MessageManager.formatMessage(
 +            "label.calc_title", "PCA", modelName), dim.width,
 +            dim.height);
 +  }
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.gui;
  
+ import java.util.Locale;
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.event.ActionEvent;
@@@ -64,13 -66,11 +66,13 @@@ import jalview.datamodel.DBRefEntry
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.MappedFeatures;
  import jalview.datamodel.PDBEntry;
 +import jalview.datamodel.ResidueCount;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
  import jalview.gui.ColourMenuHelper.ColourChangeListener;
  import jalview.gui.JalviewColourChooser.ColourChooserListener;
 +import jalview.io.CountReader;
  import jalview.io.FileFormatI;
  import jalview.io.FileFormats;
  import jalview.io.FormatAdapter;
@@@ -89,9 -89,6 +91,9 @@@ import jalview.util.StringUtils
  import jalview.util.UrlLink;
  import jalview.viewmodel.seqfeatures.FeatureRendererModel;
  
 +import java.io.IOException;
 +import java.net.MalformedURLException;
 +
  /**
   * The popup menu that is displayed on right-click on a sequence id, or in the
   * sequence alignment.
@@@ -306,7 -303,7 +308,7 @@@ public class PopupMenu extends JPopupMe
        jalview.util.BrowserLauncher.openURL(url);
      } catch (Exception ex)
      {
 -      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +      JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                MessageManager.getString("label.web_browser_not_found_unix"),
                MessageManager.getString("label.web_browser_not_found"),
                JvOptionPane.WARNING_MESSAGE);
          }
        }
  
 +      if (seq.hasHMMProfile())
 +      {
 +        menuItem = new JMenuItem(MessageManager
 +                .getString("action.add_background_frequencies"));
 +        menuItem.addActionListener(new ActionListener()
 +        {
 +          @Override
 +          public void actionPerformed(ActionEvent e)
 +          {
 +            try
 +            {
 +              ResidueCount counts = CountReader.getBackgroundFrequencies(ap,
 +                      seq);
 +              if (counts != null)
 +              {
 +                seq.getHMM().setBackgroundFrequencies(counts);
 +                ap.alignFrame.buildColourMenu();
 +              }
 +            } catch (MalformedURLException e1)
 +            {
 +              e1.printStackTrace();
 +            } catch (IOException e1)
 +            {
 +              e1.printStackTrace();
 +            }
 +          }
 +        });
 +        add(menuItem);
 +      }
 +
        menuItem = new JMenuItem(
                MessageManager.getString("action.hide_sequences"));
        menuItem.addActionListener(new ActionListener()
          buildGroupURLMenu(sg, groupLinks);
        }
        // Add a 'show all structures' for the current selection
 -      Hashtable<String, PDBEntry> pdbe = new Hashtable<>(), reppdb = new Hashtable<>();
 +      Hashtable<String, PDBEntry> pdbe = new Hashtable<>();
 +      Hashtable<String, PDBEntry> reppdb = new Hashtable<>();
  
        SequenceI sqass = null;
        for (SequenceI sq : alignPanel.av.getSequenceSelection())
          for (int d = 0; d < nd; d++)
          {
            DBRefEntry e = dbr.get(d);
-           String src = e.getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase();
+           String src = e.getSource(); // jalview.util.DBRefUtils.getCanonicalName(dbr[d].getSource()).toUpperCase(Locale.ROOT);
            Object[] sarray = commonDbrefs.get(src);
            if (sarray == null)
            {
        boolean usingNames = false;
        // Now see which parts of the group apply for this URL
        String ltarget = urlLink.getTarget(); // jalview.util.DBRefUtils.getCanonicalName(urlLink.getTarget());
-       Object[] idset = commonDbrefs.get(ltarget.toUpperCase());
+       Object[] idset = commonDbrefs.get(ltarget.toUpperCase(Locale.ROOT));
        String[] seqstr, ids; // input to makeUrl
        if (idset != null)
        {
        menuItem.setEnabled(true);
        for (String calcId : tipEntries.keySet())
        {
 -        tooltip.append("<br/>" + calcId + "/" + tipEntries.get(calcId));
 +        tooltip.append("<br>" + calcId + "/" + tipEntries.get(calcId));
        }
        String tooltipText = JvSwingUtils.wrapTooltip(true,
                tooltip.toString());
                    ap.paintAlignment(false, false);
                  }
                  sequence.setDescription(dialog.getDescription());
 -                ap.av.firePropertyChange("alignment", null,
 -                        ap.av.getAlignment().getSequences());
 +                ap.av.notifyAlignment();
                }
              });
    }
          refresh();
        }
      };
 -    JalviewColourChooser.showColourChooser(Desktop.getDesktop(),
 +    JalviewColourChooser.showColourChooser(Desktop.getDesktopPane(),
              title, Color.BLUE, listener);
    }
  
                startEnd, caseChange);
  
        ap.alignFrame.addHistoryItem(caseCommand);
 +      ap.av.notifyAlignment();
  
 -      ap.av.firePropertyChange("alignment", null,
 -              ap.av.getAlignment().getSequences());
  
      }
    }
                            sg.getStartRes(), sg.getEndRes() + 1,
                            ap.av.getAlignment());
                    ap.alignFrame.addHistoryItem(editCommand);
 -                  ap.av.firePropertyChange("alignment", null,
 -                          ap.av.getAlignment().getSequences());
 +                  ap.av.notifyAlignment();
                  }
                });
      }
@@@ -20,6 -20,6 +20,7 @@@
   */
  package jalview.gui;
  
++
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
@@@ -27,19 -27,17 +28,20 @@@ import java.awt.Dimension
  import java.awt.Font;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 +import java.awt.event.FocusAdapter;
 +import java.awt.event.FocusEvent;
  import java.awt.event.MouseEvent;
  import java.io.File;
  import java.util.ArrayList;
  import java.util.List;
+ import java.util.concurrent.CompletableFuture;
  
  import javax.help.HelpSetException;
  import javax.swing.JComboBox;
  import javax.swing.JFileChooser;
  import javax.swing.JInternalFrame;
  import javax.swing.JPanel;
 +import javax.swing.JTextField;
  import javax.swing.ListSelectionModel;
  import javax.swing.RowFilter;
  import javax.swing.RowSorter;
@@@ -53,12 -51,10 +55,12 @@@ import javax.swing.table.TableColumn
  import javax.swing.table.TableModel;
  import javax.swing.table.TableRowSorter;
  
 +import jalview.hmmer.HmmerCommand;
 +import jalview.util.FileUtils;
  import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
  import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
  import jalview.bin.Cache;
+ import jalview.ext.pymol.PymolManager;
  import jalview.gui.Help.HelpId;
  import jalview.gui.StructureViewer.ViewerType;
  import jalview.io.BackupFiles;
@@@ -86,160 -82,60 +88,164 @@@ import jalview.ws.sifts.SiftsSettings
   * @author $author$
   * @version $Revision$
   */
- public class Preferences extends GPreferences
 -/*
 - * for merge with Jalview-JS
 - public class Preferences extends GPreferences implements ApplicationSingletonI
 - */
 -public class Preferences extends GPreferences
 -{
 -  public static final String ENABLE_SPLIT_FRAME = "ENABLE_SPLIT_FRAME";
 -  public static final String SCALE_PROTEIN_TO_CDNA = "SCALE_PROTEIN_TO_CDNA";
++public class Preferences extends GPreferences implements ApplicationSingletonI
 +{
 +  // suggested list delimiter character
 +  public static final String COMMA = ",";
  
 -  public static final String DEFAULT_COLOUR = "DEFAULT_COLOUR";
 +  public static final String HMMSEARCH_SEQCOUNT = "HMMSEARCH_SEQCOUNT";
  
 -  public static final String DEFAULT_COLOUR_PROT = "DEFAULT_COLOUR_PROT";
 +  public static final String HMMINFO_GLOBAL_BACKGROUND = "HMMINFO_GLOBAL_BACKGROUND";
  
 -  public static final String DEFAULT_COLOUR_NUC = "DEFAULT_COLOUR_NUC";
 +  public static final String HMMALIGN_TRIM_TERMINI = "HMMALIGN_TRIM_TERMINI";
 +  
 +  public static final String ADD_SS_ANN = "ADD_SS_ANN";
  
    public static final String ADD_TEMPFACT_ANN = "ADD_TEMPFACT_ANN";
  
 -  public static final String ADD_SS_ANN = "ADD_SS_ANN";
 +  public static final String ALLOW_UNPUBLISHED_PDB_QUERYING = "ALLOW_UNPUBLISHED_PDB_QUERYING";
  
 -  public static final String USE_RNAVIEW = "USE_RNAVIEW";
 +  public static final String ANNOTATIONCOLOUR_MAX = "ANNOTATIONCOLOUR_MAX";
  
 -  public static final String STRUCT_FROM_PDB = "STRUCT_FROM_PDB";
 +  public static final String ANNOTATIONCOLOUR_MIN = "ANNOTATIONCOLOUR_MIN";
  
 -  public static final String STRUCTURE_DISPLAY = "STRUCTURE_DISPLAY";
 +  public static final String ANTI_ALIAS = "ANTI_ALIAS";
 +
 +  public static final String AUTO_CALC_CONSENSUS = "AUTO_CALC_CONSENSUS";
 +
 +  public static final String AUTOASSOCIATE_PDBANDSEQS = "AUTOASSOCIATE_PDBANDSEQS";
 +
 +  public static final String BLOSUM62_PCA_FOR_NUCLEOTIDE = "BLOSUM62_PCA_FOR_NUCLEOTIDE";
 +
 +  public static final String CENTRE_COLUMN_LABELS = "CENTRE_COLUMN_LABELS";
  
    public static final String CHIMERA_PATH = "CHIMERA_PATH";
  
+   public static final String CHIMERAX_PATH = "CHIMERAX_PATH";
 +  public static final String DBREFFETCH_USEPICR = "DBREFFETCH_USEPICR";
 +
 +  public static final String DEFAULT_COLOUR = "DEFAULT_COLOUR";
 +
 +  public static final String DEFAULT_COLOUR_NUC = "DEFAULT_COLOUR_NUC";
 +  public static final String DEFAULT_COLOUR_PROT = "DEFAULT_COLOUR_PROT";
 +
 +  public static final String ENABLE_SPLIT_FRAME = "ENABLE_SPLIT_FRAME";
 +
 +  public static final String FIGURE_AUTOIDWIDTH = "FIGURE_AUTOIDWIDTH";
 +
 +  public static final String FIGURE_FIXEDIDWIDTH = "FIGURE_FIXEDIDWIDTH";
 +
 +  public static final String FOLLOW_SELECTIONS = "FOLLOW_SELECTIONS";
 +
 +  public static final String FONT_NAME = "FONT_NAME";
 +
 +  public static final String FONT_SIZE = "FONT_SIZE";
 +
 +  public static final String FONT_STYLE = "FONT_STYLE";
 +  
 +  public static final String HMMER_PATH = "HMMER_PATH";
 +
 +  public static final String CYGWIN_PATH = "CYGWIN_PATH";
 +
 +  public static final String HMMSEARCH_DBS = "HMMSEARCH_DBS";
 +
 +  public static final String GAP_COLOUR = "GAP_COLOUR";
 +
 +  public static final String GAP_SYMBOL = "GAP_SYMBOL";
 +
 +  public static final String HIDDEN_COLOUR = "HIDDEN_COLOUR";
 +
 +  public static final String HIDE_INTRONS = "HIDE_INTRONS";
 +
 +  public static final String ID_ITALICS = "ID_ITALICS";
 +
 +  public static final String ID_ORG_HOSTURL = "ID_ORG_HOSTURL";
 +
 +  public static final String MAP_WITH_SIFTS = "MAP_WITH_SIFTS";
 +
 +  public static final String NOQUESTIONNAIRES = "NOQUESTIONNAIRES";
 +
 +  public static final String NORMALISE_CONSENSUS_LOGO = "NORMALISE_CONSENSUS_LOGO";
 +
 +  public static final String NORMALISE_LOGO = "NORMALISE_LOGO";
 +
 +  public static final String PAD_GAPS = "PAD_GAPS";
 +
 +  public static final String PDB_DOWNLOAD_FORMAT = "PDB_DOWNLOAD_FORMAT";
 +
+   public static final String PYMOL_PATH = "PYMOL_PATH";
 -  public static final String SORT_ANNOTATIONS = "SORT_ANNOTATIONS";
 +  public static final String QUESTIONNAIRE = "QUESTIONNAIRE";
 +
 +  public static final String RELAXEDSEQIDMATCHING = "RELAXEDSEQIDMATCHING";
 +
 +  public static final String RIGHT_ALIGN_IDS = "RIGHT_ALIGN_IDS";
 +
 +  public static final String SCALE_PROTEIN_TO_CDNA = "SCALE_PROTEIN_TO_CDNA";
 +
 +  public static final String SHOW_ANNOTATIONS = "SHOW_ANNOTATIONS";
  
    public static final String SHOW_AUTOCALC_ABOVE = "SHOW_AUTOCALC_ABOVE";
  
 +  public static final String SHOW_CONSENSUS = "SHOW_CONSENSUS";
 +
 +  public static final String SHOW_CONSENSUS_HISTOGRAM = "SHOW_CONSENSUS_HISTOGRAM";
 +
 +  public static final String SHOW_CONSENSUS_LOGO = "SHOW_CONSENSUS_LOGO";
 +
 +  public static final String SHOW_CONSERVATION = "SHOW_CONSERVATION";
 +
 +  public static final String SHOW_DBREFS_TOOLTIP = "SHOW_DBREFS_TOOLTIP";
 +
 +  public static final String SHOW_GROUP_CONSENSUS = "SHOW_GROUP_CONSENSUS";
 +
 +  public static final String SHOW_GROUP_CONSERVATION = "SHOW_GROUP_CONSERVATION";
 +
 +  public static final String SHOW_JVSUFFIX = "SHOW_JVSUFFIX";
 +
 +  public static final String SHOW_NPFEATS_TOOLTIP = "SHOW_NPFEATS_TOOLTIP";
    public static final String SHOW_OCCUPANCY = "SHOW_OCCUPANCY";
  
    public static final String SHOW_OV_HIDDEN_AT_START = "SHOW_OV_HIDDEN_AT_START";
  
 +  public static final String SHOW_OVERVIEW = "SHOW_OVERVIEW";
 +
 +  public static final String SHOW_QUALITY = "SHOW_QUALITY";
 +
 +  public static final String SHOW_UNCONSERVED = "SHOW_UNCONSERVED";
 +
 +  public static final String SORT_ALIGNMENT = "SORT_ALIGNMENT";
 +
 +  public static final String SORT_ANNOTATIONS = "SORT_ANNOTATIONS";
 +
 +  public static final String SORT_BY_TREE = "SORT_BY_TREE";
 +
 +  public static final String STRUCT_FROM_PDB = "STRUCT_FROM_PDB";
 +
 +  public static final String STRUCTURE_DISPLAY = "STRUCTURE_DISPLAY";
 +
 +  public static final String STRUCTURE_DIMENSIONS = "STRUCTURE_DIMENSIONS";
 +
 +  public static final String UNIPROT_DOMAIN = "UNIPROT_DOMAIN";
 +
 +  public static final String USE_FULL_SO = "USE_FULL_SO";
    public static final String USE_LEGACY_GAP = "USE_LEGACY_GAP";
  
 -  public static final String GAP_COLOUR = "GAP_COLOUR";
 +  public static final String USE_RNAVIEW = "USE_RNAVIEW";
  
 -  public static final String HIDDEN_COLOUR = "HIDDEN_COLOUR";
 +  public static final String USER_DEFINED_COLOURS = "USER_DEFINED_COLOURS";
 +
 +  public static final String WRAP_ALIGNMENT = "WRAP_ALIGNMENT";
  
    private static final int MIN_FONT_SIZE = 1;
  
    private static final int MAX_FONT_SIZE = 30;
  
+   private String previousProxyType;
+   private static Preferences INSTANCE = null; // add "final"
 -
    /**
     * Holds name and link separated with | character. Sequence ID must be
     * $SEQUENCE_ID$ or $SEQUENCE_ID=/.possible | chars ./=$
  
    /**
     * Holds name and link separated with | character. Sequence IDS and Sequences
-    * must be $SEQUENCEIDS$ or $SEQUENCEIDS=/.possible | chars ./=$ and
-    * $SEQUENCES$ or $SEQUENCES=/.possible | chars ./=$ and separation character
-    * for first and second token specified after a pipe character at end |,|.
-    * (TODO: proper escape for using | to separate ids or sequences
+    * must be $SEQUENCEIDS$ or $SEQUENCEIDS=/.possible | chars ./=$ and $SEQUENCES$
+    * or $SEQUENCES=/.possible | chars ./=$ and separation character for first and
+    * second token specified after a pipe character at end |,|. (TODO: proper
+    * escape for using | to separate ids or sequences
     */
  
    public static List<String> groupURLLinks;
  
    private WsPreferences wsPrefs;
  
 +  private SlivkaPreferences slivkaPrefs;
    private OptionsParam promptEachTimeOpt = new OptionsParam(
            MessageManager.getString("label.prompt_each_time"),
            "Prompt each time");
    private OptionsParam textOpt = new OptionsParam(
            MessageManager.getString("action.text"), "Text");
  
+   // get singleton Preferences instance
+   public static Preferences getInstance()
+   {
+     if (INSTANCE == null || INSTANCE.frame == null
+             || INSTANCE.frame.isClosed())
+     {
+       INSTANCE = new Preferences();
+     }
+     return INSTANCE;
+     /*
+      * Replace code with the following for Jalvew-JS
+     Preferences INSTANCE = ApplicationSingletonProvider.getInstance(Preferences.class);
+     if (INSTANCE == null || INSTANCE.frame == null
+             || INSTANCE.frame.isClosed())
+     {
+       ApplicationSingletonProvider.remove(Preferences.class);
+       INSTANCE = ApplicationSingletonProvider.getInstance(Preferences.class);
+     }
+     return INSTANCE;
+     */
+   }
+   public static void openPreferences()
+   {
+     openPreferences(null, null);
+   }
+   public static void openPreferences(TabRef selectTab, String message)
+   {
+     Preferences p = getInstance();
+     if (selectTab != null)
+       p.selectTab(selectTab, message);
+     p.frame.show();
+     p.frame.moveToFront();
+     p.frame.grabFocus();
+   }
+   public void selectTab(TabRef selectTab, String message)
+   {
+     this.selectTab(selectTab);
+     if (message != null)
+       this.setMessage(message);
+     this.frame.show();
+   }
    /**
     * Creates a new Preferences object.
     */
-   public Preferences()
+   private Preferences()
    {
      super();
      frame = new JInternalFrame();
      {
        wsPrefs = new WsPreferences();
        wsTab.add(wsPrefs, BorderLayout.CENTER);
 +      slivkaPrefs = new SlivkaPreferences();
 +      slivkaTab.add(slivkaPrefs, BorderLayout.CENTER);
      }
      int width = 500, height = 450;
      if (Platform.isAMacAndNotJS())
      frame.setMinimumSize(new Dimension(width, height));
  
      /*
 +     * Set HMMER tab defaults
 +     */
 +    hmmrTrimTermini.setSelected(Cache.getDefault(HMMALIGN_TRIM_TERMINI, false));
 +    if (Cache.getDefault(HMMINFO_GLOBAL_BACKGROUND, false))
 +    {
 +      hmmerBackgroundUniprot.setSelected(true);
 +    }
 +    else
 +    {
 +      hmmerBackgroundAlignment.setSelected(true);
 +    }
 +    hmmerSequenceCount
 +            .setText(Cache.getProperty(HMMSEARCH_SEQCOUNT));
 +    hmmerPath.setText(Cache.getProperty(HMMER_PATH));
 +    hmmerPath.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent e)
 +      {
 +        validateHmmerPath();
 +      }
 +    });
 +    hmmerPath.addFocusListener(new FocusAdapter()
 +    {
 +      @Override
 +      public void focusLost(FocusEvent e)
 +      {
 +        validateHmmerPath();
 +      }
 +    });
 +    if (cygwinPath != null)
 +    {
 +      String path = Cache.getProperty(CYGWIN_PATH);
 +      if (path == null)
 +      {
 +        path = FileUtils.getPathTo("bash");
 +      }
 +      cygwinPath.setText(path);
 +      cygwinPath.addActionListener(new ActionListener()
 +      {
 +        @Override
 +        public void actionPerformed(ActionEvent e)
 +        {
 +          validateCygwinPath();
 +        }
 +      });
 +      cygwinPath.addFocusListener(new FocusAdapter()
 +      {
 +        @Override
 +        public void focusLost(FocusEvent e)
 +        {
 +          validateCygwinPath();
 +        }
 +      });
 +    }
 +
 +    /*
       * Set Visual tab defaults
       */
      seqLimit.setSelected(Cache.getDefault("SHOW_JVSUFFIX", true));
              Cache.getDefault("SHOW_CONSENSUS_HISTOGRAM", true));
      showConsensLogo
              .setSelected(Cache.getDefault("SHOW_CONSENSUS_LOGO", false));
 +    showInformationHistogram.setSelected(
 +            Cache.getDefault("SHOW_INFORMATION_HISTOGRAM", true));
 +    showHMMLogo.setSelected(Cache.getDefault("SHOW_HMM_LOGO", false));
      showNpTooltip
              .setSelected(Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true));
      showDbRefTooltip
      startupCheckbox
              .setSelected(Cache.getDefault("SHOW_STARTUP_FILE", true));
      startupFileTextfield.setText(Cache.getDefault("STARTUP_FILE",
-             Cache.getDefault("www.jalview.org", "http://www.jalview.org")
-                     + "/examples/exampleFile_2_3.jar"));
+             Cache.getDefault("www.jalview.org", "https://www.jalview.org")
+                     + "/examples/exampleFile_2_7.jvp"));
  
      /*
       * Set Colours tab defaults
      /*
       * Set overview panel defaults
       */
-     gapColour.setBackground(
-             Cache.getDefaultColour(GAP_COLOUR,
-                     jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP));
-     hiddenColour.setBackground(
-             Cache.getDefaultColour(HIDDEN_COLOUR,
-                     jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_HIDDEN));
+     gapColour.setBackground(Cache.getDefaultColour(GAP_COLOUR,
+             jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP));
+     hiddenColour.setBackground(Cache.getDefaultColour(HIDDEN_COLOUR,
+             jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_HIDDEN));
      useLegacyGap.setSelected(Cache.getDefault(USE_LEGACY_GAP, false));
      gapLabel.setEnabled(!useLegacyGap.isSelected());
      gapColour.setEnabled(!useLegacyGap.isSelected());
              .setSelected(Cache.getDefault(SHOW_OV_HIDDEN_AT_START, false));
  
      /*
-      * Set Structure tab defaults.
+      * Set Structure tab defaults
       */
-     final boolean structSelected = Cache.getDefault(STRUCT_FROM_PDB, false);
+     final boolean structSelected = Cache.getDefault(STRUCT_FROM_PDB, true);
      structFromPdb.setSelected(structSelected);
-     useRnaView.setSelected(Cache.getDefault(USE_RNAVIEW, false));
-     useRnaView.setEnabled(structSelected);
-     addSecondaryStructure.setSelected(Cache.getDefault(ADD_SS_ANN, false));
+     addSecondaryStructure.setSelected(Cache.getDefault(ADD_SS_ANN, true));
      addSecondaryStructure.setEnabled(structSelected);
-     addTempFactor.setSelected(Cache.getDefault(ADD_TEMPFACT_ANN, false));
+     addTempFactor.setSelected(Cache.getDefault(ADD_TEMPFACT_ANN, true));
      addTempFactor.setEnabled(structSelected);
 -
+     /*
+      * set choice of structure viewer, and path if saved as a preference;
+      * default to Jmol (first choice) if an unexpected value is found
+      */
 -    String viewerType = Cache.getDefault(STRUCTURE_DISPLAY,
++    String viewerType = ViewerType.JMOL.name();
 +    if (!Platform.isJS())
 +    {
-       structViewer.setSelectedItem(
-             Cache.getDefault(STRUCTURE_DISPLAY, ViewerType.JMOL.name()));
++      Cache.getDefault(STRUCTURE_DISPLAY,
+             ViewerType.JMOL.name());
 +    }
-     chimeraPath.setText(Cache.getDefault(CHIMERA_PATH, ""));
-     chimeraPath.addActionListener(new ActionListener()
++    // TODO - disable external viewers for JS
+     structViewer.setSelectedItem(viewerType);
+     String viewerPath = "";
+     ViewerType type = null;
+     try
+     {
+       type = ViewerType.valueOf(viewerType);
+       switch (type)
+       {
+       case JMOL:
+         break;
+       case CHIMERA:
+         viewerPath = Cache.getDefault(CHIMERA_PATH, "");
+         break;
+       case CHIMERAX:
+         viewerPath = Cache.getDefault(CHIMERAX_PATH, "");
+         break;
+       case PYMOL:
+         viewerPath = Cache.getDefault(PYMOL_PATH, "");
+         break;
+       }
+     } catch (IllegalArgumentException e)
+     {
+       Cache.log.error("Unknown structure viewer type: " + viewerType
+               + ", defaulting to Jmol");
+       type = ViewerType.JMOL;
+     }
+     structureViewerPath.setText(viewerPath);
+     structureViewerPath.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         validateChimeraPath();
+         if (validateViewerPath())
+         {
+           String path = structureViewerPath.getText();
+           try {
+           ViewerType type = ViewerType.valueOf(viewerType);
+           switch (type)
+           {
+           case JMOL:
+             break;
+           case CHIMERA:
+             Cache.setProperty(CHIMERA_PATH, path);
+             break;
+           case CHIMERAX:
+             Cache.setProperty(CHIMERAX_PATH, path);
+             break;
+           case PYMOL:
+             Cache.setProperty(PYMOL_PATH, path);
+             break;
+           }
+         } catch (IllegalArgumentException x)
+         {
+           Cache.log.error("Failed to set path - unknown viewer type",x);
+         }
+         }
        }
      });
  
      doReset.addActionListener(onReset);
  
      // filter to display only custom urls
-     final RowFilter<TableModel, Object> customUrlFilter = new RowFilter<>()
+     final RowFilter<TableModel, Object> customUrlFilter = new RowFilter<TableModel, Object>()
      {
        @Override
        public boolean include(
        }
      }
  
-     useProxy.setSelected(Cache.getDefault("USE_PROXY", false));
-     useProxy_actionPerformed(); // make sure useProxy is correctly initialised
-     proxyServerTB.setText(Cache.getDefault("PROXY_SERVER", ""));
-     proxyPortTB.setText(Cache.getDefault("PROXY_PORT", ""));
+     String proxyTypeString = Cache.getDefault("USE_PROXY", "false");
+     previousProxyType = proxyTypeString;
+     switch (proxyTypeString)
+     {
+     case Cache.PROXYTYPE_NONE:
+       proxyType.setSelected(noProxy.getModel(), true);
+       break;
+     case Cache.PROXYTYPE_SYSTEM:
+       proxyType.setSelected(systemProxy.getModel(), true);
+       break;
+     case Cache.PROXYTYPE_CUSTOM:
+       proxyType.setSelected(customProxy.getModel(), true);
+       break;
+     default:
+       Cache.log.warn(
+               "Incorrect PROXY_TYPE - should be 'none' (clear proxy properties), 'false' (system settings), 'true' (custom settings): "
+                       + proxyTypeString);
+     }
+     proxyServerHttpTB.setText(Cache.getDefault("PROXY_SERVER", ""));
+     proxyPortHttpTB.setText(Cache.getDefault("PROXY_PORT", ""));
+     proxyServerHttpsTB.setText(Cache.getDefault("PROXY_SERVER_HTTPS", ""));
+     proxyPortHttpsTB.setText(Cache.getDefault("PROXY_PORT_HTTPS", ""));
+     proxyAuth.setSelected(Cache.getDefault("PROXY_AUTH", false));
+     proxyAuthUsernameTB
+             .setText(Cache.getDefault("PROXY_AUTH_USERNAME", ""));
+     // we are not storing or retrieving proxy password from .jalview_properties
+     proxyAuthPasswordPB.setText(Cache.proxyAuthPassword == null ? ""
+             : new String(Cache.proxyAuthPassword));
+     setCustomProxyEnabled();
+     applyProxyButtonEnabled(false);
  
      defaultBrowser.setText(Cache.getDefault("DEFAULT_BROWSER", ""));
  
  
      annotations_actionPerformed(null); // update the display of the annotation
                                         // settings
 -
 +    
 +    
      /*
       * Set Backups tab defaults
       */
      comboBox.addItem(promptEachTimeOpt);
      comboBox.addItem(lineArtOpt);
      comboBox.addItem(textOpt);
 -
 +    
      /*
       * JalviewJS doesn't support Lineart so force it to Text
       */
        return;
      }
  
+     /* 
+      * Set proxy settings first (to be before web services refresh)
+      */
+     saveProxySettings();
      /*
       * Save Visual settings
       */
 -    Cache.applicationProperties.setProperty("SHOW_JVSUFFIX",
 +    Cache.setPropertyNoSave("SHOW_JVSUFFIX",
              Boolean.toString(seqLimit.isSelected()));
 -    Cache.applicationProperties.setProperty("RIGHT_ALIGN_IDS",
 +    Cache.setPropertyNoSave("RIGHT_ALIGN_IDS",
              Boolean.toString(rightAlign.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_FULLSCREEN",
 +    Cache.setPropertyNoSave("SHOW_FULLSCREEN",
              Boolean.toString(fullScreen.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_OVERVIEW",
 +    Cache.setPropertyNoSave("SHOW_OVERVIEW",
              Boolean.toString(openoverv.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS",
 +    Cache.setPropertyNoSave("SHOW_ANNOTATIONS",
              Boolean.toString(annotations.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_CONSERVATION",
 +    Cache.setPropertyNoSave("SHOW_CONSERVATION",
              Boolean.toString(conservation.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_QUALITY",
 +    Cache.setPropertyNoSave("SHOW_QUALITY",
              Boolean.toString(quality.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_IDENTITY",
 +    Cache.setPropertyNoSave("SHOW_IDENTITY",
              Boolean.toString(identity.isSelected()));
  
 -    Cache.applicationProperties.setProperty("GAP_SYMBOL",
 +    Cache.setPropertyNoSave("GAP_SYMBOL",
              gapSymbolCB.getSelectedItem().toString());
  
 -    Cache.applicationProperties.setProperty("FONT_NAME",
 +    Cache.setPropertyNoSave("FONT_NAME",
              fontNameCB.getSelectedItem().toString());
 -    Cache.applicationProperties.setProperty("FONT_STYLE",
 +    Cache.setPropertyNoSave("FONT_STYLE",
              fontStyleCB.getSelectedItem().toString());
 -    Cache.applicationProperties.setProperty("FONT_SIZE",
 +    Cache.setPropertyNoSave("FONT_SIZE",
              fontSizeCB.getSelectedItem().toString());
  
 -    Cache.applicationProperties.setProperty("ID_ITALICS",
 +    Cache.setPropertyNoSave("ID_ITALICS",
              Boolean.toString(idItalics.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_UNCONSERVED",
 +    Cache.setPropertyNoSave("SHOW_UNCONSERVED",
              Boolean.toString(showUnconserved.isSelected()));
 -    Cache.applicationProperties.setProperty(SHOW_OCCUPANCY,
 +    Cache.setPropertyNoSave(SHOW_OCCUPANCY,
              Boolean.toString(showOccupancy.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_GROUP_CONSENSUS",
 +    Cache.setPropertyNoSave("SHOW_GROUP_CONSENSUS",
              Boolean.toString(showGroupConsensus.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_GROUP_CONSERVATION",
 +    Cache.setPropertyNoSave("SHOW_GROUP_CONSERVATION",
              Boolean.toString(showGroupConservation.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_CONSENSUS_HISTOGRAM",
 +    Cache.setPropertyNoSave("SHOW_CONSENSUS_HISTOGRAM",
              Boolean.toString(showConsensHistogram.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_CONSENSUS_LOGO",
 +    Cache.setPropertyNoSave("SHOW_CONSENSUS_LOGO",
              Boolean.toString(showConsensLogo.isSelected()));
 -    Cache.applicationProperties.setProperty("ANTI_ALIAS",
 +    Cache.setPropertyNoSave("SHOW_INFORMATION_HISTOGRAM",
 +            Boolean.toString(showConsensHistogram.isSelected()));
 +    Cache.setPropertyNoSave("SHOW_HMM_LOGO",
 +            Boolean.toString(showHMMLogo.isSelected()));
 +    Cache.setPropertyNoSave("ANTI_ALIAS",
              Boolean.toString(smoothFont.isSelected()));
 -    Cache.applicationProperties.setProperty(SCALE_PROTEIN_TO_CDNA,
 +    Cache.setPropertyNoSave(SCALE_PROTEIN_TO_CDNA,
              Boolean.toString(scaleProteinToCdna.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_NPFEATS_TOOLTIP",
 +    Cache.setPropertyNoSave("SHOW_NPFEATS_TOOLTIP",
              Boolean.toString(showNpTooltip.isSelected()));
 -    Cache.applicationProperties.setProperty("SHOW_DBREFS_TOOLTIP",
 +    Cache.setPropertyNoSave("SHOW_DBREFS_TOOLTIP",
              Boolean.toString(showDbRefTooltip.isSelected()));
  
 -    Cache.applicationProperties.setProperty("WRAP_ALIGNMENT",
 +    Cache.setPropertyNoSave("WRAP_ALIGNMENT",
              Boolean.toString(wrap.isSelected()));
  
 -    Cache.applicationProperties.setProperty("STARTUP_FILE",
 +    Cache.setPropertyNoSave("STARTUP_FILE",
              startupFileTextfield.getText());
 -    Cache.applicationProperties.setProperty("SHOW_STARTUP_FILE",
 +    Cache.setPropertyNoSave("SHOW_STARTUP_FILE",
              Boolean.toString(startupCheckbox.isSelected()));
  
 -    Cache.applicationProperties.setProperty("SORT_ALIGNMENT",
 +    Cache.setPropertyNoSave("SORT_ALIGNMENT",
              sortby.getSelectedItem().toString());
  
      // convert description of sort order to enum name for save
              .forDescription(sortAnnBy.getSelectedItem().toString());
      if (annSortOrder != null)
      {
 -      Cache.applicationProperties.setProperty(SORT_ANNOTATIONS,
 +      Cache.setPropertyNoSave(SORT_ANNOTATIONS,
                annSortOrder.name());
      }
  
      final boolean showAutocalcFirst = sortAutocalc.getSelectedIndex() == 0;
 -    Cache.applicationProperties.setProperty(SHOW_AUTOCALC_ABOVE,
 +    Cache.setPropertyNoSave(SHOW_AUTOCALC_ABOVE,
              Boolean.valueOf(showAutocalcFirst).toString());
  
      /*
       * Save Colours settings
       */
 -    Cache.applicationProperties.setProperty(DEFAULT_COLOUR_PROT,
 +    Cache.setPropertyNoSave(DEFAULT_COLOUR_PROT,
              protColour.getSelectedItem().toString());
 -    Cache.applicationProperties.setProperty(DEFAULT_COLOUR_NUC,
 +    Cache.setPropertyNoSave(DEFAULT_COLOUR_NUC,
              nucColour.getSelectedItem().toString());
 -    Cache.setColourProperty("ANNOTATIONCOLOUR_MIN",
 +    Cache.setColourPropertyNoSave("ANNOTATIONCOLOUR_MIN",
              minColour.getBackground());
 -    Cache.setColourProperty("ANNOTATIONCOLOUR_MAX",
 +    Cache.setColourPropertyNoSave("ANNOTATIONCOLOUR_MAX",
              maxColour.getBackground());
  
      /*
 +     * Save HMMER settings
 +     */
 +    Cache.setPropertyNoSave(HMMALIGN_TRIM_TERMINI,
 +            Boolean.toString(hmmrTrimTermini.isSelected()));
 +    Cache.setPropertyNoSave(HMMINFO_GLOBAL_BACKGROUND,
 +            Boolean.toString(hmmerBackgroundUniprot.isSelected()));
 +    Cache.setPropertyNoSave(HMMSEARCH_SEQCOUNT,
 +            hmmerSequenceCount.getText());
 +    Cache.setOrRemove(HMMER_PATH, hmmerPath.getText());
 +    if (cygwinPath != null)
 +    {
 +      Cache.setOrRemove(CYGWIN_PATH, cygwinPath.getText());
 +    }
 +    AlignFrame[] frames = Desktop.getAlignFrames();
 +    if (frames != null && frames.length > 0)
 +    {
 +      for (AlignFrame f : frames)
 +      {
 +        f.updateHMMERStatus();
 +      }
 +    }
 +    
 +    hmmrTrimTermini.setSelected(Cache.getDefault(HMMALIGN_TRIM_TERMINI, false));
 +    if (Cache.getDefault(HMMINFO_GLOBAL_BACKGROUND, false))
 +    {
 +      hmmerBackgroundUniprot.setSelected(true);
 +    }
 +    else
 +    {
 +      hmmerBackgroundAlignment.setSelected(true);
 +    }
 +    hmmerSequenceCount
 +            .setText(Cache.getProperty(HMMSEARCH_SEQCOUNT));
 +    hmmerPath.setText(Cache.getProperty(HMMER_PATH));
 +
 +    /*
       * Save Overview settings
       */
 -    Cache.setColourProperty(GAP_COLOUR, gapColour.getBackground());
 -    Cache.setColourProperty(HIDDEN_COLOUR, hiddenColour.getBackground());
 -    Cache.applicationProperties.setProperty(USE_LEGACY_GAP,
 +    Cache.setColourPropertyNoSave(GAP_COLOUR, gapColour.getBackground());
 +    Cache.setColourPropertyNoSave(HIDDEN_COLOUR, hiddenColour.getBackground());
 +    Cache.setPropertyNoSave(USE_LEGACY_GAP,
              Boolean.toString(useLegacyGap.isSelected()));
 -    Cache.applicationProperties.setProperty(SHOW_OV_HIDDEN_AT_START,
 +    Cache.setPropertyNoSave(SHOW_OV_HIDDEN_AT_START,
              Boolean.toString(showHiddenAtStart.isSelected()));
  
      /*
       * Save Structure settings
       */
 -    Cache.applicationProperties.setProperty(ADD_TEMPFACT_ANN,
 +    Cache.setPropertyNoSave(ADD_TEMPFACT_ANN,
              Boolean.toString(addTempFactor.isSelected()));
 -    Cache.applicationProperties.setProperty(ADD_SS_ANN,
 +    Cache.setPropertyNoSave(ADD_SS_ANN,
              Boolean.toString(addSecondaryStructure.isSelected()));
-     Cache.setPropertyNoSave(USE_RNAVIEW,
-             Boolean.toString(useRnaView.isSelected()));
 -    Cache.applicationProperties.setProperty(STRUCT_FROM_PDB,
 +    Cache.setPropertyNoSave(STRUCT_FROM_PDB,
              Boolean.toString(structFromPdb.isSelected()));
-     if (!Platform.isJS())
++    if (!Platform.isJS()) {
+     String viewer = structViewer.getSelectedItem().toString();
+     String viewerPath = structureViewerPath.getText();
 -    Cache.applicationProperties.setProperty(STRUCTURE_DISPLAY, viewer);
++    Cache.setPropertyNoSave(STRUCTURE_DISPLAY, viewer);
+     if (viewer.equals(ViewerType.CHIMERA.name()))
+     {
+       Cache.setOrRemove(CHIMERA_PATH, viewerPath);
+     }
+     else if (viewer.equals(ViewerType.CHIMERAX.name()))
      {
-       Cache.setPropertyNoSave(STRUCTURE_DISPLAY,
-               structViewer.getSelectedItem().toString());
+       Cache.setOrRemove(CHIMERAX_PATH, viewerPath);
      }
-     Cache.setOrRemove(CHIMERA_PATH, chimeraPath.getText());
+     else if (viewer.equals(ViewerType.PYMOL.name()))
+     {
+       Cache.setOrRemove(PYMOL_PATH, viewerPath);
+     }
 -    Cache.applicationProperties.setProperty("MAP_WITH_SIFTS",
++    } // nojs
 +    Cache.setPropertyNoSave("MAP_WITH_SIFTS",
              Boolean.toString(siftsMapping.isSelected()));
      SiftsSettings.setMapWithSifts(siftsMapping.isSelected());
  
      /*
       * Save Output settings
       */
 -    Cache.applicationProperties.setProperty("EPS_RENDERING",
 +    Cache.setPropertyNoSave("EPS_RENDERING",
              ((OptionsParam) epsRendering.getSelectedItem()).getCode());
 -    Cache.applicationProperties.setProperty("HTML_RENDERING",
 +    Cache.setPropertyNoSave("HTML_RENDERING",
              ((OptionsParam) htmlRendering.getSelectedItem()).getCode());
 -    Cache.applicationProperties.setProperty("SVG_RENDERING",
 +    Cache.setPropertyNoSave("SVG_RENDERING",
              ((OptionsParam) svgRendering.getSelectedItem()).getCode());
  
      /*
       * Save Connections settings
       */
 -    // Proxy settings set first (to catch web services)
 -
++    // Proxy settings were already set first (to catch web services)
      Cache.setOrRemove("DEFAULT_BROWSER", defaultBrowser.getText());
  
      jalview.util.BrowserLauncher.resetBrowser();
      String menuLinks = sequenceUrlLinks.writeUrlsAsString(true);
      if (menuLinks.isEmpty())
      {
 -      Cache.applicationProperties.remove("SEQUENCE_LINKS");
 +      Cache.removePropertyNoSave("SEQUENCE_LINKS");
      }
      else
      {
 -      Cache.applicationProperties.setProperty("SEQUENCE_LINKS",
 +      Cache.setPropertyNoSave("SEQUENCE_LINKS",
                menuLinks.toString());
      }
  
      String nonMenuLinks = sequenceUrlLinks.writeUrlsAsString(false);
      if (nonMenuLinks.isEmpty())
      {
 -      Cache.applicationProperties.remove("STORED_LINKS");
 +      Cache.removePropertyNoSave("STORED_LINKS");
      }
      else
      {
 -      Cache.applicationProperties.setProperty("STORED_LINKS",
 +      Cache.setPropertyNoSave("STORED_LINKS",
                nonMenuLinks.toString());
      }
  
 -    Cache.applicationProperties.setProperty("DEFAULT_URL",
 +    Cache.setPropertyNoSave("DEFAULT_URL",
              sequenceUrlLinks.getPrimaryUrlId());
  
-     Cache.setPropertyNoSave("USE_PROXY",
-             Boolean.toString(useProxy.isSelected()));
-     Cache.setOrRemove("PROXY_SERVER", proxyServerTB.getText());
-     Cache.setOrRemove("PROXY_PORT", proxyPortTB.getText());
-     if (useProxy.isSelected())
-     {
-       System.setProperty("http.proxyHost", proxyServerTB.getText());
-       System.setProperty("http.proxyPort", proxyPortTB.getText());
-     }
-     else
-     {
-       System.setProperty("http.proxyHost", "");
-       System.setProperty("http.proxyPort", "");
-     }
      Cache.setProperty("VERSION_CHECK",
              Boolean.toString(versioncheck.isSelected()));
      if (Cache.getProperty("USAGESTATS") != null || usagestats.isSelected())
      /*
       * Save Output settings
       */
 -    Cache.applicationProperties.setProperty("BLC_JVSUFFIX",
 +    Cache.setPropertyNoSave("BLC_JVSUFFIX",
              Boolean.toString(blcjv.isSelected()));
 -    Cache.applicationProperties.setProperty("CLUSTAL_JVSUFFIX",
 +    Cache.setPropertyNoSave("CLUSTAL_JVSUFFIX",
              Boolean.toString(clustaljv.isSelected()));
 -    Cache.applicationProperties.setProperty("FASTA_JVSUFFIX",
 +    Cache.setPropertyNoSave("FASTA_JVSUFFIX",
              Boolean.toString(fastajv.isSelected()));
 -    Cache.applicationProperties.setProperty("MSF_JVSUFFIX",
 +    Cache.setPropertyNoSave("MSF_JVSUFFIX",
              Boolean.toString(msfjv.isSelected()));
 -    Cache.applicationProperties.setProperty("PFAM_JVSUFFIX",
 +    Cache.setPropertyNoSave("PFAM_JVSUFFIX",
              Boolean.toString(pfamjv.isSelected()));
 -    Cache.applicationProperties.setProperty("PILEUP_JVSUFFIX",
 +    Cache.setPropertyNoSave("PILEUP_JVSUFFIX",
              Boolean.toString(pileupjv.isSelected()));
 -    Cache.applicationProperties.setProperty("PIR_JVSUFFIX",
 +    Cache.setPropertyNoSave("PIR_JVSUFFIX",
              Boolean.toString(pirjv.isSelected()));
 -    Cache.applicationProperties.setProperty("PIR_MODELLER",
 +    Cache.setPropertyNoSave("PIR_MODELLER",
              Boolean.toString(modellerOutput.isSelected()));
 -    Cache.applicationProperties.setProperty("EXPORT_EMBBED_BIOJSON",
 +    Cache.setPropertyNoSave("EXPORT_EMBBED_BIOJSON",
              Boolean.toString(embbedBioJSON.isSelected()));
      jalview.io.PIRFile.useModellerOutput = modellerOutput.isSelected();
  
 -    Cache.applicationProperties.setProperty("FIGURE_AUTOIDWIDTH",
 +    Cache.setPropertyNoSave("FIGURE_AUTOIDWIDTH",
              Boolean.toString(autoIdWidth.isSelected()));
      userIdWidth_actionPerformed();
 -    Cache.applicationProperties.setProperty("FIGURE_FIXEDIDWIDTH",
 +    Cache.setPropertyNoSave("FIGURE_FIXEDIDWIDTH",
              userIdWidth.getText());
  
      /*
       * Save Editing settings
       */
 -    Cache.applicationProperties.setProperty("AUTO_CALC_CONSENSUS",
 +    Cache.setPropertyNoSave("AUTO_CALC_CONSENSUS",
              Boolean.toString(autoCalculateConsCheck.isSelected()));
 -    Cache.applicationProperties.setProperty("SORT_BY_TREE",
 +    Cache.setPropertyNoSave("SORT_BY_TREE",
              Boolean.toString(sortByTree.isSelected()));
 -    Cache.applicationProperties.setProperty("PAD_GAPS",
 +    Cache.setPropertyNoSave("PAD_GAPS",
              Boolean.toString(padGaps.isSelected()));
  
      if (!Platform.isJS())
      /*
       * Save Backups settings
       */
 -    Cache.applicationProperties.setProperty(BackupFiles.ENABLED,
 +    Cache.setPropertyNoSave(BackupFiles.ENABLED,
              Boolean.toString(enableBackupFiles.isSelected()));
      int preset = getComboIntStringKey(backupfilesPresetsCombo);
-     Cache.setPropertyNoSave(BackupFiles.NS + "_PRESET", Integer.toString(preset));
 -    Cache.applicationProperties.setProperty(BackupFiles.NS + "_PRESET",
 -            Integer.toString(preset));
++    Cache.applicationProperties.setProperty(BackupFiles.NS + "_PRESET", Integer.toString(preset));
  
      if (preset == BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM)
      {
        BackupFilesPresetEntry customBFPE = getBackupfilesCurrentEntry();
        BackupFilesPresetEntry.backupfilesPresetEntriesValues.put(
                BackupFilesPresetEntry.BACKUPFILESSCHEMECUSTOM, customBFPE);
-       Cache.setPropertyNoSave(BackupFilesPresetEntry.CUSTOMCONFIG,
 -      Cache.applicationProperties.setProperty(
 -              BackupFilesPresetEntry.CUSTOMCONFIG, customBFPE.toString());
++      Cache.applicationProperties
++              .setProperty(BackupFilesPresetEntry.CUSTOMCONFIG,
 +                      customBFPE.toString());
      }
  
      BackupFilesPresetEntry savedBFPE = BackupFilesPresetEntry.backupfilesPresetEntriesValues
              .get(preset);
 -    Cache.applicationProperties.setProperty(
 +    Cache.setPropertyNoSave(
              BackupFilesPresetEntry.SAVEDCONFIG, savedBFPE.toString());
  
      Cache.saveProperties();
 -    Desktop.instance.doConfigureStructurePrefs();
 +    Desktop.getInstance().doConfigureStructurePrefs();
      try
      {
        frame.setClosed(true);
      {
      }
    }
 -
+   public void saveProxySettings()
+   {
+     String newProxyType = customProxy.isSelected() ? Cache.PROXYTYPE_CUSTOM
+             : noProxy.isSelected() ? Cache.PROXYTYPE_NONE
+                     : Cache.PROXYTYPE_SYSTEM;
 -    Cache.applicationProperties.setProperty("USE_PROXY", newProxyType);
++    Cache.setPropertyNoSave("USE_PROXY", newProxyType);
+     Cache.setOrRemove("PROXY_SERVER", proxyServerHttpTB.getText());
+     Cache.setOrRemove("PROXY_PORT", proxyPortHttpTB.getText());
+     Cache.setOrRemove("PROXY_SERVER_HTTPS", proxyServerHttpsTB.getText());
+     Cache.setOrRemove("PROXY_PORT_HTTPS", proxyPortHttpsTB.getText());
+     Cache.setOrRemove("PROXY_AUTH",
+             Boolean.toString(proxyAuth.isSelected()));
+     Cache.setOrRemove("PROXY_AUTH_USERNAME", proxyAuthUsernameTB.getText());
+     Cache.proxyAuthPassword = proxyAuthPasswordPB.getPassword();
+     Cache.setProxyPropertiesFromPreferences(previousProxyType);
+     if (newProxyType.equals(Cache.PROXYTYPE_CUSTOM)
+             || !newProxyType.equals(previousProxyType))
+     {
+       // force a re-lookup of ws if proxytype is custom or has changed
+       wsPrefs.update++;
+     }
+     previousProxyType = newProxyType;
+   }
  
 -  /**
 -   * Do any necessary validation before saving settings. Return focus to the first
 -   * tab which fails validation.
 +  public static void setAppletDefaults()
 +  {
 +
 +    // http://www.jalview.org/old/v2_8/examples/appletParameters.html
 +
 +    // showConservation true or false Default is true.
 +    // showQuality true or false Default is true.
 +    // showConsensus true or false Default is true.
 +    // showFeatureSettings true or false Shows the feature settings window when
 +    // startin
 +    // showTreeBootstraps true or false (default is true) show or hide branch
 +    // bootstraps
 +    // showTreeDistances true or false (default is true) show or hide branch
 +    // lengths
 +    // showUnlinkedTreeNodes true or false (default is false) indicate if
 +    // unassociated nodes should be highlighted in the tree view
 +    // showUnconserved true of false (default is false) When true, only gaps and
 +    // symbols different to the consensus sequence ions of the alignment
 +    // showGroupConsensus true of false (default is false) When true, shows
 +    // consensus annotation row for any groups on the alignment. (since 2.7)
 +    // showGroupConservation true of false (default is false) When true, shows
 +    // amino-acid property conservation annotation row for any groups on the
 +    // showConsensusHistogram true of false (default is true) When true, shows
 +    // the percentage occurence of the consensus symbol for each column as a
 +    // showSequenceLogo true of false (default is false) When true, shows a
 +    // sequence logo above the consensus sequence (overlaid above the Consensus
 +
 +    Cache.setPropertyNoSave(SHOW_CONSERVATION, "true");
 +    Cache.setPropertyNoSave(SHOW_QUALITY, "false");
 +    Cache.setPropertyNoSave(SHOW_CONSENSUS, "true");
 +    Cache.setPropertyNoSave(SHOW_UNCONSERVED, "false");
 +    Cache.setPropertyNoSave(SHOW_GROUP_CONSERVATION, "false");
 +    Cache.setPropertyNoSave(SHOW_GROUP_CONSENSUS, "false");
 +
 +    // TODO -- just a start here
 +  }
 + /**
 +   * Do any necessary validation before saving settings. Return focus to the
 +   * first tab which fails validation.
     * 
     * @return
     */
    @Override
    protected boolean validateStructure()
    {
-     return validateChimeraPath();
+     return validateViewerPath();
  
    }
  
        FileFormatI format = chooser.getSelectedFormat();
        if (format != null)
        {
 -        Cache.applicationProperties.setProperty("DEFAULT_FILE_FORMAT",
 +        Cache.setPropertyNoSave("DEFAULT_FILE_FORMAT",
                  format.getName());
        }
        startupFileTextfield
     * DOCUMENT ME!
     * 
     * @param e
 -   *            DOCUMENT ME!
 +   *          DOCUMENT ME!
     */
    @Override
    public void cancel_actionPerformed(ActionEvent e)
     * DOCUMENT ME!
     * 
     * @param e
 -   *            DOCUMENT ME!
 +   *          DOCUMENT ME!
     */
    @Override
    public void annotations_actionPerformed(ActionEvent e)
              && (identity.isSelected() || showGroupConsensus.isSelected()));
      showConsensLogo.setEnabled(annotations.isSelected()
              && (identity.isSelected() || showGroupConsensus.isSelected()));
 +    showInformationHistogram.setEnabled(annotations.isSelected());
 +    showHMMLogo.setEnabled(annotations.isSelected());
    }
  
    @Override
      boolean valid = false;
      while (!valid)
      {
 -      if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
 +      if (JvOptionPane.showInternalConfirmDialog(Desktop.getDesktopPane(), link,
                MessageManager.getString("label.new_sequence_url_link"),
                JvOptionPane.OK_CANCEL_OPTION, -1,
                null) == JvOptionPane.OK_OPTION)
      boolean valid = false;
      while (!valid)
      {
 -      if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
 +      if (JvOptionPane.showInternalConfirmDialog(Desktop.getDesktopPane(), link,
                MessageManager.getString("label.edit_sequence_url_link"),
                JvOptionPane.OK_CANCEL_OPTION, -1,
                null) == JvOptionPane.OK_OPTION)
      if (!useLegacyGap.isSelected())
      {
        JalviewColourChooser.showColourChooser(this,
 -              MessageManager.getString("label.select_gap_colour"), gap);
 +              MessageManager.getString("label.select_gap_colour"),
 +              gap);
      }
    }
  
    public void hiddenColour_actionPerformed(JPanel hidden)
    {
      JalviewColourChooser.showColourChooser(this,
 -            MessageManager.getString("label.select_hidden_colour"), hidden);
 +            MessageManager.getString("label.select_hidden_colour"),
 +            hidden);
    }
  
    @Override
      } catch (NumberFormatException x)
      {
        userIdWidth.setText("");
 -      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +      JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                MessageManager
                        .getString("warn.user_defined_width_requirements"),
                MessageManager.getString("label.invalid_id_column_width"),
    }
  
    /**
-    * Returns true if chimera path is to a valid executable, else show an error
-    * dialog.
+    * Returns true if structure viewer path is to a valid executable, else shows an
+    * error dialog. Does nothing if the path is empty, as is the case for Jmol
+    * (built in to Jalview) or when Jalview is left to try default paths.
     */
-   private boolean validateChimeraPath()
+   private boolean validateViewerPath()
    {
-     if (chimeraPath.getText().trim().length() > 0)
+     if (structureViewerPath.getText().trim().length() > 0)
      {
-       File f = new File(chimeraPath.getText());
+       File f = new File(structureViewerPath.getText());
        if (!f.canExecute())
        {
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
-                 MessageManager.getString("label.invalid_chimera_path"),
-                 MessageManager.getString("label.invalid_name"),
+                 MessageManager.getString("label.invalid_viewer_path"),
+                 MessageManager.getString("label.invalid_viewer_path"),
                  JvOptionPane.ERROR_MESSAGE);
          return false;
        }
      }
      return true;
    }
 +  
 +  /**
 +   * Returns true if the given text field contains a path to a folder that
 +   * contains an executable with the given name, else false (after showing a
 +   * warning dialog). The executable name will be tried with .exe appended if not
 +   * found.
 +   * 
 +   * @param textField
 +   * @param executable
 +   */
 +  protected boolean validateExecutablePath(JTextField textField, String executable)
 +  {
 +    String folder = textField.getText().trim();
 +
 +    if (FileUtils.getExecutable(executable, folder) != null)
 +    {
 +      return true;
 +    }
 +    if (folder.length() > 0)
 +    {
 +      JvOptionPane.showInternalMessageDialog(Desktop.getInstance(),
 +              MessageManager.formatMessage("label.executable_not_found",
 +                      executable),
 +              MessageManager.getString("label.invalid_folder"),
 +              JvOptionPane.ERROR_MESSAGE);
 +    }
 +    return false;
 +  }
 +
 +  /**
 +   * Checks if a file can be executed
 +   * 
 +   * @param path
 +   *          the path to the file
 +   * @return
 +   */
 +  public boolean canExecute(String path)
 +  {
 +    File file = new File(path);
 +    if (!file.canExecute())
 +    {
 +      file = new File(path + ".exe");
 +      {
 +        if (!file.canExecute())
 +        {
 +          return false;
 +        }
 +      }
 +    }
 +    return true;
 +  }
  
    /**
-    * If Chimera is selected, check it can be found on default or user-specified
-    * path, if not show a warning/help dialog.
+    * If Chimera or ChimeraX or Pymol is selected, check it can be found on default
+    * or user-specified path, if not show a warning/help dialog
     */
    @Override
    protected void structureViewer_actionPerformed(String selectedItem)
    {
-     if (!selectedItem.equals(ViewerType.CHIMERA.name()))
+     if (selectedItem.equals(ViewerType.JMOL.name()))
      {
+       structureViewerPath.setEnabled(false);
+       structureViewerPathLabel.setEnabled(false);
        return;
      }
      boolean found = false;
+     structureViewerPath.setEnabled(true);
+     structureViewerPathLabel.setEnabled(true);
+     structureViewerPathLabel.setText(MessageManager
+             .formatMessage("label.viewer_path", selectedItem));
  
      /*
-      * Try user-specified and standard paths for Chimera executable.
+      * Try user-specified and standard paths for structure viewer executable
       */
-     List<String> paths = StructureManager.getChimeraPaths();
-     paths.add(0, chimeraPath.getText());
+     String viewerPath = "";
+     List<String> paths = null;
+     try
+     {
+       ViewerType viewerType = ViewerType.valueOf(selectedItem);
+       switch (viewerType)
+       {
+       case JMOL:
+         // dealt with above
+         break;
+       case CHIMERA:
+         viewerPath = Cache.getDefault(CHIMERA_PATH, "");
+         paths = StructureManager.getChimeraPaths(false);
+         break;
+       case CHIMERAX:
+         viewerPath = Cache.getDefault(CHIMERAX_PATH, "");
+         paths = StructureManager.getChimeraPaths(true);
+         break;
+       case PYMOL:
+         viewerPath = Cache.getDefault(PYMOL_PATH, "");
+         paths = PymolManager.getPymolPaths();
+         break;
+       }
+     } catch (IllegalArgumentException e)
+     {
+       // only valid entries should be in the drop-down
+     }
+     structureViewerPath.setText(viewerPath);
+     paths.add(0, structureViewerPath.getText());
      for (String path : paths)
      {
        if (new File(path.trim()).canExecute())
          break;
        }
      }
      if (!found)
      {
        String[] options = { "OK", "Help" };
 -      int showHelp = JvOptionPane.showInternalOptionDialog(Desktop.desktop,
 +      int showHelp = JvOptionPane.showInternalOptionDialog(Desktop.getDesktopPane(),
                JvSwingUtils.wrapTooltip(true,
-                       MessageManager.getString("label.chimera_missing")),
+                       MessageManager.getString("label.viewer_missing")),
                "", JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
                null, options, options[0]);
 -
        if (showHelp == JvOptionPane.NO_OPTION)
        {
+         this.selectTab(Preferences.TabRef.STRUCTURE_TAB, null);
          try
          {
            Help.showHelpWindow(HelpId.StructureViewer);
            e.printStackTrace();
          }
        }
-     }
+       else if (showHelp == JvOptionPane.OK_OPTION)
+       {
+         this.selectTab(Preferences.TabRef.STRUCTURE_TAB, null);
+         CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
+           try
+           {
+             for (int i = 0; i < 3; i++)
+             {
+               structureViewerPath.setBackground(Color.PINK);
+               Thread.sleep(500);
+               structureViewerPath.setBackground(Color.WHITE);
+               Thread.sleep(500);
+             }
+           } catch (InterruptedException e)
+           {
+           }
+         });
+       }
 -    }
++     }
 +  }
 +
 +  @Override
 +  protected void validateHmmerPath()
 +  {
 +    validateExecutablePath(hmmerPath, HmmerCommand.HMMBUILD);
 +  }
 +
 +  @Override
 +  protected void validateCygwinPath()
 +  {
 +    validateExecutablePath(cygwinPath, "run");
    }
  
    public class OptionsParam
@@@ -57,8 -57,7 +57,8 @@@ import javax.swing.JPanel
  public class SeqCanvas extends JPanel implements ViewportListenerI
  {
    /**
 -   * vertical gap in pixels between sequences and annotations when in wrapped mode
 +   * vertical gap in pixels between sequences and annotations when in wrapped
 +   * mode
     */
    static final int SEQS_ANNOTATION_GAP = 3;
  
@@@ -76,7 -75,7 +76,7 @@@
  
    private final SequenceRenderer seqRdr;
  
 -  boolean fastPaint = false;
 +  private boolean fastPaint = false;
  
    private boolean fastpainting = false;
  
  
    private int wrappedVisibleWidths; // number of wrapped widths displayed
  
 +  private int availWidth;
 +
 +  private int availHeight;
 +
 +  private boolean allowFastPaint;
    // Don't do this! Graphics handles are supposed to be transient
 -  //private Graphics2D gg;
 +  // private Graphics2D gg;
  
    /**
     * Creates a new SeqCanvas object.
  
    public SequenceRenderer getSequenceRenderer()
    {
 -    return seqRdr; 
 +    return seqRdr;
    }
  
    public FeatureRenderer getFeatureRenderer()
      int yPos = ypos + charHeight;
      int startX = startx;
      int endX = endx;
 -    
      if (av.hasHiddenColumns())
      {
        HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
          }
        }
  
 -      
        /*
         * white fill the space for the scale
         */
          }
        }
  
        // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
        // + horizontal + " " + vertical + " " + startRes + " " + endRes
        // + " " + startSeq + " " + endSeq);
        gg.copyArea(horizontal * charWidth, vertical * charHeight,
                img.getWidth(), img.getHeight(), -horizontal * charWidth,
                -vertical * charHeight);
 -      /** @j2sNative xxi = this.img */
        gg.translate(transX, transY);
        drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
        gg.translate(-transX, -transY);
        // Call repaint on alignment panel so that repaints from other alignment
        // panel components can be aggregated. Otherwise performance of the
        // overview window and others may be adversely affected.
 -      // System.out.println("SeqCanvas fastPaint() repaint() request...");
        av.getAlignPanel().repaint();
      } finally
      {
    @Override
    public void paintComponent(Graphics g)
    {
 +    if (av.getAlignPanel().getHoldRepaint())
 +    {
 +      return;
 +    }
  
 -    int charHeight = av.getCharHeight();
 -    int charWidth = av.getCharWidth();
 -
 -    int width = getWidth();
 -    int height = getHeight();
 -
 -    width -= (width % charWidth);
 -    height -= (height % charHeight);
 -
 -    // BH 2019 can't possibly fastPaint if either width or height is 0
 +    getAvailSizes();
  
 -    if (width == 0 || height == 0)
 +    if (availWidth == 0 || availHeight == 0)
      {
        return;
      }
      ViewportRanges ranges = av.getRanges();
      int startRes = ranges.getStartRes();
      int startSeq = ranges.getStartSeq();
      // }
  
      Rectangle vis, clip;
 -    if (img != null
 -            && (fastPaint
 -                    || (vis = getVisibleRect()).width != (clip = g
 -                            .getClipBounds()).width
 -                    || vis.height != clip.height))
 +    if (allowFastPaint  && img != null
 +            && (fastPaint || (vis = getVisibleRect()).width != (clip = g.getClipBounds()).width
 +                          || vis.height != clip.height))
      {
        g.drawImage(img, 0, 0, this);
        drawSelectionGroup((Graphics2D) g, startRes, endRes, startSeq,
      }
      else
      {
 +      allowFastPaint = true;
        // img is a cached version of the last view we drew.
        // If we have no img or the size has changed, make a new one.
        //
 -      if (img == null || width != img.getWidth()
 -              || height != img.getHeight())
 +      if (img == null || availWidth != img.getWidth()
 +              || availHeight != img.getHeight())
        {
 -        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
 +        img = new BufferedImage(availWidth, availHeight,
 +                BufferedImage.TYPE_INT_RGB);
        }
  
        Graphics2D gg = (Graphics2D) img.getGraphics();
        }
  
        gg.setColor(Color.white);
 -      gg.fillRect(0, 0, img.getWidth(), img.getHeight());
 +      gg.fillRect(0, 0, availWidth, availHeight);
  
        if (av.getWrapAlignment())
        {
-         drawWrappedPanel(gg, availWidth, availHeight, ranges.getStartRes());
 -        drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
++        drawWrappedPanel(gg, width, height, ranges.getStartRes());
        }
        else
        {
        drawCursor(g, startRes, endRes, startSeq, endSeq);
      }
    }
 -  
    /**
     * Draw an alignment panel for printing
     * 
    {
      drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
  
 -    drawSelectionGroup((Graphics2D) g1, startRes, endRes,
 -            startSeq, endSeq);
 +    drawSelectionGroup((Graphics2D) g1, startRes, endRes, startSeq, endSeq);
    }
  
    /**
      if (group != null)
      {
        drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
 -                startRes);
 +              startRes);
      }
    }
  
    /**
-    * Using the current font, determine fields labelWidthEast and labelWidthWest,
-    * and return the number of residues that can fill the remaining width
+    * Returns the visible width of the canvas in residues, after allowing for
+    * East or West scales (if shown)
     * 
-    * @param w
+    * @param canvasWidth
     *          the width in pixels (possibly including scales)
     * 
-    * @return the visible width in residues, after allowing for East or West
-    *         scales (if shown)
-    * 
+    * @return
     */
-   public int getWrappedCanvasWidth(int w)
+   public int getWrappedCanvasWidth(int canvasWidth)
    {
      int charWidth = av.getCharWidth();
  
      FontMetrics fm = getFontMetrics(av.getFont());
  
-     int labelWidth = (av.getScaleRightWrapped() || av.getScaleLeftWrapped()
-             ? getLabelWidth(fm)
-             : 0);
+     int labelWidth = 0;
+     
+     if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
+     {
+       labelWidth = getLabelWidth(fm);
+     }
  
      labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
  
      labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
  
-     return (w - labelWidthEast - labelWidthWest) / charWidth;
+     return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
    }
  
    /**
        maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
      }
  
 +    // quick int log10
      int length = 0;
      for (int i = maxWidth; i > 0; i /= 10)
      {
     * window
     * 
     * @param g
 -   * @param canvasWidth
 +   * @param availWidth
     *          available width in pixels
 -   * @param canvasHeight
 +   * @param availHeight
     *          available height in pixels
     * @param startColumn
     *          the first column (0...) of the alignment to draw
     */
 -  public void drawWrappedPanel(Graphics g, int canvasWidth,
 -          int canvasHeight, final int startColumn)
 +  public void drawWrappedPanel(Graphics g, int availWidth, int availHeight,
 +          final int startColumn)
    {
 -    int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
 -            canvasHeight);
 +    int wrappedWidthInResidues = calculateWrappedGeometry();
      av.setWrappedWidth(wrappedWidthInResidues);
      ViewportRanges ranges = av.getRanges();
      ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
  
      // we need to call this again to make sure the startColumn +
      // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
      // correctly.
 -    calculateWrappedGeometry(canvasWidth, canvasHeight);
 +    calculateWrappedGeometry();
  
      /*
       * draw one width at a time (excluding any scales shown),
      int currentWidth = 0;
      while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
      {
 -      int endColumn = Math
 -              .min(maxWidth, start + wrappedWidthInResidues - 1);
 -      drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
 +      int endColumn = Math.min(maxWidth,
 +              start + wrappedWidthInResidues - 1);
 +      drawWrappedWidth(g, ypos, start, endColumn, availHeight);
        ypos += wrappedRepeatHeightPx;
        start += wrappedWidthInResidues;
        currentWidth++;
      drawWrappedDecorators(g, startColumn);
    }
  
 +  private void getAvailSizes()
 +  {
 +    int charHeight = av.getCharHeight();
 +    int charWidth = av.getCharWidth();
 +    availWidth = getWidth();
 +    availHeight = getHeight();
 +    availWidth -= (availWidth % charWidth);
 +    availHeight -= (availHeight % charHeight);
 +  }
    /**
     * Calculates and saves values needed when rendering a wrapped alignment.
     * These depend on many factors, including
     * <li>whether scales are shown left, right or above the alignment</li>
     * </ul>
     * 
 +   * @param availWidth
 +   * @param availHeight
 +   * @return the number of residue columns in each width
 +   */
 +  protected int calculateWrappedGeometry()
 +  {
 +    getAvailSizes();
 +    return calculateWrappedGeometry(availWidth, availHeight);
 +
 +  }
 +
 +  /**
 +   * for test only
     * @param canvasWidth
     * @param canvasHeight
 -   * @return the number of residue columns in each width
 +   * @return
     */
 -  protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
 +  public int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
    {
      int charHeight = av.getCharHeight();
  
      /*
       * compute height in pixels of the wrapped widths
       * - start with space above plus sequences
       */
 -    wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
 -    wrappedRepeatHeightPx += av.getAlignment().getHeight()
 -            * charHeight;
 +    wrappedRepeatHeightPx = wrappedSpaceAboveAlignment
 +            + av.getAlignment().getHeight() * charHeight;
  
      /*
       * add annotations panel height if shown
      int charWidth = av.getCharWidth();
      int xOffset = labelWidthWest
              + ((startColumn - ranges.getStartRes()) % viewportWidth)
 -            * charWidth;
 +                    * charWidth;
  
      g.translate(xOffset, 0);
  
        if (av.getScaleRightWrapped())
        {
          int x = labelWidthWest + viewportWidth * charWidth;
 -        
          g.translate(x, 0);
          drawVerticalScale(g, startCol, endColumn, ypos, false);
          g.translate(-x, 0);
         */
        g.translate(labelWidthWest, 0);
        g.setColor(Color.white);
 -      g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
 -              * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
 +      g.fillRect(0, ypos - wrappedSpaceAboveAlignment,
 +              viewportWidth * charWidth + labelWidthWest,
 +              wrappedSpaceAboveAlignment);
        g.setColor(Color.black);
        g.translate(-labelWidthWest, 0);
  
      }
    }
  
 +  private final static BasicStroke dottedStroke = new BasicStroke(1,
 +          BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 3f, new float[]
 +          { 5f, 3f }, 0f);
 +
 +  private final static BasicStroke basicStroke = new BasicStroke();
    /*
     * Draw a selection group over a wrapped alignment
     */
    private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
 -          int canvasWidth,
 -          int canvasHeight, int startRes)
 +          int canvasWidth, int canvasHeight, int startRes)
    {
      // chop the wrapped alignment extent up into panel-sized blocks and treat
      // each block as if it were a block from an unwrapped alignment
 -    g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
 -            BasicStroke.JOIN_ROUND, 3f, new float[]
 -            { 5f, 3f }, 0f));
 +    g.setStroke(dottedStroke);
      g.setColor(Color.RED);
  
      int charWidth = av.getCharWidth();
              / charWidth;
      int startx = startRes;
      int maxwidth = av.getAlignment().getVisibleWidth();
 +    // JAL-3253-applet had this:
 +    // // height gap above each panel
 +    // int charHeight = av.getCharHeight();
 +    // int hgap = charHeight;
 +    // if (av.getScaleAboveWrapped())
 +    // {
 +    // hgap += charHeight;
 +    // }
 +    // int dy = getAnnotationHeight() + hgap
 +    // + av.getAlignment().getHeight() * charHeight;
 +    // int ypos = hgap; // vertical offset
 +
 +    // this is from 0b573ed (gmungoc)
 +    int dy = wrappedRepeatHeightPx;
      int ypos = wrappedSpaceAboveAlignment;
  
      while ((ypos <= canvasHeight) && (startx < maxwidth))
        }
  
        g.translate(labelWidthWest, 0);
        drawUnwrappedSelection(g, group, startx, endx, 0,
 -              av.getAlignment().getHeight() - 1,
 -              ypos);
 +              av.getAlignment().getHeight() - 1, ypos);
        g.translate(-labelWidthWest, 0);
  
 -      ypos += wrappedRepeatHeightPx;
 +      // update vertical offset
 +      ypos += dy;
  
 +      // update horizontal offset
        startx += cWidth;
      }
 -    g.setStroke(new BasicStroke());
 +    g.setStroke(basicStroke);
    }
  
    /**
 -   * Answers zero if annotations are not shown, otherwise recalculates and answers
 -   * the total height of all annotation rows in pixels
 +   * Answers zero if annotations are not shown, otherwise recalculates and
 +   * answers the total height of all annotation rows in pixels
     * 
     * @return
     */
     *         the cursor drawn on it, if any
     */
    private void drawCursor(Graphics g, int startRes, int endRes,
 -          int startSeq,
 -          int endSeq)
 +          int startSeq, int endSeq)
    {
      // convert the cursorY into a position on the visible alignment
      int cursor_ypos = cursorY;
      }
    }
  
    /**
     * Draw a selection group over an unwrapped alignment
     * 
            int startRes, int endRes, int startSeq, int endSeq, int offset)
    {
      int charWidth = av.getCharWidth();
 -          
      if (!av.hasHiddenColumns())
      {
        drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
          blockStart = region[0];
  
          g.translate(screenY * charWidth, 0);
 -        drawPartialGroupOutline(g, group,
 -                blockStart, blockEnd, startSeq, endSeq, offset);
 +        drawPartialGroupOutline(g, group, blockStart, blockEnd, startSeq,
 +                endSeq, offset);
  
          g.translate(-screenY * charWidth, 0);
          screenY += blockEnd - blockStart + 1;
        g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
      }
    }
 -  
    /**
     * Highlights search results in the visible region by rendering as white text
     * on a black background. Any previous highlighting is removed. Answers true
      return highlightSearchResults(results, false);
  
    }
 -  
    /**
     * Highlights search results in the visible region by rendering as white text
     * on a black background. Any previous highlighting is removed. Answers true
        {
          firstCol = alignment.getHiddenColumns()
                  .absoluteToVisibleColumn(firstCol);
 -        lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
 +        lastCol = alignment.getHiddenColumns()
 +                .absoluteToVisibleColumn(lastCol);
        }
        int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
        int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
    public void propertyChange(PropertyChangeEvent evt)
    {
      String eventName = evt.getPropertyName();
 -    // System.err.println(">>SeqCanvas propertyChange " + eventName);
 -    if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
 +    // BH 2019.07.27 removes dead code introduced in aad3650 and simplifies
 +    // logic, emphasizing no check for ENDRES or ENDSEQ
 +
 +    // Both scrolling and resizing change viewport ranges: scrolling changes
 +    // both start and end points, but resize only changes end values.
 +    // Here we only want to fastpaint on a scroll, with resize using a normal
 +    // paint, so scroll events are identified as changes to the horizontal or
 +    // vertical start value.
 +
 +    // Make sure we're not trying to draw a panel
 +    // larger than the visible window
 +    int scrollX = 0;
 +    int scrollY = 0;
 +    switch (eventName)
      {
 +    case SequenceGroup.SEQ_GROUP_CHANGED:
        fastPaint = true;
        repaint();
        return;
 -    }
 -    else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
 -    {
 +    case ViewportRanges.MOVE_VIEWPORT:
        fastPaint = false;
 -      // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
        repaint();
        return;
 -    }
 -
 -    int scrollX = 0;
 -    if (eventName.equals(ViewportRanges.STARTRES)
 -            || eventName.equals(ViewportRanges.STARTRESANDSEQ))
 -    {
 -      // Make sure we're not trying to draw a panel
 -      // larger than the visible window
 -      if (eventName.equals(ViewportRanges.STARTRES))
 -      {
 -        scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
 -      }
 -      else
 +    case ViewportRanges.STARTSEQ:
 +      // meaning STARTOREND
 +      // typically scroll, but possibly just the end changed
 +      fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
 +      return;
 +    case ViewportRanges.STARTRES:
 +      // meaning STARTOREND
 +      scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
 +      break;
 +    case ViewportRanges.STARTRESANDSEQ:
 +      scrollX = ((int[]) evt.getNewValue())[0]
 +              - ((int[]) evt.getOldValue())[0];
 +      scrollY = ((int[]) evt.getNewValue())[1]
 +              - ((int[]) evt.getOldValue())[1];
 +      if (scrollX != 0 && scrollY != 0)
        {
 -        scrollX = ((int[]) evt.getNewValue())[0]
 -                - ((int[]) evt.getOldValue())[0];
 -      }
 -      ViewportRanges vpRanges = av.getRanges();
 +        // all sorts of problems in JavaScript if this is commented out.
 +        repaint();
 +        return;
  
 -      int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
 -      if (scrollX > range)
 -      {
 -        scrollX = range;
 -      }
 -      else if (scrollX < -range)
 -      {
 -        scrollX = -range;
        }
 +      break;
 +    default:
 +      return;
      }
 +
 +    ViewportRanges vpRanges = av.getRanges();
 +    int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
 +    scrollX = Math.max(Math.min(scrollX, range), -range);
 +    // only STARTRES or STARTRESANDSEQ:
 +    if (av.getWrapAlignment())
 +    {
 +      fastPaintWrapped(scrollX);
 +    }
 +    else
 +    {
 +      fastPaint(scrollX, scrollY);
 +    }
 +
 +    // BH 2019.07.27 was:
 +    // if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
 +    // {
 +    // fastPaint = true;
 +    // repaint();
 +    // return;
 +    // }
 +    // else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
 +    // {
 +    // fastPaint = false;
 +    // // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
 +    // repaint();
 +    // return;
 +    // }
 +    //
 +    // if (eventName.equals(ViewportRanges.STARTRES)
 +    // || eventName.equals(ViewportRanges.STARTRESANDSEQ))
 +    // {
 +    // // Make sure we're not trying to draw a panel
 +    // // larger than the visible window
 +    // if (eventName.equals(ViewportRanges.STARTRES))
 +    // {
 +    // scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
 +    // }
 +    // else
 +    // {
 +    // scrollX = ((int[]) evt.getNewValue())[0]
 +    // - ((int[]) evt.getOldValue())[0];
 +    // }
 +    // ViewportRanges vpRanges = av.getRanges();
 +    //
 +    // int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
 +    // if (scrollX > range)
 +    // {
 +    // scrollX = range;
 +    // }
 +    // else if (scrollX < -range)
 +    // {
 +    // scrollX = -range;
 +    // }
 +    // }
      // Both scrolling and resizing change viewport ranges: scrolling changes
      // both start and end points, but resize only changes end values.
      // Here we only want to fastpaint on a scroll, with resize using a normal
      // paint, so scroll events are identified as changes to the horizontal or
      // vertical start value.
 -    if (eventName.equals(ViewportRanges.STARTRES))
 -    {
 -      if (av.getWrapAlignment())
 -      {
 -        fastPaintWrapped(scrollX);
 -      }
 -      else
 -      {
 -        fastPaint(scrollX, 0);
 -      }
 -    }
 -    else if (eventName.equals(ViewportRanges.STARTSEQ))
 -    {
 -      // scroll
 -      fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
 -    }
 -    else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
 -    {
 -      if (av.getWrapAlignment())
 -      {
 -        fastPaintWrapped(scrollX);
 -      }
 -      else
 -      {
 -        fastPaint(scrollX, 0);
 -      }
 -    }
 -    else if (eventName.equals(ViewportRanges.STARTSEQ))
 -    {
 -      // scroll
 -      fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
 -    }
 -    else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
 -    {
 -      if (av.getWrapAlignment())
 -      {
 -        fastPaintWrapped(scrollX);
 -      }
 -    }
 +    // BH 2019.07.27 was:
 +    // if (eventName.equals(ViewportRanges.STARTRES))
 +    // {
 +    // if (av.getWrapAlignment())
 +    // {
 +    // fastPaintWrapped(scrollX);
 +    // }
 +    // else
 +    // {
 +    // fastPaint(scrollX, 0);
 +    // }
 +    // }
 +    // else if (eventName.equals(ViewportRanges.STARTSEQ))
 +    // {
 +    // // scroll
 +    // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
 +    // }
 +    // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
 +    // {
 +    // if (av.getWrapAlignment())
 +    // {
 +    // fastPaintWrapped(scrollX);
 +    // }
 +    // else
 +    // {
 +    // fastPaint(scrollX, 0);
 +    // }
 +    // }
 +    //
 +    // BH oops!
 +    //
 +    // else if (eventName.equals(ViewportRanges.STARTSEQ))
 +    // {
 +    // // scroll
 +    // fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
 +    // }
 +    // else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
 +    // {
 +    // if (av.getWrapAlignment())
 +    // {
 +    // fastPaintWrapped(scrollX);
 +    // }
 +    // }
    }
  
    /**
  
      try
      {
 -      
        Graphics gg = img.getGraphics();
 -      
 -      calculateWrappedGeometry(getWidth(), getHeight());
 +      calculateWrappedGeometry();
  
        /*
         * relocate the regions of the alignment that are still visible
        if (scrollX < 0)
        {
          int startRes = ranges.getStartRes();
 -        drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes, startRes
 -                - scrollX - 1, getHeight());
 +        drawWrappedWidth(gg, wrappedSpaceAboveAlignment, startRes,
 +                startRes - scrollX - 1, getHeight());
        }
        else
        {
        drawWrappedDecorators(gg, ranges.getStartRes());
  
        gg.dispose();
 -      
        repaint();
      } finally
      {
      }
  
      Graphics gg = img.getGraphics();
 -    
      ViewportRanges ranges = av.getRanges();
      int viewportWidth = ranges.getViewportWidth();
      int charWidth = av.getCharWidth();
       */
      int visibleWidths = wrappedVisibleWidths;
      int canvasHeight = getHeight();
 -    boolean lastWidthPartHeight = (wrappedVisibleWidths * wrappedRepeatHeightPx) > canvasHeight;
 +    boolean lastWidthPartHeight = (wrappedVisibleWidths
 +            * wrappedRepeatHeightPx) > canvasHeight;
  
      if (lastWidthPartHeight)
      {
        /*
         * white fill first to erase annotations
         */
 -      
 -      
        gg.translate(xOffset, 0);
        gg.setColor(Color.white);
 -      gg.fillRect(labelWidthWest, ypos,
 -              (endRes - startRes + 1) * charWidth, wrappedRepeatHeightPx);
 +      gg.fillRect(labelWidthWest, ypos, (endRes - startRes + 1) * charWidth,
 +              wrappedRepeatHeightPx);
        gg.translate(-xOffset, 0);
  
        drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
 -      
      }
  
      /*
        gg.fillRect(0, canvasHeight - heightBelow, getWidth(), heightBelow);
      }
      gg.dispose();
 - }
 +  }
  
    /**
     * Shifts the visible alignment by the specified number of columns - left if
          if (y + wrappedRepeatHeightPx < canvasHeight - wrappedRepeatHeightPx
                  && (xpos + viewportWidth <= xMax))
          {
 -          gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx, -positions
 -                  * charWidth, heightToCopy, widthToCopy,
 +          gg.copyArea(labelWidthWest, y + wrappedRepeatHeightPx,
 +                  -positions * charWidth, heightToCopy, widthToCopy,
                    -wrappedRepeatHeightPx);
          }
          y += wrappedRepeatHeightPx;
      gg.dispose();
    }
  
 -  
    /**
     * Redraws any positions in the search results in the visible region of a
     * wrapped alignment. Any highlights are drawn depending on the search results
  
      boolean matchFound = false;
  
 -    calculateWrappedGeometry(getWidth(), getHeight());
 +    calculateWrappedGeometry();
      int wrappedWidth = av.getWrappedWidth();
      int wrappedHeight = wrappedRepeatHeightPx;
  
      }
  
      int firstVisibleColumn = ranges.getStartRes();
 -    int lastVisibleColumn = ranges.getStartRes() + repeats
 -            * ranges.getViewportWidth() - 1;
 +    int lastVisibleColumn = ranges.getStartRes()
 +            + repeats * ranges.getViewportWidth() - 1;
  
      AlignmentI alignment = av.getAlignment();
      if (av.hasHiddenColumns())
  
      int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
  
 -    
      Graphics gg = img.getGraphics();
  
      for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
                 * transX: offset from left edge of canvas to residue position
                 */
                int transX = labelWidthWest
 -                      + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
 -                      * av.getCharWidth();
 +                      + ((displayColumn - ranges.getStartRes())
 +                              % wrappedWidth) * av.getCharWidth();
  
                /*
                 * transY: offset from top edge of canvas to residue position
          }
        }
      }
 -  
      gg.dispose();
  
      return matchFound;
      return labelWidthWest;
    }
  
 +  /**
 +   * Clears the flag that allows a 'fast paint' on the next repaint, so
 +   * requiring a full repaint
 +   */
 +  public void setNoFastPaint()
 +  {
 +    allowFastPaint = false;
 +  }
 +
  }
@@@ -87,7 -87,6 +87,7 @@@ public class SeqPanel extends JPane
          SequenceListener, SelectionListener
  {
    /*
 +   * 
     * a class that holds computed mouse position
     * - column of the alignment (0...)
     * - sequence offset (0...)
     */
    public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel)
    {
 +    setName("SeqPanel");
      seqARep = new SequenceAnnotationReport(true);
      ToolTipManager.sharedInstance().registerComponent(this);
      ToolTipManager.sharedInstance().setInitialDelay(0);
    /**
     * Computes the column and sequence row (and possibly annotation row when in
     * wrapped mode) for the given mouse position
+    * <p>
+    * Mouse position is not set if in wrapped mode with the cursor either between
+    * sequences, or over the left or right vertical scale.
     * 
     * @param evt
     * @return
      int alignmentHeight = av.getAlignment().getHeight();
      if (av.getWrapAlignment())
      {
 -      seqCanvas.calculateWrappedGeometry(seqCanvas.getWidth(),
 -              seqCanvas.getHeight());
 +      seqCanvas.calculateWrappedGeometry();
  
        /*
         * yPos modulo height of repeating width
    /**
     * Returns the aligned sequence position (base 0) at the mouse position, or
     * the closest visible one
+    * <p>
+    * Returns -1 if in wrapped mode with the mouse over either left or right
+    * vertical scale.
     * 
     * @param evt
     * @return
        if (editCommand != null && editCommand.getSize() > 0)
        {
          ap.alignFrame.addHistoryItem(editCommand);
 -        av.firePropertyChange("alignment", null,
 -                av.getAlignment().getSequences());
 +        ap.av.notifyAlignment();
        }
      } finally
      {
  
    void moveCursor(int dx, int dy)
    {
-     seqCanvas.cursorX += dx;
-     seqCanvas.cursorY += dy;
+     moveCursor(dx, dy,false);
+   }
+   void moveCursor(int dx, int dy, boolean nextWord)
+   {
      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
  
-     if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
+     if (nextWord)
      {
-       int original = seqCanvas.cursorX - dx;
        int maxWidth = av.getAlignment().getWidth();
-       if (!hidden.isVisible(seqCanvas.cursorX))
+       int maxHeight=av.getAlignment().getHeight();
+       SequenceI seqAtRow = av.getAlignment().getSequenceAt(seqCanvas.cursorY);
+       // look for next gap or residue
+       boolean isGap = Comparison.isGap(seqAtRow.getCharAt(seqCanvas.cursorX));
+       int p = seqCanvas.cursorX,lastP,r=seqCanvas.cursorY,lastR;
+       do
        {
-         int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
-         int[] region = hidden.getRegionWithEdgeAtRes(visx);
-         if (region != null) // just in case
+         lastP = p;
+         lastR = r;
+         if (dy != 0)
          {
-           if (dx == 1)
+           r += dy;
+           if (r < 0)
            {
-             // moving right
-             seqCanvas.cursorX = region[1] + 1;
+             r = 0;
            }
-           else if (dx == -1)
+           if (r >= maxHeight)
            {
-             // moving left
-             seqCanvas.cursorX = region[0] - 1;
+             r = maxHeight - 1;
            }
+           seqAtRow = av.getAlignment().getSequenceAt(r);
          }
-         seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
-       }
+         p = nextVisible(hidden, maxWidth, p, dx);
+       } while ((dx != 0 ? p != lastP : r != lastR)
+               && isGap == Comparison.isGap(seqAtRow.getCharAt(p)));
+       seqCanvas.cursorX=p;
+       seqCanvas.cursorY=r;
+     } else {
+       int maxWidth = av.getAlignment().getWidth();
+       seqCanvas.cursorX = nextVisible(hidden, maxWidth, seqCanvas.cursorX, dx);
+       seqCanvas.cursorY += dy;
+     }
+     scrollToVisible(false);
+   }
+   private int nextVisible(HiddenColumns hidden,int maxWidth, int original, int dx)
+   {
+     int newCursorX=original+dx;
+     if (av.hasHiddenColumns() && !hidden.isVisible(newCursorX))
+     {
+       int visx = hidden.absoluteToVisibleColumn(newCursorX - dx);
+       int[] region = hidden.getRegionWithEdgeAtRes(visx);
  
-       if (seqCanvas.cursorX >= maxWidth
-               || !hidden.isVisible(seqCanvas.cursorX))
+       if (region != null) // just in case
        {
-         seqCanvas.cursorX = original;
+         if (dx == 1)
+         {
+           // moving right
+           newCursorX = region[1] + 1;
+         }
+         else if (dx == -1)
+         {
+           // moving left
+           newCursorX = region[0] - 1;
+         }
        }
      }
-     scrollToVisible(false);
+     newCursorX = (newCursorX < 0) ? 0 : newCursorX;
+     if (newCursorX >= maxWidth
+             || !hidden.isVisible(newCursorX))
+     {
+       newCursorX = original;
+     }
+     return newCursorX;
    }
    /**
     * Scroll to make the cursor visible in the viewport.
     * 
      AlignFrame af = Desktop.getAlignFrameFor(complement);
      FeatureRendererModel fr2 = af.getFeatureRenderer();
  
-     int j = results.getSize();
+     List<SearchResultMatchI> matches = results.getResults();
+     int j = matches.size();
      List<String> infos = new ArrayList<>();
      for (int i = 0; i < j; i++)
      {
-       SearchResultMatchI match = results.getResults().get(i);
+       SearchResultMatchI match = matches.get(i);
        int pos = match.getStart();
        if (pos == match.getEnd())
        {
                    pos);
            if (mf != null)
            {
-             unshownFeatures = seqARep.appendFeatures(tooltipText,
+             unshownFeatures += seqARep.appendFeatures(tooltipText,
                      pos, mf, fr2, MAX_TOOLTIP_LENGTH);
            }
          }
  
      String tooltip = AnnotationPanel.buildToolTip(anns[rowIndex], column,
              anns);
 -    if (!tooltip.equals(lastTooltip))
 +    boolean tooltipChanged = tooltip == null ? lastTooltip != null : !tooltip.equals(lastTooltip);
 +    if (tooltipChanged)
      {
        lastTooltip = tooltip;
        lastFormattedTooltip = tooltip == null ? null
    {
      return lastSearchResults;
    }
 +  
 +  /**
 +   * scroll to the given row/column - or nearest visible location
 +   * 
 +   * @param row
 +   * @param column
 +   */
 +  public void scrollTo(int row, int column)
 +  {
 +
 +    row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
 +    column = column < 0 ? ap.av.getRanges().getStartRes() : column;
 +    ap.scrollTo(column, column, row, true, true);
 +  }
 +
 +  /**
 +   * scroll to the given row - or nearest visible location
 +   * 
 +   * @param row
 +   */
 +  public void scrollToRow(int row)
 +  {
 +
 +    row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
 +    ap.scrollTo(ap.av.getRanges().getStartRes(),
 +            ap.av.getRanges().getStartRes(), row, true, true);
 +  }
 +
 +  /**
 +   * scroll to the given column - or nearest visible location
 +   * 
 +   * @param column
 +   */
 +  public void scrollToColumn(int column)
 +  {
 +
 +    column = column < 0 ? ap.av.getRanges().getStartRes() : column;
 +    ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true,
 +            true);
 +  }
 +
  }
   */
  package jalview.gui;
  
 +import jalview.api.FeatureSettingsModelI;
 +import jalview.bin.Cache;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.DBRefEntry;
 +import jalview.datamodel.SequenceI;
 +import jalview.fts.core.GFTSPanel;
 +import jalview.fts.service.pdb.PDBFTSPanel;
 +import jalview.fts.service.uniprot.UniprotFTSPanel;
 +import jalview.io.FileFormatI;
 +import jalview.io.gff.SequenceOntologyI;
 +import jalview.util.DBRefUtils;
 +import jalview.util.MessageManager;
 +import jalview.util.Platform;
 +import jalview.ws.seqfetcher.DbSourceProxy;
 +
  import java.awt.BorderLayout;
  import java.awt.Font;
  import java.awt.event.ActionEvent;
@@@ -57,6 -42,22 +57,6 @@@ import javax.swing.JScrollPane
  import javax.swing.JTextArea;
  import javax.swing.SwingConstants;
  
 -import jalview.api.FeatureSettingsModelI;
 -import jalview.bin.Cache;
 -import jalview.datamodel.AlignmentI;
 -import jalview.datamodel.DBRefEntry;
 -import jalview.datamodel.SequenceI;
 -import jalview.fts.core.GFTSPanel;
 -import jalview.fts.service.pdb.PDBFTSPanel;
 -import jalview.fts.service.threedbeacons.TDBeaconsFTSPanel;
 -import jalview.fts.service.uniprot.UniprotFTSPanel;
 -import jalview.io.FileFormatI;
 -import jalview.io.gff.SequenceOntologyI;
 -import jalview.util.DBRefUtils;
 -import jalview.util.MessageManager;
 -import jalview.util.Platform;
 -import jalview.ws.seqfetcher.DbSourceProxy;
 -
  /**
   * A panel where the use may choose a database source, and enter one or more
   * accessions, to retrieve entries from the database.
   */
  public class SequenceFetcher extends JPanel implements Runnable
  {
+   private class StringPair
+   {
+     private String key;
+     private String display;
+     public StringPair(String s1, String s2)
+     {
+       key = s1;
+       display = s2;
+     }
+     public StringPair(String s)
+     {
+       this(s, s);
+     }
+     public String getKey()
+     {
+       return key;
+     }
+     public String getDisplay()
+     {
+       return display;
+     }
+     @Override
+     public String toString()
+     {
+       return display;
+     }
+     public boolean equals(StringPair other)
+     {
+       return other.key == this.key;
+     }
+   }
 -
    private static jalview.ws.SequenceFetcher sfetch = null;
  
    JLabel exampleAccession;
  
-   JComboBox<String> database;
+   JComboBox<StringPair> database;
  
    JCheckBox replacePunctuation;
  
  
      frame = new JInternalFrame();
      frame.setContentPane(this);
 -    Desktop.addInternalFrame(frame, getFrameTitle(), true, 400,
 -            Platform.isAMacAndNotJS() ? 240 : 180);
 +    Desktop.addInternalFrame(frame, getFrameTitle(), Desktop.FRAME_MAKE_VISIBLE, 400, 
 +              Platform.isAMacAndNotJS() ? 240 : 180, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_SET_MIN_SIZE_300);
    }
  
    private String getFrameTitle()
  
      database = new JComboBox<>();
      database.setFont(JvSwingUtils.getLabelFont());
-     database.setPrototypeDisplayValue("ENSEMBLGENOMES   ");
+     StringPair instructionItem = new StringPair(
+             MessageManager.getString("action.select_ddbb"));
+     database.setPrototypeDisplayValue(instructionItem);
      String[] sources = new jalview.ws.SequenceFetcher().getSupportedDb();
      Arrays.sort(sources, String.CASE_INSENSITIVE_ORDER);
-     database.addItem(MessageManager.getString("action.select_ddbb"));
+     database.addItem(instructionItem);
      for (String source : sources)
      {
-       database.addItem(source);
+       List<DbSourceProxy> slist = sfetch.getSourceProxy(source);
+       if (slist.size() == 1 && slist.get(0) != null)
+       {
+         database.addItem(new StringPair(source, slist.get(0).getDbName()));
+       }
+       else
+       {
+         database.addItem(new StringPair(source));
+       }
      }
-     database.setSelectedItem(selectedDb);
+     setDatabaseSelectedItem(selectedDb);
      if (database.getSelectedIndex() == -1)
      {
        database.setSelectedIndex(0);
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         String currentSelection = (String) database.getSelectedItem();
+         String currentSelection = ((StringPair) database.getSelectedItem())
+                 .getKey();
          updateExampleQuery(currentSelection);
  
          if ("pdb".equalsIgnoreCase(currentSelection))
            frame.dispose();
            new UniprotFTSPanel(SequenceFetcher.this);
          }
+         else if ("3d-beacons".equalsIgnoreCase(currentSelection))
+         {
+           frame.dispose();
+           new TDBeaconsFTSPanel(SequenceFetcher.this);
+         }
          else
          {
            otherSourceAction();
      this.add(databasePanel, BorderLayout.NORTH);
    }
  
+   private void setDatabaseSelectedItem(String db)
+   {
+     for (int i = 0; i < database.getItemCount(); i++)
+     {
+       StringPair sp = database.getItemAt(i);
+       if (sp != null && db != null && db.equals(sp.getKey()))
+       {
+         database.setSelectedIndex(i);
+         return;
+       }
+     }
+   }
 -
    /**
     * Answers a semi-colon-delimited string with the example query or queries for
     * the selected database
     */
    protected void example_actionPerformed()
    {
-     String eq = getExampleQueries((String) database.getSelectedItem());
+     String eq = getExampleQueries(
+             ((StringPair) database.getSelectedItem()).getKey());
      textArea.setText(eq);
      repaint();
    }
        text = text.replace(",", ";");
      }
      text = text.replaceAll("(\\s|[; ])+", ";");
 -    if (!t0.equals(text))
 +    if (!t0.equals(text)) 
      {
 -      textArea.setText(text);
 +        textArea.setText(text);
      }
      if (text.isEmpty())
      {
      List<String> presultTitle = new ArrayList<>();
      List<AlignmentI> presult = new ArrayList<>();
      List<AlignmentI> aresult = new ArrayList<>();
-     List<DbSourceProxy> sources = sfetch
-             .getSourceProxy((String) database.getSelectedItem());
+     List<DbSourceProxy> sources = sfetch.getSourceProxy(
+             ((StringPair) database.getSelectedItem()).getKey());
      Iterator<DbSourceProxy> proxies = sources.iterator();
      String[] qries = textArea.getText().trim().split(";");
      List<String> nextFetch = Arrays.asList(qries);
        } catch (Exception e)
        {
          showErrorMessage("Error retrieving " + textArea.getText() + " from "
-                 + database.getSelectedItem());
+                 + ((StringPair) database.getSelectedItem()).getDisplay());
          // error
          // +="Couldn't retrieve sequences from "+database.getSelectedItem();
          System.err.println("Retrieval failed for source ='"
-                 + database.getSelectedItem() + "' and query\n'"
-                 + textArea.getText() + "'\n");
+                 + ((StringPair) database.getSelectedItem()).getDisplay()
+                 + "' and query\n'" + textArea.getText() + "'\n");
          e.printStackTrace();
        } catch (OutOfMemoryError e)
        {
          showErrorMessage("Out of Memory when retrieving "
-                 + textArea.getText() + " from " + database.getSelectedItem()
+                 + textArea.getText() + " from "
+                 + ((StringPair) database.getSelectedItem()).getDisplay()
                  + "\nPlease see the Jalview FAQ for instructions for increasing the memory available to Jalview.\n");
          e.printStackTrace();
        } catch (Error e)
        {
          showErrorMessage("Serious Error retrieving " + textArea.getText()
-                 + " from " + database.getSelectedItem());
+                 + " from "
+                 + ((StringPair) database.getSelectedItem()).getDisplay());
          e.printStackTrace();
        }
  
      } catch (OutOfMemoryError oome)
      {
        new OOMWarning("fetching " + multiacc + " from "
-               + database.getSelectedItem(), oome, this);
+               + ((StringPair) database.getSelectedItem()).getDisplay(),
+               oome, this);
      }
    }
  
  
      for (String q : queries)
      {
 -      // BH 2019.01.25 dbr is never used.
 -      // DBRefEntry dbr = new DBRefEntry();
 -      // dbr.setSource(proxy.getDbSource());
 -      // dbr.setVersion(null);
 +      // BH 2019.01.25 dbr is never used.
 +//      DBRefEntry dbr = new DBRefEntry();
 +//      dbr.setSource(proxy.getDbSource());
 +//      dbr.setVersion(null);
        String accId = proxy.getAccessionIdFromQuery(q);
 -      // dbr.setAccessionId(accId);
 +//      dbr.setAccessionId(accId);
        boolean rfound = false;
        for (int r = 0, nr = rs.length; r < nr; r++)
        {
     */
    public String getDefaultRetrievalTitle()
    {
-     return "Retrieved from " + database.getSelectedItem();
+     return "Retrieved from "
+             + ((StringPair) database.getSelectedItem()).getDisplay();
    }
  
-   AlignmentI parseResult(AlignmentI al, String title,
+   /**
+    * constructs an alignment frame given the data and metadata
+    * 
+    * @param al
+    * @param title
+    * @param currentFileFormat
+    * @param preferredFeatureColours
+    * @return the alignment
+    */
+   public AlignmentI parseResult(AlignmentI al, String title,
            FileFormatI currentFileFormat,
            FeatureSettingsModelI preferredFeatureColours)
    {
            }
          }
  
 -        af.getViewport().applyFeaturesStyle(preferredFeatureColours);
 +        if (preferredFeatureColours != null)
 +        {
 +          af.getViewport().applyFeaturesStyle(preferredFeatureColours);
 +        }
          if (Cache.getDefault("HIDE_INTRONS", true))
          {
            af.hideFeatureColumns(SequenceOntologyI.EXON, false);
        @Override
        public void run()
        {
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop, error,
 +        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(), error,
                  MessageManager.getString("label.error_retrieving_data"),
                  JvOptionPane.WARNING_MESSAGE);
        }
   */
  package jalview.gui;
  
++
  import java.awt.BorderLayout;
  import java.awt.Color;
+ import java.awt.Component;
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.Graphics;
  import java.awt.Image;
  import java.awt.MediaTracker;
- import java.awt.Toolkit;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  
  import javax.swing.JInternalFrame;
+ import javax.swing.JLabel;
  import javax.swing.JLayeredPane;
  import javax.swing.JPanel;
  import javax.swing.JTextPane;
  import javax.swing.event.HyperlinkEvent;
  import javax.swing.event.HyperlinkListener;
  
+ import jalview.util.ChannelProperties;
  import jalview.util.Platform;
- import javajs.async.SwingJSUtils.StateHelper;
- import javajs.async.SwingJSUtils.StateMachine;
--
  /**
   * DOCUMENT ME!
   * 
   * @author $author$
   * @version $Revision$
   */
 +@SuppressWarnings("serial")
  public class SplashScreen extends JPanel
 -        implements Runnable, HyperlinkListener
 +        implements HyperlinkListener, StateMachine
  {
 +  
 +  private static final int STATE_INIT = 0;
 +
 +  private static final int STATE_LOOP = 1;
 +
 +  private static final int STATE_DONE = 2;
 +
    private static final int SHOW_FOR_SECS = 5;
  
-   private int FONT_SIZE = (Platform.isJS() ? 14 : 11);
 -  private static final int FONT_SIZE = 11;
++  private static final int FONT_SIZE = (Platform.isJS() ? 14 : 11);
+   private boolean visible = true;
+   private JPanel iconimg = new JPanel(new BorderLayout());
+   // could change fg, bg, font later to use ChannelProperties (these are not
+   // actually being used!)
+   private static Color bg = Color.WHITE;
+   private static Color fg = Color.BLACK;
  
-   private JPanel imgPanel = new JPanel(new BorderLayout());
 -  private static Font font = new Font("SansSerif", Font.PLAIN, FONT_SIZE);
 -
+   /*
+    * as JTextPane in Java, JLabel in javascript
+    */
+   private Component splashText;
  
    private JInternalFrame iframe;
  
-   private Image image, logo;
+   private Image image;
  
-   protected boolean isStartup = false;
+   private boolean transientDialog = false;
  
    private long oldTextLength = -1;
  
-   private StateHelper helper;
 -  public static int logoSize = 32;
 -
    /*
     * allow click in the initial splash screen to dismiss it
     * immediately (not if opened from About menu)
      @Override
      public void mousePressed(MouseEvent evt)
      {
-       if (isStartup)
+       if (transientDialog)
        {
          try
          {
 -          visible = false;
            closeSplash();
          } catch (Exception ex)
          {
    /**
     * Constructor that displays the splash screen
     * 
 -   * @param isTransient
 +   * @param isStartup
     *          if true the panel removes itself on click or after a few seconds;
 -   *          if false it stays up until closed by the user
 +   *          if false it stays up until closed by the user (from Help..About menu)
     */
 -  public SplashScreen(boolean isTransient)
 +  public SplashScreen(boolean isStartup)
    {
-     this.isStartup = isStartup;
 -    this.transientDialog = isTransient;
++    this.transientDialog = isStartup;
 +    // we must get the image in JavaScript BEFORE starting the helper,
 +    // as it will take a 1 ms clock tick to obtain width and height information.
-     image = Toolkit.getDefaultToolkit().createImage(
-             getClass().getResource("/images/Jalview_Logo.png"));
++    image = ChannelProperties.getImage("banner");
++    logo = ChannelProperties.getImage("logo.48");
++    font = new Font("SansSerif", Font.PLAIN, FONT_SIZE);
 +    helper = new StateHelper(this);
 +    helper.next(STATE_INIT);
 +  }
  
 -    if (Platform.isJS()) // BH 2019
 -    {
 -      splashText = new JLabel("");
 -      run();
 -    }
 -    else
 -    {
 -      /**
 -       * Java only
 -       *
 -       * @j2sIgnore
 -       */
 -      {
 -        splashText = new JTextPane();
 -        splashText.setBackground(bg);
 -        splashText.setForeground(fg);
 -        splashText.setFont(font);
 -        Thread t = new Thread(this);
 -        t.start();
 -      }
 -    }
 +  protected void initSplashScreenWindow()
 +  {
 +    addMouseListener(closer);
 +    waitForImages();
 +    setLayout(new BorderLayout());
 +    iframe = new JInternalFrame();
 +    iframe.setFrameIcon(null);
 +    iframe.setClosable(true);
 +    iframe.setContentPane(this);
 +    iframe.setLayer(JLayeredPane.PALETTE_LAYER);  
 +    SplashImage splashimg = new SplashImage(image);
 +    imgPanel.add(splashimg, BorderLayout.CENTER);
 +    add(imgPanel, BorderLayout.NORTH);
 +    Desktop.getDesktopPane().add(iframe);
 +    refreshText();
    }
  
    /**
 -   * ping the jalview version page then create and display the jalview
 -   * splashscreen window.
 +   * Both Java and JavaScript have to wait for images, but this method will
 +   * accomplish nothing for JavaScript. We have already taken care of image
 +   * loading with our state loop in JavaScript.
 +   * 
     */
 -  void initSplashScreenWindow()
 +  private void waitForImages()
    {
 -    addMouseListener(closer);
 -
 -    try
 +    if (Platform.isJS())
 +      return;
 +    MediaTracker mt = new MediaTracker(this);
-     try
-     {
-       mt.addImage(image, 0);
-       logo = Toolkit.getDefaultToolkit().createImage(
-               getClass().getResource("/images/Jalview_Logo_small.png"));
-     } catch (Exception ex)
-     {
-     }
-     if (logo != null)
-     {
-       mt.addImage(logo, 1);
-     }
++    mt.addImage(image, 0);
++    mt.addImage(logo, 1);
 +    do
      {
 -      if (!Platform.isJS())
 +      try
 +      {
 +        mt.waitForAll();
 +      } catch (InterruptedException x)
        {
 -        image = ChannelProperties.getImage("banner");
 -        Image logo = ChannelProperties.getImage("logo.48");
 -        MediaTracker mt = new MediaTracker(this);
 -        if (image != null)
 -        {
 -          mt.addImage(image, 0);
 -        }
 -        if (logo != null)
 -        {
 -          mt.addImage(logo, 1);
 -        }
 -        do
 -        {
 -          try
 -          {
 -            mt.waitForAll();
 -          } catch (InterruptedException x)
 -          {
 -          }
 -          if (mt.isErrorAny())
 -          {
 -            System.err.println("Error when loading images!");
 -          }
 -        } while (!mt.checkAll());
 -        Desktop.instance.setIconImages(ChannelProperties.getIconList());
        }
 -    } catch (Exception ex)
 +      if (mt.isErrorAny())
 +      {
 +        System.err.println("Error when loading images!");
 +        break;
 +      }
 +    } while (!mt.checkAll());
 +    if (logo != null)
      {
 +      Desktop.getInstance().setIconImage(logo);
      }
 -
+     this.setBackground(bg);
+     this.setForeground(fg);
+     this.setFont(font);
 +  }
  
 -    iframe = new JInternalFrame();
 -    iframe.setFrameIcon(null);
 -    iframe.setClosable(true);
 -    this.setLayout(new BorderLayout());
 -    iframe.setContentPane(this);
 -    iframe.setLayer(JLayeredPane.PALETTE_LAYER);
 -    iframe.setBackground(bg);
 -    iframe.setForeground(fg);
 -    iframe.setFont(font);
 -
 -    if (Platform.isJS())
 +  /**
 +   * update text in author text panel reflecting current version information
 +   */
 +  protected boolean refreshText()
 +  {
 +    String newtext = Desktop.getInstance().getAboutMessage();
++    // System.err.println("Text found: \n"+newtext+"\nEnd of newtext.");
 +    if (oldTextLength == newtext.length())
      {
 -      // ignore in JavaScript
 +      return false;
 +    }
-     oldTextLength = newtext.length();
++  
 +    iframe.setVisible(false);
-     JTextPane jtp = new JTextPane();
-     jtp.setEditable(false);
-     jtp.setContentType("text/html");
-     jtp.setText("<html>" + newtext + "</html>");
-     jtp.addHyperlinkListener(this);
-     jtp.setFont(new Font("Verdana", Font.PLAIN, FONT_SIZE));
-     jtp.addMouseListener(closer);
-     jtp.setVisible(true);
-     jtp.setSize(new Dimension(750, 425));
-     add(jtp, BorderLayout.CENTER);
++    oldTextLength = newtext.length();
++    if (Platform.isJS()) // BH 2019
++    {
++      /*
++       * SwingJS doesn't have HTMLEditorKit, required for a JTextPane
++       * to display formatted html, so we use a simple alternative
++       */
++      String text = "<html><br><img src=\""
++              + ChannelProperties.getImageURL("banner") + "\"/>" + newtext
++              + "<br></html>";
++      JLabel ta = new JLabel(text);
++      ta.setOpaque(true);
++      ta.setBackground(Color.white);
++      splashText = ta;
+     }
+     else
+     /**
+      * Java only
 -     * 
++     *
+      * @j2sIgnore
+      */
+     {
 -      ((JTextPane) splashText).setEditable(false);
 -      splashText.setBackground(bg);
 -      splashText.setForeground(fg);
 -      splashText.setFont(font);
 -
 -      SplashImage splashimg = new SplashImage(image);
 -      iconimg.add(splashimg, BorderLayout.LINE_START);
 -      iconimg.setBackground(bg);
 -      add(iconimg, BorderLayout.NORTH);
++      JTextPane jtp = new JTextPane();
++      jtp.setEditable(false);
++      jtp.setBackground(bg);
++      jtp.setForeground(fg);
++      jtp.setFont(font);
++      jtp.setContentType("text/html");
++      jtp.setText("<html>" + newtext + "</html>");
++      jtp.addHyperlinkListener(this);
++      splashText = jtp;
+     }
 -    add(splashText, BorderLayout.CENTER);
+     splashText.addMouseListener(closer);
 -    Desktop.desktop.add(iframe);
 -    refreshText();
++
++    splashText.setVisible(true);
++    splashText.setSize(new Dimension(750,
++            375 + logoSize + (Platform.isJS() ? 40 : 0)));
++    splashText.setBackground(bg);
++    splashText.setForeground(fg);
++    splashText.setFont(font);
++    add(splashText, BorderLayout.CENTER);
 +    revalidate();
-     int h = jtp.getHeight() + imgPanel.getHeight();
-     iframe.setBounds(Math.max(0, (iframe.getParent().getWidth() - 750) / 2),
-            Math.max(0,  (iframe.getParent().getHeight() - h)/2), 750, h);
++    int width = Math.max(splashText.getWidth(), iconimg.getWidth());
++    int height = splashText.getHeight() + iconimg.getHeight();
++    iframe.setBounds((iframe.getParent().getWidth() - width) / 2,
++            (iframe.getParent().getHeight() - height) / 2, 750,
++            width,height);
 +    iframe.validate();
 +    iframe.setVisible(true);
 +    return true;
    }
  
 -  /**
 -   * update text in author text panel reflecting current version information
 -   */
 -  protected boolean refreshText()
 +  protected void closeSplash()
    {
 -    String newtext = Desktop.instance.getAboutMessage();
 -    // System.err.println("Text found: \n"+newtext+"\nEnd of newtext.");
 -    if (oldTextLength != newtext.length())
 +    try
 +    {
 +
 +      iframe.setClosed(true);
 +    } catch (Exception ex)
      {
 -      iframe.setVisible(false);
 -      oldTextLength = newtext.length();
 -      if (Platform.isJS()) // BH 2019
 -      {
 -        /*
 -         * SwingJS doesn't have HTMLEditorKit, required for a JTextPane
 -         * to display formatted html, so we use a simple alternative
 -         */
 -        String text = "<html><br><img src=\""
 -                + ChannelProperties.getImageURL("banner") + "\"/>" + newtext
 -                + "<br></html>";
 -        JLabel ta = new JLabel(text);
 -        ta.setOpaque(true);
 -        ta.setBackground(Color.white);
 -        splashText = ta;
 -      }
 -      else
 -      /**
 -       * Java only
 -       *
 -       * @j2sIgnore
 -       */
 -      {
 -        JTextPane jtp = new JTextPane();
 -        jtp.setEditable(false);
 -        jtp.setBackground(bg);
 -        jtp.setForeground(fg);
 -        jtp.setFont(font);
 -        jtp.setContentType("text/html");
 -        jtp.setText("<html>" + newtext + "</html>");
 -        jtp.addHyperlinkListener(this);
 -        splashText = jtp;
 -      }
 -      splashText.addMouseListener(closer);
 -
 -      splashText.setVisible(true);
 -      splashText.setSize(new Dimension(750,
 -              375 + logoSize + (Platform.isJS() ? 40 : 0)));
 -      splashText.setBackground(bg);
 -      splashText.setForeground(fg);
 -      splashText.setFont(font);
 -      add(splashText, BorderLayout.CENTER);
 -      revalidate();
 -      int width = Math.max(splashText.getWidth(), iconimg.getWidth());
 -      int height = splashText.getHeight() + iconimg.getHeight();
 -      iframe.setBounds(
 -              Math.max(0, (Desktop.instance.getWidth() - width) / 2),
 -              Math.max(0, (Desktop.instance.getHeight() - height) / 2),
 -              width, height);
 -      iframe.validate();
 -      iframe.setVisible(true);
 -      return true;
      }
    }
  
    /**
 -   * Create splash screen, display it and clear it off again.
 +   * A simple state machine with just three states: init, loop, and done. Ideal
 +   * for a simple while/sleep loop that works in Java and JavaScript
 +   * identically.
 +   * 
     */
    @Override
 -  public void run()
 +  public boolean stateLoop()
    {
 -    initSplashScreenWindow();
 -
 -    long startTime = System.currentTimeMillis() / 1000;
 -
 -    while (visible)
 +    while (true)
      {
 -      iframe.repaint();
 -      try
 +      switch (helper.getState())
        {
 -        Thread.sleep(500);
 -      } catch (Exception ex)
 -      {
 -      }
 -
 -      if (transientDialog && ((System.currentTimeMillis() / 1000)
 -              - startTime) > SHOW_FOR_SECS)
 -      {
 -        visible = false;
 -      }
 -
 -      if (visible && refreshText())
 -      {
 -        iframe.repaint();
 -      }
 -      if (!transientDialog)
 -      {
 -        return;
 +      case STATE_INIT:
 +        initSplashScreenWindow();
 +        helper.setState(STATE_LOOP);
 +        continue;
 +      case STATE_LOOP:
 +        if (!isVisible())
 +        {
 +          helper.setState(STATE_DONE);
 +          continue;
 +        }
 +        if (refreshText())
 +        {
 +          iframe.repaint();
 +        }
 +        if (isStartup)
 +          helper.delayedState(SHOW_FOR_SECS * 1000, STATE_DONE);
 +        return true;
 +      default:
 +      case STATE_DONE:
 +        setVisible(false);
 +        closeSplash();
 +        Desktop.getInstance().startDialogQueue();
 +        return true;
        }
      }
 -
 -    closeSplash();
 -    Desktop.instance.startDialogQueue();
 -  }
 -
 -  /**
 -   * DOCUMENT ME!
 -   */
 -  public void closeSplash()
 -  {
 -    try
 -    {
 -
 -      iframe.setClosed(true);
 -    } catch (Exception ex)
 -    {
 -    }
    }
  
 -  public class SplashImage extends JPanel
 +  private class SplashImage extends JPanel
    {
      Image image;
  
      @Override
      public void paintComponent(Graphics g)
      {
-       g.setColor(Color.white);
+       g.setColor(bg);
        g.fillRect(0, 0, getWidth(), getHeight());
-       g.setColor(Color.black);
+       g.setColor(fg);
+       g.setFont(new Font(font.getFontName(), Font.BOLD, FONT_SIZE + 6));
  
        if (image != null)
        {
   */
  package jalview.gui;
  
 +import jalview.api.AlignViewportI;
 +import jalview.api.AlignViewControllerGuiI;
 +import jalview.api.FeatureSettingsControllerI;
 +import jalview.api.SplitContainerI;
 +import jalview.controller.FeatureSettingsControllerGuiI;
 +import jalview.datamodel.AlignmentI;
 +import jalview.jbgui.GAlignFrame;
 +import jalview.jbgui.GSplitFrame;
 +import jalview.structure.StructureSelectionManager;
 +import jalview.util.MessageManager;
 +import jalview.util.Platform;
 +import jalview.viewmodel.AlignmentViewport;
 +
  import java.awt.BorderLayout;
  import java.awt.Component;
  import java.awt.Dimension;
@@@ -62,6 -49,18 +62,6 @@@ import javax.swing.event.ChangeListener
  import javax.swing.event.InternalFrameAdapter;
  import javax.swing.event.InternalFrameEvent;
  
 -import jalview.api.AlignViewControllerGuiI;
 -import jalview.api.FeatureSettingsControllerI;
 -import jalview.api.SplitContainerI;
 -import jalview.controller.FeatureSettingsControllerGuiI;
 -import jalview.datamodel.AlignmentI;
 -import jalview.jbgui.GAlignFrame;
 -import jalview.jbgui.GSplitFrame;
 -import jalview.structure.StructureSelectionManager;
 -import jalview.util.MessageManager;
 -import jalview.util.Platform;
 -import jalview.viewmodel.AlignmentViewport;
 -
  /**
   * An internal frame on the desktop that hosts a horizontally split view of
   * linked DNA and Protein alignments. Additional views can be created in linked
@@@ -153,7 -152,7 +153,7 @@@ public class SplitFrame extends GSplitF
      // allow about 65 pixels for Desktop decorators on Windows
  
      int newHeight = Math.min(height,
 -            Desktop.instance.getHeight() - DESKTOP_DECORATORS_HEIGHT);
 +            Desktop.getInstance().getHeight() - DESKTOP_DECORATORS_HEIGHT);
      if (newHeight != height)
      {
        int oldDividerLocation = getDividerLocation();
      // TODO if CommandListener is only ever 1:1 for complementary views,
      // may change broadcast pattern to direct messaging (more efficient)
      final StructureSelectionManager ssm = StructureSelectionManager
 -            .getStructureSelectionManager(Desktop.instance);
 +            .getStructureSelectionManager(Desktop.getInstance());
      ssm.addCommandListener(((AlignFrame) getTopFrame()).getViewport());
      ssm.addCommandListener(((AlignFrame) getBottomFrame()).getViewport());
    }
      topFrame.alignPanel.adjustAnnotationHeight();
      bottomFrame.alignPanel.adjustAnnotationHeight();
  
 -    final AlignViewport topViewport = topFrame.viewport;
 -    final AlignViewport bottomViewport = bottomFrame.viewport;
 +    final AlignViewportI topViewport = topFrame.viewport;
 +    final AlignViewportI bottomViewport = bottomFrame.viewport;
      final AlignmentI topAlignment = topViewport.getAlignment();
      final AlignmentI bottomAlignment = bottomViewport.getAlignment();
      boolean topAnnotations = topViewport.isShowAnnotation();
       * Ctrl-W / Cmd-W - close view or window
       */
      KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W,
 -            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
 +            Platform.SHORTCUT_KEY_MASK, false);
      action = new AbstractAction()
      {
        @Override
       * Ctrl-T / Cmd-T open new view
       */
      KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T,
 -            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
 +            Platform.SHORTCUT_KEY_MASK, false);
      AbstractAction action = new AbstractAction()
      {
        @Override
      adjustLayout();
  
      final StructureSelectionManager ssm = StructureSelectionManager
 -            .getStructureSelectionManager(Desktop.instance);
 +            .getStructureSelectionManager(Desktop.getInstance());
      ssm.addCommandListener(newTopPanel.av);
      ssm.addCommandListener(newBottomPanel.av);
    }
     */
    protected void expandViews_actionPerformed()
    {
 -    Desktop.instance.explodeViews(this);
 +    Desktop.getInstance().explodeViews(this);
    }
  
    /**
     */
    protected void gatherViews_actionPerformed()
    {
 -    Desktop.instance.gatherViews(this);
 +    Desktop.getInstance().gatherViews(this);
    }
  
    /**
       * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
       */
      KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F,
 -            jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
 +            Platform.SHORTCUT_KEY_MASK, false);
      AbstractAction action = new AbstractAction()
      {
        @Override
          if (c != null && c instanceof AlignFrame)
          {
            AlignFrame af = (AlignFrame) c;
-           new Finder(af.viewport, af.alignPanel);
+           boolean dna = af.getViewport().getAlignment().isNucleotide();
+           String scope = MessageManager.getString("label.in") + " "
+                   + (dna ? MessageManager.getString("label.nucleotide")
+                           : MessageManager.getString("label.protein"));
+           new Finder(af.alignPanel, true, scope);
          }
        }
      };
    {
      return featureSettingsUI != null && !featureSettingsUI.isClosed();
    }
 -}
 +}
  
  package jalview.gui;
  
+ import java.awt.event.ActionEvent;
+ import java.awt.event.ActionListener;
  import java.awt.event.ItemEvent;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashSet;
  import java.util.LinkedHashSet;
  import java.util.List;
- import java.util.Objects;
- import java.util.Set;
- import java.util.Vector;
+ import java.util.Locale;
+ import java.util.concurrent.Executors;
  
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
  import javax.swing.JLabel;
+ import javax.swing.JMenu;
+ import javax.swing.JMenuItem;
+ import javax.swing.JPopupMenu;
  import javax.swing.JTable;
  import javax.swing.SwingUtilities;
  import javax.swing.table.AbstractTableModel;
  import jalview.api.structures.JalviewStructureDisplayI;
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
- import jalview.datamodel.DBRefEntry;
- import jalview.datamodel.DBRefSource;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
  import jalview.fts.api.FTSData;
  import jalview.fts.api.FTSDataColumnI;
  import jalview.fts.api.FTSRestClientI;
+ import jalview.fts.core.FTSDataColumnPreferences;
  import jalview.fts.core.FTSRestRequest;
  import jalview.fts.core.FTSRestResponse;
  import jalview.fts.service.pdb.PDBFTSRestClient;
+ import jalview.fts.service.threedbeacons.TDB_FTSData;
+ import jalview.gui.structurechooser.PDBStructureChooserQuerySource;
+ import jalview.gui.structurechooser.StructureChooserQuerySource;
+ import jalview.gui.structurechooser.ThreeDBStructureChooserQuerySource;
  import jalview.io.DataSourceType;
+ import jalview.jbgui.FilterOption;
  import jalview.jbgui.GStructureChooser;
+ import jalview.structure.StructureMapping;
+ import jalview.structure.StructureSelectionManager;
  import jalview.util.MessageManager;
+ import jalview.ws.DBRefFetcher;
+ import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
+ import jalview.ws.seqfetcher.DbSourceProxy;
+ import jalview.ws.sifts.SiftsSettings;
  
  /**
   * Provides the behaviors for the Structure chooser Panel
@@@ -65,9 -79,7 +78,7 @@@
  public class StructureChooser extends GStructureChooser
          implements IProgressIndicator
  {
-   static final String AUTOSUPERIMPOSE = "AUTOSUPERIMPOSE";
-   private static int MAX_QLENGTH = 7820;
+   private static final String AUTOSUPERIMPOSE = "AUTOSUPERIMPOSE";
  
    private SequenceI selectedSequence;
  
  
    private Collection<FTSData> discoveredStructuresSet;
  
-   private FTSRestRequest lastPdbRequest;
+   private StructureChooserQuerySource data;
  
-   private FTSRestClientI pdbRestClient;
+   @Override
+   protected FTSDataColumnPreferences getFTSDocFieldPrefs()
+   {
+     return data.getDocFieldPrefs();
+   }
  
    private String selectedPdbFileName;
  
  
    private boolean cachedPDBExists;
  
-   static StructureViewer lastTargetedView = null;
+   private Collection<FTSData> lastDiscoveredStructuresSet;
+   private boolean canQueryTDB = false;
+   private boolean notQueriedTDBYet = true;
+   List<SequenceI> seqsWithoutSourceDBRef = null;
+   private static StructureViewer lastTargetedView = null;
  
    public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq,
            AlignmentPanel ap)
    {
+     // which FTS engine to use
+     data = StructureChooserQuerySource.getQuerySourceFor(selectedSeqs);
+     initDialog();
 -
      this.ap = ap;
      this.selectedSequence = selectedSeq;
      this.selectedSequences = selectedSeqs;
      this.progressIndicator = (ap == null) ? null : ap.alignFrame;
      init();
    }
  
    /**
+    * sets canQueryTDB if protein sequences without a canonical uniprot ref or at
+    * least one structure are discovered.
+    */
+   private void populateSeqsWithoutSourceDBRef()
+   {
+     seqsWithoutSourceDBRef = new ArrayList<SequenceI>();
+     boolean needCanonical = false;
+     for (SequenceI seq : selectedSequences)
+     {
+       if (seq.isProtein())
+       {
+         int dbRef = ThreeDBStructureChooserQuerySource
+                 .checkUniprotRefs(seq.getDBRefs());
+         if (dbRef < 0)
+         {
+           if (dbRef == -1)
+           {
+             // need to retrieve canonicals
+             needCanonical = true;
+             seqsWithoutSourceDBRef.add(seq);
+           }
+           else
+           {
+             // could be a sequence with pdb ref
+             if (seq.getAllPDBEntries() == null
+                     || seq.getAllPDBEntries().size() == 0)
+             {
+               seqsWithoutSourceDBRef.add(seq);
+             }
+           }
+         }
+       }
+     }
+     // retrieve database refs for protein sequences
+     if (!seqsWithoutSourceDBRef.isEmpty())
+     {
+       canQueryTDB = true;
+       if (needCanonical)
+       {
+         // triggers display of the 'Query TDB' button
+         notQueriedTDBYet = true;
+       }
+     }
+   };
+   /**
     * Initializes parameters used by the Structure Chooser Panel
     */
    protected void init()
      }
  
      chk_superpose.setSelected(Cache.getDefault(AUTOSUPERIMPOSE, true));
+     btn_queryTDB.addActionListener(new ActionListener()
+     {
+       @Override
+       public void actionPerformed(ActionEvent e)
+       {
+         promptForTDBFetch(false);
+       }
+     });
+     Executors.defaultThreadFactory().newThread(new Runnable()
+     {
+       public void run()
+       {
+         populateSeqsWithoutSourceDBRef();
+         initialStructureDiscovery();
+       }
+     }).start();
+   }
+   // called by init
+   private void initialStructureDiscovery()
+   {
+     // check which FTS engine to use
+     data = StructureChooserQuerySource.getQuerySourceFor(selectedSequences);
  
      // ensure a filter option is in force for search
      populateFilterComboBox(true, cachedPDBExists);
-     Thread discoverPDBStructuresThread = new Thread(new Runnable()
 -
+     // looks for any existing structures already loaded
+     // for the sequences (the cached ones)
+     // then queries the StructureChooserQuerySource to
+     // discover more structures.
+     //
+     // Possible optimisation is to only begin querying
+     // the structure chooser if there are no cached structures.
+     long startTime = System.currentTimeMillis();
+     updateProgressIndicator(
+             MessageManager.getString("status.loading_cached_pdb_entries"),
+             startTime);
+     loadLocalCachedPDBEntries();
+     updateProgressIndicator(null, startTime);
+     updateProgressIndicator(
+             MessageManager.getString("status.searching_for_pdb_structures"),
+             startTime);
+     fetchStructuresMetaData();
+     // revise filter options if no results were found
+     populateFilterComboBox(isStructuresDiscovered(), cachedPDBExists);
+     discoverStructureViews();
+     updateProgressIndicator(null, startTime);
+     mainFrame.setVisible(true);
+     updateCurrentView();
+   }
+   /**
+    * raises dialog for Uniprot fetch followed by 3D beacons search
+    * @param ignoreGui - when true, don't ask, just fetch 
+    */
+   public void promptForTDBFetch(boolean ignoreGui)
+   {
+     final long progressId = System.currentTimeMillis();
+     // final action after prompting and discovering db refs
+     final Runnable strucDiscovery = new Runnable()
      {
        @Override
        public void run()
        {
-         long startTime = System.currentTimeMillis();
-         updateProgressIndicator(MessageManager
-                 .getString("status.loading_cached_pdb_entries"), startTime);
-         loadLocalCachedPDBEntries();
-         updateProgressIndicator(null, startTime);
-         updateProgressIndicator(MessageManager.getString(
-                 "status.searching_for_pdb_structures"), startTime);
-         fetchStructuresMetaData();
-         // revise filter options if no results were found
-         populateFilterComboBox(isStructuresDiscovered(), cachedPDBExists);
-         discoverStructureViews();
-         updateProgressIndicator(null, startTime);
-         mainFrame.setVisible(true);
-         updateCurrentView();
+         mainFrame.setEnabled(false);
+         cmb_filterOption.setEnabled(false);
+         progressBar.setProgressBar(MessageManager.getString("status.searching_3d_beacons"), progressId);
+         // TODO: warn if no accessions discovered
+         populateSeqsWithoutSourceDBRef();
+         // redo initial discovery - this time with 3d beacons
+         // Executors.
+         previousWantedFields=null;
+         lastSelected=(FilterOption) cmb_filterOption.getSelectedItem();
+         cmb_filterOption.setSelectedItem(null);
+         cachedPDBExists=false; // reset to initial
+         initialStructureDiscovery();
+         if (!isStructuresDiscovered())
+         {
+           progressBar.setProgressBar(MessageManager.getString("status.no_structures_discovered_from_3d_beacons"), progressId);
+           btn_queryTDB.setToolTipText(MessageManager.getString("status.no_structures_discovered_from_3d_beacons"));
+           btn_queryTDB.setEnabled(false);
+         } else {
+           cmb_filterOption.setSelectedIndex(0); // select 'best'
+           btn_queryTDB.setVisible(false);
+           progressBar.setProgressBar(null, progressId);
+         }
+         mainFrame.setEnabled(true);
+         cmb_filterOption.setEnabled(true);
        }
-     });
-     discoverPDBStructuresThread.start();
+     };
+     final FetchFinishedListenerI afterDbRefFetch = new FetchFinishedListenerI()
+     {
+       
+       @Override
+       public void finished()
+       {
+         // filter has been selected, so we set flag to remove ourselves
+         notQueriedTDBYet = false;
+         // new thread to discover structures - via 3d beacons
+         Executors.defaultThreadFactory().newThread(strucDiscovery).start();
+         
+       }
+     };
+     
+     // fetch db refs if OK pressed
+     final Runnable discoverCanonicalDBrefs = new Runnable() 
+     {
+       @Override
+       public void run()
+       {
+         populateSeqsWithoutSourceDBRef();
+         final int y = seqsWithoutSourceDBRef.size();
+         if (y > 0)
+         {
+           final SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
+                   .toArray(new SequenceI[y]);
+           DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef,
+                   progressBar, new DbSourceProxy[]
+                   { new jalview.ws.dbsources.Uniprot() }, null, false);
+           dbRefFetcher.addListener(afterDbRefFetch);
+           // ideally this would also gracefully run with callbacks
+           dbRefFetcher.fetchDBRefs(true);
+         } else {
+           // call finished action directly
+           afterDbRefFetch.finished();
+         }
+       }
+     };
+     final Runnable revertview = new Runnable() {
+       public void run() {
+         if (lastSelected!=null) {
+           cmb_filterOption.setSelectedItem(lastSelected);
+         }
+       };
+     };
+     if (ignoreGui)
+     {
+       Executors.defaultThreadFactory().newThread(discoverCanonicalDBrefs).start();
+       return;
+     }
+     // need cancel and no to result in the discoverPDB action - mocked is
+     // 'cancel' TODO: mock should be OK
+     JvOptionPane.newOptionDialog(this)
+             .setResponseHandler(JvOptionPane.OK_OPTION,
+                     discoverCanonicalDBrefs)
+             .setResponseHandler(JvOptionPane.CANCEL_OPTION, revertview)
+             .setResponseHandler(JvOptionPane.NO_OPTION, revertview)
+             .showDialog(
+                     MessageManager.formatMessage(
+                             "label.fetch_references_for_3dbeacons",
+                             seqsWithoutSourceDBRef.size()),
+                     MessageManager
+                             .getString("label.3dbeacons"),
+                     JvOptionPane.YES_NO_OPTION, JvOptionPane.PLAIN_MESSAGE,
+                     null, new Object[]
+                     { MessageManager.getString("action.ok"),
+                         MessageManager.getString("action.cancel") },
+                     MessageManager.getString("action.ok"));
    }
  
    /**
     */
    private void discoverStructureViews()
    {
 -    if (Desktop.instance != null)
 +    if (Desktop.getInstance() != null)
      {
        targetView.removeAllItems();
        if (lastTargetedView != null && !lastTargetedView.isVisible())
          lastTargetedView = null;
        }
        int linkedViewsAt = 0;
 -      for (StructureViewerBase view : Desktop.instance
 +      for (StructureViewerBase view : Desktop.getInstance()
                .getStructureViewers(null, null))
        {
          StructureViewer viewHandler = (lastTargetedView != null
    void fetchStructuresMetaData()
    {
      long startTime = System.currentTimeMillis();
-     pdbRestClient = PDBFTSRestClient.getInstance();
-     Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
+     Collection<FTSDataColumnI> wantedFields = data.getDocFieldPrefs()
              .getStructureSummaryFields();
  
      discoveredStructuresSet = new LinkedHashSet<>();
      HashSet<String> errors = new HashSet<>();
 -
+     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
+             .getSelectedItem());
 -
      for (SequenceI seq : selectedSequences)
      {
        FTSRestResponse resultList;
        try
        {
-         resultList = pdbRestClient.executeRequest(pdbRequest);
+         resultList = data.fetchStructuresMetaData(seq, wantedFields,
+                 selectedFilterOpt, !chk_invertFilter.isSelected());
+         // null response means the FTSengine didn't yield a query for this
+         // consider designing a special exception if we really wanted to be
+         // OOCrazy
+         if (resultList == null)
+         {
+           continue;
+         }
        } catch (Exception e)
        {
          e.printStackTrace();
          errors.add(e.getMessage());
          continue;
        }
-       lastPdbRequest = pdbRequest;
        if (resultList.getSearchSummary() != null
                && !resultList.getSearchSummary().isEmpty())
        {
      if (discoveredStructuresSet != null
              && !discoveredStructuresSet.isEmpty())
      {
-       getResultTable().setModel(FTSRestResponse
-               .getTableModel(lastPdbRequest, discoveredStructuresSet));
+       getResultTable()
+               .setModel(data.getTableModel(discoveredStructuresSet));
 -
        noOfStructuresFound = discoveredStructuresSet.size();
+       lastDiscoveredStructuresSet = discoveredStructuresSet;
        mainFrame.setTitle(MessageManager.formatMessage(
                "label.structure_chooser_no_of_structures",
                noOfStructuresFound, totalTime));
    }
  
    /**
-    * Builds a query string for a given sequences using its DBRef entries
-    * 
-    * @param seq
-    *          the sequences to build a query for
-    * @return the built query string
-    */
-   static String buildQuery(SequenceI seq)
-   {
-     boolean isPDBRefsFound = false;
-     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)
-     {
-       for (PDBEntry entry : seq.getAllPDBEntries())
-       {
-         if (isValidSeqName(entry.getId()))
-         {
-           String id = entry.getId().toLowerCase();
-           queryBuilder.append("pdb_id:").append(id).append(" OR ");
-           isPDBRefsFound = true;
-           pdbids.add(id);
-         }
-       }
-     }
-     List<DBRefEntry> refs = seq.getDBRefs();
-     if (refs != null && refs.size() != 0)
-     {
-       for (int ib = 0, nb = refs.size(); ib < nb; ib++)
-       {
-         DBRefEntry dbRef = refs.get(ib);
-         if (isValidSeqName(getDBRefId(dbRef))
-                 && queryBuilder.length() < MAX_QLENGTH)
-         {
-           if (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT))
-           {
-             queryBuilder.append("uniprot_accession:")
-                     .append(getDBRefId(dbRef)).append(" OR ");
-             queryBuilder.append("uniprot_id:").append(getDBRefId(dbRef))
-                     .append(" OR ");
-             isUniProtRefsFound = true;
-           }
-           else if (dbRef.getSource().equalsIgnoreCase(DBRefSource.PDB))
-           {
-             String id = getDBRefId(dbRef).toLowerCase();
-             if (!pdbids.contains(id))
-             {
-               queryBuilder.append("pdb_id:").append(id).append(" OR ");
-               isPDBRefsFound = true;
-               pdbids.add(id);
-             }
-           }
-           else
-           {
-             seqRefs.add(getDBRefId(dbRef));
-           }
-         }
-       }
-     }
-     if (!isPDBRefsFound && !isUniProtRefsFound)
-     {
-       String seqName = seq.getName();
-       seqName = sanitizeSeqName(seqName);
-       String[] names = seqName.toLowerCase().split("\\|");
-       for (String name : names)
-       {
-         // System.out.println("Found name : " + name);
-         name.trim();
-         if (isValidSeqName(name))
-         {
-           seqRefs.add(name);
-         }
-       }
-       for (String seqRef : seqRefs)
-       {
-         queryBuilder.append("text:").append(seqRef).append(" OR ");
-       }
-     }
-     int endIndex = queryBuilder.lastIndexOf(" OR ");
-     if (queryBuilder.toString().length() < 6)
-     {
-       return null;
-     }
-     String query = queryBuilder.toString().substring(0, endIndex);
-     return query;
-   }
-   /**
-    * Remove the following special characters from input string +, -, &, !, (, ),
-    * {, }, [, ], ^, ", ~, *, ?, :, \
-    * 
-    * @param seqName
-    * @return
-    */
-   static String sanitizeSeqName(String seqName)
-   {
-     Objects.requireNonNull(seqName);
-     return seqName.replaceAll("\\[\\d*\\]", "")
-             .replaceAll("[^\\dA-Za-z|_]", "").replaceAll("\\s+", "+");
-   }
-   /**
-    * Ensures sequence ref names are not less than 3 characters and does not
-    * contain a database name
-    * 
-    * @param seqName
-    * @return
-    */
-   static boolean isValidSeqName(String seqName)
-   {
-     // System.out.println("seqName : " + seqName);
-     String ignoreList = "pdb,uniprot,swiss-prot";
-     if (seqName.length() < 3)
-     {
-       return false;
-     }
-     if (seqName.contains(":"))
-     {
-       return false;
-     }
-     seqName = seqName.toLowerCase();
-     for (String ignoredEntry : ignoreList.split(","))
-     {
-       if (seqName.contains(ignoredEntry))
-       {
-         return false;
-       }
-     }
-     return true;
-   }
-   static String getDBRefId(DBRefEntry dbRef)
-   {
-     String ref = dbRef.getAccessionId().replaceAll("GO:", "");
-     return ref;
-   }
-   /**
     * Filters a given list of discovered structures based on supplied argument
     * 
     * @param fieldToFilterBy
    {
      Thread filterThread = new Thread(new Runnable()
      {
 -
        @Override
        public void run()
        {
          long startTime = System.currentTimeMillis();
-         pdbRestClient = PDBFTSRestClient.getInstance();
          lbl_loading.setVisible(true);
-         Collection<FTSDataColumnI> wantedFields = pdbDocFieldPrefs
+         Collection<FTSDataColumnI> wantedFields = data.getDocFieldPrefs()
                  .getStructureSummaryFields();
          Collection<FTSData> filteredResponse = new HashSet<>();
          HashSet<String> errors = new HashSet<>();
  
          for (SequenceI seq : selectedSequences)
          {
            FTSRestResponse resultList;
            try
            {
-             resultList = pdbRestClient.executeRequest(pdbRequest);
+             resultList = data.selectFirstRankedQuery(seq,
+                     discoveredStructuresSet, wantedFields, fieldToFilterBy,
+                     !chk_invertFilter.isSelected());
 -
            } catch (Exception e)
            {
              e.printStackTrace();
              errors.add(e.getMessage());
              continue;
            }
-           lastPdbRequest = pdbRequest;
            if (resultList.getSearchSummary() != null
                    && !resultList.getSearchSummary().isEmpty())
            {
            Collection<FTSData> reorderedStructuresSet = new LinkedHashSet<>();
            reorderedStructuresSet.addAll(filteredResponse);
            reorderedStructuresSet.addAll(discoveredStructuresSet);
-           getResultTable().setModel(FTSRestResponse
-                   .getTableModel(lastPdbRequest, reorderedStructuresSet));
+           getResultTable()
+                   .setModel(data.getTableModel(reorderedStructuresSet));
  
            FTSRestResponse.configureTableColumn(getResultTable(),
                    wantedFields, tempUserPrefs);
    protected void populateFilterComboBox(boolean haveData,
            boolean cachedPDBExist)
    {
+     populateFilterComboBox(haveData, cachedPDBExist, null);
+   }
+   /**
+    * Populates the filter combo-box options dynamically depending on discovered
+    * structures
+    */
+   protected void populateFilterComboBox(boolean haveData,
+           boolean cachedPDBExist, FilterOption lastSel)
+   {
 -
      /*
       * temporarily suspend the change listener behaviour
       */
      cmb_filterOption.removeItemListener(this);
 +
+     int selSet = -1;
      cmb_filterOption.removeAllItems();
      if (haveData)
      {
-       cmb_filterOption.addItem(new FilterOption(
-               MessageManager.getString("label.best_quality"),
-               "overall_quality", VIEWS_FILTER, false));
-       cmb_filterOption.addItem(new FilterOption(
-               MessageManager.getString("label.best_resolution"),
-               "resolution", VIEWS_FILTER, false));
-       cmb_filterOption.addItem(new FilterOption(
-               MessageManager.getString("label.most_protein_chain"),
-               "number_of_protein_chains", VIEWS_FILTER, false));
-       cmb_filterOption.addItem(new FilterOption(
-               MessageManager.getString("label.most_bound_molecules"),
-               "number_of_bound_molecules", VIEWS_FILTER, false));
-       cmb_filterOption.addItem(new FilterOption(
-               MessageManager.getString("label.most_polymer_residues"),
-               "number_of_polymer_residues", VIEWS_FILTER, true));
+       List<FilterOption> filters = data
+               .getAvailableFilterOptions(VIEWS_FILTER);
+       data.updateAvailableFilterOptions(VIEWS_FILTER, filters,
+               lastDiscoveredStructuresSet);
+       int p = 0;
+       for (FilterOption filter : filters)
+       {
+         if (lastSel != null && filter.equals(lastSel))
+         {
+           selSet = p;
+         }
+         p++;
+         cmb_filterOption.addItem(filter);
+       }
      }
 -
      cmb_filterOption.addItem(
              new FilterOption(MessageManager.getString("label.enter_pdb_id"),
-                     "-", VIEWS_ENTER_ID, false));
+                     "-", VIEWS_ENTER_ID, false, null));
      cmb_filterOption.addItem(
              new FilterOption(MessageManager.getString("label.from_file"),
-                     "-", VIEWS_FROM_FILE, false));
+                     "-", VIEWS_FROM_FILE, false, null));
+     if (canQueryTDB && notQueriedTDBYet)
+     {
+       btn_queryTDB.setVisible(true);
+     }
  
      if (cachedPDBExist)
      {
        FilterOption cachedOption = new FilterOption(
                MessageManager.getString("label.cached_structures"), "-",
-               VIEWS_LOCAL_PDB, false);
+               VIEWS_LOCAL_PDB, false, null);
        cmb_filterOption.addItem(cachedOption);
-       cmb_filterOption.setSelectedItem(cachedOption);
+       if (selSet == -1)
+       {
+         cmb_filterOption.setSelectedItem(cachedOption);
+       }
      }
 +
+     if (selSet > -1)
+     {
+       cmb_filterOption.setSelectedIndex(selSet);
+     }
      cmb_filterOption.addItemListener(this);
    }
  
    {
      FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
              .getSelectedItem());
+     
+     if (lastSelected == selectedFilterOpt)
+     {
+       // don't need to do anything, probably
+       return;
+     }
+     // otherwise, record selection
+     // and update the layout and dialog accordingly
+     lastSelected = selectedFilterOpt;
 -
      layout_switchableViews.show(pnl_switchableViews,
              selectedFilterOpt.getView());
      String filterTitle = mainFrame.getTitle();
      mainFrame.setTitle(frameTitle);
      chk_invertFilter.setVisible(false);
+     
      if (selectedFilterOpt.getView() == VIEWS_FILTER)
      {
        mainFrame.setTitle(filterTitle);
-       chk_invertFilter.setVisible(true);
-       filterResultSet(selectedFilterOpt.getValue());
+       // TDB Query has no invert as yet
+       chk_invertFilter.setVisible(selectedFilterOpt
+               .getQuerySource() instanceof PDBStructureChooserQuerySource);
+       if (data != selectedFilterOpt.getQuerySource()
+               || data.needsRefetch(selectedFilterOpt))
+       {
+         data = selectedFilterOpt.getQuerySource();
+         // rebuild the views completely, since prefs will also change
+         tabRefresh();
+         return;
+       }
+       else
+       {
+         filterResultSet(selectedFilterOpt.getValue());
+       }
      }
      else if (selectedFilterOpt.getView() == VIEWS_ENTER_ID
              || selectedFilterOpt.getView() == VIEWS_FROM_FILE)
              .setEnabled(selectedCount > 1 || targetView.getItemCount() > 0);
    }
  
+   @Override
+   protected boolean showPopupFor(int selectedRow, int x, int y)
+   {
+     FilterOption selectedFilterOpt = ((FilterOption) cmb_filterOption
+             .getSelectedItem());
+     String currentView = selectedFilterOpt.getView();
+      
+     if (currentView == VIEWS_FILTER && data instanceof ThreeDBStructureChooserQuerySource)
+     {
+       
+       TDB_FTSData row=((ThreeDBStructureChooserQuerySource)data).getFTSDataFor(getResultTable(), selectedRow, discoveredStructuresSet);
+       String pageUrl = row.getModelViewUrl(); 
+       JPopupMenu popup = new JPopupMenu("3D Beacons");
+       JMenuItem viewUrl = new JMenuItem("View model web page");
+       viewUrl.addActionListener(
+               new ActionListener() {
+                 @Override
+                 public void actionPerformed(ActionEvent e)
+                 {
+                   Desktop.showUrl(pageUrl);
+                 }
+               }
+               );
+       popup.add(viewUrl);
+       SwingUtilities.invokeLater(new Runnable()  {
+         public void run() { popup.show(getResultTable(), x, y); }
+       });
+       return true;
+     }
+     // event not handled by us
+     return false;
+   }
    /**
     * Validates inputs from the Manual PDB entry panel
     */
    {
      validateSelections();
    }
 +
+   private FilterOption lastSelected=null;
    /**
     * Handles the state change event for the 'filter' combo-box and 'invert'
     * check-box
      }
      return found;
    }
--
    /**
     * Handles the 'New View' action
     */
    public void showStructures(boolean waitUntilFinished)
    {
  
-     final int preferredHeight = pnl_filter.getHeight();
+     final StructureSelectionManager ssm = ap.getStructureSelectionManager();
  
-     final StructureViewer theViewer = getTargetedStructureViewer();
-     boolean superimpose = chk_superpose.isSelected();
+     final int preferredHeight = pnl_filter.getHeight();
  
      Runnable viewStruc = new Runnable()
      {
  
          if (currentView == VIEWS_FILTER)
          {
-           int pdbIdColIndex = restable.getColumn("PDB Id").getModelIndex();
-           int refSeqColIndex = restable.getColumn("Ref Sequence")
-                   .getModelIndex();
            int[] selectedRows = restable.getSelectedRows();
            PDBEntry[] pdbEntriesToView = new PDBEntry[selectedRows.length];
-           int count = 0;
            List<SequenceI> selectedSeqsToView = new ArrayList<>();
-           for (int row : selectedRows)
-           {
-             String pdbIdStr = restable.getValueAt(row, pdbIdColIndex)
-                     .toString();
-             SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
-                     refSeqColIndex);
-             selectedSeqsToView.add(selectedSeq);
-             PDBEntry pdbEntry = selectedSeq.getPDBEntry(pdbIdStr);
-             if (pdbEntry == null)
-             {
-               pdbEntry = getFindEntry(pdbIdStr,
-                       selectedSeq.getAllPDBEntries());
-             }
+           pdbEntriesToView = data.collectSelectedRows(restable,
+                   selectedRows, selectedSeqsToView);
  
-             if (pdbEntry == null)
-             {
-               pdbEntry = new PDBEntry(pdbIdStr, null, "pdb");
-               selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
-             }
-             pdbEntriesToView[count++] = pdbEntry;
-           }
            SequenceI[] selectedSeqs = selectedSeqsToView
                    .toArray(new SequenceI[selectedSeqsToView.size()]);
-           sViewer = StructureViewer.launchStructureViewer(ap, pdbEntriesToView,
-                   selectedSeqs, superimpose, theViewer, progressBar);
+           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
+                   selectedSeqs);
          }
          else if (currentView == VIEWS_LOCAL_PDB)
          {
            List<SequenceI> selectedSeqsToView = new ArrayList<>();
            for (int row : selectedRows)
            {
-             PDBEntry pdbEntry = (PDBEntry) tbl_local_pdb.getValueAt(row,
-                     pdbIdColIndex);
+             PDBEntry pdbEntry = ((PDBEntryTableModel) tbl_local_pdb
+                     .getModel()).getPDBEntryAt(row).getPdbEntry();
 -
              pdbEntriesToView[count++] = pdbEntry;
              SequenceI selectedSeq = (SequenceI) tbl_local_pdb
                      .getValueAt(row, refSeqColIndex);
            }
            SequenceI[] selectedSeqs = selectedSeqsToView
                    .toArray(new SequenceI[selectedSeqsToView.size()]);
-           sViewer = StructureViewer.launchStructureViewer(ap, pdbEntriesToView,
-                   selectedSeqs, superimpose, theViewer, progressBar);
+           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
+                   selectedSeqs);
          }
          else if (currentView == VIEWS_ENTER_ID)
          {
              if (pdbIdStr.split(":").length > 1)
              {
                pdbEntry.setId(pdbIdStr.split(":")[0]);
-               pdbEntry.setChainCode(pdbIdStr.split(":")[1].toUpperCase());
+               pdbEntry.setChainCode(pdbIdStr.split(":")[1].toUpperCase(Locale.ROOT));
              }
              else
              {
            }
  
            PDBEntry[] pdbEntriesToView = new PDBEntry[] { pdbEntry };
-           sViewer = StructureViewer.launchStructureViewer(ap, pdbEntriesToView,
+           sViewer = launchStructureViewer(ssm, pdbEntriesToView, ap,
                    new SequenceI[]
-                   { selectedSequence }, superimpose, theViewer,
-                   progressBar);
+                   { selectedSequence });
          }
          else if (currentView == VIEWS_FROM_FILE)
          {
            {
              selectedSequence = userSelectedSeq;
            }
-           PDBEntry fileEntry = AssociatePdbFileWithSeq.associatePdbWithSeq(selectedPdbFileName,
-                           DataSourceType.FILE, selectedSequence, true);
-           sViewer = StructureViewer.launchStructureViewer(ap, new PDBEntry[] { fileEntry },
-                   new SequenceI[]
-                   { selectedSequence }, superimpose, theViewer,
-                   progressBar);
+           PDBEntry fileEntry = new AssociatePdbFileWithSeq()
+                   .associatePdbWithSeq(selectedPdbFileName,
+                           DataSourceType.FILE, selectedSequence, true,
 -                          Desktop.instance);
++                          Desktop.getInstance());
+           sViewer = launchStructureViewer(ssm, new PDBEntry[] { fileEntry },
+                   ap, new SequenceI[]
+                   { selectedSequence });
          }
          SwingUtilities.invokeLater(new Runnable()
          {
      }
    }
  
-   private PDBEntry getFindEntry(String id, Vector<PDBEntry> pdbEntries)
+   /**
+    * Answers a structure viewer (new or existing) configured to superimpose
+    * added structures or not according to the user's choice
+    * 
+    * @param ssm
+    * @return
+    */
+   StructureViewer getTargetedStructureViewer(StructureSelectionManager ssm)
    {
-     Objects.requireNonNull(id);
-     Objects.requireNonNull(pdbEntries);
-     PDBEntry foundEntry = null;
-     for (PDBEntry entry : pdbEntries)
-     {
-       if (entry.getId().equalsIgnoreCase(id))
-       {
-         return entry;
-       }
-     }
-     return foundEntry;
+     Object sv = targetView.getSelectedItem();
+     return sv == null ? new StructureViewer(ssm) : (StructureViewer) sv;
    }
  
    /**
-    * Answers a structure viewer (new or existing) configured to superimpose
-    * added structures or not according to the user's choice
+    * Adds PDB structures to a new or existing structure viewer
     * 
     * @param ssm
+    * @param pdbEntriesToView
+    * @param alignPanel
+    * @param sequences
     * @return
     */
-   StructureViewer getTargetedStructureViewer()
+   private StructureViewer launchStructureViewer(
+           StructureSelectionManager ssm, final PDBEntry[] pdbEntriesToView,
+           final AlignmentPanel alignPanel, SequenceI[] sequences)
    {
-     return (StructureViewer) targetView.getSelectedItem();
+     long progressId = sequences.hashCode();
+     setProgressBar(MessageManager
+             .getString("status.launching_3d_structure_viewer"), progressId);
+     final StructureViewer theViewer = getTargetedStructureViewer(ssm);
+     boolean superimpose = chk_superpose.isSelected();
+     theViewer.setSuperpose(superimpose);
+     /*
+      * remember user's choice of superimpose or not
+      */
+     Cache.setProperty(AUTOSUPERIMPOSE,
+             Boolean.valueOf(superimpose).toString());
+     setProgressBar(null, progressId);
+     if (SiftsSettings.isMapWithSifts())
+     {
+       List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
+       int p = 0;
+       // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
+       // real PDB ID. For moment, we can also safely do this if there is already
+       // a known mapping between the PDBEntry and the sequence.
+       for (SequenceI seq : sequences)
+       {
+         PDBEntry pdbe = pdbEntriesToView[p++];
+         if (pdbe != null && pdbe.getFile() != null)
+         {
+           StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
+           if (smm != null && smm.length > 0)
+           {
+             for (StructureMapping sm : smm)
+             {
+               if (sm.getSequence() == seq)
+               {
+                 continue;
+               }
+             }
+           }
+         }
+         if (seq.getPrimaryDBRefs().isEmpty())
+         {
+           seqsWithoutSourceDBRef.add(seq);
+           continue;
+         }
+       }
+       if (!seqsWithoutSourceDBRef.isEmpty())
+       {
+         int y = seqsWithoutSourceDBRef.size();
+         setProgressBar(MessageManager.formatMessage(
+                 "status.fetching_dbrefs_for_sequences_without_valid_refs",
+                 y), progressId);
+         SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
+                 .toArray(new SequenceI[y]);
+         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
+         dbRefFetcher.fetchDBRefs(true);
+         setProgressBar("Fetch complete.", progressId); // todo i18n
+       }
+     }
+     if (pdbEntriesToView.length > 1)
+     {
+       setProgressBar(
+               MessageManager.getString(
+                       "status.fetching_3d_structures_for_selected_entries"),
+               progressId);
+       theViewer.viewStructures(pdbEntriesToView, sequences, alignPanel);
+     }
+     else
+     {
+       setProgressBar(MessageManager.formatMessage(
+               "status.fetching_3d_structures_for",
+               pdbEntriesToView[0].getId()), progressId);
+       theViewer.viewStructures(pdbEntriesToView[0], sequences, alignPanel);
+     }
+     setProgressBar(null, progressId);
+     // remember the last viewer we used...
+     lastTargetedView = theViewer;
+     return theViewer;
    }
  
    /**
            isValidPBDEntry = false;
            if (text.length() > 0)
            {
-             String searchTerm = text.toLowerCase();
+             // TODO move this pdb id search into the PDB specific
+             // FTSSearchEngine
+             // for moment, it will work fine as is because it is self-contained
+             String searchTerm = text.toLowerCase(Locale.ROOT);
              searchTerm = searchTerm.split(":")[0];
              // System.out.println(">>>>> search term : " + searchTerm);
              List<FTSDataColumnI> wantedFields = new ArrayList<>();
              pdbRequest.setWantedFields(wantedFields);
              pdbRequest.setSearchTerm(searchTerm + ")");
              pdbRequest.setAssociatedSequence(selectedSequence);
-             pdbRestClient = PDBFTSRestClient.getInstance();
+             FTSRestClientI pdbRestClient = PDBFTSRestClient.getInstance();
              wantedFields.add(pdbRestClient.getPrimaryKeyColumn());
              FTSRestResponse resultList;
              try
          public void run()
          {
            fetchStructuresMetaData();
+           // populateFilterComboBox(true, cachedPDBExists);
 -
            filterResultSet(
                    ((FilterOption) cmb_filterOption.getSelectedItem())
                            .getValue());
          value = entry.getSequence();
          break;
        case 1:
-         value = entry.getPdbEntry();
+         value = entry.getQualifiedId();
          break;
        case 2:
          value = entry.getPdbEntry().getChainCode() == null ? "_"
        this.pdbEntry = pdbEntry;
      }
  
+     public String getQualifiedId()
+     {
+       if (pdbEntry.hasProvider())
+       {
+         return pdbEntry.getProvider() + ":" + pdbEntry.getId();
+       }
+       return pdbEntry.toString();
+     }
      public SequenceI getSequence()
      {
        return sequence;
    {
      progressBar.setProgressBar(message, id);
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    progressBar.removeProgressBar(id);
 +  }
  
    @Override
    public void registerHandler(long id, IProgressIndicatorHandler handler)
      return sViewer == null ? null : sViewer.sview;
    }
  
+   @Override
+   protected void setFTSDocFieldPrefs(FTSDataColumnPreferences newPrefs)
+   {
+     data.setDocFieldPrefs(newPrefs);
+   }
+   /**
+    * 
+    * @return true when all initialisation threads have finished and dialog is
+    *         visible
+    */
+   public boolean isDialogVisible()
+   {
+     return mainFrame != null && data != null && cmb_filterOption != null
+             && mainFrame.isVisible()
+             && cmb_filterOption.getSelectedItem() != null;
+   }
+   /**
+    * 
+    * @return true if the 3D-Beacons query button will/has been displayed
+    */
+   public boolean isCanQueryTDB() {
+         return canQueryTDB;
+   }
+   public boolean isNotQueriedTDBYet()
+   {
+     return notQueriedTDBYet;
+   }
  }
   */
  package jalview.gui;
  
+ import java.util.ArrayList;
+ import java.util.HashMap;
+ import java.util.LinkedHashMap;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Map.Entry;
 -
  import jalview.api.structures.JalviewStructureDisplayI;
  import jalview.bin.Cache;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
  import jalview.datamodel.StructureViewerModel;
- import jalview.structure.StructureMapping;
  import jalview.structure.StructureSelectionManager;
- import jalview.util.MessageManager;
- import jalview.util.Platform;
- import jalview.ws.DBRefFetcher;
- import jalview.ws.sifts.SiftsSettings;
  
- import java.awt.Rectangle;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Map.Entry;
 +
  /**
   * A proxy for handling structure viewers, that orchestrates adding selected
   * structures, associated with sequences in Jalview, to an existing viewer, or
   */
  public class StructureViewer
  {
 +  
 +  static
 +  {
 +    Platform.loadStaticResource("core/core_jvjmol.z.js",
 +            "org.jmol.viewer.Viewer");
 +  }
 +
 +
 +  
 +  
    private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type ";
  
    StructureSelectionManager ssm;
@@@ -71,7 -55,7 +65,7 @@@
  
    public enum ViewerType
    {
-     JMOL, CHIMERA
+     JMOL, CHIMERA, CHIMERAX, PYMOL
    };
  
    /**
@@@ -96,6 -80,7 +90,7 @@@
      return sv;
    }
  
+   
    @Override
    public String toString()
    {
      }
      return "New View";
    }
-   public ViewerType getViewerType()
+   /**
+    * 
+    * @return ViewerType for currently configured structure viewer 
+    */
+   public static ViewerType getViewerType()
    {
      String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
              ViewerType.JMOL.name());
        sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
                ap);
      }
+     else if (viewerType.equals(ViewerType.CHIMERAX))
+     {
+       sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs,
+               ap);
+     }
+     else if (viewerType.equals(ViewerType.PYMOL))
+     {
+       sview = new PymolViewer(pdbsForFile, superposeAdded, theSeqs, ap);
+     }
      else
      {
        Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
      {
        sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
      }
+     else if (viewerType.equals(ViewerType.CHIMERAX))
+     {
+       sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap);
+     }
+     else if (viewerType.equals(ViewerType.PYMOL))
+     {
+       sview = new PymolViewer(pdb, seqsForPdb, null, ap);
+     }
      else
      {
        Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
    }
  
    /**
-    * Create a new panel controlling a structure viewer.
+    * Creates a new panel controlling a structure viewer
     * 
     * @param type
 +   * @param pdbf
 +   * @param id
 +   * @param sq
     * @param alignPanel
     * @param viewerData
-    * @param fileloc
-    * @param rect
+    * @param sessionFile
     * @param vid
     * @return
     */
-   public JalviewStructureDisplayI createView(ViewerType type, String[] pdbf,
-           String[] id, SequenceI[][] sq, AlignmentPanel alignPanel,
-           StructureViewerModel viewerData, String fileloc, Rectangle rect,
-           String vid)
+   public static JalviewStructureDisplayI createView(ViewerType type,
+           AlignmentPanel alignPanel, StructureViewerModel viewerData,
+           String sessionFile, String vid)
    {
-     final boolean useinViewerSuperpos = viewerData.isAlignWithPanel();
-     final boolean usetoColourbyseq = viewerData.isColourWithAlignPanel();
-     final boolean viewerColouring = viewerData.isColourByViewer();
+     JalviewStructureDisplayI viewer = null;
 +
      switch (type)
      {
      case JMOL:
-       sview = new AppJmol(pdbf, id, sq, alignPanel, usetoColourbyseq,
-               useinViewerSuperpos, viewerColouring, fileloc, rect, vid);
+       viewer = new AppJmol(viewerData, alignPanel, sessionFile, vid);
+       // todo or construct and then openSession(sessionFile)?
        break;
      case CHIMERA:
-       Cache.log.error(
-               "Unsupported structure viewer type " + type.toString());
+       viewer = new ChimeraViewFrame(viewerData, alignPanel, sessionFile,
+               vid);
+       break;
+     case CHIMERAX:
+       viewer = new ChimeraXViewFrame(viewerData, alignPanel, sessionFile,
+               vid);
+       break;
+     case PYMOL:
+       viewer = new PymolViewer(viewerData, alignPanel, sessionFile, vid);
        break;
      default:
        Cache.log.error(UNKNOWN_VIEWER_TYPE + type.toString());
      }
-     return sview;
+     return viewer;
    }
  
 -
    public boolean isBusy()
    {
      if (sview != null)
      superposeAdded = alignAddedStructures;
    }
  
-   /**
-    * Launch a minimal implementation of a StructureViewer.
-    * 
-    * @param alignPanel
-    * @param pdb
-    * @param seqs
-    * @return
-    */
-   public static StructureViewer launchStructureViewer(
-           AlignmentPanel alignPanel, PDBEntry pdb, SequenceI[] seqs)
-   {
-     return launchStructureViewer(alignPanel, new PDBEntry[] { pdb }, seqs,
-             false, null, null);
-   }
-   /**
-    * Adds PDB structures to a new or existing structure viewer
-    * 
-    * @param ssm
-    * @param pdbEntriesToView
-    * @param alignPanel
-    * @param sequences
-    * @return
-    */
-   protected static StructureViewer launchStructureViewer(
-           final AlignmentPanel ap, final PDBEntry[] pdbEntriesToView,
-           SequenceI[] sequences, boolean superimpose,
-           StructureViewer theViewer, IProgressIndicator pb)
-   {
-     final StructureSelectionManager ssm = ap.getStructureSelectionManager();
-     if (theViewer == null)
-       theViewer = new StructureViewer(ssm);
-     long progressId = sequences.hashCode();
-     if (pb != null)
-       pb.setProgressBar(MessageManager.getString(
-               "status.launching_3d_structure_viewer"), progressId);
-     theViewer.setSuperpose(superimpose);
-   
-     /*
-      * remember user's choice of superimpose or not
-      */
-     Cache.setProperty(StructureChooser.AUTOSUPERIMPOSE,
-             Boolean.valueOf(superimpose).toString());
-   
-     if (pb != null)
-       pb.setProgressBar(null, progressId);
-     if (SiftsSettings.isMapWithSifts())
-     {
-       List<SequenceI> seqsWithoutSourceDBRef = new ArrayList<>();
-       int p = 0;
-       // TODO: skip PDBEntry:Sequence pairs where PDBEntry doesn't look like a
-       // real PDB ID. For moment, we can also safely do this if there is already
-       // a known mapping between the PDBEntry and the sequence.
-       for (SequenceI seq : sequences)
-       {
-         PDBEntry pdbe = pdbEntriesToView[p++];
-         if (pdbe != null && pdbe.getFile() != null)
-         {
-           StructureMapping[] smm = ssm.getMapping(pdbe.getFile());
-           if (smm != null && smm.length > 0)
-           {
-             for (StructureMapping sm : smm)
-             {
-               if (sm.getSequence() == seq)
-               {
-                 continue;
-               }
-             }
-           }
-         }
-         if (seq.getPrimaryDBRefs().isEmpty())
-         {
-           seqsWithoutSourceDBRef.add(seq);
-           continue;
-         }
-       }
-       if (!seqsWithoutSourceDBRef.isEmpty())
-       {
-         int y = seqsWithoutSourceDBRef.size();
-         if (pb != null)
-           pb.setProgressBar(MessageManager.formatMessage(
-                   "status.fetching_dbrefs_for_sequences_without_valid_refs",
-                   y), progressId);
-         SequenceI[] seqWithoutSrcDBRef = seqsWithoutSourceDBRef
-                 .toArray(new SequenceI[y]);
-         DBRefFetcher dbRefFetcher = new DBRefFetcher(seqWithoutSrcDBRef);
-         dbRefFetcher.fetchDBRefs(true);
-   
-         if (pb != null)
-           pb.setProgressBar("Fetch complete.", progressId); // todo i18n
-       }
-     }
-     if (pdbEntriesToView.length > 1)
-     {
-       if (pb != null)
-         pb.setProgressBar(MessageManager.getString(
-                 "status.fetching_3d_structures_for_selected_entries"),
-                 progressId);
-       theViewer.viewStructures(pdbEntriesToView, sequences, ap);
-     }
-     else
-     {
-       if (pb != null)
-         pb.setProgressBar(MessageManager.formatMessage(
-                 "status.fetching_3d_structures_for",
-                 pdbEntriesToView[0].getId()), progressId);
-       theViewer.viewStructures(pdbEntriesToView[0], sequences, ap);
-     }
-     if (pb != null)
-       pb.setProgressBar(null, progressId);
-     // remember the last viewer we used...
-     StructureChooser.lastTargetedView = theViewer;
-     return theViewer;
-   }
  }
   */
  package jalview.gui;
  
- import jalview.api.AlignmentViewPanel;
- import jalview.bin.Cache;
- import jalview.datamodel.Alignment;
- import jalview.datamodel.AlignmentI;
- import jalview.datamodel.HiddenColumns;
- import jalview.datamodel.PDBEntry;
- import jalview.datamodel.SequenceI;
- import jalview.gui.JalviewColourChooser.ColourChooserListener;
- import jalview.gui.StructureViewer.ViewerType;
- import jalview.gui.ViewSelectionMenu.ViewSetProvider;
- import jalview.io.DataSourceType;
- import jalview.io.JalviewFileChooser;
- import jalview.io.JalviewFileView;
- import jalview.jbgui.GStructureViewer;
- import jalview.schemes.ColourSchemeI;
- import jalview.schemes.ColourSchemes;
- import jalview.structure.StructureMapping;
- import jalview.structures.models.AAStructureBindingModel;
- import jalview.util.MessageManager;
  import java.awt.Color;
  import java.awt.Component;
  import java.awt.event.ActionEvent;
@@@ -54,6 -34,7 +34,7 @@@ import java.io.IOException
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.List;
+ import java.util.Random;
  import java.util.Vector;
  
  import javax.swing.ButtonGroup;
@@@ -64,6 -45,28 +45,28 @@@ import javax.swing.JRadioButtonMenuItem
  import javax.swing.event.MenuEvent;
  import javax.swing.event.MenuListener;
  
+ import jalview.api.AlignmentViewPanel;
+ import jalview.bin.Cache;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.SequenceI;
+ import jalview.gui.JalviewColourChooser.ColourChooserListener;
+ import jalview.gui.StructureViewer.ViewerType;
+ import jalview.gui.ViewSelectionMenu.ViewSetProvider;
+ import jalview.io.DataSourceType;
+ import jalview.io.JalviewFileChooser;
+ import jalview.io.JalviewFileView;
+ import jalview.jbgui.GStructureViewer;
+ import jalview.schemes.ColourSchemeI;
+ import jalview.schemes.ColourSchemes;
+ import jalview.structure.StructureMapping;
+ import jalview.structures.models.AAStructureBindingModel;
+ import jalview.util.BrowserLauncher;
+ import jalview.util.MessageManager;
+ import jalview.ws.dbsources.EBIAlfaFold;
+ import jalview.ws.dbsources.Pdb;
+ import jalview.ws.utils.UrlDownloadClient;
  /**
   * Base class with common functionality for JMol, Chimera or other structure
   * viewers.
@@@ -90,13 -93,13 +93,13 @@@ public abstract class StructureViewerBa
    /**
     * list of alignment panels to use for superposition
     */
-   protected Vector<AlignmentPanel> _alignwith = new Vector<>();
+   protected Vector<AlignmentViewPanel> _alignwith = new Vector<>();
  
    /**
     * list of alignment panels that are used for colouring structures by aligned
     * sequences
     */
-   protected Vector<AlignmentPanel> _colourwith = new Vector<>();
+   protected Vector<AlignmentViewPanel> _colourwith = new Vector<>();
  
    private String viewId = null;
  
     */
    protected volatile boolean seqColoursApplied = false;
  
+   private IProgressIndicator progressBar = null;
+   private Random random = new Random();
    /**
     * Default constructor
     */
    {
      alignAddedStructures = alignAdded;
    }
+   
+   /**
+    * called by the binding model to indicate when adding structures is happening or has been completed
+    * @param addingStructures
+    */
+   public synchronized void setAddingStructures(boolean addingStructures)
+   {
+     this.addingStructures = addingStructures;
+   }
  
    /**
     * 
      return _aps.contains(ap2.av.getSequenceSetId());
    }
  
-   public boolean isUsedforaligment(AlignmentPanel ap2)
+   public boolean isUsedforaligment(AlignmentViewPanel ap2)
    {
  
      return (_alignwith != null) && _alignwith.contains(ap2);
    }
  
-   public boolean isUsedforcolourby(AlignmentPanel ap2)
+   @Override
+   public boolean isUsedForColourBy(AlignmentViewPanel ap2)
    {
      return (_colourwith != null) && _colourwith.contains(ap2);
    }
      this.viewId = viewId;
    }
  
-   public abstract String getStateInfo();
    protected void buildActionMenu()
    {
      if (_alignwith == null)
        _alignwith.add(ap);
      }
      ;
+     // TODO: refactor to allow concrete classes to register buttons for adding
+     // here
+     // currently have to override to add buttons back in after they are cleared
+     // in this loop
      for (Component c : viewerActionMenu.getMenuComponents())
      {
        if (c != alignStructs)
      }
    }
  
+   @Override
    public AlignmentPanel getAlignmentPanel()
    {
      return ap;
     * 
     * @param nap
     */
-   public void removeAlignmentPanel(AlignmentPanel nap)
+   @Override
+   public void removeAlignmentPanel(AlignmentViewPanel nap)
    {
      try
      {
  
    public abstract ViewerType getViewerType();
  
-   protected abstract IProgressIndicator getIProgressIndicator();
    /**
     * add a new structure (with associated sequences and chains) to this viewer,
     * retrieving it if necessary first.
     */
    protected List<StructureViewerBase> getViewersFor(AlignmentPanel alp)
    {
 -    return Desktop.instance.getStructureViewers(alp, this.getClass());
 +    return Desktop.getInstance().getStructureViewers(alp, this.getClass());
    }
  
    @Override
       * create the mappings
       */
      apanel.getStructureSelectionManager().setMapping(seq, chains,
-             pdbFilename, DataSourceType.FILE, getIProgressIndicator());
+             pdbFilename, DataSourceType.FILE, getProgressIndicator());
  
      /*
       * alert the FeatureRenderer to show new (PDB RESNUM) features
      }
    }
  
-   abstract void showSelectedChains();
    /**
     * Action on selecting one of Jalview's registered colour schemes
     */
      ColourSchemeI cs = ColourSchemes.getInstance()
              .getColourScheme(colourSchemeName, getAlignmentPanel().av, al,
                      null);
-     getBinding().setJalviewColourScheme(cs);
+     getBinding().colourByJalviewColourScheme(cs);
    }
  
    /**
        @Override
        public void actionPerformed(ActionEvent actionEvent)
        {
-         viewerColour_actionPerformed(actionEvent);
+         viewerColour_actionPerformed();
        }
      });
      colourMenu.add(viewerColour);
        @Override
        public void actionPerformed(ActionEvent actionEvent)
        {
-         background_actionPerformed(actionEvent);
+         background_actionPerformed();
        }
      });
      colourMenu.add(backGround);
        @Override
        public void actionPerformed(ActionEvent actionEvent)
        {
-         seqColour_actionPerformed(actionEvent);
+         seqColour_actionPerformed();
        }
      });
  
        @Override
        public void actionPerformed(ActionEvent actionEvent)
        {
-         chainColour_actionPerformed(actionEvent);
+         chainColour_actionPerformed();
        }
      });
  
        @Override
        public void actionPerformed(ActionEvent actionEvent)
        {
-         chargeColour_actionPerformed(actionEvent);
+         chargeColour_actionPerformed();
        }
      });
  
      viewerColour = new JRadioButtonMenuItem();
-     // text is set in overrides of this method
+     viewerColour
+             .setText(MessageManager.getString("label.colour_with_viewer"));
+     viewerColour.setToolTipText(MessageManager
+             .getString("label.let_viewer_manage_structure_colours"));
      viewerColour.setName(ViewerColour.ByViewer.name());
      viewerColour.setSelected(!binding.isColourBySequence());
  
                  }
                  else
                  {
-                   // update the Chimera display now.
-                   seqColour_actionPerformed(null);
+                   // update the viewer display now.
+                   seqColour_actionPerformed();
                  }
                }
              });
        @Override
        public void itemStateChanged(ItemEvent e)
        {
-         alignStructs.setEnabled(!_alignwith.isEmpty());
-         alignStructs.setToolTipText(MessageManager.formatMessage(
-                 "label.align_structures_using_linked_alignment_views",
-                 _alignwith.size()));
+         if (_alignwith.isEmpty())
+         {
+           alignStructs.setEnabled(false);
+           alignStructs.setToolTipText(null);
+         }
+         else
+         {
+           alignStructs.setEnabled(true);
+           alignStructs.setToolTipText(MessageManager.formatMessage(
+                   "label.align_structures_using_linked_alignment_views",
+                   _alignwith.size()));
+         }
        }
      };
      viewSelectionMenu = new ViewSelectionMenu(
        }
      });
  
-     buildColourMenu();
-   }
+     viewerActionMenu.setText(getViewerName());
+     helpItem.setText(MessageManager.formatMessage("label.viewer_help",
+             getViewerName()));
  
-   @Override
-   public void setJalviewColourScheme(ColourSchemeI cs)
-   {
-     getBinding().setJalviewColourScheme(cs);
+     buildColourMenu();
    }
  
    /**
     * the operation.
     */
    @Override
-   protected String alignStructs_actionPerformed(ActionEvent actionEvent)
-   {
-     return alignStructs_withAllAlignPanels();
-   }
-   protected String alignStructs_withAllAlignPanels()
+   protected String alignStructsWithAllAlignPanels()
    {
      if (getAlignmentPanel() == null)
      {
      String reply = null;
      try
      {
-       AlignmentI[] als = new Alignment[_alignwith.size()];
-       HiddenColumns[] alc = new HiddenColumns[_alignwith.size()];
-       int[] alm = new int[_alignwith.size()];
-       int a = 0;
-       for (AlignmentPanel alignPanel : _alignwith)
-       {
-         als[a] = alignPanel.av.getAlignment();
-         alm[a] = -1;
-         alc[a++] = alignPanel.av.getAlignment().getHiddenColumns();
-       }
-       reply = getBinding().superposeStructures(als, alm, alc);
-       if (reply != null)
+       reply = getBinding().superposeStructures(_alignwith);
+       if (reply != null && !reply.isEmpty())
        {
          String text = MessageManager
                  .formatMessage("error.superposition_failed", reply);
      } catch (Exception e)
      {
        StringBuffer sp = new StringBuffer();
-       for (AlignmentPanel alignPanel : _alignwith)
+       for (AlignmentViewPanel alignPanel : _alignwith)
        {
-         sp.append("'" + alignPanel.alignFrame.getTitle() + "' ");
+         sp.append("'" + alignPanel.getViewName() + "' ");
        }
        Cache.log.info("Couldn't align structures with the " + sp.toString()
                + "associated alignment panels.", e);
     * background of the structure viewer
     */
    @Override
-   public void background_actionPerformed(ActionEvent actionEvent)
+   public void background_actionPerformed()
    {
      String ttl = MessageManager.getString("label.select_background_colour");
      ColourChooserListener listener = new ColourChooserListener()
    }
  
    @Override
-   public void viewerColour_actionPerformed(ActionEvent actionEvent)
+   public void viewerColour_actionPerformed()
    {
      if (viewerColour.isSelected())
      {
    }
  
    @Override
-   public void chainColour_actionPerformed(ActionEvent actionEvent)
+   public void chainColour_actionPerformed()
    {
      chainColour.setSelected(true);
      getBinding().colourByChain();
    }
  
    @Override
-   public void chargeColour_actionPerformed(ActionEvent actionEvent)
+   public void chargeColour_actionPerformed()
    {
      chargeColour.setSelected(true);
      getBinding().colourByCharge();
    }
  
    @Override
-   public void seqColour_actionPerformed(ActionEvent actionEvent)
+   public void seqColour_actionPerformed()
    {
      AAStructureBindingModel binding = getBinding();
      binding.setColourBySequence(seqColour.isSelected());
          }
        }
        // Set the colour using the current view for the associated alignframe
-       for (AlignmentPanel alignPanel : _colourwith)
+       for (AlignmentViewPanel alignPanel : _colourwith)
        {
          binding.colourBySequence(alignPanel);
        }
    }
  
    @Override
-   public void pdbFile_actionPerformed(ActionEvent actionEvent)
+   public void pdbFile_actionPerformed()
    {
      // TODO: JAL-3048 not needed for Jalview-JS - save PDB file
      JalviewFileChooser chooser = new JalviewFileChooser(
    }
  
    @Override
-   public void viewMapping_actionPerformed(ActionEvent actionEvent)
+   public void viewMapping_actionPerformed()
    {
      CutAndPasteTransfer cap = new CutAndPasteTransfer();
      try
       * enable 'Superpose with' if more than one mapped structure
       */
      viewSelectionMenu.setEnabled(false);
-     if (getBinding().getStructureFiles().length > 1
+     if (getBinding().getMappedStructureCount() > 1
              && getBinding().getSequence().length > 1)
      {
        viewSelectionMenu.setEnabled(true);
  
      if (!binding.isLoadingFromArchive())
      {
-       seqColour_actionPerformed(null);
+       seqColour_actionPerformed();
      }
    }
  
      toFront();
    }
  
+   @Override
+   public long startProgressBar(String msg)
+   {
+     // TODO would rather have startProgress/stopProgress as the
+     // IProgressIndicator interface
+     long tm = random.nextLong();
+     if (progressBar != null)
+     {
+       progressBar.setProgressBar(msg, tm);
+     }
+     return tm;
+   }
+   @Override
+   public void stopProgressBar(String msg, long handle)
+   {
+     if (progressBar != null)
+     {
+       progressBar.setProgressBar(msg, handle);
+     }
+   }
+   protected IProgressIndicator getProgressIndicator()
+   {
+     return progressBar;
+   }
+   protected void setProgressIndicator(IProgressIndicator pi)
+   {
+     progressBar = pi;
+   }
+   public void setProgressMessage(String message, long id)
+   {
+     if (progressBar != null)
+     {
+       progressBar.setProgressBar(message, id);
+     }
+   }
+   @Override
+   public void showConsole(boolean show)
+   {
+     // default does nothing
+   }
+   /**
+    * Show only the selected chain(s) in the viewer
+    */
+   protected void showSelectedChains()
+   {
+     List<String> toshow = new ArrayList<>();
+     for (int i = 0; i < chainMenu.getItemCount(); i++)
+     {
+       if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
+       {
+         JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
+         if (item.isSelected())
+         {
+           toshow.add(item.getText());
+         }
+       }
+     }
+     getBinding().showChains(toshow);
+   }
+   /**
+    * Tries to fetch a PDB file and save to a temporary local file. Returns the
+    * saved file path if successful, or null if not.
+    * 
+    * @param processingEntry
+    * @return
+    */
+   protected String fetchPdbFile(PDBEntry processingEntry)
+   {
+     String filePath = null;
+     Pdb pdbclient = new Pdb();
+     EBIAlfaFold afclient =  new EBIAlfaFold();
+     AlignmentI pdbseq = null;
+     String pdbid = processingEntry.getId();
+     long handle = System.currentTimeMillis()
+             + Thread.currentThread().hashCode();
+   
+     /*
+      * Write 'fetching PDB' progress on AlignFrame as we are not yet visible
+      */
+     String msg = MessageManager.formatMessage("status.fetching_pdb",
+             new Object[]
+             { pdbid });
+     getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
+     // long hdl = startProgressBar(MessageManager.formatMessage(
+     // "status.fetching_pdb", new Object[]
+     // { pdbid }));
+     try
+     {
+       if (afclient.isValidReference(pdbid))
+       {
+         pdbseq = afclient.getSequenceRecords(pdbid);
+       } else {
+           if (processingEntry.hasRetrievalUrl())
+           {
+             // retrieve from URL to new local tmpfile
+             File tmpFile = File.createTempFile(pdbid,
+                     "." + (PDBEntry.Type.MMCIF.toString().equals(
+                             processingEntry.getType().toString()) ? "cif"
+                                     : "pdb"));
+             String fromUrl = processingEntry.getRetrievalUrl();
+             UrlDownloadClient.download(fromUrl, tmpFile);
+             
+             // may not need this check ?
+             String file = tmpFile.getAbsolutePath();
+             if (file != null)
+             {
+               pdbseq = EBIAlfaFold.importDownloadedStructureFromUrl(fromUrl,tmpFile,pdbid,null,null,null);
+             }
+           } else {
+             pdbseq = pdbclient.getSequenceRecords(pdbid);
+           }
+       }
+     } catch (Exception e)
+     {
+       System.err.println(
+               "Error retrieving PDB id " + pdbid + ": " + e.getMessage());
+     } finally
+     {
+       msg = pdbid + " " + MessageManager.getString("label.state_completed");
+       getAlignmentPanel().alignFrame.setProgressBar(msg, handle);
+       // stopProgressBar(msg, hdl);
+     }
+     /*
+      * If PDB data were saved and are not invalid (empty alignment), return the
+      * file path.
+      */
+     if (pdbseq != null && pdbseq.getHeight() > 0)
+     {
+       // just use the file name from the first sequence's first PDBEntry
+       filePath = new File(pdbseq.getSequenceAt(0).getAllPDBEntries()
+               .elementAt(0).getFile()).getAbsolutePath();
+       processingEntry.setFile(filePath);
+     }
+     return filePath;
+   }
+   /**
+    * If supported, saves the state of the structure viewer to a temporary file
+    * and returns the file, else returns null
+    * 
+    * @return
+    */
+   public File saveSession()
+   {
+     if (getBinding() == null) { return  null;}
+     File session = getBinding().saveSession();
+     long l = session.length();
+     int wait=50;
+     do {
+       try {
+         Thread.sleep(5);
+       } catch (InterruptedException e) {
+       } 
+       long nextl = session.length();
+       if (nextl!=l)
+       {
+         wait = 50;
+         l=nextl;
+       }
+     } while (--wait>0);
+     return session;
+   }
+   /**
+    * Close down this instance of Jalview's Chimera viewer, giving the user the
+    * option to close the associated Chimera window (process). They may wish to
+    * keep it open until they have had an opportunity to save any work.
+    * 
+    * @param forceClose
+    *          if true, close any linked Chimera process; if false, prompt first
+    */
+   @Override
+   public void closeViewer(boolean forceClose)
+   {
+     AAStructureBindingModel binding = getBinding();
+     if (binding != null && binding.isViewerRunning())
+     {
+       if (!forceClose)
+       {
+         String viewerName = getViewerName();
+         String prompt = MessageManager
+                 .formatMessage("label.confirm_close_viewer", new Object[]
+                 { binding.getViewerTitle(viewerName, false), viewerName });
+         prompt = JvSwingUtils.wrapTooltip(true, prompt);
+         int confirm = JvOptionPane.showConfirmDialog(this, prompt,
+                 MessageManager.getString("label.close_viewer"),
+                 JvOptionPane.YES_NO_CANCEL_OPTION);
+         /*
+          * abort closure if user hits escape or Cancel
+          */
+         if (confirm == JvOptionPane.CANCEL_OPTION
+                 || confirm == JvOptionPane.CLOSED_OPTION)
+         {
+           return;
+         }
+         forceClose = confirm == JvOptionPane.YES_OPTION;
+       }
+     }
+     if (binding != null)
+     {
+       binding.closeViewer(forceClose);
+     }
+     setAlignmentPanel(null);
+     _aps.clear();
+     _alignwith.clear();
+     _colourwith.clear();
+     // TODO: check for memory leaks where instance isn't finalised because jmb
+     // holds a reference to the window
+     // jmb = null;
+     dispose();
+   }
+   @Override
+   public void showHelp_actionPerformed()
+   {
+     try
+     {
+       String url = getBinding().getHelpURL();
+       if (url != null)
+       {
+         BrowserLauncher.openURL(url);
+       }
+     } catch (IOException ex)
+     {
+       System.err
+               .println("Show " + getViewerName() + " failed with: "
+                       + ex.getMessage());
+     }
+   }
+   @Override
+   public boolean hasViewerActionsMenu()
+   {
+     return viewerActionMenu != null && viewerActionMenu.isEnabled()
+             && viewerActionMenu.getItemCount() > 0
+             && viewerActionMenu.isVisible();
+   }
  }
   */
  package jalview.gui;
  
- import jalview.analysis.Conservation;
- import jalview.analysis.TreeModel;
- import jalview.api.AlignViewportI;
- import jalview.datamodel.Sequence;
- import jalview.datamodel.SequenceGroup;
- import jalview.datamodel.SequenceI;
- import jalview.datamodel.SequenceNode;
- import jalview.gui.JalviewColourChooser.ColourChooserListener;
- import jalview.schemes.ColourSchemeI;
- import jalview.structure.SelectionSource;
- import jalview.util.Format;
- import jalview.util.MessageManager;
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.Font;
@@@ -49,9 -36,10 +36,10 @@@ import java.awt.print.PageFormat
  import java.awt.print.Printable;
  import java.awt.print.PrinterException;
  import java.awt.print.PrinterJob;
- import java.util.Enumeration;
  import java.util.Hashtable;
  import java.util.List;
+ import java.util.Map;
+ import java.util.Map.Entry;
  import java.util.Vector;
  
  import javax.swing.JPanel;
@@@ -59,6 -47,19 +47,19 @@@ import javax.swing.JScrollPane
  import javax.swing.SwingUtilities;
  import javax.swing.ToolTipManager;
  
+ import jalview.analysis.Conservation;
+ import jalview.analysis.TreeModel;
+ import jalview.api.AlignViewportI;
+ import jalview.datamodel.Sequence;
+ import jalview.datamodel.SequenceGroup;
+ import jalview.datamodel.SequenceI;
+ import jalview.datamodel.SequenceNode;
+ import jalview.gui.JalviewColourChooser.ColourChooserListener;
+ import jalview.schemes.ColourSchemeI;
+ import jalview.structure.SelectionSource;
+ import jalview.util.Format;
+ import jalview.util.MessageManager;
  /**
   * DOCUMENT ME!
   * 
@@@ -103,9 -104,9 +104,9 @@@ public class TreeCanvas extends JPanel 
  
    int labelLength = -1;
  
-   Hashtable nameHash = new Hashtable();
+   Map<Object, Rectangle> nameHash = new Hashtable<>();
  
-   Hashtable nodeHash = new Hashtable();
+   Map<SequenceNode, Rectangle> nodeHash = new Hashtable<>();
  
    SequenceNode highlightNode;
  
     */
    public Object findElement(int x, int y)
    {
-     Enumeration keys = nameHash.keys();
-     while (keys.hasMoreElements())
+     for (Entry<Object, Rectangle> entry : nameHash.entrySet())
      {
-       Object ob = keys.nextElement();
-       Rectangle rect = (Rectangle) nameHash.get(ob);
+       Rectangle rect = entry.getValue();
  
        if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
                && (y <= (rect.y + rect.height)))
        {
-         return ob;
+         return entry.getKey();
        }
      }
  
-     keys = nodeHash.keys();
-     while (keys.hasMoreElements())
+     for (Entry<SequenceNode, Rectangle> entry : nodeHash.entrySet())
      {
-       Object ob = keys.nextElement();
-       Rectangle rect = (Rectangle) nodeHash.get(ob);
+       Rectangle rect = entry.getValue();
  
        if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
                && (y <= (rect.y + rect.height)))
        {
-         return ob;
+         return entry.getKey();
        }
      }
  
      if ((node.left() == null) && (node.right() == null))
      {
        double height = node.height;
-       double dist = node.dist;
-       int xstart = (int) ((height - dist) * wscale) + offx;
+ //      double dist = node.dist;
+ //      int xstart = (int) ((height - dist) * wscale) + offx;
        int xend = (int) (height * wscale) + offx;
  
        int ypos = (int) (node.ycount * chunk) + offy;
      {
        fm = g.getFontMetrics(font);
  
-       if (nameHash.size() == 0)
+       int nameCount = nameHash.size();
+       if (nameCount == 0)
        {
          repaint();
        }
  
        if (fitToWindow || (!fitToWindow && (scrollPane
-               .getHeight() > ((fm.getHeight() * nameHash.size()) + offy))))
+               .getHeight() > ((fm.getHeight() * nameCount) + offy))))
        {
          draw(g, scrollPane.getWidth(), scrollPane.getHeight());
          setPreferredSize(null);
        else
        {
          setPreferredSize(new Dimension(scrollPane.getWidth(),
-                 fm.getHeight() * nameHash.size()));
-         draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
+                 fm.getHeight() * nameCount));
+         draw(g, scrollPane.getWidth(), fm.getHeight() * nameCount);
        }
  
        scrollPane.revalidate();
       */
      if (e.isPopupTrigger())
      {
 -      chooseSubtreeColour();
 +      if (highlightNode != null) {
 +        chooseSubtreeColour();
 +      }
        e.consume(); // prevent mouseClicked happening
      }
    }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.gui;
  
+ import java.util.Locale;
  import jalview.analysis.AlignmentSorter;
  import jalview.analysis.AverageDistanceTree;
  import jalview.analysis.NJTree;
@@@ -159,6 -161,7 +161,7 @@@ public class TreePanel extends GTreePan
          {
            av.removePropertyChangeListener(listener);
          }
+         releaseReferences();
        }
      });
  
    }
  
    /**
+    * Ensure any potentially large object references are nulled
+    */
+   public void releaseReferences()
+   {
+     this.tree = null;
+     this.treeCanvas.tree = null;
+     this.treeCanvas.nodeHash = null;
+     this.treeCanvas.nameHash = null;
+   }
+   /**
     * @return
     */
    protected PropertyChangeListener addAlignmentListener()
    {
      final PropertyChangeListener listener = new PropertyChangeListener()
      {
 +      @SuppressWarnings("unchecked")
        @Override
        public void propertyChange(PropertyChangeEvent evt)
        {
 -        if (evt.getPropertyName().equals("alignment"))
 -        {
 +        switch (evt.getPropertyName()) {
 +        case AlignmentViewport.PROPERTY_ALIGNMENT:
            if (tree == null)
            {
              System.out.println("tree is null");
            {
              System.out.println(
                      "new alignment sequences vector value is null");
 +            return;
            }
  
            tree.updatePlaceHolders((List<SequenceI>) evt.getNewValue());
            treeCanvas.nameHash.clear(); // reset the mapping between canvas
            // rectangles and leafnodes
            repaint();
 +          break;
          }
        }
      };
      String tree = MessageManager.getString("label.tree");
      ImageExporter exporter = new ImageExporter(writer, null, imageFormat,
              tree);
-     exporter.doExport(null, this, width, height, tree.toLowerCase());
+     exporter.doExport(null, this, width, height, tree.toLowerCase(Locale.ROOT));
    }
  
    /**
              // search dbrefs, features and annotation
              List<DBRefEntry> refs = jalview.util.DBRefUtils
                      .selectRefs(sq.getDBRefs(), new String[]
-                     { labelClass.toUpperCase() });
+                     { labelClass.toUpperCase(Locale.ROOT) });
              if (refs != null)
              {
                for (int i = 0, ni = refs.size(); i < ni; i++)
       * i18n description of Neighbour Joining or Average Distance method
       */
      String treecalcnm = MessageManager
-             .getString("label.tree_calc_" + treeType.toLowerCase());
+             .getString("label.tree_calc_" + treeType.toLowerCase(Locale.ROOT));
  
      /*
       * short score model name (long description can be too long)
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.gui;
  
+ import java.util.Locale;
  import jalview.bin.Cache;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
@@@ -149,8 -151,8 +151,8 @@@ public class UserDefinedColours extend
      frame = new JInternalFrame();
      frame.setContentPane(this);
      Desktop.addInternalFrame(frame,
 -            MessageManager.getString("label.user_defined_colours"),
 -            MY_FRAME_WIDTH, MY_FRAME_HEIGHT, true);
 +            MessageManager.getString("label.user_defined_colours"), Desktop.FRAME_MAKE_VISIBLE,
 +            MY_FRAME_WIDTH, MY_FRAME_HEIGHT, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_SET_MIN_SIZE_300);
    }
  
    /**
        {
          int row = i / cols + 1;
          int index = (row * cols) + i;
-         JButton button = makeButton(ResidueProperties.aa[i].toLowerCase(),
-                 ResidueProperties.aa[i].toLowerCase(), lowerCaseButtons, i);
+         JButton button = makeButton(ResidueProperties.aa[i].toLowerCase(Locale.ROOT),
+                 ResidueProperties.aa[i].toLowerCase(Locale.ROOT), lowerCaseButtons, i);
  
          buttonPanel.add(button, index);
        }
    {
      if (isNoSelectionMade())
      {
 -      JvOptionPane.showMessageDialog(Desktop.desktop,
 +      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                MessageManager
                        .getString("label.no_colour_selection_in_scheme"),
                MessageManager.getString("label.no_colour_selection_warn"),
          String[] options = new String[] { title,
              MessageManager.getString("label.dont_save_changes"), };
          final String question = JvSwingUtils.wrapTooltip(true, message);
 -        int response = JvOptionPane.showOptionDialog(Desktop.desktop,
 +        int response = JvOptionPane.showOptionDialog(Desktop.getDesktopPane(),
                  question, title, JvOptionPane.DEFAULT_OPTION,
                  JvOptionPane.PLAIN_MESSAGE, null, options, options[0]);
  
    {
      if (isNoSelectionMade())
      {
 -      JvOptionPane.showMessageDialog(Desktop.desktop,
 +      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                MessageManager
                        .getString("label.no_colour_selection_in_scheme"),
                MessageManager.getString("label.no_colour_selection_warn"),
      String name = schemeName.getText().trim();
      if (name.length() < 1)
      {
 -      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +      JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                MessageManager
                        .getString("label.user_colour_scheme_must_have_name"),
                MessageManager.getString("label.no_name_colour_scheme"),
         * @j2sIgnore
         */
        {
 -        int reply = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
 +        int reply = JvOptionPane.showInternalConfirmDialog(Desktop.getDesktopPane(),
                  MessageManager.formatMessage(
                          "label.colour_scheme_exists_overwrite", new Object[]
                          { name, name }),
@@@ -173,7 -173,7 +173,7 @@@ public class VamsasApplication implemen
            }
          } catch (InvalidSessionDocumentException e)
          {
 -          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +          JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
  
                    MessageManager.getString(
                            "label.vamsas_doc_couldnt_be_opened_as_new_session"),
    {
      if (!inSession())
      {
-       throw new Error(MessageManager.getString(
-               "error.implementation_error_vamsas_operation_not_init"));
+       throw new Error(
+               "Implementation error! Vamsas Operations when client not initialised and connected");
      }
      addDocumentUpdateHandler();
      addStoreDocumentHandler();
    {
      if (!inSession())
      {
-       throw new Error(MessageManager
-               .getString("error.jalview_no_connected_vamsas_session"));
+       throw new Error("Jalview not connected to Vamsas session");
      }
      Cache.log.info("Jalview disconnecting from the Vamsas Session.");
      try
      VamsasAppDatastore vds = new VamsasAppDatastore(doc, vobj2jv, jv2vobj,
              baseProvEntry(), alRedoState);
      // wander through frames
 -    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 +    JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
  
      if (frames == null)
      {
                    Cache.log.debug(
                            "Asking user if the vamsas session should be stored.");
                    int reply = JvOptionPane.showInternalConfirmDialog(
 -                          Desktop.desktop,
 +                          Desktop.getDesktopPane(),
                            "The current VAMSAS session has unsaved data - do you want to save it ?",
                            "VAMSAS Session Shutdown",
                            JvOptionPane.YES_NO_OPTION,
                    if (reply == JvOptionPane.YES_OPTION)
                    {
                      Cache.log.debug("Prompting for vamsas store filename.");
 -                    Desktop.instance.vamsasSave_actionPerformed(null);
 +                    Desktop.getInstance().vamsasSave_actionPerformed(null);
                      Cache.log
                              .debug("Finished attempt at storing document.");
                    }
    public void disableGui(boolean b)
    {
      // JAL-3311 TODO: remove this class!
 -    // Desktop.instance.setVamsasUpdate(b);
 +    // Desktop.getInstance().setVamsasUpdate(b);
    }
  
    Hashtable _backup_vobj2jv;
          return;
        }
  
-       throw new Error(MessageManager.getString(
-               "error.implementation_error_cannot_recover_vamsas_object_mappings"));
+       throw new Error(
+               "IMPLEMENTATION ERROR: Cannot recover vamsas object mappings - no backup was made");
      }
      jv2vobj.clear();
      Iterator el = _backup_jv2vobj.entrySet().iterator();
        {
          final IPickManager pm = vclient.getPickManager();
          final StructureSelectionManager ssm = StructureSelectionManager
 -                .getStructureSelectionManager(Desktop.instance);
 +                .getStructureSelectionManager(Desktop.getInstance());
          final VamsasApplication me = this;
          pm.registerMessageHandler(new IMessageHandler()
          {
   */
  package jalview.gui;
  
 -import java.util.Locale;
 +import jalview.jbgui.GWebserviceInfo;
 +import jalview.util.MessageManager;
 +import jalview.util.Platform;
 +import jalview.ws.WSClientI;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
@@@ -36,6 -33,6 +36,7 @@@ import java.awt.MediaTracker
  import java.awt.RenderingHints;
  import java.awt.event.ActionEvent;
  import java.awt.image.BufferedImage;
++import java.util.Locale;
  import java.util.Vector;
  
  import javax.swing.JComponent;
@@@ -52,6 -49,11 +53,6 @@@ import javax.swing.event.InternalFrameE
  import javax.swing.text.html.HTMLEditorKit;
  import javax.swing.text.html.StyleSheet;
  
 -import jalview.jbgui.GWebserviceInfo;
 -import jalview.util.ChannelProperties;
 -import jalview.util.MessageManager;
 -import jalview.ws.WSClientI;
 -
  /**
   * Base class for web service client thread and gui TODO: create StAX parser to
   * extract html body content reliably when preparing html formatted job statuses
@@@ -260,7 -262,6 +261,7 @@@ public class WebserviceInfo extends GWe
    public WebserviceInfo(String title, String info, int width, int height,
            boolean makeVisible)
    {
 +    // no references
      init(title, info, width, height, makeVisible);
    }
  
    {
      frame = new JInternalFrame();
      frame.setContentPane(this);
 -    Desktop.addInternalFrame(frame, title, makeVisible, width, height);
 +    Desktop.addInternalFrame(frame, title, makeVisible, width, height, Desktop.FRAME_ALLOW_RESIZE, Desktop.FRAME_SET_MIN_SIZE_300);
      frame.setClosable(false);
  
      progressBar = new ProgressBar(statusPanel, statusBar);
      this.title = title;
      setInfoText(info);
  
-     java.net.URL url = getClass()
-             .getResource("/images/Jalview_Logo_small_with_border.png");
-     image = java.awt.Toolkit.getDefaultToolkit().createImage(url);
+     image = ChannelProperties.getImage("rotatable_logo.48");
  
      MediaTracker mt = new MediaTracker(this);
      mt.addImage(image, 0);
      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 InternalFrameAdapter()
 +    if (!Platform.isJS())
      {
 -      @Override
 -      public void internalFrameClosed(InternalFrameEvent evt)
 -      {
 -        // System.out.println("Shutting down webservice client");
 -        WSClientI service = thisinfo.getthisService();
 -        if (service != null && service.isCancellable())
 -        {
 -          service.cancelJob();
 -        }
 -      }
 -    });
 +      // No animation for the moment//
 +      Thread thread = new Thread(ap);
 +      thread.start();
 +    }
 +    final WebserviceInfo thisinfo = this;
 +    frame.addInternalFrameListener(
 +            new InternalFrameAdapter()
 +            {
 +              @Override
 +              public void internalFrameClosed(InternalFrameEvent evt)
 +              {
 +                // System.out.println("Shutting down webservice client");
 +                WSClientI service = thisinfo.getthisService();
 +                if (service != null && service.isCancellable())
 +                {
 +                  service.cancelJob();
 +                }
 +              }
 +            });
      frame.validate();
  
    }
      {
        return null;
      }
-     String lowertxt = text.toLowerCase();
+     String lowertxt = text.toLowerCase(Locale.ROOT);
      int htmlpos = leaveFirst ? -1 : lowertxt.indexOf("<body");
  
      int htmlend = leaveLast ? -1 : lowertxt.indexOf("</body");
      {
        return "";
      }
-     String lowertxt = text.toLowerCase();
+     String lowertxt = text.toLowerCase(Locale.ROOT);
      int htmlpos = lowertxt.indexOf("<body");
      int htmlend = lowertxt.indexOf("</body");
      int doctype = lowertxt.indexOf("<!doctype");
        @Override
        public void run()
        {
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop, message,
 +        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(), message,
                  title, JvOptionPane.WARNING_MESSAGE);
  
        }
      }
  
      @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);
    }
    {
      progressBar.setProgressBar(message, id);
    }
 +  
 +  @Override
 +  public void removeProgressBar(long id)
 +  {
 +    progressBar.removeProgressBar(id);
 +  }
  
    @Override
    public void registerHandler(final long id,
   */
  package jalview.gui;
  
 +import jalview.gui.OptsAndParamsPage.OptionBox;
 +import jalview.gui.OptsAndParamsPage.ParamBox;
 +import jalview.util.MessageManager;
 +import jalview.ws.api.UIinfo;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.OptionI;
 +import jalview.ws.params.ParamDatastoreI;
 +import jalview.ws.params.ParameterI;
 +import jalview.ws.params.WsParamSetI;
 +
  import java.awt.BorderLayout;
  import java.awt.Component;
  import java.awt.Dimension;
@@@ -45,11 -35,13 +45,11 @@@ import java.awt.event.HierarchyBoundsLi
  import java.awt.event.HierarchyEvent;
  import java.awt.event.ItemEvent;
  import java.awt.event.ItemListener;
 -import java.awt.event.WindowEvent;
 -import java.awt.event.WindowListener;
 -import java.net.URL;
  import java.util.Hashtable;
 -import java.util.Iterator;
  import java.util.List;
  import java.util.Vector;
 +import java.util.concurrent.CompletableFuture;
 +import java.util.concurrent.CompletionStage;
  
  import javax.swing.JButton;
  import javax.swing.JComboBox;
@@@ -58,12 -50,31 +58,12 @@@ import javax.swing.JFrame
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
 -import javax.swing.JSplitPane;
  import javax.swing.JTextArea;
 +import javax.swing.WindowConstants;
  import javax.swing.border.TitledBorder;
  import javax.swing.event.DocumentEvent;
  import javax.swing.event.DocumentListener;
  
 -import compbio.metadata.Argument;
 -import compbio.metadata.Option;
 -import compbio.metadata.Parameter;
 -import compbio.metadata.Preset;
 -import compbio.metadata.PresetManager;
 -import compbio.metadata.RunnerConfig;
 -import jalview.bin.Cache;
 -import jalview.gui.OptsAndParamsPage.OptionBox;
 -import jalview.gui.OptsAndParamsPage.ParamBox;
 -import jalview.util.MessageManager;
 -import jalview.ws.jws2.JabaParamStore;
 -import jalview.ws.jws2.JabaPreset;
 -import jalview.ws.jws2.Jws2Discoverer;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 -import jalview.ws.params.ArgumentI;
 -import jalview.ws.params.OptionI;
 -import jalview.ws.params.ParamDatastoreI;
 -import jalview.ws.params.ParameterI;
 -import jalview.ws.params.WsParamSetI;
  import net.miginfocom.swing.MigLayout;
  
  /**
  public class WsJobParameters extends JPanel implements ItemListener,
          ActionListener, DocumentListener, OptsParametersContainerI
  {
 -  URL linkImageURL = getClass().getResource("/images/link.gif");
 +  private static final int PREFERRED_WIDTH = 540;
 +
 +  private static final int DEFAULT_HEIGHT = 640;
 +
 +  // the default parameter set shown to the user
 +  private static final String SVC_DEF = "Defaults";
 +
 +  private int maxOptWidth = 200;
 +
 +  // URL linkImageURL = getClass().getResource("/images/link.gif");
  
 -  private static final String SVC_DEF = "Defaults"; // this is the null
 -                                                    // parameter set as shown to
 -                                                    // user
 +  // TODO ABSRACT FROM JABAWS CLASSES
  
 +  // completion stage representing whether start was clicked
 +  private final CompletableFuture<Boolean> completionStage = new CompletableFuture<>();
 +  
    /**
     * manager for options and parameters.
     */
 -  OptsAndParamsPage opanp = new OptsAndParamsPage(this);
 +  OptsAndParamsPage opanp;
  
 -  /**
 +  /*
     * panel containing job options
     */
 -  JPanel jobOptions = new JPanel();
 +  JPanel optionsPanel = new JPanel();
  
 -  /**
 +  /*
     * panel containing job parameters
     */
 -  JPanel paramList = new JPanel();
 -
 -  JPanel SetNamePanel = new JPanel();
 -
 -  JPanel setDetails = new JPanel();
 +  JPanel paramsPanel = new JPanel();
  
 -  JSplitPane settingsPanel = new JSplitPane();
 -
 -  JPanel jobPanel = new JPanel();
 -
 -  JScrollPane jobOptionsPane = new JScrollPane();
 +  JPanel setNamePanel = new JPanel();
  
    JButton createpref = new JButton();
  
  
    JButton updatepref = new JButton();
  
 -  JButton startjob = new JButton();
 -
 -  JButton canceljob = new JButton();
 -
 -  JComboBox setName = new JComboBox();
 +  JComboBox<String> setName = new JComboBox<>();
  
    JTextArea setDescr = new JTextArea();
  
    JScrollPane paramPane = new JScrollPane();
 -
 -  // ScrollablePanel optsAndparams = new ScrollablePanel();
 -  JPanel optsAndparams = new JPanel();
 -
 -  RunnerConfig serviceOptions;
 +  
 +  JButton startjob = JvSwingUtils.makeButton(
 +          MessageManager.getString("action.start_job"),
 +          MessageManager.getString("label.start_job_current_settings"),
 +          this::startjob_actionPerformed);
 +  JButton canceljob = JvSwingUtils.makeButton(
 +          MessageManager.getString("action.cancel_job"),
 +          MessageManager.getString("label.cancel_job_close_dialog"),
 +          this::canceljob_actionPerformed);
  
    ParamDatastoreI paramStore;
  
 -  private int MAX_OPTWIDTH = 200;
 +  // set true when 'Start Job' is clicked
 +  boolean startJob = false;
  
 -  WsJobParameters(Jws2Instance service)
 -  {
 -    this(service, null);
 -  }
 +  JFrame frame = null;
  
 -  public WsJobParameters(Jws2Instance service, WsParamSetI preset)
 -  {
 -    this(null, service, preset, null);
 -  }
 +  UIinfo service;
  
 -  /**
 -   * 
 -   * @param desktop
 -   *          - if null, create new JFrame outside of desktop
 -   * @param service
 -   * @param preset
 +  /*
 +   * list of service presets in the gui
 +   */
 +  Hashtable<String, String> servicePresets = null;
 +
 +  /*
 +   * set if dialog is being set - so handlers will avoid spurious events
     */
 -  public WsJobParameters(JFrame parent, Jws2Instance service,
 -          WsParamSetI preset, List<Argument> jobArgset)
 +  boolean settingDialog = false;
 +
 +  private Hashtable<Object, Object> modifiedElements = new Hashtable<>();
 +
 +  String lastParmSet = null;
 +
 +  public WsJobParameters(ParamDatastoreI store, WsParamSetI preset,
 +          List<ArgumentI> args)
    {
 -    this(parent, null, service, preset, jobArgset);
 +    super();
 +
 +    // parameters dialog in 'compact' format (help as tooltips)
 +    opanp = new OptsAndParamsPage(this, true);
 +    jbInit();
 +    this.paramStore = store;
 +    this.service = null;
 +    init(preset, args);
 +    validate();
    }
  
    /**
 +   * Constructor given a set of parameters and presets, a service to be invoked,
 +   * and a list of (Jabaws client) arguments
     * 
 -   * @param parent
     * @param paramStorei
     * @param service
     * @param preset
     * @param jobArgset
     */
 -  public WsJobParameters(JFrame parent, ParamDatastoreI paramStorei,
 -          Jws2Instance service, WsParamSetI preset,
 -          List<Argument> jobArgset)
 +  public WsJobParameters(ParamDatastoreI paramStorei, UIinfo service,
 +          WsParamSetI preset, List<ArgumentI> jobArgset)
    {
      super();
 +    // parameters dialog in 'expanded' format (help text boxes)
 +    opanp = new OptsAndParamsPage(this, false);
      jbInit();
      this.paramStore = paramStorei;
 -    if (paramStore == null)
 +    if (paramStore == null && service != null)
      {
        paramStore = service.getParamStore();
      }
      this.service = service;
 -    // argSetModified(false);
 -    // populate parameter table
 -    initForService(service, preset, jobArgset);
 -    // display in new JFrame attached to parent.
 +    initForService(preset, jobArgset);
      validate();
    }
  
 -  int response = -1;
 -
 -  JDialog frame = null;
    /**
 -   * shows a modal dialog containing the parameters.
 +   * Shows a modal dialog containing the parameters and Start or Cancel options.
 +   * Answers true if the job is started, false if cancelled.
     * 
     * @return
     */
 -  public boolean showRunDialog()
 +  public CompletionStage<Boolean> showRunDialog()
    {
 -
 -    frame = new JDialog(Desktop.instance, true);
 -
 -    frame.setTitle(MessageManager.formatMessage("label.edit_params_for",
 -            new String[]
 -            { service.getActionText() }));
 -    Rectangle deskr = Desktop.instance.getBounds();
++    // Should JFrame hahve a parent of getDesktop ?
 +    frame = new JFrame();
 +    frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
 +    if (service != null)
 +    {
 +      frame.setTitle(MessageManager.formatMessage("label.edit_params_for",
 +              new String[] { service.getActionText() }));
 +    }
 +    Rectangle deskr = Desktop.getInstance().getBounds();
      Dimension pref = this.getPreferredSize();
      frame.setBounds(
              new Rectangle((int) (deskr.getCenterX() - pref.width / 2),
  
        }
      });
 +    
      frame.setVisible(true);
 -    if (response > 0)
 -    {
 -      return true;
 -    }
 -    return false;
 +    return completionStage;
    }
  
    private void jbInit()
      updatepref = JvSwingUtils.makeButton(
              MessageManager.getString("action.update"),
              MessageManager.getString("label.update_user_parameter_set"),
 -            new ActionListener()
 -            {
 -
 -              @Override
 -              public void actionPerformed(ActionEvent e)
 -              {
 -                update_actionPerformed(e);
 -              }
 -            });
 +            this::update_actionPerformed);
      deletepref = JvSwingUtils.makeButton(
              MessageManager.getString("action.delete"),
              MessageManager.getString("label.delete_user_parameter_set"),
 -            new ActionListener()
 -            {
 -
 -              @Override
 -              public void actionPerformed(ActionEvent e)
 -              {
 -                delete_actionPerformed(e);
 -              }
 -            });
 +            this::delete_actionPerformed);
      createpref = JvSwingUtils.makeButton(
              MessageManager.getString("action.create"),
              MessageManager.getString("label.create_user_parameter_set"),
 -            new ActionListener()
 -            {
 -
 -              @Override
 -              public void actionPerformed(ActionEvent e)
 -              {
 -                create_actionPerformed(e);
 -              }
 -            });
 +            this::create_actionPerformed);
      revertpref = JvSwingUtils.makeButton(
              MessageManager.getString("action.revert"),
              MessageManager
                      .getString("label.revert_changes_user_parameter_set"),
 -            new ActionListener()
 -            {
 +            this::revert_actionPerformed);
  
 -              @Override
 -              public void actionPerformed(ActionEvent e)
 -              {
 -                revert_actionPerformed(e);
 -              }
 -            });
 -    startjob = JvSwingUtils.makeButton(
 -            MessageManager.getString("action.start_job"),
 -            MessageManager.getString("label.start_job_current_settings"),
 -            new ActionListener()
 -            {
 -              @Override
 -              public void actionPerformed(ActionEvent e)
 -              {
 -                startjob_actionPerformed(e);
 -              }
 -            });
 -    canceljob = JvSwingUtils.makeButton(
 -            MessageManager.getString("action.cancel_job"),
 -            MessageManager.getString("label.cancel_job_close_dialog"),
 -            new ActionListener()
 -            {
 -              @Override
 -              public void actionPerformed(ActionEvent e)
 -              {
 -                canceljob_actionPerformed(e);
 -              }
 -            });
  
 +    JPanel setDetails = new JPanel();
      setDetails.setBorder(
              new TitledBorder(MessageManager.getString("label.details")));
      setDetails.setLayout(new BorderLayout());
      setName.getEditor().addActionListener(this);
      JPanel setNameInfo = new JPanel(new FlowLayout(FlowLayout.LEFT));
      GridBagLayout gbl = new GridBagLayout();
 -    SetNamePanel.setLayout(gbl);
 +    setNamePanel.setLayout(gbl);
  
      JLabel setNameLabel = new JLabel(
              MessageManager.getString("label.current_parameter_set_name"));
      revertpref.setVisible(false);
      createpref.setVisible(false);
      JPanel setsavebuts = new JPanel();
 -    setsavebuts.setLayout(new FlowLayout(FlowLayout.LEFT)); // GridLayout(1,2));
 -    ((FlowLayout) setsavebuts.getLayout()).setHgap(10);
 -    ((FlowLayout) setsavebuts.getLayout()).setVgap(0);
 +    setsavebuts.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 0)); // GridLayout(1,2));
      JPanel spacer = new JPanel();
      spacer.setPreferredSize(new Dimension(2, 30));
      setsavebuts.add(spacer);
      // setsavebuts.setSize(new Dimension(150, 30));
      JPanel buttonArea = new JPanel(new GridLayout(1, 1));
      buttonArea.add(setsavebuts);
 -    SetNamePanel.add(setNameInfo);
 +    setNamePanel.add(setNameInfo);
      GridBagConstraints gbc = new GridBagConstraints();
      gbc.gridheight = 2;
      gbl.setConstraints(setNameInfo, gbc);
 -    SetNamePanel.add(buttonArea);
 +    setNamePanel.add(buttonArea);
      gbc = new GridBagConstraints();
      gbc.gridx = 0;
      gbc.gridy = 2;
  
      // paramPane.setPreferredSize(new Dimension(360, 400));
      // paramPane.setPreferredSize(null);
 -    jobOptions.setBorder(
 +    optionsPanel.setBorder(
              new TitledBorder(MessageManager.getString("label.options")));
 -    jobOptions.setOpaque(true);
 -    paramList.setBorder(
 +    optionsPanel.setOpaque(true);
 +    paramsPanel.setBorder(
              new TitledBorder(MessageManager.getString("label.parameters")));
 -    paramList.setOpaque(true);
 -    JPanel bjo = new JPanel(new BorderLayout()),
 -            bjp = new JPanel(new BorderLayout());
 -    bjo.add(jobOptions, BorderLayout.CENTER);
 -    bjp.add(paramList, BorderLayout.CENTER);
 -    bjp.setOpaque(true);
 -    bjo.setOpaque(true);
 +    paramsPanel.setOpaque(true);
      // optsAndparams.setScrollableWidth(ScrollableSizeHint.FIT);
      // optsAndparams.setScrollableHeight(ScrollableSizeHint.NONE);
      // optsAndparams.setLayout(new BorderLayout());
 +    JPanel optsAndparams = new JPanel();
      optsAndparams.setLayout(new BorderLayout());
 -    optsAndparams.add(jobOptions, BorderLayout.NORTH);
 -    optsAndparams.add(paramList, BorderLayout.CENTER);
 +    optsAndparams.add(optionsPanel, BorderLayout.NORTH);
 +    optsAndparams.add(paramsPanel, BorderLayout.CENTER);
      JPanel jp = new JPanel(new BorderLayout());
      jp.add(optsAndparams, BorderLayout.CENTER);
      paramPane.getViewport().setView(jp);
      paramPane.setBorder(null);
      setLayout(new BorderLayout());
 +    JPanel jobPanel = new JPanel();
      jobPanel.setPreferredSize(null);
      jobPanel.setLayout(new BorderLayout());
      jobPanel.add(setDetails, BorderLayout.NORTH);
      jobPanel.add(paramPane, BorderLayout.CENTER);
      // jobPanel.setOrientation(JSplitPane.VERTICAL_SPLIT);
  
 -    add(SetNamePanel, BorderLayout.NORTH);
 +    add(setNamePanel, BorderLayout.NORTH);
      add(jobPanel, BorderLayout.CENTER);
  
      JPanel dialogpanel = new JPanel();
      dialogpanel.add(canceljob);
      // JAL-1580: setMaximumSize() doesn't work, so just size for the worst case:
      // check for null is for JUnit usage
 -    final int windowHeight = Desktop.instance == null ? 540
 -            : Desktop.instance.getHeight();
 +    final int windowHeight = Desktop.getInstance() == null ? DEFAULT_HEIGHT
 +            : Desktop.getInstance().getHeight();
      setPreferredSize(new Dimension(540, windowHeight));
      add(dialogpanel, BorderLayout.SOUTH);
      validate();
  
    protected void canceljob_actionPerformed(ActionEvent e)
    {
 -    response = 0;
 +    startJob = false;
      if (frame != null)
      {
        frame.setVisible(false);
      }
 +    completionStage.complete(false);
    }
  
    protected void startjob_actionPerformed(ActionEvent e)
    {
 -    response = 1;
 +    startJob = true;
      if (frame != null)
      {
        frame.setVisible(false);
      }
 +    completionStage.complete(true);
    }
  
 -  Jws2Instance service;
 +  void initForService(WsParamSetI paramSet, List<ArgumentI> jobArgset)
 +  {
 +    settingDialog = true;
  
 -  /**
 -   * list of service presets in the gui
 -   */
 -  Hashtable servicePresets = null;
 +    init(paramSet, jobArgset);
  
 -  /**
 -   * set if dialog is being set - so handlers will avoid spurious events
 -   */
 -  boolean settingDialog = false;
 +  }
  
 -  void initForService(Jws2Instance service, WsParamSetI jabap,
 -          List<Argument> jabajobArgset)
 +  void init(WsParamSetI p, List<ArgumentI> jobArgset)
    {
 -    WsParamSetI p = null;
 -    List<ArgumentI> jobArgset = null;
 -    settingDialog = true;
 -    { // instantiate the abstract proxy for Jaba objects
 -      jobArgset = jabajobArgset == null ? null
 -              : JabaParamStore.getJwsArgsfromJaba(jabajobArgset);
 -      p = jabap; // (jabap != null) ? paramStore.getPreset(jabap.getName()) :
 -                 // null;
 -    }
 -
 -    Hashtable exnames = new Hashtable();
 +    Hashtable<String, String> exnames = new Hashtable<>();
      for (int i = 0, iSize = setName.getItemCount(); i < iSize; i++)
      {
        exnames.put(setName.getItemAt(i), setName.getItemAt(i));
      }
 -    servicePresets = new Hashtable();
 +    servicePresets = new Hashtable<>();
      // Add the default entry - if not present already.
      if (!exnames.contains(SVC_DEF))
      {
        exnames.put(SVC_DEF, SVC_DEF);
        servicePresets.put(SVC_DEF, SVC_DEF);
      }
 -    String curname = (p == null ? "" : p.getName());
 +    // String curname = (p == null ? "" : p.getName());
      for (WsParamSetI pr : paramStore.getPresets())
      {
        if (!pr.isModifiable())
        }
      }
      settingDialog = false;
    }
  
 -  @SuppressWarnings("unchecked")
    private void updateTable(WsParamSetI p, List<ArgumentI> jobArgset)
    {
      boolean setDefaultParams = false;
              OptionI opt = (OptionI) myarg;
              OptionBox ob = opanp.addOption(opt);
              ob.resetToDefault(setDefaultParams);
 -            if (MAX_OPTWIDTH < ob.getPreferredSize().width)
 +            if (maxOptWidth < ob.getPreferredSize().width)
              {
 -              MAX_OPTWIDTH = ob.getPreferredSize().width;
 +              maxOptWidth = ob.getPreferredSize().width;
              }
              ob.validate();
              cw += ob.getPreferredSize().width + 5;
      return modifiedElements.size() > 0;
    }
  
 -  private Hashtable modifiedElements = new Hashtable();
    /**
     * reset gui and modification state settings
     */
      if (b && modifiedElements.size() > 0)
      {
        makeSetNameValid(!isUserPreset);
 -      SetNamePanel.revalidate();
 +      setNamePanel.revalidate();
      }
      updateButtonDisplay();
    }
      // sync the gui with the preset database
      for (int i = 0, iS = setName.getItemCount(); i < iS; i++)
      {
 -      String snm = (String) setName.getItemAt(i);
 +      String snm = setName.getItemAt(i);
        if (snm.equals(nm))
        {
          makeupdate = true;
      settingDialog = stn;
    }
  
 +  /**
 +   * Rebuilds the Options and Parameters panels
 +   */
    @Override
    public void refreshParamLayout()
    {
 -    // optsAndparams.setPreferredSize(null);
 -    FlowLayout fl = new FlowLayout(FlowLayout.LEFT);
 -    int sep = fl.getVgap();
 -    boolean fh = true;
 -    int os = 0,
 -            s = jobOptions.getBorder().getBorderInsets(jobOptions).bottom
 -                    + jobOptions.getBorder().getBorderInsets(jobOptions).top
 -                    + 2 * sep;
 -    /**
 -     * final height for viewport
 -     */
 -    int finalh = s;
 -    int panewidth = paramPane.getViewport().getSize().width - 120
 -            - jobOptions.getBorder().getBorderInsets(jobOptions).left
 -            + jobOptions.getBorder().getBorderInsets(jobOptions).right;
 -
 -    int w = 2 * fl.getHgap()
 -            + (MAX_OPTWIDTH > OptsAndParamsPage.PARAM_WIDTH ? MAX_OPTWIDTH
 -                    : OptsAndParamsPage.PARAM_WIDTH);
 -    int hgap = fl.getHgap(), cw = hgap;
 +    final int rightMargin = 40;
 +    final int availableWidth = paramPane.getViewport().getSize().width
 +            - rightMargin
 +            - optionsPanel.getBorder().getBorderInsets(optionsPanel).left
 +            + optionsPanel.getBorder().getBorderInsets(optionsPanel).right;
  
      if (opanp.getOptSet().size() > 0)
      {
 +      int hgap = 5;
 +      int currentWidth = hgap;
  
 -      jobOptions.setLayout(new MigLayout("", "", ""));
 -      jobOptions.removeAll();
 +      /*
 +       * layout constraint 'nogrid' prevents vertical column alignment,
 +       * allowing controls to flow without extra space inserted to align
 +       */
 +      optionsPanel.setLayout(new MigLayout("nogrid", "", ""));
 +      optionsPanel.removeAll();
 +      JPanel lastAdded = null;
  
 +      /*
 +       * add each control in turn; if adding would overflow the right margin,
 +       * remove and re-add the previous parameter with "wrap" (after) 
 +       * in order to start a new row
 +       */
        for (OptionBox pbox : opanp.getOptSet().values())
        {
          pbox.validate();
 -        cw += pbox.getSize().width + hgap;
 -        if (cw + 120 > panewidth)
 -        {
 -          jobOptions.add(pbox, "wrap");
 -          // System.out.println("Wrap on "+pbox.option.getName());
 -          cw = hgap + pbox.getSize().width;
 -          fh = true;
 -        }
 -        else
 +        int boxWidth = pbox.getSize().width;
 +        currentWidth += boxWidth + hgap;
 +        boolean wrapAfterLast = currentWidth > availableWidth
 +                && lastAdded != null;
 +        // System.out.println(String.format(
 +        // "%s width=%d, paneWidth=%d, currentWidth=%d, wrapAfterLast=%s",
 +        // pbox.toString(), boxWidth, panewidth, currentWidth,
 +        // wrapAfterLast));
 +        if (wrapAfterLast)
          {
 -          jobOptions.add(pbox);
 -        }
 -        if (fh)
 -        {
 -          finalh += pbox.getSize().height + fl.getVgap();
 -          fh = false;
 +          optionsPanel.remove(lastAdded);
 +          optionsPanel.add(lastAdded, "wrap");
 +          currentWidth = hgap + boxWidth;
          }
 +        optionsPanel.add(pbox);
 +        lastAdded = pbox;
        }
 -      jobOptions.revalidate();
 +      optionsPanel.revalidate();
      }
      else
      {
 -      jobOptions.setVisible(false);
 +      optionsPanel.setVisible(false);
      }
  
 -    // Now layout the parameters assuming they occupy one column - to calculate
 -    // total height of options+parameters
 -    fl = new FlowLayout(FlowLayout.LEFT);
 -    // helpful hint from
 -    // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout
 -    fl.setAlignOnBaseline(true);
      if (opanp.getParamSet().size() > 0)
      {
 -      paramList.removeAll();
 -      paramList.setLayout(new MigLayout("", "", ""));
 -      fh = true;
 +      paramsPanel.removeAll();
 +      paramsPanel.setLayout(new MigLayout("", "", ""));
 +      int hgap = 5;
 +      int currentWidth = hgap;
 +
 +      JPanel lastAdded = null;
        for (ParamBox pbox : opanp.getParamSet().values())
        {
          pbox.validate();
 -        cw += pbox.getSize().width + hgap;
 -        if (cw + 160 > panewidth)
 -        {
 -          paramList.add(pbox, "wrap");
 -          cw = pbox.getSize().width + hgap;
 -          fh = true;
 -        }
 -        else
 -        {
 -          paramList.add(pbox);
 -        }
 -        if (fh)
 +        int boxWidth = pbox.getSize().width;
 +        currentWidth += boxWidth + hgap;
 +        boolean wrapAfterLast = currentWidth > availableWidth
 +                && lastAdded != null;
 +        if (wrapAfterLast)
          {
 -          finalh += pbox.getSize().height + fl.getVgap();
 -          fh = false;
 +          paramsPanel.remove(lastAdded);
 +          paramsPanel.add(lastAdded, "wrap");
 +          currentWidth = pbox.getSize().width + hgap;
          }
 -
 +        paramsPanel.add(pbox);
 +        lastAdded = pbox;
        }
 +
        /*
         * s = 2 * sep; for (ParamBox pbox : opanp.getParamSet().values()) {
         * pbox.validate(); s += sep +
         * .getBorder().getBorderInsets(paramList).bottom+paramList
         * .getBorder().getBorderInsets(paramList).top;
         */
 -      paramList.revalidate();
 +      paramsPanel.revalidate();
      }
      else
      {
 -      paramList.setVisible(false);
 +      paramsPanel.setVisible(false);
      }
      // TODO: waste some time trying to eliminate any unnecessary .validate calls
      // here
      paramPane.revalidate();
      revalidate();
    }
 -  /**
 -   * testing method - grab a service and parameter set and show the window
 -   * 
 -   * @param args
 -   * @j2sIgnore
 -   */
 -  public static void main(String[] args)
 -  {
 -    jalview.ws.jws2.Jws2Discoverer disc = jalview.ws.jws2.Jws2Discoverer
 -            .getDiscoverer();
 -    int p = 0;
 -    if (args.length > 0)
 -    {
 -      Vector<String> services = new Vector<>();
 -      services.addElement(args[p++]);
 -      Jws2Discoverer.getDiscoverer().setServiceUrls(services);
 -    }
 -    try
 -    {
 -      disc.run();
 -    } catch (Exception e)
 -    {
 -      System.err.println("Aborting. Problem discovering services.");
 -      e.printStackTrace();
 -      return;
 -    }
 -    Jws2Instance lastserv = null;
 -    for (Jws2Instance service : disc.getServices())
 -    {
 -      lastserv = service;
 -      if (p >= args.length || service.serviceType.equalsIgnoreCase(args[p]))
 -      {
 -        if (lastserv != null)
 -        {
 -          List<Preset> prl = null;
 -          Preset pr = null;
 -          if (++p < args.length)
 -          {
 -            PresetManager prman = lastserv.getPresets();
 -            if (prman != null)
 -            {
 -              pr = prman.getPresetByName(args[p]);
 -              if (pr == null)
 -              {
 -                // just grab the last preset.
 -                prl = prman.getPresets();
 -              }
 -            }
 -          }
 -          else
 -          {
 -            PresetManager prman = lastserv.getPresets();
 -            if (prman != null)
 -            {
 -              prl = prman.getPresets();
 -            }
 -          }
 -          Iterator<Preset> en = (prl == null) ? null : prl.iterator();
 -          while (en != null && en.hasNext())
 -          {
 -            if (en != null)
 -            {
 -              if (!en.hasNext())
 -              {
 -                en = prl.iterator();
 -              }
 -              pr = en.next();
 -            }
 -            {
 -              System.out.println("Testing opts dupes for "
 -                      + lastserv.getUri() + " : " + lastserv.getActionText()
 -                      + ":" + pr.getName());
 -              List<Option> rg = lastserv.getRunnerConfig().getOptions();
 -              for (Option o : rg)
 -              {
 -                try
 -                {
 -                  Option cpy = jalview.ws.jws2.ParameterUtils.copyOption(o);
 -                } catch (Exception e)
 -                {
 -                  System.err.println("Failed to copy " + o.getName());
 -                  e.printStackTrace();
 -                } catch (Error e)
 -                {
 -                  System.err.println("Failed to copy " + o.getName());
 -                  e.printStackTrace();
 -                }
 -              }
 -            }
 -            {
 -              System.out.println("Testing param dupes:");
 -              List<Parameter> rg = lastserv.getRunnerConfig()
 -                      .getParameters();
 -              for (Parameter o : rg)
 -              {
 -                try
 -                {
 -                  Parameter cpy = jalview.ws.jws2.ParameterUtils
 -                          .copyParameter(o);
 -                } catch (Exception e)
 -                {
 -                  System.err.println("Failed to copy " + o.getName());
 -                  e.printStackTrace();
 -                } catch (Error e)
 -                {
 -                  System.err.println("Failed to copy " + o.getName());
 -                  e.printStackTrace();
 -                }
 -              }
 -            }
 -            {
 -              System.out.println("Testing param write:");
 -              List<String> writeparam = null, readparam = null;
 -              try
 -              {
 -                writeparam = jalview.ws.jws2.ParameterUtils
 -                        .writeParameterSet(
 -                                pr.getArguments(lastserv.getRunnerConfig()),
 -                                " ");
 -                System.out.println("Testing param read :");
 -                List<Option> pset = jalview.ws.jws2.ParameterUtils
 -                        .processParameters(writeparam,
 -                                lastserv.getRunnerConfig(), " ");
 -                readparam = jalview.ws.jws2.ParameterUtils
 -                        .writeParameterSet(pset, " ");
 -                Iterator<String> o = pr.getOptions().iterator(),
 -                        s = writeparam.iterator(), t = readparam.iterator();
 -                boolean failed = false;
 -                while (s.hasNext() && t.hasNext())
 -                {
 -                  String on = o.next(), sn = s.next(), st = t.next();
 -                  if (!sn.equals(st))
 -                  {
 -                    System.out.println(
 -                            "Original was " + on + " Phase 1 wrote " + sn
 -                                    + "\tPhase 2 wrote " + st);
 -                    failed = true;
 -                  }
 -                }
 -                if (failed)
 -                {
 -                  System.out.println(
 -                          "Original parameters:\n" + pr.getOptions());
 -                  System.out.println(
 -                          "Wrote parameters in first set:\n" + writeparam);
 -                  System.out.println(
 -                          "Wrote parameters in second set:\n" + readparam);
 -
 -                }
 -              } catch (Exception e)
 -              {
 -                e.printStackTrace();
 -              }
 -            }
 -            WsJobParameters pgui = new WsJobParameters(lastserv,
 -                    new JabaPreset(lastserv, pr));
 -            JFrame jf = new JFrame(MessageManager
 -                    .formatMessage("label.ws_parameters_for", new String[]
 -                    { lastserv.getActionText() }));
 -            JPanel cont = new JPanel(new BorderLayout());
 -            pgui.validate();
 -            cont.setPreferredSize(pgui.getPreferredSize());
 -            cont.add(pgui, BorderLayout.CENTER);
 -            jf.setLayout(new BorderLayout());
 -            jf.add(cont, BorderLayout.CENTER);
 -            jf.validate();
 -            final Thread thr = Thread.currentThread();
 -            jf.addWindowListener(new WindowListener()
 -            {
 -
 -              @Override
 -              public void windowActivated(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -              @Override
 -              public void windowClosed(WindowEvent e)
 -              {
 -              }
 -
 -              @Override
 -              public void windowClosing(WindowEvent e)
 -              {
 -                thr.interrupt();
 -
 -              }
 -
 -              @Override
 -              public void windowDeactivated(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -              @Override
 -              public void windowDeiconified(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -              @Override
 -              public void windowIconified(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -              @Override
 -              public void windowOpened(WindowEvent e)
 -              {
 -                // TODO Auto-generated method stub
 -
 -              }
 -
 -            });
 -            jf.setVisible(true);
 -            boolean inter = false;
 -            while (!inter)
 -            {
 -              try
 -              {
 -                Thread.sleep(10000);
 -              } catch (Exception e)
 -              {
 -                inter = true;
 -              }
 -            }
 -            jf.dispose();
 -          }
 -        }
 -      }
 -    }
 -  }
    public boolean isServiceDefaults()
    {
      return (!isModified()
      return opanp.getCurrentSettings();
    }
  
 -  String lastParmSet = null;
    /*
     * Hashtable<String, Object[]> editedParams = new Hashtable<String,
     * Object[]>();
      int n = 0;
      // remove any set names in the drop down menu that aren't either a reserved
      // setting, or a user defined or service preset.
 -    Vector items = new Vector();
 +    Vector<String> items = new Vector<>();
      while (n < setName.getItemCount())
      {
 -      String item = (String) setName.getItemAt(n);
 +      String item = setName.getItemAt(n);
        if (!item.equals(SVC_DEF) && !paramStore.presetExists(item))
        {
          setName.removeItemAt(n);
      initArgSetModified();
      syncSetNamesWithStore();
      setName.setSelectedItem(lastParmSet);
 -    SetNamePanel.validate();
 +    setNamePanel.validate();
      validate();
      settingDialog = false;
    }
     */
    protected void updateWebServiceMenus()
    {
 +    if (Desktop.getInstance() == null)
 +    {
 +      return;
 +    }
      for (AlignFrame alignFrame : Desktop.getAlignFrames())
      {
 -      alignFrame.BuildWebServiceMenu();
 +      alignFrame.buildWebServicesMenu();
      }
    }
  
    @Override
    public void itemStateChanged(ItemEvent e)
    {
 -    if (e.getSource() == setName && e.getStateChange() == e.SELECTED)
 +    if (e.getSource() == setName
 +            && e.getStateChange() == ItemEvent.SELECTED)
      {
        final String setname = (String) setName.getSelectedItem();
-       // System.out.println("Item state changed for " + setname
-       // + " (handling ? " + !settingDialog + ")");
 -      if (Cache.log.isDebugEnabled())
 -      {
 -        Cache.log.debug("Item state changed for " + setname
 -                + " (handling ? " + !settingDialog + ")");
 -      }
++      System.out.println("Item state changed for " + setname
++              + " (handling ? " + !settingDialog + ")");
        if (settingDialog)
        {
          // ignore event
  
    }
  
 -  private void _renameExistingPreset(String oldName, String curSetName2)
 -  {
 -    paramStore.updatePreset(oldName, curSetName2, setDescr.getText(),
 -            getJobParams());
 -  }
 -
    /**
     * store current settings as given name. You should then reset gui.
     * 
@@@ -20,7 -20,6 +20,7 @@@
   */
  package jalview.gui;
  
 +
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
@@@ -28,7 -27,6 +28,7 @@@ import java.awt.Dimension
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.net.URL;
 +import java.util.ArrayList;
  import java.util.List;
  import java.util.Vector;
  
@@@ -38,11 -36,10 +38,10 @@@ import javax.swing.JTable
  import javax.swing.JTextField;
  import javax.swing.table.AbstractTableModel;
  import javax.swing.table.TableCellRenderer;
  import jalview.bin.Cache;
  import jalview.jbgui.GWsPreferences;
  import jalview.util.MessageManager;
 +import jalview.ws.WSDiscovererI;
  import jalview.ws.jws2.Jws2Discoverer;
  import jalview.ws.rest.RestServiceDescription;
  
@@@ -67,7 -64,7 +66,7 @@@ public class WsPreferences extends GWsP
    private void initFromPreferences()
    {
  
 -    wsUrls = Jws2Discoverer.getDiscoverer().getServiceUrls();
 +    wsUrls = Jws2Discoverer.getInstance().getServiceUrls();
      if (!wsUrls.isEmpty())
      {
        oldUrls = new Vector<String>(wsUrls);
      int r = 0;
      for (String url : wsUrls)
      {
 -      int status = Jws2Discoverer.getDiscoverer().getServerStatusFor(url);
 +      int status = Jws2Discoverer.getInstance().getServerStatusFor(url);
        tdat[r][1] = Integer.valueOf(status);
        tdat[r++][0] = url;
      }
        String t = new String("");
        switch (((Integer) status).intValue())
        {
 -      case 1:
 +      case WSDiscovererI.STATUS_OK:
          // cb.setSelected(true);
          // cb.setBackground(
          c = Color.green;
          break;
 -      case 0:
 +      case WSDiscovererI.STATUS_NO_SERVICES:
          // cb.setSelected(true);
          // cb.setBackground(
          c = Color.lightGray;
          break;
 -      case -1:
 +      case WSDiscovererI.STATUS_INVALID:
          // cb.setSelected(false);
          // cb.setBackground(
          c = Color.red;
          break;
 +      case WSDiscovererI.STATUS_UNKNOWN:
        default:
          // cb.setSelected(false);
          // cb.setBackground(
  
    private void updateServiceList()
    {
 -    Jws2Discoverer.getDiscoverer().setServiceUrls(wsUrls);
 +    Jws2Discoverer.getInstance().setServiceUrls(wsUrls);
    }
  
    private void updateRsbsServiceList()
      boolean valid = false;
      int resp = JvOptionPane.CANCEL_OPTION;
      while (!valid && (resp = JvOptionPane.showInternalConfirmDialog(
 -            Desktop.desktop, panel, title,
 +            Desktop.getDesktopPane(), panel, title,
              JvOptionPane.OK_CANCEL_OPTION)) == JvOptionPane.OK_OPTION)
      {
        try
        } catch (Exception e)
        {
          valid = false;
 -        JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                  MessageManager.getString("label.invalid_url"));
        }
      }
      if (valid && resp == JvOptionPane.OK_OPTION)
      {
 -      int validate = JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
 +      int validate = JvOptionPane.showInternalConfirmDialog(Desktop.getDesktopPane(),
                MessageManager.getString("info.validate_jabaws_server"),
                MessageManager.getString("label.test_server"),
                JvOptionPane.YES_NO_OPTION);
  
        if (validate == JvOptionPane.OK_OPTION)
        {
 -        if (Jws2Discoverer.testServiceUrl(foo))
 +        if (Jws2Discoverer.getInstance().testServiceUrl(foo))
          {
            return foo.toString();
          }
          else
          {
 -          int opt = JvOptionPane.showInternalOptionDialog(Desktop.desktop,
 +          int opt = JvOptionPane.showInternalOptionDialog(Desktop.getDesktopPane(),
                    "The Server  '" + foo.toString()
                            + "' failed validation,\ndo you want to add it anyway? ",
                    "Server Validation Failed", JvOptionPane.YES_NO_OPTION,
            }
            else
            {
 -            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +            JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                      MessageManager.getString(
                              "warn.server_didnt_pass_validation"));
            }
            if (lastrefresh != update)
            {
              lastrefresh = update;
 -            Desktop.instance.startServiceDiscovery(true); // wait around for all
 +            Desktop.getInstance().startServiceDiscovery(true); // wait around for all
                                                            // threads to complete
              updateList();
  
          public void run()
          {
            long ct = System.currentTimeMillis();
 -          Desktop.instance.setProgressBar(MessageManager
 +          Desktop.getInstance().setProgressBar(MessageManager
                    .getString("status.refreshing_web_service_menus"), ct);
            if (lastrefresh != update)
            {
              lastrefresh = update;
 -            Desktop.instance.startServiceDiscovery(true);
 +            Desktop.getInstance().startServiceDiscovery(true);
              updateList();
            }
 -          Desktop.instance.setProgressBar(null, ct);
 +          Desktop.getInstance().setProgressBar(null, ct);
          }
  
        }).start();
    /**
     * state counters for ensuring that updates only happen if config has changed.
     */
 -  protected long update = 0;
 -
 -  private long lastrefresh = 0;
 +  private long update = 0, lastrefresh = 0;
  
    /*
     * (non-Javadoc)
    @Override
    protected void resetWs_actionPerformed(ActionEvent e)
    {
 -    Jws2Discoverer.getDiscoverer().setServiceUrls(null);
 -    List<String> nwsUrls = Jws2Discoverer.getDiscoverer().getServiceUrls();
 +    Jws2Discoverer.getInstance().setServiceUrls(null);
 +    List<String> nwsUrls = Jws2Discoverer.getInstance().getServiceUrls();
      if (!wsUrls.equals(nwsUrls))
      {
        update++;
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.io;
  
+ import java.util.Locale;
  import jalview.api.AlignExportSettingsI;
  import jalview.api.AlignmentViewPanel;
  import jalview.datamodel.Alignment;
@@@ -158,7 -160,11 +160,7 @@@ public class AppletFormatAdapte
    {
  
      this.selectedFile = selectedFile;
 -    if (selectedFile != null)
 -    {
 -      this.inFile = selectedFile.getPath();
 -    }
 -    this.inFile = file;
 +    inFile = (selectedFile == null ? file : selectedFile.getPath());
      try
      {
        if (fileFormat.isStructureFile())
      
      String data = dataObject.toString();
      DataSourceType protocol = DataSourceType.PASTE;
-     String ft = data.toLowerCase().trim();
+     String ft = data.toLowerCase(Locale.ROOT).trim();
      if (ft.indexOf("http:") == 0 || ft.indexOf("https:") == 0
              || ft.indexOf("file:") == 0)
      {
   */
  package jalview.io;
  
- 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;
+ import java.nio.file.Files;
+ import java.nio.file.Path;
+ import java.nio.file.Paths;
+ import java.nio.file.StandardCopyOption;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.TreeMap;
  
+ import jalview.bin.Cache;
+ import jalview.gui.Desktop;
+ import jalview.gui.JvOptionPane;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
 -
  /*
   * BackupFiles used for manipulating (naming rolling/deleting) backup/version files when an alignment or project file is saved.
   * User configurable options are:
@@@ -96,6 -100,10 +100,9 @@@ public class BackupFile
    private static final SimpleDateFormat sdf = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss");
  
+   private static final String newTempFileSuffix = "_newfile";
+   private static final String oldTempFileSuffix = "_oldfile_tobedeleted";
 -
    public BackupFiles(String filename)
    {
      this(new File(filename));
    {
      classInit();
      this.file = file;
-     BackupFilesPresetEntry bfpe = BackupFilesPresetEntry.getSavedBackupEntry();
+     BackupFilesPresetEntry bfpe = BackupFilesPresetEntry
+             .getSavedBackupEntry();
      this.suffix = bfpe.suffix;
      this.noMax = bfpe.keepAll;
      this.max = bfpe.rollMax;
        {
          String tempfilename = file.getName();
          File tempdir = file.getParentFile();
-         temp = File.createTempFile(tempfilename, TEMP_FILE_EXT + "_newfile",
-                 tempdir);
+         Cache.trace(
+                 "BACKUPFILES [file!=null] attempting to create temp file for "
+                         + tempfilename + " in dir " + tempdir);
+         temp = File.createTempFile(tempfilename,
+                 TEMP_FILE_EXT + newTempFileSuffix, tempdir);
+         Cache.debug(
+                 "BACKUPFILES using temp file " + temp.getAbsolutePath());
        }
        else
        {
+         Cache.trace(
+                 "BACKUPFILES [file==null] attempting to create default temp file "
+                         + DEFAULT_TEMP_FILE + " with extension "
+                         + TEMP_FILE_EXT);
          temp = File.createTempFile(DEFAULT_TEMP_FILE, TEMP_FILE_EXT);
        }
      } catch (IOException e)
      {
-       System.out.println(
-               "Could not create temp file to save into (IOException)");
+       Cache.error("Could not create temp file to save to (IOException)");
+       Cache.error(e.getMessage());
+       Cache.debug(Cache.getStackTraceString(e));
      } catch (Exception e)
      {
-       System.out.println("Exception ctreating temp file for saving");
+       Cache.error("Exception creating temp file for saving");
+       Cache.debug(Cache.getStackTraceString(e));
      }
      this.setTempFile(temp);
    }
  
    public static void classInit()
    {
-     setEnabled(Cache.getDefault(ENABLED, !Platform.isJS()));
+     Cache.initLogger();
+     Cache.trace("BACKUPFILES classInit");
+     boolean e = Cache.getDefault(ENABLED, !Platform.isJS());
+     setEnabled(e);
+     Cache.trace("BACKUPFILES " + (e ? "enabled" : "disabled"));
      BackupFilesPresetEntry bfpe = BackupFilesPresetEntry
              .getSavedBackupEntry();
+     Cache.trace("BACKUPFILES preset scheme " + bfpe.toString());
      setConfirmDelete(bfpe.confirmDelete);
+     Cache.trace("BACKUPFILES confirm delete " + bfpe.confirmDelete);
    }
  
    public static void setEnabled(boolean flag)
        path = this.getTempFile().getCanonicalPath();
      } catch (IOException e)
      {
-       System.out.println(
-               "IOException when getting Canonical Path of temp file '"
-                       + this.getTempFile().getName() + "'");
+       Cache.error("IOException when getting Canonical Path of temp file '"
+               + this.getTempFile().getName() + "'");
+       Cache.debug(Cache.getStackTraceString(e));
      }
      return path;
    }
  
    public boolean renameTempFile()
    {
-     return tempFile.renameTo(file);
+     return moveFileToFile(tempFile, file);
    }
  
    // roll the backupfiles
              || suffix.length() == 0)
      {
        // nothing to do
+       Cache.debug("BACKUPFILES rollBackupFiles nothing to do." + ", "
+               + "filename: " + (file != null ? file.getName() : "null")
+               + ", " + "file exists: " + file.exists() + ", " + "enabled: "
+               + enabled + ", " + "max: " + max + ", " + "suffix: '" + suffix
+               + "'");
        return true;
      }
  
+     Cache.trace("BACKUPFILES rollBackupFiles starting");
 -
      String dir = "";
      File dirFile;
      try
      {
        dirFile = file.getParentFile();
        dir = dirFile.getCanonicalPath();
+       Cache.trace("BACKUPFILES dir: " + dir);
      } catch (Exception e)
      {
-       System.out.println(
-               "Could not get canonical path for file '" + file + "'");
+       Cache.error("Could not get canonical path for file '" + file + "'");
+       Cache.error(e.getMessage());
+       Cache.debug(Cache.getStackTraceString(e));
        return false;
      }
      String filename = file.getName();
      String basename = filename;
  
+     Cache.trace("BACKUPFILES filename is " + filename);
      boolean ret = true;
      // Create/move backups up one
  
      File[] backupFiles = dirFile.listFiles(bff);
      int nextIndexNum = 0;
  
+     Cache.trace("BACKUPFILES backupFiles.length: " + backupFiles.length);
      if (backupFiles.length == 0)
      {
        // No other backup files. Just need to move existing file to backupfile_1
+       Cache.trace(
+               "BACKUPFILES no existing backup files, setting index to 1");
        nextIndexNum = 1;
      }
      else
        if (reverseOrder)
        {
          // backup style numbering
 +
+         Cache.trace("BACKUPFILES rolling files in reverse order");
  
          int tempMax = noMax ? -1 : max;
          // noMax == true means no limits
              tempMax = i;
            }
          }
-         
 -
          File previousFile = null;
          File fileToBeDeleted = null;
          for (int n = tempMax; n > 0; n--)
              // no "oldest" file to delete
              previousFile = backupfile_n;
              fileToBeDeleted = null;
+             Cache.trace("BACKUPFILES No oldest file to delete");
              continue;
            }
  
              File replacementFile = backupfile_n;
              long fileToBeDeletedLMT = fileToBeDeleted.lastModified();
              long replacementFileLMT = replacementFile.lastModified();
+             Cache.trace("BACKUPFILES fileToBeDeleted is "
+                     + fileToBeDeleted.getAbsolutePath());
+             Cache.trace("BACKUPFILES replacementFile is "
+                     + backupfile_n.getAbsolutePath());
  
              try
              {
                File oldestTempFile = nextTempFile(fileToBeDeleted.getName(),
                        dirFile);
-               
 -
                if (fileToBeDeletedLMT > replacementFileLMT)
                {
                  String fileToBeDeletedLMTString = sdf
                          .format(fileToBeDeletedLMT);
                  String replacementFileLMTString = sdf
                          .format(replacementFileLMT);
-                 System.out.println("WARNING! I am set to delete backupfile "
+                 Cache.warn("WARNING! I am set to delete backupfile "
                          + fileToBeDeleted.getName()
                          + " has modification time "
                          + fileToBeDeletedLMTString
  
                  boolean delete = confirmNewerDeleteFile(fileToBeDeleted,
                          replacementFile, true);
+                 Cache.trace("BACKUPFILES " + (delete ? "confirmed" : "not")
+                         + " deleting file "
+                         + fileToBeDeleted.getAbsolutePath()
+                         + " which is newer than "
+                         + replacementFile.getAbsolutePath());
  
                  if (delete)
                  {
                  }
                  else
                  {
-                   fileToBeDeleted.renameTo(oldestTempFile);
+                   Cache.debug("BACKUPFILES moving "
+                           + fileToBeDeleted.getAbsolutePath() + " to "
+                           + oldestTempFile.getAbsolutePath());
+                   moveFileToFile(fileToBeDeleted, oldestTempFile);
                  }
                }
                else
                {
-                 fileToBeDeleted.renameTo(oldestTempFile);
+                 Cache.debug("BACKUPFILES going to move "
+                         + fileToBeDeleted.getAbsolutePath() + " to "
+                         + oldestTempFile.getAbsolutePath());
+                 moveFileToFile(fileToBeDeleted, oldestTempFile);
                  addDeleteFile(oldestTempFile);
                }
  
              } catch (Exception e)
              {
-               System.out.println(
+               Cache.error(
                        "Error occurred, probably making new temp file for '"
                                + fileToBeDeleted.getName() + "'");
-               e.printStackTrace();
+               Cache.error(Cache.getStackTraceString(e));
              }
  
              // reset
            {
              if (previousFile != null)
              {
-               ret = ret && backupfile_n.renameTo(previousFile);
+               // using boolean '&' instead of '&&' as don't want moveFileToFile
+               // attempt to be conditional (short-circuit)
+               ret = ret & moveFileToFile(backupfile_n, previousFile);
              }
            }
  
          // index to use for the latest backup
          nextIndexNum = 1;
        }
-       else
+       else // not reverse numbering
        {
          // version style numbering (with earliest file deletion if max files
          // reached)
  
          bfTreeMap.values().toArray(backupFiles);
+         StringBuilder bfsb = new StringBuilder();
+         for (int i = 0; i < backupFiles.length; i++)
+         {
+           if (bfsb.length() > 0)
+           {
+             bfsb.append(", ");
+           }
+           bfsb.append(backupFiles[i].getName());
+         }
+         Cache.trace("BACKUPFILES backupFiles: " + bfsb.toString());
  
          // noMax == true means keep all backup files
          if ((!noMax) && bfTreeMap.size() >= max)
          {
+           Cache.trace("BACKUPFILES noMax: " + noMax + ", " + "max: " + max
+                   + ", " + "bfTreeMap.size(): " + bfTreeMap.size());
            // need to delete some files to keep number of backups to designated
-           // max
-           int numToDelete = bfTreeMap.size() - max + 1;
+           // max.
+           // Note that if the suffix is not numbered then do not delete any
+           // backup files later or we'll delete the new backup file (there can
+           // be only one).
+           int numToDelete = suffix.indexOf(NUM_PLACEHOLDER) > -1
+                   ? bfTreeMap.size() - max + 1
+                   : 0;
+           Cache.trace("BACKUPFILES numToDelete: " + numToDelete);
            // the "replacement" file is the latest backup file being kept (it's
            // not replacing though)
            File replacementFile = numToDelete < backupFiles.length
              File fileToBeDeleted = backupFiles[i];
              boolean delete = true;
  
+             Cache.trace("BACKUPFILES fileToBeDeleted: " + fileToBeDeleted);
 -
              boolean newer = false;
              if (replacementFile != null)
              {
                  String replacementFileLMTString = sdf
                          .format(replacementFileLMT);
  
-                 System.out
-                         .println("WARNING! I am set to delete backupfile '"
-                                 + fileToBeDeleted.getName()
-                                 + "' has modification time "
+                 Cache.warn("WARNING! I am set to delete backupfile '"
+                         + fileToBeDeleted.getName()
+                         + "' has modification time "
                          + fileToBeDeletedLMTString
-                                 + " which is newer than the oldest backupfile being kept '"
+                         + " which is newer than the oldest backupfile being kept '"
                          + replacementFile.getName()
-                                 + "' with modification time "
+                         + "' with modification time "
                          + replacementFileLMTString);
  
                  delete = confirmNewerDeleteFile(fileToBeDeleted,
                  {
                    // User has confirmed delete -- no need to add it to the list
                    fileToBeDeleted.delete();
+                   Cache.debug("BACKUPFILES deleting fileToBeDeleted: "
+                           + fileToBeDeleted);
                    delete = false;
                  }
                  else
                  {
                    // keeping file, nothing to do!
+                   Cache.debug("BACKUPFILES keeping fileToBeDeleted: "
+                           + fileToBeDeleted);
                  }
                }
              }
              if (delete)
              {
                addDeleteFile(fileToBeDeleted);
+               Cache.debug("BACKUPFILES addDeleteFile(fileToBeDeleted): "
+                       + fileToBeDeleted);
              }
  
            }
      String latestBackupFilename = dir + File.separatorChar
              + BackupFilenameParts.getBackupFilename(nextIndexNum, basename,
                      suffix, digits);
-     ret |= file.renameTo(new File(latestBackupFilename));
+     Cache.trace("BACKUPFILES Moving old file [" + file
+             + "] to latestBackupFilename [" + latestBackupFilename + "]");
+     // using boolean '&' instead of '&&' as don't want moveFileToFile attempt to
+     // be conditional (short-circuit)
+     ret = ret & moveFileToFile(file, new File(latestBackupFilename));
+     Cache.debug("BACKUPFILES moving " + file + " to " + latestBackupFilename
+             + " was " + (ret ? "" : "NOT ") + "successful");
 +
      if (tidyUp)
      {
+       Cache.debug("BACKUPFILES tidying up files");
        tidyUpFiles();
      }
  
          saveFile = nextTempFile(ftbd.getName(), ftbd.getParentFile());
        } catch (Exception e)
        {
-         System.out.println(
+         Cache.error(
                  "Error when confirming to keep backup file newer than other backup files.");
          e.printStackTrace();
        }
                "label.newerdelete_replacement_line", new String[]
                { ftbd.getName(), rf.getName(), ftbdLMT, rfLMT, ftbdSize,
                    rfSize }));
+       // "Backup file\n''{0}''\t(modified {2}, size {4})\nis to be deleted and
+       // replaced by apparently older file \n''{1}''\t(modified {3}, size
+       // {5}).""
        messageSB.append("\n\n");
        messageSB.append(MessageManager.formatMessage(
                "label.confirm_deletion_or_rename", new String[]
                { ftbd.getName(), saveFile.getName() }));
+       // "Confirm deletion of ''{0}'' or rename to ''{1}''?"
        String[] options = new String[] {
            MessageManager.getString("label.delete"),
            MessageManager.getString("label.rename") };
  
-       confirmButton = JvOptionPane.showOptionDialog(Desktop.getDesktopPane(),
-               messageSB.toString(),
-               MessageManager.getString("label.backupfiles_confirm_delete"),
-               JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
-               null, options, options[0]);
++      // TODO enable JvOptionPane to behave appropriately when batch/headless
+       confirmButton = Platform.isHeadless() ? JvOptionPane.YES_OPTION
 -              : JvOptionPane.showOptionDialog(Desktop.desktop,
++              : JvOptionPane.showOptionDialog(Desktop.getDesktopPane(),
+                       messageSB.toString(),
+                       MessageManager.getString(
+                               "label.backupfiles_confirm_delete"),
 -                      // "Confirm delete"
+                       JvOptionPane.YES_NO_OPTION,
+                       JvOptionPane.WARNING_MESSAGE, null, options,
+                       options[0]);
      }
      else
      {
                .formatMessage("label.newerdelete_line", new String[]
                { ftbd.getName(), rf.getName(), ftbdLMT, rfLMT, ftbdSize,
                    rfSize }));
+       // "Backup file\n''{0}''\t(modified {2}, size {4})\nis to be deleted but
+       // is newer than the oldest remaining backup file \n''{1}''\t(modified
+       // {3}, size {5})."
        messageSB.append("\n\n");
        messageSB.append(MessageManager
                .formatMessage("label.confirm_deletion", new String[]
                { ftbd.getName() }));
+       // "Confirm deletion of ''{0}''?"
        String[] options = new String[] {
            MessageManager.getString("label.delete"),
            MessageManager.getString("label.keep") };
  
-       confirmButton = JvOptionPane.showOptionDialog(Desktop.getDesktopPane(),
+       confirmButton = Platform.isHeadless() ? JvOptionPane.YES_OPTION
 -              : JvOptionPane.showOptionDialog(Desktop.desktop,
 -                      messageSB.toString(),
 -                      MessageManager.getString(
 -                              "label.backupfiles_confirm_delete"),
 -                      // "Confirm delete"
 -                      JvOptionPane.YES_NO_OPTION,
 -                      JvOptionPane.WARNING_MESSAGE, null, options,
 -                      options[0]);
++              : JvOptionPane.showOptionDialog(Desktop.getDesktopPane(),
 +              messageSB.toString(),
 +              MessageManager.getString("label.backupfiles_confirm_delete"),
 +              JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
 +              null, options, options[0]);
      }
  
 +
      // return should be TRUE if file is to be deleted
      return (confirmButton == JvOptionPane.YES_OPTION);
    }
          messageSB = new StringBuilder();
          messageSB.append(MessageManager
                  .getString("label.backupfiles_confirm_delete_old_files"));
+         // "Delete the following older backup files? (see the Backups tab in
+         // Preferences for more options)"
          for (int i = 0; i < deleteFiles.size(); i++)
          {
            File df = deleteFiles.get(i);
                    new String[]
                    { sdf.format(df.lastModified()),
                        Long.toString(df.length()) }));
+           // "(modified {0}, size {1})"
          }
  
-         int confirmButton = JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
-                 messageSB.toString(),
-                 MessageManager
-                         .getString("label.backupfiles_confirm_delete"),
-                 JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE);
+         int confirmButton = Platform.isHeadless() ? JvOptionPane.YES_OPTION
 -                : JvOptionPane.showConfirmDialog(Desktop.desktop,
++                : JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
+                         messageSB.toString(),
+                         MessageManager.getString(
+                                 "label.backupfiles_confirm_delete"),
+                         // "Confirm delete"
+                         JvOptionPane.YES_NO_OPTION,
+                         JvOptionPane.WARNING_MESSAGE);
  
          doDelete = (confirmButton == JvOptionPane.YES_OPTION);
        }
          for (int i = 0; i < deleteFiles.size(); i++)
          {
            File fileToDelete = deleteFiles.get(i);
+           Cache.trace("BACKUPFILES about to delete fileToDelete:"
+                   + fileToDelete);
            fileToDelete.delete();
-           System.out.println("DELETING '" + fileToDelete.getName() + "'");
+           Cache.warn("deleted '" + fileToDelete.getName() + "'");
          }
        }
  
    }
  
    private TreeMap<Integer, File> sortBackupFilesAsTreeMap(
-           File[] backupFiles,
-           String basename)
+           File[] backupFiles, String basename)
    {
      // sort the backup files (based on integer found in the suffix) using a
      // precomputed Hashmap for speed
      boolean rename = false;
      if (write)
      {
-       roll = this.rollBackupFiles(false);
+       roll = this.rollBackupFiles(false); // tidyUpFiles at the end
        rename = this.renameTempFile();
      }
  
      if (!okay)
      {
        StringBuilder messageSB = new StringBuilder();
-       messageSB.append(MessageManager.getString( "label.backupfiles_confirm_save_file_backupfiles_roll_wrong"));
+       messageSB.append(MessageManager.getString(
+               "label.backupfiles_confirm_save_file_backupfiles_roll_wrong"));
+       // "Something possibly went wrong with the backups of this file."
        if (rename)
        {
          if (messageSB.length() > 0)
          }
          messageSB.append(MessageManager.getString(
                  "label.backupfiles_confirm_save_new_saved_file_ok"));
+         // "The new saved file seems okay."
        }
        else
        {
          }
          messageSB.append(MessageManager.getString(
                  "label.backupfiles_confirm_save_new_saved_file_not_ok"));
+         // "The new saved file might not be okay."
        }
-       int confirmButton = JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
-               messageSB.toString(),
-               MessageManager
-                       .getString("label.backupfiles_confirm_save_file"),
-               JvOptionPane.OK_OPTION, JvOptionPane.WARNING_MESSAGE);
+       if (messageSB.length() > 0)
+       {
+         messageSB.append("\n");
+       }
+       messageSB
+               .append(MessageManager.getString("label.continue_operation"));
+       int confirmButton = Platform.isHeadless() ? JvOptionPane.OK_OPTION
 -              : JvOptionPane.showConfirmDialog(Desktop.desktop,
++              : JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
+                       messageSB.toString(),
+                       MessageManager.getString(
+                               "label.backupfiles_confirm_save_file"),
+                       // "Confirm save file"
+                       JvOptionPane.OK_OPTION, JvOptionPane.WARNING_MESSAGE);
        okay = confirmButton == JvOptionPane.OK_OPTION;
      }
      if (okay)
        dirFile = file.getParentFile();
      } catch (Exception e)
      {
-       System.out.println(
-               "Could not get canonical path for file '" + file + "'");
+       Cache.error("Could not get canonical path for file '" + file + "'");
        return new TreeMap<>();
      }
  
      int pos = deleteFiles.indexOf(fileToBeDeleted);
      if (pos > -1)
      {
+       Cache.debug("BACKUPFILES not adding file "
+               + fileToBeDeleted.getAbsolutePath()
+               + " to the delete list (already at index" + pos + ")");
        return true;
      }
      else
      {
+       Cache.debug("BACKUPFILES adding file "
+               + fileToBeDeleted.getAbsolutePath() + " to the delete list");
        deleteFiles.add(fileToBeDeleted);
      }
      return ret;
    }
  
+   public static boolean moveFileToFile(File oldFile, File newFile)
+   {
+     Cache.initLogger();
+     boolean ret = false;
+     Path oldPath = Paths.get(oldFile.getAbsolutePath());
+     Path newPath = Paths.get(newFile.getAbsolutePath());
+     try
+     {
+       // delete destination file - not usually necessary but Just In Case...
+       Cache.trace("BACKUPFILES deleting " + newFile.getAbsolutePath());
+       newFile.delete();
+       Cache.trace("BACKUPFILES moving " + oldFile.getAbsolutePath() + " to "
+               + newFile.getAbsolutePath());
+       Files.move(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING);
+       ret = true;
+       Cache.trace("BACKUPFILES move seems to have succeeded");
+     } catch (IOException e)
+     {
+       Cache.warn("Could not move file '" + oldPath.toString() + "' to '"
+               + newPath.toString() + "'");
+       Cache.error(e.getMessage());
+       Cache.debug(Cache.getStackTraceString(e));
+       ret = false;
+     } catch (Exception e)
+     {
+       Cache.error(e.getMessage());
+       Cache.debug(Cache.getStackTraceString(e));
+       ret = false;
+     }
+     return ret;
+   }
  }
   */
  package jalview.io;
  
+ import java.io.IOException;
  import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.DBRefSource;
  import jalview.datamodel.PDBEntry;
  import jalview.ext.jmol.JmolParser;
  import jalview.structure.StructureImportSettings;
  
- import java.io.IOException;
  public enum FileFormat implements FileFormatI
  {
    Fasta("Fasta", "fa, fasta, mfa, fastq", true, true)
        return new PhylipFile();
      }
    },
+   GenBank("GenBank Flatfile", "gb, gbk", true, false)
+   {
+     @Override
+     public AlignmentFileReaderI getReader(FileParse source)
+             throws IOException
+     {
+       return new GenBankFile(source, "GenBank");
+     }
+     @Override
+     public AlignmentFileWriterI getWriter(AlignmentI al)
+     {
+       return null;
+     }
+   },
+   Embl("ENA Flatfile", "txt", true, false)
+   {
+     @Override
+     public AlignmentFileReaderI getReader(FileParse source)
+             throws IOException
+     {
+       // Always assume we import from EMBL for now
+       return new EmblFlatFile(source, DBRefSource.EMBL);
+     }
+     @Override
+     public AlignmentFileWriterI getWriter(AlignmentI al)
+     {
+       return null;
+     }
+   },
    Jnet("JnetFile", "", false, false)
    {
      @Override
      {
        return true;
      }
 +  },
 +  HMMER3("HMMER3", "hmm", true, true)
 +  {
 +    @Override
 +    public AlignmentFileReaderI getReader(FileParse source)
 +            throws IOException
 +    {
 +      return new HMMFile(source);
 +    }
 +
 +    @Override
 +    public AlignmentFileWriterI getWriter(AlignmentI al)
 +    {
 +      return new HMMFile();
 +    }
 +  },   BSML("BSML", "bbb", true, false)
 +  {
 +    @Override
 +    public AlignmentFileReaderI getReader(FileParse source)
 +            throws IOException
 +    {
 +      return new BSMLFile(source);
 +    }
 +
 +    @Override
 +    public AlignmentFileWriterI getWriter(AlignmentI al)
 +    {
 +      return null;
 +    }
    };
  
 +
    private boolean writable;
  
    private boolean readable;
     * @param shortName
     * @param extensions
     *          comma-separated list of file extensions associated with the format
-    * @param isReadable
-    * @param isWritable
+    * @param isReadable - can be recognised by IdentifyFile and imported with the given reader
+    * @param isWritable - can be exported with the returned writer
     */
    private FileFormat(String shortName, String extensions,
            boolean isReadable, boolean isWritable)
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.io;
  
+ import java.util.Locale;
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.LinkedHashMap;
@@@ -27,9 -29,6 +29,9 @@@ import java.util.List
  import java.util.Map;
  import java.util.Set;
  
 +import jalview.bin.ApplicationSingletonProvider;
 +import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
 +
  /**
   * A singleton registry of alignment file formats known to Jalview. On startup,
   * the 'built-in' formats are added (from the FileFormat enum). Additional
   * @author gmcarstairs
   *
   */
 -public class FileFormats
 +public class FileFormats implements ApplicationSingletonI
  {
 -  private static FileFormats instance = new FileFormats();
 -
 -  /*
 -   * A lookup map of file formats by upper-cased name
 -   */
 -  private static Map<String, FileFormatI> formats;
 -
 -  /*
 -   * Formats in this set are capable of being identified by IdentifyFile 
 -   */
 -  private static Set<FileFormatI> identifiable;
 -
    public static FileFormats getInstance()
    {
 -    return instance;
 +    return (FileFormats) ApplicationSingletonProvider.getInstance(FileFormats.class);
    }
  
    /**
      reset();
    }
  
 +  /*
 +   * A lookup map of file formats by upper-cased name
 +   */
 +  private Map<String, FileFormatI> formats;
 +
 +  /*
 +   * Formats in this set are capable of being identified by IdentifyFile 
 +   */
 +  private Set<FileFormatI> identifiable;
 +
 +
    /**
     * Reset to just the built-in file formats packaged with Jalview. These are
     * added (and will be shown in menus) in the order of their declaration in the
    protected void registerFileFormat(FileFormatI format,
            boolean isIdentifiable)
    {
-     String name = format.getName().toUpperCase();
+     String name = format.getName().toUpperCase(Locale.ROOT);
      if (formats.containsKey(name))
      {
        System.err.println("Overwriting file format: " + format.getName());
     */
    public void deregisterFileFormat(String name)
    {
-     FileFormatI ff = formats.remove(name.toUpperCase());
+     FileFormatI ff = formats.remove(name.toUpperCase(Locale.ROOT));
      identifiable.remove(ff);
    }
  
     */
    public FileFormatI forName(String format)
    {
-     return format == null ? null : formats.get(format.toUpperCase());
+     return format == null ? null : formats.get(format.toUpperCase(Locale.ROOT));
    }
  
    /**
@@@ -20,7 -20,6 +20,7 @@@
   */
  package jalview.io;
  
 +import java.awt.Dimension;
  import java.io.File;
  import java.io.IOException;
  import java.util.StringTokenizer;
@@@ -47,16 -46,10 +47,14 @@@ 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.util.ArrayList;
 +import java.util.List;
  public class FileLoader implements Runnable
  {
 +  private static final String TAB = "\t";
    String file;
  
    DataSourceType protocol;
      return alignFrame;
    }
  
 -  public void updateRecentlyOpened()
 +  public void LoadFileOntoAlignmentWaitTillLoaded(AlignViewport viewport,
 +          String file, DataSourceType sourceType, FileFormatI format)
    {
      Vector<String> recent = new Vector<>();
      if (protocol == DataSourceType.PASTE)
 +    this.viewport = viewport;
 +    this.file = file;
 +    this.protocol = sourceType;
 +    this.format = format;
 +    _LoadFileWaitTillLoaded();
 +  }
 +
 +
 +  /**
 +   * Updates (or creates) the tab-separated list of recently opened files held
 +   * under the given property name by inserting the filePath at the front of the
 +   * list. Duplicates are removed, and the list is limited to 11 entries. The
 +   * method returns the updated value of the property.
 +   * 
 +   * @param filePath
 +   * @param sourceType
 +   */
 +  public static String updateRecentlyOpened(String filePath,
 +          DataSourceType sourceType)
 +  {
 +    if (sourceType != DataSourceType.FILE
 +            && sourceType != DataSourceType.URL)
      {
 -      // do nothing if the file was pasted in as text... there is no filename to
 -      // refer to it as.
 -      return;
 +      return null;
      }
 -    if (file != null
 -            && file.indexOf(System.getProperty("java.io.tmpdir")) > -1)
 +    String propertyName = sourceType == DataSourceType.FILE ? "RECENT_FILE"
 +            : "RECENT_URL";
 +    String historyItems = Cache.getProperty(propertyName);
 +    if (filePath != null
 +            && filePath.indexOf(System.getProperty("java.io.tmpdir")) > -1)
      {
        // ignore files loaded from the system's temporary directory
 -      return;
 +      return null;
      }
 -    String type = protocol == DataSourceType.FILE ? "RECENT_FILE"
 -            : "RECENT_URL";
  
 -    String historyItems = Cache.getProperty(type);
 -
 -    StringTokenizer st;
 +    List<String> recent = new ArrayList<>();
  
      if (historyItems != null)
      {
 -      st = new StringTokenizer(historyItems, "\t");
 +      StringTokenizer st = new StringTokenizer(historyItems, TAB);
  
        while (st.hasMoreTokens())
        {
 -        recent.addElement(st.nextToken().trim());
 +        String trimmed = st.nextToken().trim();
 +      recent.add(trimmed);
        }
      }
  
 -    if (recent.contains(file))
 +    /*
 +     * if file was already in the list, it moves to the top
 +     */
 +    if (recent.contains(filePath))
      {
 -      recent.remove(file);
 +      recent.remove(filePath);
      }
  
 -    StringBuffer newHistory = new StringBuffer(file);
 +    StringBuilder newHistory = new StringBuilder(filePath);
      for (int i = 0; i < recent.size() && i < 10; i++)
      {
 -      newHistory.append("\t");
 -      newHistory.append(recent.elementAt(i));
 +      newHistory.append(TAB);
 +      newHistory.append(recent.get(i));
      }
  
 -    Cache.setProperty(type, newHistory.toString());
 +    String newProperty = newHistory.toString();
 +    Cache.setProperty(propertyName, newProperty);
  
 -    if (protocol == DataSourceType.FILE)
 -    {
 -      Cache.setProperty("DEFAULT_FILE_FORMAT", format.getName());
 -    }
 +    return newProperty;
    }
  
    @Override
      Runtime rt = Runtime.getRuntime();
      try
      {
 -      if (Desktop.instance != null)
 +      if (Desktop.getInstance() != null)
        {
 -        Desktop.instance.startLoading(file);
 +        Desktop.getInstance().startLoading(file);
        }
        if (format == null)
        {
  
        if (format == null)
        {
 -        Desktop.instance.stopLoading();
 +        Desktop.getInstance().stopLoading();
          System.err.println("The input file \"" + file
                  + "\" has null or unidentifiable data content!");
          if (!Jalview.isHeadlessMode())
          {
 -          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +          JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                    MessageManager.getString("label.couldnt_read_data")
                            + " in " + file + "\n"
                            + AppletFormatAdapter.getSupportedFormats(),
        }
        // TODO: cache any stream datasources as a temporary file (eg. PDBs
        // retrieved via URL)
 -      if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
 +      if (Desktop.getDesktopPane() != null && Desktop.getDesktopPane().isShowMemoryUsage())
        {
          System.gc();
          memused = (rt.maxMemory() - rt.totalMemory() + rt.freeMemory()); // free
                  // register PDB entries with desktop's structure selection
                  // manager
                  StructureSelectionManager
 -                        .getStructureSelectionManager(Desktop.instance)
 +                        .getStructureSelectionManager(Desktop.getInstance())
                          .registerPDBEntry(pdbe);
                }
              }
                    .getFeatureColourScheme();
            if (viewport != null)
            {
++            // TODO: test if this needs to be done after addAlignment ? (like in 2.11.2)  
 +            if (proxyColourScheme != null)
 +            {
 +              viewport.applyFeaturesStyle(proxyColourScheme);
 +            }
              // append to existing alignment
              viewport.addAlignment(al, title);
 -            viewport.applyFeaturesStyle(proxyColourScheme);
 +            if (source instanceof HMMFile)
 +            {
 +              AlignmentI alignment = viewport.getAlignment();
 +              SequenceI seq = alignment
 +                      .getSequenceAt(alignment.getHeight() - 1);
 +              if (seq.hasHMMProfile())
 +              {
 +                /* 
 +                 * fudge: move HMM consensus sequence from last to first
 +                 */
 +                alignment.deleteSequence(alignment.getAbsoluteHeight() - 1);
 +                alignment.insertSequenceAt(0, seq);
 +              }
 +              viewport.getAlignPanel().adjustAnnotationHeight();
 +              viewport.updateSequenceIdColours();
 +            }
            }
            else
            {
              // add metadata and update ui
              if (!(protocol == DataSourceType.PASTE))
              {
 -              alignFrame.setFileName(file, format);
 -              alignFrame.setFileObject(selectedFile); // BH 2018 SwingJS
 +              alignFrame.setFile(file, selectedFile, protocol, format);
              }
              if (proxyColourScheme != null)
              {
                // status in Jalview 3
                // TODO: define 'virtual desktop' for benefit of headless scripts
                // that perform queries to find the 'current working alignment'
 -              Desktop.addInternalFrame(alignFrame, title,
 +              
 +              Dimension dim = Platform.getDimIfEmbedded(alignFrame,
                        AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
 +              alignFrame.setSize(dim);
 +              Desktop.addInternalFrame(alignFrame, title, dim.width,
 +                      dim.height);
              }
  
              try
          }
          else
          {
 -          if (Desktop.instance != null)
 +          if (Desktop.getInstance() != null)
            {
 -            Desktop.instance.stopLoading();
 +            Desktop.getInstance().stopLoading();
            }
  
            final String errorMessage = MessageManager.getString(
                    "label.couldnt_load_file") + " " + title + "\n" + error;
            // TODO: refactor FileLoader to be independent of Desktop / Applet GUI
            // bits ?
 -          if (raiseGUI && Desktop.desktop != null)
 +          if (raiseGUI && Desktop.getDesktopPane() != null)
            {
              javax.swing.SwingUtilities.invokeLater(new Runnable()
              {
                @Override
                public void run()
                {
 -                JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +                JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                          errorMessage,
                          MessageManager
                                  .getString("label.error_loading_file"),
          }
        }
  
 -      updateRecentlyOpened();
 +      updateRecentlyOpened(file, protocol);
 +
 +      if (protocol == DataSourceType.FILE && format != null)
 +      {
 +        Cache.setProperty("DEFAULT_FILE_FORMAT", format.getName());
 +      }
  
      } catch (Exception er)
      {
            @Override
            public void run()
            {
 -            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +            JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                      MessageManager.formatMessage(
                              "label.problems_opening_file", new String[]
                              { file }),
            @Override
            public void run()
            {
 -            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +            JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                      MessageManager.formatMessage(
                              "warn.out_of_memory_loading_file", new String[]
                              { file }),
      // memory
      // after
      // load
 -    if (Desktop.desktop != null && Desktop.desktop.isShowMemoryUsage())
 +    if (Desktop.getDesktopPane() != null && Desktop.getDesktopPane().isShowMemoryUsage())
      {
        if (alignFrame != null)
        {
        }
      }
      // remove the visual delay indicator
 -    if (Desktop.instance != null)
 +    if (Desktop.getInstance() != null)
      {
 -      Desktop.instance.stopLoading();
 +      Desktop.getInstance().stopLoading();
      }
  
    }
@@@ -20,6 -20,8 +20,7 @@@
   */
  package jalview.io;
  
+ import java.util.Locale;
 -
  import java.io.File;
  import java.io.IOException;
  
@@@ -178,18 -180,26 +179,31 @@@ public class IdentifyFil
              break;
            }
          }
-         data = data.toUpperCase();
+         data = data.toUpperCase(Locale.ROOT);
  
          if (data.startsWith(ScoreMatrixFile.SCOREMATRIX))
          {
            reply = FileFormat.ScoreMatrix;
            break;
          }
 +        if (data.startsWith("HMMER3"))
 +        {
 +          reply = FileFormat.HMMER3;
 +          break;
 +        }
+         if (data.startsWith("LOCUS"))
+         {
+           reply = FileFormat.GenBank;
+           break;
+         }
+         if (data.startsWith("ID "))
+         {
+           if (data.substring(2).trim().split(";").length == 7)
+           {
+             reply = FileFormat.Embl;
+             break;
+           }
+         }
          if (data.startsWith("H ") && !aaIndexHeaderRead)
          {
            aaIndexHeaderRead = true;
          if ((lessThan > -1)) // possible Markup Language data i.e HTML,
                               // RNAML, XML
          {
-           String upper = data.toUpperCase();
+           String upper = data.toUpperCase(Locale.ROOT);
            if (upper.substring(lessThan).startsWith("<HTML"))
            {
              reply = FileFormat.Html;
              reply = FileFormat.Rnaml;
              break;
            }
 +          if (upper.substring(lessThan).startsWith("<BSML"))
 +          {
 +            reply = FileFormat.BSML;
 +            break;
 +          }
          }
  
          if ((data.length() < 1) || (data.indexOf("#") == 0))
  // TODO: Extended SequenceNodeI to hold parsed NHX strings
  package jalview.io;
  
+ import java.util.Locale;
  import jalview.datamodel.SequenceNode;
  import jalview.util.MessageManager;
 +import jalview.util.Platform;
  
  import java.io.BufferedReader;
  import java.io.File;
@@@ -38,8 -39,6 +40,8 @@@ import java.util.StringTokenizer
  
  import com.stevesoft.pat.Regex;
  
 +// TODO This class does not conform to Java standards for field name capitalization.
 +
  /**
   * 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
@@@ -79,7 -78,7 +81,7 @@@
   */
  public class NewickFile extends FileParse
  {
 -  SequenceNode root;
 +  private SequenceNode root;
  
    private boolean HasBootstrap = false;
  
    private boolean RootHasDistance = false;
  
    // File IO Flags
 -  boolean ReplaceUnderscores = false;
 +  private boolean ReplaceUnderscores = false;
 +
 +  private boolean printRootInfo = true;
 +
 +  private static final int REGEX_PERL_NODE_REQUIRE_QUOTE = 0;
 +
 +  private static final int REGEX_PERL_NODE_ESCAPE_QUOTE = 1;
 +
 +  private static final int REGEX_PERL_NODE_UNQUOTED_WHITESPACE = 2;
 +
 +  private static final int REGEX_MAJOR_SYMS = 3;
 +
 +  private static final int REGEX_QNODE_NAME = 4;
 +
 +  private static final int REGEX_COMMENT = 5;
 +
 +  private static final int REGEX_UQNODE_NAME = 6;
  
 -  boolean printRootInfo = true;
 +  private static final int REGEX_NBOOTSTRAP = 7;
 +
 +  private static final int REGEX_NDIST = 8;
 +
 +  private static final int REGEX_NO_LINES = 9;
 +
 +  private static final int REGEX_PERL_EXPAND_QUOTES = 10;
 +
 +  private static final int REGEX_MAX = 11;
 +
 +  private static final Regex[] REGEX = new Regex[REGEX_MAX];
 +
 +  private static Regex getRegex(int id)
 +  {
 +    if (REGEX[id] == null)
 +    {
 +      String code = null;
 +      String code2 = null;
 +      String codePerl = null;
 +      switch (id)
 +      {
 +      case REGEX_PERL_NODE_REQUIRE_QUOTE:
 +        codePerl = "m/[\\[,:'()]/";
 +        break;
 +      case REGEX_PERL_NODE_ESCAPE_QUOTE:
 +        codePerl = "s/'/''/";
 +        break;
 +      case REGEX_PERL_NODE_UNQUOTED_WHITESPACE:
 +        codePerl = "s/\\/w/_/";
 +        break;
 +      case REGEX_PERL_EXPAND_QUOTES:
 +        codePerl = "s/''/'/";
 +        break;
 +      case REGEX_MAJOR_SYMS:
 +        code = "[(\\['),;]";
 +        break;
 +      case REGEX_QNODE_NAME:
 +        code = "'([^']|'')+'";
 +        break;
 +      case REGEX_COMMENT:
 +        code = "]";
 +        break;
 +      case REGEX_UQNODE_NAME:
 +        code = "\\b([^' :;\\](),]+)";
 +        break;
 +      case REGEX_NBOOTSTRAP:
 +        code = "\\s*([0-9+]+)\\s*:";
 +        break;
 +      case REGEX_NDIST:
 +        code = ":([-0-9Ee.+]+)";
 +        break;
 +      case REGEX_NO_LINES:
 +        code = "\n+";
 +        code2 = "";
 +        break;
 +      default:
 +        return null;
 +      }
 +      return codePerl == null ? Platform.newRegex(code, code2)
 +              : Platform.newRegexPerl(codePerl);
 +    }
 +    return REGEX[id];
 +  }
  
 -  private Regex[] NodeSafeName = new Regex[] {
 -      new Regex().perlCode("m/[\\[,:'()]/"), // test for
 -      // requiring
 -      // quotes
 -      new Regex().perlCode("s/'/''/"), // escaping quote
 -      // characters
 -      new Regex().perlCode("s/\\/w/_/") // unqoted whitespace
 -      // transformation
 -  };
  
 -  char QuoteChar = '\'';
 +  private char quoteChar = '\'';
  
    /**
     * Creates a new NewickFile object.
     */
    public void parse() throws IOException
    {
 +    Platform.ensureRegex();
      String nf;
  
      { // fill nf with complete tree file
      boolean ascending = false; // flag indicating that we are leaving the
      // current node
  
 -    Regex majorsyms = new Regex(
 -            "[(\\['),;]");
 +    Regex majorsyms = getRegex(REGEX_MAJOR_SYMS); // "[(\\['),;]"
  
      int nextcp = 0;
      int ncp = cp;
        // Deal with quoted fields
        case '\'':
  
 -        Regex qnodename = new Regex(
 -                "'([^']|'')+'");
 +        Regex qnodename = getRegex(REGEX_QNODE_NAME);// "'([^']|'')+'");
  
          if (qnodename.searchFrom(nf, fcp))
          {
            nodename = new String(
                    qnodename.stringMatched().substring(1, nl - 1));
            // unpack any escaped colons
 -          Regex xpandquotes = Regex
 -                  .perlCode("s/''/'/");
 +          Regex xpandquotes = getRegex(REGEX_PERL_EXPAND_QUOTES);
            String widernodename = xpandquotes.replaceAll(nodename);
            nodename = widernodename;
            // jump to after end of quoted nodename
             * '"+nf.substring(cp,fcp)+"'"); }
             */
            // verify termination.
 -          Regex comment = new Regex(
 -                  "]");
 +          Regex comment = getRegex(REGEX_COMMENT); // "]"
            if (comment.searchFrom(nf, fcp))
            {
              // Skip the comment field
                    + fstring.substring(cend + 1);
  
          }
 -        Regex uqnodename = new Regex(
 -                "\\b([^' :;\\](),]+)");
 -        Regex nbootstrap = new Regex(
 -                "\\s*([0-9+]+)\\s*:");
 -        Regex ndist = new Regex(
 -                ":([-0-9Ee.+]+)");
 +        Regex uqnodename = getRegex(REGEX_UQNODE_NAME);// "\\b([^' :;\\](),]+)"
 +        Regex nbootstrap = getRegex(REGEX_NBOOTSTRAP);// "\\s*([0-9+]+)\\s*:");
 +        Regex ndist = getRegex(REGEX_NDIST);// ":([-0-9Ee.+]+)");
  
          if (!parsednodename && uqnodename.search(fstring)
                  && ((uqnodename.matchedFrom(1) == 0) || (fstring
            try
            {
              // parse out code/value pairs
-             if (code.toLowerCase().equals("b"))
+             if (code.toLowerCase(Locale.ROOT).equals("b"))
              {
                int v = -1;
                Float iv = Float.valueOf(value);
     */
    char getQuoteChar()
    {
 -    return QuoteChar;
 +    return quoteChar;
    }
  
    /**
     */
    char setQuoteChar(char c)
    {
 -    char old = QuoteChar;
 -    QuoteChar = c;
 +    char old = quoteChar;
 +    quoteChar = c;
  
      return old;
    }
     */
    private String nodeName(String name)
    {
 -    if (NodeSafeName[0].search(name))
 +    if (getRegex(REGEX_PERL_NODE_REQUIRE_QUOTE).search(name))
      {
 -      return QuoteChar + NodeSafeName[1].replaceAll(name) + QuoteChar;
 +      return quoteChar
 +              + getRegex(REGEX_PERL_NODE_ESCAPE_QUOTE).replaceAll(name)
 +              + quoteChar;
      }
      else
      {
 -      return NodeSafeName[2].replaceAll(name);
 +      return getRegex(REGEX_PERL_NODE_UNQUOTED_WHITESPACE).replaceAll(name);
      }
    }
  
        trf.parse();
        System.out.println("Original file :\n");
  
 -      Regex nonl = new Regex("\n+", "");
 +      Regex nonl = getRegex(REGEX_NO_LINES);// "\n+", "");
        System.out.println(nonl.replaceAll(newickfile.toString()) + "\n");
  
        System.out.println("Parsed file.\n");
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.io;
  
+ import java.util.Locale;
  import java.util.Collection;
  import java.util.Comparator;
  import java.util.LinkedHashMap;
@@@ -27,7 -29,6 +29,7 @@@ import java.util.List
  import java.util.Map;
  
  import jalview.api.FeatureColourI;
 +import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.DBRefSource;
  import jalview.datamodel.GeneLociI;
@@@ -238,7 -239,7 +240,7 @@@ public class SequenceAnnotationRepor
        {
          if (sb0.length() > 6)
          {
 -          sb.append("<br/>");
 +          sb.append("<br>");
          }
          sb.append(feature.getType()).append(" ").append(begin).append(":")
                  .append(end);
  
      if (sb0.length() > 6)
      {
 -      sb.append("<br/>");
 +      sb.append("<br>");
      }
      // TODO: remove this hack to display link only features
      boolean linkOnly = feature.getValue("linkonly") != null;
           * truncate overlong descriptions unless they contain an href
           * before the truncation point (as truncation could leave corrupted html)
           */
-         int linkindex = description.toLowerCase().indexOf("<a ");
+         int linkindex = description.toLowerCase(Locale.ROOT).indexOf("<a ");
          boolean hasLink = linkindex > -1
                  && linkindex < MAX_DESCRIPTION_LENGTH;
 -        if (description.length() > MAX_DESCRIPTION_LENGTH && !hasLink)
 +        if (
 +                // BH suggestion maxlength == 0 && 
 +                description.length() > MAX_DESCRIPTION_LENGTH && !hasLink)
          {
            description = description.substring(0, MAX_DESCRIPTION_LENGTH)
                    + ELLIPSIS;
            {
              for (List<String> urllink : createLinksFrom(null, urlstring))
              {
 -              sb.append("<br/> <a href=\""
 +              sb.append("<br> <a href=\""
                        + urllink.get(3)
                        + "\" target=\""
                        + urllink.get(0)
                        + "\">"
-                       + (urllink.get(0).toLowerCase()
-                               .equals(urllink.get(1).toLowerCase()) ? urllink
+                       + (urllink.get(0).toLowerCase(Locale.ROOT)
+                               .equals(urllink.get(1).toLowerCase(Locale.ROOT)) ? urllink
                                .get(0) : (urllink.get(0) + ":" + urllink
                                                .get(1)))
 -                      + "</a><br/>");
 +                      + "</a><br>");
              }
            } catch (Exception x)
            {
        sb.append(tmp);
        maxWidth = Math.max(maxWidth, tmp.length());
      }
 +
      SequenceI ds = sequence;
      while (ds.getDatasetSequence() != null)
      {
        ds = ds.getDatasetSequence();
      }
  
 +    /*
 +     * add any annotation scores
 +     */
 +    AlignmentAnnotation[] anns = ds.getAnnotation();
 +    for (int i = 0; anns != null && i < anns.length; i++)
 +    {
 +      AlignmentAnnotation aa = anns[i];
 +      if (aa != null && aa.hasScore() && aa.sequenceRef != null)
 +      {
 +        sb.append("<br>").append(aa.label).append(": ")
 +                .append(aa.getScore());
 +      }
 +    }
 +
      if (showDbRefs)
      {
        maxWidth = Math.max(maxWidth, appendDbRefs(sb, ds, summary));
          maxWidth = Math.max(maxWidth, sz);
        }
      }
 +
 +
 +    if (sequence.getAnnotation("Search Scores") != null)
 +    {
 +      sb.append("<br>");
 +      String eValue = " E-Value: "
 +              + sequence.getAnnotation("Search Scores")[0].getEValue();
 +      String bitScore = " Bit Score: "
 +              + sequence.getAnnotation("Search Scores")[0].getBitScore();
 +      sb.append(eValue);
 +      sb.append("<br>");
 +      sb.append(bitScore);
 +      maxWidth = Math.max(maxWidth, eValue.length());
 +      maxWidth = Math.max(maxWidth, bitScore.length());
 +    }
 +    sb.append("<br>");
      sb.append("</i>");
 +
      return maxWidth;
    }
  
        countForSource++;
        if (countForSource == 1 || !summary)
        {
 -        sb.append("<br/>");
 +        sb.append("<br>");
        }
        if (countForSource <= MAX_REFS_PER_SOURCE || !summary)
        {
      }
      if (moreSources)
      {
 -      sb.append("<br/>").append(source).append(COMMA).append(ELLIPSIS);
 +      sb.append("<br>").append(source).append(COMMA).append(ELLIPSIS);
      }
      if (ellipsis)
      {
 -      sb.append("<br/>(");
 +      sb.append("<br>(");
        sb.append(MessageManager.getString("label.output_seq_details"));
        sb.append(")");
      }
   */
  package jalview.io;
  
 -import java.util.Locale;
 +import jalview.analysis.Rna;
 +import jalview.datamodel.AlignmentAnnotation;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.Annotation;
 +import jalview.datamodel.DBRefEntry;
 +import jalview.datamodel.DBRefSource;
 +import jalview.datamodel.Mapping;
 +import jalview.datamodel.Sequence;
 +import jalview.datamodel.SequenceFeature;
 +import jalview.datamodel.SequenceI;
 +import jalview.schemes.ResidueProperties;
 +import jalview.util.Comparison;
 +import jalview.util.DBRefUtils;
 +import jalview.util.Format;
 +import jalview.util.MessageManager;
 +import jalview.util.Platform;
  
  import java.io.BufferedReader;
  import java.io.FileReader;
@@@ -48,6 -33,6 +48,7 @@@ import java.util.Enumeration
  import java.util.Hashtable;
  import java.util.LinkedHashMap;
  import java.util.List;
++import java.util.Locale;
  import java.util.Map;
  import java.util.Vector;
  
@@@ -56,6 -41,21 +57,6 @@@ import com.stevesoft.pat.Regex
  import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
  import fr.orsay.lri.varna.factories.RNAFactory;
  import fr.orsay.lri.varna.models.rna.RNA;
 -import jalview.analysis.Rna;
 -import jalview.datamodel.AlignmentAnnotation;
 -import jalview.datamodel.AlignmentI;
 -import jalview.datamodel.Annotation;
 -import jalview.datamodel.DBRefEntry;
 -import jalview.datamodel.DBRefSource;
 -import jalview.datamodel.Mapping;
 -import jalview.datamodel.Sequence;
 -import jalview.datamodel.SequenceFeature;
 -import jalview.datamodel.SequenceI;
 -import jalview.schemes.ResidueProperties;
 -import jalview.util.Comparison;
 -import jalview.util.DBRefUtils;
 -import jalview.util.Format;
 -import jalview.util.MessageManager;
  
  // import org.apache.log4j.*;
  
@@@ -79,117 -79,31 +80,116 @@@ public class StockholmFile extends Alig
  {
    private static final String ANNOTATION = "annotation";
  
 -//  private static final Regex OPEN_PAREN = new Regex("(<|\\[)", "(");
 -//
 -//  private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")");
 -
 -  public static final Regex DETECT_BRACKETS = new Regex(
 -          "(<|>|\\[|\\]|\\(|\\)|\\{|\\})");
 -
 +  private static final char UNDERSCORE = '_';
 +  
    // WUSS extended symbols. Avoid ambiguity with protein SS annotations by using NOT_RNASS first.
 +
    public static final String RNASS_BRACKETS = "<>[](){}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
  
 +  public static final int REGEX_STOCKHOLM = 0;
 +
 +  public static final int REGEX_BRACKETS = 1;
    // use the following regex to decide an annotations (whole) line is NOT an RNA
    // SS (it contains only E,H,e,h and other non-brace/non-alpha chars)
 -  private static final Regex NOT_RNASS = new Regex(
 -          "^[^<>[\\](){}A-DF-Za-df-z]*$");
 +  public static final int REGEX_NOT_RNASS = 2;
 +
 +  private static final int REGEX_ANNOTATION = 3;
 +
 +  private static final int REGEX_PFAM = 4;
 +
 +  private static final int REGEX_RFAM = 5;
 +
 +  private static final int REGEX_ALIGN_END = 6;
 +
 +  private static final int REGEX_SPLIT_ID = 7;
 +
 +  private static final int REGEX_SUBTYPE = 8;
 +
 +  private static final int REGEX_ANNOTATION_LINE = 9;
 +
 +  private static final int REGEX_REMOVE_ID = 10;
 +
 +  private static final int REGEX_OPEN_PAREN = 11;
 +
 +  private static final int REGEX_CLOSE_PAREN = 12;
 +
 +  public static final int REGEX_MAX = 13;
 +
 +  private static Regex REGEX[] = new Regex[REGEX_MAX];
 +
 +  /**
 +   * Centralize all actual Regex instantialization in Platform.
 +   * // JBPNote: Why is this 'centralisation' better ?
 +   * @param id
 +   * @return
 +   */
 +  private static Regex getRegex(int id)
 +  {
 +    if (REGEX[id] == null)
 +    {
 +      String pat = null, pat2 = null;
 +      switch (id)
 +      {
 +      case REGEX_STOCKHOLM:
 +        pat = "# STOCKHOLM ([\\d\\.]+)";
 +        break;
 +      case REGEX_BRACKETS:
 +        // for reference; not used
 +        pat = "(<|>|\\[|\\]|\\(|\\)|\\{|\\})";
 +        break;
 +      case REGEX_NOT_RNASS:
 +        pat = "^[^<>[\\](){}A-DF-Za-df-z]*$";
 +        break;
 +      case REGEX_ANNOTATION:
 +        pat = "(\\w+)\\s*(.*)";
 +        break;
 +      case REGEX_PFAM:
 +        pat = "PF[0-9]{5}(.*)";
 +        break;
 +      case REGEX_RFAM:
 +        pat = "RF[0-9]{5}(.*)";
 +        break;
 +      case REGEX_ALIGN_END:
 +        pat = "^\\s*\\/\\/";
 +        break;
 +      case REGEX_SPLIT_ID:
 +        pat = "(\\S+)\\/(\\d+)\\-(\\d+)";
 +        break;
 +      case REGEX_SUBTYPE:
 +        pat = "(\\S+)\\s+(\\S*)\\s+(.*)";
 +        break;
 +      case REGEX_ANNOTATION_LINE:
 +        pat = "#=(G[FSRC]?)\\s+(.*)";
 +        break;
 +      case REGEX_REMOVE_ID:
 +        pat = "(\\S+)\\s+(\\S+)";
 +        break;
 +      case REGEX_OPEN_PAREN:
 +        pat = "(<|\\[)";
 +        pat2 = "(";
 +        break;
 +      case REGEX_CLOSE_PAREN:
 +        pat = "(>|\\])";
 +        pat2 = ")";
 +        break;
 +      default:
 +        return null;
 +      }
 +      REGEX[id] = Platform.newRegex(pat, pat2);
 +    }
 +    return REGEX[id];
 +  }
  
    StringBuffer out; // output buffer
  
 -  AlignmentI al;
 +  private AlignmentI al;
  
    public StockholmFile()
    {
    }
  
    /**
 -   * Creates a new StockholmFile object for output.
 +   * Creates a new StockholmFile object for output
     */
    public StockholmFile(AlignmentI al)
    {
      // First, we have to check that this file has STOCKHOLM format, i.e. the
      // first line must match
  
 -    r = new Regex("# STOCKHOLM ([\\d\\.]+)");
 +    r = getRegex(REGEX_STOCKHOLM);
      if (!r.search(nextLine()))
      {
        throw new IOException(MessageManager
      }
  
      // We define some Regexes here that will be used regularily later
 -    rend = new Regex("^\\s*\\/\\/"); // Find the end of an alignment
 -    p = new Regex("(\\S+)\\/(\\d+)\\-(\\d+)"); // split sequence id in
 +    rend = getRegex(REGEX_ALIGN_END);//"^\\s*\\/\\/"); // Find the end of an alignment
 +    p = getRegex(REGEX_SPLIT_ID);//"(\\S+)\\/(\\d+)\\-(\\d+)"); // split sequence id in
      // id/from/to
 -    s = new Regex("(\\S+)\\s+(\\S*)\\s+(.*)"); // Parses annotation subtype
 -    r = new Regex("#=(G[FSRC]?)\\s+(.*)"); // Finds any annotation line
 -    x = new Regex("(\\S+)\\s+(\\S+)"); // split id from sequence
 +    s = getRegex(REGEX_SUBTYPE);// "(\\S+)\\s+(\\S*)\\s+(.*)"); // Parses
 +                                // annotation subtype
 +    r = getRegex(REGEX_ANNOTATION_LINE);// "#=(G[FSRC]?)\\s+(.*)"); // Finds any
 +                                        // annotation line
 +    x = getRegex(REGEX_REMOVE_ID);// "(\\S+)\\s+(\\S+)"); // split id from
 +                                  // sequence
  
      // Convert all bracket types to parentheses (necessary for passing to VARNA)
 -    Regex openparen = new Regex("(<|\\[)", "(");
 -    Regex closeparen = new Regex("(>|\\])", ")");
 +    Regex openparen = getRegex(REGEX_OPEN_PAREN);//"(<|\\[)", "(");
 +    Regex closeparen = getRegex(REGEX_CLOSE_PAREN);//"(>|\\])", ")");
  
  //    // Detect if file is RNA by looking for bracket types
 -//    Regex detectbrackets = new Regex("(<|>|\\[|\\]|\\(|\\))");
 +    // Regex detectbrackets = getRegex("(<|>|\\[|\\]|\\(|\\))");
  
      rend.optimize();
      p.optimize();
          this.noSeqs = seqs.size();
  
          String dbsource = null;
 -        Regex pf = new Regex("PF[0-9]{5}(.*)"); // Finds AC for Pfam
 -        Regex rf = new Regex("RF[0-9]{5}(.*)"); // Finds AC for Rfam
 +        Regex pf = getRegex(REGEX_PFAM); // Finds AC for Pfam
 +        Regex rf = getRegex(REGEX_RFAM); // Finds AC for Rfam
          if (getAlignmentProperty("AC") != null)
          {
            String dbType = getAlignmentProperty("AC").toString();
  
            if (accAnnotations != null && accAnnotations.containsKey("AC"))
            {
 -            String dbr = (String) accAnnotations.get("AC");
 -            if (dbr != null)
 -            {
 -              // we could get very clever here - but for now - just try to
 +              String dbr = (String) accAnnotations.get("AC");
 +              if (dbr != null)
 +              {
 +                // we could get very clever here - but for now - just try to
                // guess accession type from type of sequence, source of alignment plus
                // structure
 -              // of accession
 -              guessDatabaseFor(seqO, dbr, dbsource);
 +                // of accession
 +                guessDatabaseFor(seqO, dbr, dbsource);
              }
              // else - do what ? add the data anyway and prompt the user to
              // specify what references these are ?
             */
            // Let's save the annotations, maybe we'll be able to do something
            // with them later...
 -          Regex an = new Regex("(\\w+)\\s*(.*)");
 +          Regex an = getRegex(REGEX_ANNOTATION);
            if (an.search(annContent))
            {
              if (an.stringMatched(1).equals("NH"))
              if (features.containsKey(this.id2type(type)))
              {
                // logger.debug("Found content for " + this.id2type(type));
 -              content = (Hashtable) features.get(this.id2type(type));
 +              content = (Hashtable) features
 +                      .get(this.id2type(type));
              }
              else
              {
                // logger.debug("Creating new content holder for " +
                // this.id2type(type));
                content = new Hashtable();
 -              features.put(this.id2type(type), content);
 +              features.put(id2type(type), content);
              }
              String ns = (String) content.get(ANNOTATION);
  
            Vector<AlignmentAnnotation> annotation, String label,
            String annots)
    {
 -    String convert1, convert2 = null;
 -
 -    // convert1 = OPEN_PAREN.replaceAll(annots);
 -    // convert2 = CLOSE_PAREN.replaceAll(convert1);
 +        String convert1, convert2 = null;
 +    // String convert1 = OPEN_PAREN.replaceAll(annots);
 +    // String convert2 = CLOSE_PAREN.replaceAll(convert1);
      // annots = convert2;
  
      String type = label;
      type = id2type(type);
  
      boolean isrnass = false;
      if (type.equalsIgnoreCase("secondary structure"))
      {
        ss = true;
 -      isrnass = !NOT_RNASS.search(annots); // sorry about the double negative
 +      isrnass = !getRegex(REGEX_NOT_RNASS).search(annots); // sorry about the double
 +                                                     // negative
                                             // here (it's easier for dealing with
                                             // other non-alpha-non-brace chars)
      }
      for (int i = 0; i < annots.length(); i++)
      {
        String pos = annots.substring(i, i + 1);
++      // TODO 2.12 release: verify this Stockholm IO behaviour change in release notes
 +      if (UNDERSCORE == pos.charAt(0))
 +      {
 +        pos = " ";
 +      }
        Annotation ann;
        ann = new Annotation(pos, "", ' ', 0f); // 0f is 'valid' null - will not
        // be written out
      return ref.getSource().toString() + " ; "
              + ref.getAccessionId().toString();
    }
    @Override
    public String print(SequenceI[] s, boolean jvSuffix)
    {
          }
          else
          {
 -          for (int idb = 0; idb < seq.getDBRefs().size(); idb++)
 +          for (int idb = 0; idb < ndb; idb++)
            {
 -            DBRefEntry dbref = seq.getDBRefs().get(idb);
 +            DBRefEntry dbref = seqrefs.get(idb);
              dataRef.put(tmp, dbref_to_ac_record(dbref));
              // if we put in a uniprot or EMBL record then we're done:
 -            if (isAA && DBRefSource.UNIPROT
 -                    .equals(DBRefUtils.getCanonicalName(dbref.getSource())))
 -            {
 -              break;
 -            }
 -            if (!isAA && DBRefSource.EMBL
 +            if ((isAA ? DBRefSource.UNIPROT : DBRefSource.EMBL)
                      .equals(DBRefUtils.getCanonicalName(dbref.getSource())))
              {
                break;
        if (alAnot != null)
        {
          Annotation[] ann;
 -        for (int j = 0, nj = alAnot.length; j < nj; j++)
 +        for (int j = 0; j < alAnot.length; j++)
          {
 -          String key = type2id(alAnot[j].label);
 -          boolean isrna = alAnot[j].isValidStruc();
 -
 -          if (isrna)
 -          {
 -            // hardwire to secondary structure if there is RNA secondary
 -            // structure on the annotation
 -            key = "SS";
 -          }
 -          if (key == null)
 +          if (alAnot[j].annotations != null)
            {
 +            String key = type2id(alAnot[j].label);
 +            boolean isrna = alAnot[j].isValidStruc();
  
 -            continue;
 -          }
 +            if (isrna)
 +            {
 +              // hardwire to secondary structure if there is RNA secondary
 +              // structure on the annotation
 +              key = "SS";
 +            }
 +            if (key == null)
 +            {
 +              continue;
 +            }
  
 -          // out.append("#=GR ");
 -          out.append(new Format("%-" + maxid + "s").form(
 -                  "#=GR " + printId(seq, jvSuffix) + " " + key + " "));
 -          ann = alAnot[j].annotations;
 -          String sseq = "";
 -          for (int k = 0, nk = ann.length; k < nk; k++)
 -          {
 -            sseq += outputCharacter(key, k, isrna, ann, seq);
 -          }
 -          out.append(sseq);
 -          out.append(newline);
 +            // out.append("#=GR ");
 +            out.append(new Format("%-" + maxid + "s").form(
 +                    "#=GR " + printId(s[i], jvSuffix) + " " + key + " "));
 +            ann = alAnot[j].annotations;
 +            String sseq = "";
 +            for (int k = 0; k < ann.length; k++)
 +            {
 +              sseq += outputCharacter(key, k, isrna, ann, s[i]);
 +            }
 +            out.append(sseq);
 +            out.append(newline);
 +        }
          }
        }
  
        out.append(new Format("%-" + maxid + "s")
          }
          else
          {
 -          key = type2id(aa.label.toLowerCase(Locale.ROOT));
 +          key = type2id(aa.label.toLowerCase());
            if (key == null)
            {
              label = aa.label;
      return out.toString();
    }
  
    /**
     * add an annotation character to the output row
     * 
              : seq;
    }
  
 +  /**
 +   * make a friendly ID string.
 +   * 
 +   * @param dataName
 +   * @return truncated dataName to after last '/'
 +   */
 +  private String safeName(String dataName)
 +  {
 +    int b = 0;
 +    while ((b = dataName.indexOf("/")) > -1 && b < dataName.length())
 +    {
 +      dataName = dataName.substring(b + 1).trim();
 +
 +    }
 +    int e = (dataName.length() - dataName.indexOf(".")) + 1;
 +    dataName = dataName.substring(1, e).trim();
 +    return dataName;
 +  }
 +  
 +  
    public String print()
    {
      out = new StringBuffer();
  
      }
    }
 +  
    protected static String id2type(String id)
    {
      if (typeIds.containsKey(id))
              "Warning : Unknown Stockholm annotation type: " + type);
      return key;
    }
 -
 -  /**
 -   * make a friendly ID string.
 -   * 
 -   * @param dataName
 -   * @return truncated dataName to after last '/'
 -   */
 -  private String safeName(String dataName)
 -  {
 -    int b = 0;
 -    while ((b = dataName.indexOf("/")) > -1 && b < dataName.length())
 -    {
 -      dataName = dataName.substring(b + 1).trim();
 -
 -    }
 -    int e = (dataName.length() - dataName.indexOf(".")) + 1;
 -    dataName = dataName.substring(1, e).trim();
 -    return dataName;
 -  }
  }
@@@ -35,10 -35,8 +35,10 @@@ import jalview.io.vamsas.DatastoreRegis
  import jalview.io.vamsas.Rangetype;
  import jalview.project.Jalview2XML;
  import jalview.util.MessageManager;
 +import jalview.util.jarInputStreamProvider;
  import jalview.viewmodel.AlignmentViewport;
  
 +import java.io.File;
  import java.io.IOException;
  import java.util.Enumeration;
  import java.util.HashMap;
@@@ -340,10 -338,8 +340,8 @@@ public class VamsasAppDatastor
                  if (vbound.getV_parent() != null
                          && dataset != vbound.getV_parent())
                  {
-                   throw new Error(MessageManager.getString(
-                           "error.implementation_error_cannot_map_alignment_sequences"));
-                   // This occurs because the dataset for the alignment we are
-                   // trying to
+                   throw new Error(
+                           "IMPLEMENTATION ERROR: Cannot map an alignment of sequences from different datasets into a single alignment in the vamsas document.");
                  }
                }
              }
        // /SAVE THE TREES
        // /////////////////////////////////
        // FIND ANY ASSOCIATED TREES
 -      if (Desktop.desktop != null)
 +      if (Desktop.getDesktopPane() != null)
        {
 -        javax.swing.JInternalFrame[] frames = Desktop.instance
 +        javax.swing.JInternalFrame[] frames = Desktop.getInstance()
                  .getAllFrames();
  
          for (int t = 0; t < frames.length; t++)
              // and
              // mapValuesToString
              fromxml.setSkipList(skipList);
 -            jalview.util.jarInputStreamProvider jprovider = new jalview.util.jarInputStreamProvider()
 +            jarInputStreamProvider jprovider = new jarInputStreamProvider()
              {
  
                @Override
                          "Returning client input stream for Jalview from Vamsas Document.");
                  return new JarInputStream(cappdata.getClientInputStream());
                }
 +
 +              @Override
 +              public File getFile()
 +              {
 +                return null;
 +              }
              };
              if (dojvsync)
              {
            fromxml.setSkipList(skipList);
            fromxml.setObjectMappingTables(mapKeysToString(vobj2jv),
                    mapValuesToString(jv2vobj));
 -          jalview.util.jarInputStreamProvider jarstream = new jalview.util.jarInputStreamProvider()
 +          jarInputStreamProvider jarstream = new jarInputStreamProvider()
            {
  
              @Override
                        "Returning user input stream for Jalview from Vamsas Document.");
                return new JarInputStream(cappdata.getUserInputStream());
              }
 +
 +            @Override
 +            public File getFile()
 +            {
 +              return null;
 +            }
            };
            if (dojvsync)
            {
            if (mappings != null)
            {
              jalview.structure.StructureSelectionManager
 -                    .getStructureSelectionManager(Desktop.instance)
 +                    .getStructureSelectionManager(Desktop.getInstance())
                      .registerMappings(mappings);
            }
          }
   */
  package jalview.io.packed;
  
 +import java.io.BufferedReader;
 +import java.io.IOException;
 +import java.util.ArrayList;
 +import java.util.HashMap;
 +import java.util.List;
+ import java.util.Locale;
  
+ import jalview.api.FeatureColourI;
  import jalview.datamodel.AlignmentI;
  import jalview.io.AppletFormatAdapter;
  import jalview.io.FileFormatI;
@@@ -34,6 -31,12 +36,7 @@@ import jalview.io.FormatAdapter
  import jalview.io.IdentifyFile;
  import jalview.io.packed.DataProvider.JvDataType;
  
 -import java.io.BufferedReader;
 -import java.io.IOException;
 -import java.util.ArrayList;
 -import java.util.HashMap;
 -import java.util.List;
  public class ParsePackedSet
  {
  
        String type = args[i++];
        final String file = args[i++];
        final JvDataType jtype = DataProvider.JvDataType
-               .valueOf(type.toUpperCase());
+               .valueOf(type.toUpperCase(Locale.ROOT));
        if (jtype != null)
        {
          final FileParse fp;
        else
        {
          System.out.println("Couldn't parse source type token '"
-                 + type.toUpperCase() + "'");
+                 + type.toUpperCase(Locale.ROOT) + "'");
        }
      }
      if (i < args.length)
   */
  package jalview.jbgui;
  
 +import jalview.bin.Cache;
 +import jalview.fts.core.FTSDataColumnPreferences;
 +import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
 +import jalview.fts.service.pdb.PDBFTSRestClient;
 +import jalview.gui.Desktop;
 +import jalview.gui.JalviewBooleanRadioButtons;
 +import jalview.gui.JvOptionPane;
 +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;
  import java.awt.Component;
@@@ -49,6 -33,7 +49,7 @@@ import java.awt.Insets
  import java.awt.Rectangle;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
+ import java.awt.event.FocusEvent;
  import java.awt.event.KeyEvent;
  import java.awt.event.KeyListener;
  import java.awt.event.MouseAdapter;
@@@ -56,19 -41,17 +57,20 @@@ import java.awt.event.MouseEvent
  import java.util.Arrays;
  import java.util.List;
  
 +import javax.swing.AbstractButton;
  import javax.swing.AbstractCellEditor;
  import javax.swing.BorderFactory;
 +import javax.swing.BoxLayout;
  import javax.swing.ButtonGroup;
  import javax.swing.DefaultListCellRenderer;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
 +import javax.swing.JComponent;
  import javax.swing.JFileChooser;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
+ import javax.swing.JPasswordField;
  import javax.swing.JRadioButton;
  import javax.swing.JScrollPane;
  import javax.swing.JSpinner;
@@@ -86,11 -69,27 +88,11 @@@ import javax.swing.border.EtchedBorder
  import javax.swing.border.TitledBorder;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
+ import javax.swing.event.DocumentEvent;
+ import javax.swing.event.DocumentListener;
  import javax.swing.table.TableCellEditor;
  import javax.swing.table.TableCellRenderer;
  
- import net.miginfocom.swing.MigLayout;
 -import jalview.bin.Cache;
 -import jalview.fts.core.FTSDataColumnPreferences;
 -import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
 -import jalview.fts.service.pdb.PDBFTSRestClient;
 -import jalview.gui.Desktop;
 -import jalview.gui.JalviewBooleanRadioButtons;
 -import jalview.gui.JvOptionPane;
 -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;
 -
  /**
   * Base class for the Preferences panel.
   * 
@@@ -165,15 -164,10 +167,13 @@@ public class GPreferences extends JPane
  
    protected JCheckBox showConsensLogo = new JCheckBox();
  
 +  protected JCheckBox showInformationHistogram = new JCheckBox();
 +
 +  protected JCheckBox showHMMLogo = new JCheckBox();
    protected JCheckBox showDbRefTooltip = new JCheckBox();
  
    protected JCheckBox showNpTooltip = new JCheckBox();
  
    /*
     * Structure tab and components
     */
  
    protected JCheckBox structFromPdb = new JCheckBox();
  
-   protected JCheckBox useRnaView = new JCheckBox();
 +
    protected JCheckBox addSecondaryStructure = new JCheckBox();
  
    protected JCheckBox addTempFactor = new JCheckBox();
  
    protected JComboBox<String> structViewer = new JComboBox<>();
  
-   protected JTextField chimeraPath = new JTextField();
+   protected JLabel structureViewerPathLabel;
+   protected JTextField structureViewerPath = new JTextField();
  
    protected ButtonGroup mappingMethod = new ButtonGroup();
  
    /*
     * Connections tab components
     */
+   protected JPanel connectTab;
    protected JTable linkUrlTable = new JTable();
  
    protected JButton editLink = new JButton();
  
    protected JButton userOnly = new JButton();
  
+   protected JLabel httpLabel = new JLabel();
+   protected JLabel httpsLabel = new JLabel();
    protected JLabel portLabel = new JLabel();
  
    protected JLabel serverLabel = new JLabel();
  
-   protected JTextField proxyServerTB = new JTextField();
+   protected JLabel portLabel2 = new JLabel();
+   protected JLabel serverLabel2 = new JLabel();
+   protected JLabel proxyAuthUsernameLabel = new JLabel();
+   protected JLabel proxyAuthPasswordLabel = new JLabel();
+   protected JLabel passwordNotStoredLabel = new JLabel();
  
-   protected JTextField proxyPortTB = new JTextField();
+   protected JTextField proxyServerHttpTB = new JTextField();
+   protected JTextField proxyPortHttpTB = new JTextField();
+   protected JTextField proxyServerHttpsTB = new JTextField();
+   protected JTextField proxyPortHttpsTB = new JTextField();
+   protected JCheckBox proxyAuth = new JCheckBox();
+   protected JTextField proxyAuthUsernameTB = new JTextField();
+   protected JPasswordField proxyAuthPasswordPB = new JPasswordField();
  
    protected JTextField defaultBrowser = new JTextField();
  
-   protected JCheckBox useProxy = new JCheckBox();
+   protected ButtonGroup proxyType = new ButtonGroup();
+   protected JRadioButton noProxy = new JRadioButton();
+   protected JRadioButton systemProxy = new JRadioButton();
+   protected JRadioButton customProxy = new JRadioButton();
+   protected JButton applyProxyButton = new JButton();
  
    protected JCheckBox usagestats = new JCheckBox();
  
    protected JCheckBox sortByTree = new JCheckBox();
  
    /*
 +   * hmmer tab and components
 +   */
 +  protected JPanel hmmerTab;
 +
 +  protected JCheckBox hmmrTrimTermini;
 +
 +  protected AbstractButton hmmerBackgroundUniprot;
 +
 +  protected AbstractButton hmmerBackgroundAlignment;
 +
 +  protected JTextField hmmerSequenceCount;
 +
 +  protected JTextField hmmerPath;
 +
 +  protected JTextField cygwinPath;
 +
 +  /*
     * Web Services tab
     */
    protected JPanel wsTab = new JPanel();
  
 +  protected JPanel slivkaTab = new JPanel();
    /*
     * Backups tab components
     * a lot of these are member variables instead of local variables only so that they
  
    protected JTextArea backupfilesExampleLabel = new JTextArea();
  
+   private final JTabbedPane tabbedPane = new JTabbedPane();
+   private JLabel messageLabel = new JLabel("", JLabel.CENTER);
 -
    /**
     * Creates a new GPreferences object.
     */
     */
    private void jbInit() throws Exception
    {
-     final JTabbedPane tabbedPane = new JTabbedPane();
+     // final JTabbedPane tabbedPane = new JTabbedPane();
      this.setLayout(new BorderLayout());
 -
+     // message label at top
+     this.add(messageLabel, BorderLayout.NORTH);
 -
      JPanel okCancelPanel = initOkCancelPanel();
      this.add(tabbedPane, BorderLayout.CENTER);
      this.add(okCancelPanel, BorderLayout.SOUTH);
      tabbedPane.add(initConnectionsTab(),
              MessageManager.getString("label.connections"));
  
-       if (!Platform.isJS()) 
-       {
-         tabbedPane.add(initBackupsTab(), 
-                       MessageManager.getString("label.backups"));
-       }
+     if (!Platform.isJS())
+     {
+       tabbedPane.add(initBackupsTab(),
+               MessageManager.getString("label.backups"));
+     }
  
      tabbedPane.add(initLinksTab(),
              MessageManager.getString("label.urllinks"));
      tabbedPane.add(initEditingTab(),
              MessageManager.getString("label.editing"));
  
 +    tabbedPane.add(initHMMERTab(), MessageManager.getString("label.hmmer"));
      /*
       * See WsPreferences for the real work of configuring this tab.
       */
      {
        wsTab.setLayout(new BorderLayout());
        tabbedPane.add(wsTab, MessageManager.getString("label.web_services"));
 +      slivkaTab.setLayout(new BorderLayout());
 +      tabbedPane.add(slivkaTab, "Slivka Services");
      }
  
      /*
       * Handler to validate a tab before leaving it - currently only for
-      * Structure
+      * Structure.
+      * Adding a clearMessage() so messages are cleared when changing tabs.
       */
      tabbedPane.addChangeListener(new ChangeListener()
      {
            }
          }
          lastTab = tabbedPane.getSelectedComponent();
 -
+         clearMessage();
        }
  
      });
    }
  
+   public void setMessage(String message)
+   {
+     if (message != null)
+     {
+       messageLabel.setText(message);
+       messageLabel.setFont(LABEL_FONT_BOLD);
+       messageLabel.setForeground(Color.RED.darker());
+       messageLabel.revalidate();
+       messageLabel.repaint();
+     }
+     // note message not cleared if message is null. call clearMessage()
+     // directly.
+     this.revalidate();
+     this.repaint();
+   }
+   public void clearMessage()
+   {
+     // only repaint if message exists
+     if (messageLabel.getText() != null
+             && messageLabel.getText().length() > 0)
+     {
+       messageLabel.setText("");
+       messageLabel.revalidate();
+       messageLabel.repaint();
+       this.revalidate();
+       this.repaint();
+     }
+   }
+   public static enum TabRef
+   {
+     CONNECTIONS_TAB, STRUCTURE_TAB
+   };
+   public void selectTab(TabRef selectTab)
+   {
+     // select a given tab - currently only for Connections
+     switch (selectTab)
+     {
+     case CONNECTIONS_TAB:
+       tabbedPane.setSelectedComponent(connectTab);
+       break;
+     case STRUCTURE_TAB:
+       tabbedPane.setSelectedComponent(structureTab);
+       break;
+     default:
+     }
+   }
    /**
     * Initialises the Editing tabbed panel.
     * 
    }
  
    /**
 -   * Initialises the Output tab
 +   * Initialises the hmmer tabbed panel
 +   * 
 +   * @return
 +   */
 +  private JPanel initHMMERTab()
 +  {
 +    hmmerTab = new JPanel();
 +    hmmerTab.setLayout(new BoxLayout(hmmerTab, BoxLayout.Y_AXIS));
 +    hmmerTab.setLayout(new MigLayout("flowy"));
 +
 +    /*
 +     * path to hmmer binaries folder
 +     */
 +    JPanel installationPanel = new JPanel(new MigLayout("flowy"));
 +    // new FlowLayout(FlowLayout.LEFT));
 +    JvSwingUtils.createTitledBorder(installationPanel,
 +            MessageManager.getString("label.installation"), true);
 +    hmmerTab.add(installationPanel);
 +    JLabel hmmerLocation = new JLabel(
 +            MessageManager.getString("label.hmmer_location"));
 +    hmmerLocation.setFont(LABEL_FONT);
 +    final int pathFieldLength = 40;
 +    hmmerPath = new JTextField(pathFieldLength);
 +    hmmerPath.addMouseListener(new MouseAdapter()
 +    {
 +      @Override
 +      public void mouseClicked(MouseEvent e)
 +      {
 +        if (e.getClickCount() == 2)
 +        {
 +          String chosen = openFileChooser(true);
 +          if (chosen != null)
 +          {
 +            hmmerPath.setText(chosen);
 +            validateHmmerPath();
 +          }
 +        }
 +      }
 +    });
 +    installationPanel.add(hmmerLocation);
 +    installationPanel.add(hmmerPath);
 +
 +    /*
 +     * path to Cygwin binaries folder (for Windows)
 +     */
 +    if (Platform.isWindowsAndNotJS())
 +    {
 +      JLabel cygwinLocation = new JLabel(
 +              MessageManager.getString("label.cygwin_location"));
 +      cygwinLocation.setFont(LABEL_FONT);
 +      cygwinPath = new JTextField(pathFieldLength);
 +      cygwinPath.addMouseListener(new MouseAdapter()
 +      {
 +        @Override
 +        public void mouseClicked(MouseEvent e)
 +        {
 +          if (e.getClickCount() == 2)
 +          {
 +            String chosen = openFileChooser(true);
 +            if (chosen != null)
 +            {
 +              cygwinPath.setText(chosen);
 +              validateCygwinPath();
 +            }
 +          }
 +        }
 +      });
 +      installationPanel.add(cygwinLocation);
 +      installationPanel.add(cygwinPath);
 +    }
 +
 +    /*
 +     * preferences for hmmalign
 +     */
 +    JPanel alignOptionsPanel = new JPanel(new MigLayout());
 +    // new FlowLayout(FlowLayout.LEFT));
 +    JvSwingUtils.createTitledBorder(alignOptionsPanel,
 +            MessageManager.getString("label.hmmalign_options"), true);
 +    hmmerTab.add(alignOptionsPanel);
 +    hmmrTrimTermini = new JCheckBox();
 +    hmmrTrimTermini.setFont(LABEL_FONT);
 +    hmmrTrimTermini.setText(MessageManager.getString("label.trim_termini"));
 +    alignOptionsPanel.add(hmmrTrimTermini);
 +
 +    /*
 +     * preferences for hmmsearch
 +     */
 +    JPanel searchOptions = new JPanel(new MigLayout());
 +    // FlowLayout(FlowLayout.LEFT));
 +    JvSwingUtils.createTitledBorder(searchOptions,
 +            MessageManager.getString("label.hmmsearch_options"), true);
 +    hmmerTab.add(searchOptions);
 +    JLabel sequencesToKeep = new JLabel(
 +            MessageManager.getString("label.no_of_sequences"));
 +    sequencesToKeep.setFont(LABEL_FONT);
 +    searchOptions.add(sequencesToKeep);
 +    hmmerSequenceCount = new JTextField(5);
 +    searchOptions.add(hmmerSequenceCount);
 +
 +    /*
 +     * preferences for Information Content annotation
 +     */
 +    // JPanel dummy = new JPanel(new FlowLayout(FlowLayout.LEFT));
 +    JPanel annotationOptions = new JPanel(new MigLayout("left"));
 +    JvSwingUtils.createTitledBorder(annotationOptions,
 +            MessageManager.getString("label.information_annotation"), true);
 +    // dummy.add(annotationOptions);
 +    hmmerTab.add(annotationOptions);
 +    ButtonGroup backgroundOptions = new ButtonGroup();
 +    hmmerBackgroundUniprot = new JRadioButton(
 +            MessageManager.getString("label.freq_uniprot"));
 +    hmmerBackgroundUniprot.setFont(LABEL_FONT);
 +    hmmerBackgroundAlignment = new JRadioButton(
 +            MessageManager.getString("label.freq_alignment"));
 +    hmmerBackgroundAlignment.setFont(LABEL_FONT);
 +    backgroundOptions.add(hmmerBackgroundUniprot);
 +    backgroundOptions.add(hmmerBackgroundAlignment);
 +    backgroundOptions.setSelected(hmmerBackgroundUniprot.getModel(), true);
 +    // disable buttons for now as annotation only uses Uniprot background
 +    hmmerBackgroundAlignment.setEnabled(false);
 +    hmmerBackgroundUniprot.setEnabled(false);
 +    annotationOptions.add(hmmerBackgroundUniprot, "wrap");
 +    annotationOptions.add(hmmerBackgroundAlignment);
 +
 +    return hmmerTab;
 +  }
 +
 +  /**
 +   * Initialises the Output tabbed panel.
     * 
     * @return
     */
     */
    private JPanel initConnectionsTab()
    {
-     JPanel connectTab = new JPanel();
+     connectTab = new JPanel();
      connectTab.setLayout(new GridBagLayout());
  
      // Label for browser text box
    private JPanel initConnTabProxyPanel()
    {
      // Label for server text box
-     serverLabel.setText(MessageManager.getString("label.address"));
+     serverLabel.setText(MessageManager.getString("label.host") + ": ");
      serverLabel.setHorizontalAlignment(SwingConstants.RIGHT);
      serverLabel.setFont(LABEL_FONT);
+     serverLabel2.setText(MessageManager.getString("label.host") + ": ");
+     serverLabel2.setHorizontalAlignment(SwingConstants.RIGHT);
+     serverLabel2.setFont(LABEL_FONT);
  
      // Proxy server and port text boxes
-     proxyServerTB.setFont(LABEL_FONT);
-     proxyPortTB.setFont(LABEL_FONT);
+     proxyServerHttpTB.setFont(LABEL_FONT);
+     proxyServerHttpTB.setColumns(40);
+     proxyPortHttpTB.setFont(LABEL_FONT);
+     proxyPortHttpTB.setColumns(4);
+     proxyServerHttpsTB.setFont(LABEL_FONT);
+     proxyServerHttpsTB.setColumns(40);
+     proxyPortHttpsTB.setFont(LABEL_FONT);
+     proxyPortHttpsTB.setColumns(4);
+     proxyAuthUsernameTB.setFont(LABEL_FONT);
+     proxyAuthUsernameTB.setColumns(30);
+     // check for any change to enable applyProxyButton
+     DocumentListener d = new DocumentListener()
+     {
+       @Override
+       public void changedUpdate(DocumentEvent e)
+       {
+         applyProxyButtonEnabled(true);
+       }
+       @Override
+       public void insertUpdate(DocumentEvent e)
+       {
+         applyProxyButtonEnabled(true);
+       }
+       @Override
+       public void removeUpdate(DocumentEvent e)
+       {
+         applyProxyButtonEnabled(true);
+       }
+     };
+     proxyServerHttpTB.getDocument().addDocumentListener(d);
+     proxyPortHttpTB.getDocument().addDocumentListener(d);
+     proxyServerHttpsTB.getDocument().addDocumentListener(d);
+     proxyPortHttpsTB.getDocument().addDocumentListener(d);
+     proxyAuthUsernameTB.getDocument().addDocumentListener(d);
+     proxyAuthPasswordPB.setFont(LABEL_FONT);
+     proxyAuthPasswordPB.setColumns(30);
+     proxyAuthPasswordPB.getDocument()
+             .addDocumentListener(new DocumentListener()
+             {
+               @Override
+               public void changedUpdate(DocumentEvent e)
+               {
+                 proxyAuthPasswordCheckHighlight(true);
+                 applyProxyButtonEnabled(true);
+               }
+               @Override
+               public void insertUpdate(DocumentEvent e)
+               {
+                 proxyAuthPasswordCheckHighlight(true);
+                 applyProxyButtonEnabled(true);
+               }
+               @Override
+               public void removeUpdate(DocumentEvent e)
+               {
+                 proxyAuthPasswordCheckHighlight(true);
+                 applyProxyButtonEnabled(true);
+               }
+             });
  
      // Label for Port text box
      portLabel.setFont(LABEL_FONT);
      portLabel.setHorizontalAlignment(SwingConstants.RIGHT);
-     portLabel.setText(MessageManager.getString("label.port"));
-     // Use proxy server checkbox
-     useProxy.setFont(LABEL_FONT);
-     useProxy.setHorizontalAlignment(SwingConstants.RIGHT);
-     useProxy.setHorizontalTextPosition(SwingConstants.LEADING);
-     useProxy.setText(MessageManager.getString("label.use_proxy_server"));
-     useProxy.addActionListener(new ActionListener()
+     portLabel.setText(MessageManager.getString("label.port") + ": ");
+     portLabel2.setFont(LABEL_FONT);
+     portLabel2.setHorizontalAlignment(SwingConstants.RIGHT);
+     portLabel2.setText(MessageManager.getString("label.port") + ": ");
+     httpLabel.setText("HTTP");
+     httpLabel.setFont(LABEL_FONT_BOLD);
+     httpLabel.setHorizontalAlignment(SwingConstants.LEFT);
+     httpsLabel.setText("HTTPS");
+     httpsLabel.setFont(LABEL_FONT_BOLD);
+     httpsLabel.setHorizontalAlignment(SwingConstants.LEFT);
+     proxyAuthUsernameLabel
+             .setText(MessageManager.getString("label.username") + ": ");
+     proxyAuthUsernameLabel.setFont(LABEL_FONT);
+     proxyAuthUsernameLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+     proxyAuthPasswordLabel
+             .setText(MessageManager.getString("label.password") + ": ");
+     proxyAuthPasswordLabel.setFont(LABEL_FONT);
+     proxyAuthPasswordLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+     passwordNotStoredLabel.setText(
+             "(" + MessageManager.getString("label.not_stored") + ")");
+     passwordNotStoredLabel.setFont(LABEL_FONT_ITALIC);
+     passwordNotStoredLabel.setHorizontalAlignment(SwingConstants.LEFT);
+     // Proxy type radio buttons
+     noProxy.setFont(LABEL_FONT);
+     noProxy.setHorizontalAlignment(SwingConstants.LEFT);
+     noProxy.setText(MessageManager.getString("label.no_proxy"));
+     systemProxy.setFont(LABEL_FONT);
+     systemProxy.setHorizontalAlignment(SwingConstants.LEFT);
+     systemProxy.setText(MessageManager.formatMessage("label.system_proxy",
+             displayUserHostPort(Cache.startupProxyProperties[4],
+                     Cache.startupProxyProperties[0],
+                     Cache.startupProxyProperties[1]),
+             displayUserHostPort(Cache.startupProxyProperties[6],
+                     Cache.startupProxyProperties[2],
+                     Cache.startupProxyProperties[3])));
+     customProxy.setFont(LABEL_FONT);
+     customProxy.setHorizontalAlignment(SwingConstants.LEFT);
+     customProxy.setText(
+             MessageManager.getString("label.use_proxy_server") + ":");
+     ActionListener al = new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         useProxy_actionPerformed();
+         proxyType_actionPerformed();
+       }
+     };
+     noProxy.addActionListener(al);
+     systemProxy.addActionListener(al);
+     customProxy.addActionListener(al);
+     proxyType.add(noProxy);
+     proxyType.add(systemProxy);
+     proxyType.add(customProxy);
+     proxyAuth.setFont(LABEL_FONT);
+     proxyAuth.setHorizontalAlignment(SwingConstants.LEFT);
+     proxyAuth.setText(MessageManager.getString("label.auth_required"));
+     proxyAuth.addActionListener(new ActionListener()
+     {
+       @Override
+       public void actionPerformed(ActionEvent e)
+       {
+         proxyAuth_actionPerformed();
        }
      });
 -
+     setCustomProxyEnabled();
  
      // Make proxy server panel
      JPanel proxyPanel = new JPanel();
      TitledBorder titledBorder1 = new TitledBorder(
-             MessageManager.getString("label.proxy_server"));
+             MessageManager.getString("label.proxy_servers"));
      proxyPanel.setBorder(titledBorder1);
      proxyPanel.setLayout(new GridBagLayout());
-     proxyPanel.add(serverLabel,
-             new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
-                     GridBagConstraints.WEST, GridBagConstraints.NONE,
-                     new Insets(0, 2, 2, 0), 5, 0));
-     proxyPanel.add(portLabel,
-             new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0,
-                     GridBagConstraints.WEST, GridBagConstraints.NONE,
-                     new Insets(0, 0, 2, 0), 11, 0));
-     proxyPanel.add(useProxy,
-             new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0,
-                     GridBagConstraints.WEST, GridBagConstraints.NONE,
-                     new Insets(0, 2, 5, 185), 2, -4));
-     proxyPanel.add(proxyPortTB,
-             new GridBagConstraints(3, 1, 1, 1, 1.0, 0.0,
-                     GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
-                     new Insets(0, 2, 2, 2), 54, 1));
-     proxyPanel.add(proxyServerTB,
-             new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0,
-                     GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
-                     new Insets(0, 2, 2, 0), 263, 1));
+     GridBagConstraints gbc = new GridBagConstraints();
+     gbc.fill = GridBagConstraints.HORIZONTAL;
+     gbc.weightx = 1.0;
+     GridBagConstraints c = new GridBagConstraints();
+     // Proxy type radio buttons (3)
+     JPanel ptPanel = new JPanel();
+     ptPanel.setLayout(new GridBagLayout());
+     c.weightx = 1.0;
+     c.gridy = 0;
+     c.gridx = 0;
+     c.gridwidth = 1;
+     c.fill = GridBagConstraints.HORIZONTAL;
+     ptPanel.add(noProxy, c);
+     c.gridy++;
+     ptPanel.add(systemProxy, c);
+     c.gridy++;
+     ptPanel.add(customProxy, c);
+     gbc.gridy = 0;
+     proxyPanel.add(ptPanel, gbc);
+     // host and port text boxes
+     JPanel hpPanel = new JPanel();
+     hpPanel.setLayout(new GridBagLayout());
+     // HTTP host port row
+     c.gridy = 0;
+     c.gridx = 0;
+     c.weightx = 0.1;
+     c.anchor = GridBagConstraints.LINE_START;
+     hpPanel.add(httpLabel, c);
+     c.gridx++;
+     c.weightx = 0.1;
+     c.anchor = GridBagConstraints.LINE_END;
+     hpPanel.add(serverLabel, c);
+     c.gridx++;
+     c.weightx = 1.0;
+     c.anchor = GridBagConstraints.LINE_START;
+     hpPanel.add(proxyServerHttpTB, c);
+     c.gridx++;
+     c.weightx = 0.1;
+     c.anchor = GridBagConstraints.LINE_END;
+     hpPanel.add(portLabel, c);
+     c.gridx++;
+     c.weightx = 0.2;
+     c.anchor = GridBagConstraints.LINE_START;
+     hpPanel.add(proxyPortHttpTB, c);
+     // HTTPS host port row
+     c.gridy++;
+     c.gridx = 0;
+     c.gridwidth = 1;
+     c.anchor = GridBagConstraints.LINE_START;
+     hpPanel.add(httpsLabel, c);
+     c.gridx++;
+     c.anchor = GridBagConstraints.LINE_END;
+     hpPanel.add(serverLabel2, c);
+     c.gridx++;
+     c.anchor = GridBagConstraints.LINE_START;
+     hpPanel.add(proxyServerHttpsTB, c);
+     c.gridx++;
+     c.anchor = GridBagConstraints.LINE_END;
+     hpPanel.add(portLabel2, c);
+     c.gridx++;
+     c.anchor = GridBagConstraints.LINE_START;
+     hpPanel.add(proxyPortHttpsTB, c);
+     gbc.gridy++;
+     proxyPanel.add(hpPanel, gbc);
+     if (!Platform.isJS())
+     /**
+      * java.net.Authenticator is not implemented in SwingJS. Not displaying the
+      * Authentication options in Preferences.
+      * 
+      * @j2sIgnore
+      * 
+      */
+     {
+       // Require authentication checkbox
+       gbc.gridy++;
+       proxyPanel.add(proxyAuth, gbc);
+       // username and password
+       JPanel upPanel = new JPanel();
+       upPanel.setLayout(new GridBagLayout());
+       // username row
+       c.gridy = 0;
+       c.gridx = 0;
+       c.gridwidth = 1;
+       c.weightx = 0.4;
+       c.anchor = GridBagConstraints.LINE_END;
+       upPanel.add(proxyAuthUsernameLabel, c);
+       c.gridx++;
+       c.weightx = 1.0;
+       c.anchor = GridBagConstraints.LINE_START;
+       upPanel.add(proxyAuthUsernameTB, c);
+       // password row
+       c.gridy++;
+       c.gridx = 0;
+       c.weightx = 0.4;
+       c.anchor = GridBagConstraints.LINE_END;
+       upPanel.add(proxyAuthPasswordLabel, c);
+       c.gridx++;
+       c.weightx = 1.0;
+       c.anchor = GridBagConstraints.LINE_START;
+       upPanel.add(proxyAuthPasswordPB, c);
+       c.gridx++;
+       c.weightx = 0.4;
+       c.anchor = GridBagConstraints.LINE_START;
+       upPanel.add(passwordNotStoredLabel, c);
+       gbc.gridy++;
+       proxyPanel.add(upPanel, gbc);
+     } // end j2sIgnore
+     applyProxyButton.setText(MessageManager.getString("action.apply"));
+     applyProxyButton.addActionListener(new ActionListener()
+     {
+       @Override
+       public void actionPerformed(ActionEvent e)
+       {
+         saveProxySettings();
+         applyProxyButton.setEnabled(false);
+       }
+     });
+     gbc.gridy++;
+     gbc.fill = GridBagConstraints.NONE;
+     gbc.anchor = GridBagConstraints.LINE_END;
+     proxyPanel.add(applyProxyButton, gbc);
  
      return proxyPanel;
    }
  
+   public void proxyAuthPasswordCheckHighlight(boolean enabled)
+   {
+     proxyAuthPasswordCheckHighlight(enabled, false);
+   }
+   public void proxyAuthPasswordCheckHighlight(boolean enabled,
+           boolean grabFocus)
+   {
+     if (enabled && proxyType.isSelected(customProxy.getModel())
+             && proxyAuth.isSelected()
+             && !proxyAuthUsernameTB.getText().isEmpty()
+             && proxyAuthPasswordPB.getDocument().getLength() == 0)
+     {
+       if (grabFocus)
+         proxyAuthPasswordPB.grabFocus();
+       proxyAuthPasswordPB.setBackground(Color.PINK);
+     }
+     else
+     {
+       proxyAuthPasswordPB.setBackground(Color.WHITE);
+     }
+   }
+   public void applyProxyButtonEnabled(boolean enabled)
+   {
+     applyProxyButton.setEnabled(enabled);
+   }
+   public void saveProxySettings()
+   {
+     // overridden in Preferences
+   }
+   private String displayUserHostPort(String user, String host, String port)
+   {
+     boolean hostBlank = (host == null || host.isEmpty());
+     boolean portBlank = (port == null || port.isEmpty());
+     if (hostBlank && portBlank)
+     {
+       return MessageManager.getString("label.none");
+     }
+     StringBuilder sb = new StringBuilder();
+     if (user != null)
+     {
+       sb.append(user.isEmpty() || user.indexOf(" ") > -1 ? '"' + user + '"'
+               : user);
+       sb.append("@");
+     }
+     sb.append(hostBlank ? "" : host);
+     if (!portBlank)
+     {
+       sb.append(":");
+       sb.append(port);
+     }
+     return sb.toString();
+   }
    /**
     * Initialises the checkboxes in the Connections tab
     */
      protColourLabel.setHorizontalAlignment(SwingConstants.LEFT);
      protColourLabel.setText(
              MessageManager.getString("label.prot_alignment_colour") + " ");
 -    JvSwingUtils.addtoLayout(coloursTab,
 +    GPreferences.addtoLayout(coloursTab,
              MessageManager
                      .getString("label.default_colour_scheme_for_alignment"),
              protColourLabel, protColour);
      nucColourLabel.setHorizontalAlignment(SwingConstants.LEFT);
      nucColourLabel.setText(
              MessageManager.getString("label.nuc_alignment_colour") + " ");
 -    JvSwingUtils.addtoLayout(coloursTab,
 +    GPreferences.addtoLayout(coloursTab,
              MessageManager
                      .getString("label.default_colour_scheme_for_alignment"),
              nucColourLabel, nucColour);
      annotationShding.setBorder(new TitledBorder(
              MessageManager.getString("label.annotation_shading_default")));
      annotationShding.setLayout(new GridLayout(1, 2));
 -    JvSwingUtils.addtoLayout(annotationShding,
 +    GPreferences.addtoLayout(annotationShding,
              MessageManager.getString(
                      "label.default_minimum_colour_annotation_shading"),
              mincolourLabel, minColour);
 -    JvSwingUtils.addtoLayout(annotationShding,
 +    GPreferences.addtoLayout(annotationShding,
              MessageManager.getString(
                      "label.default_maximum_colour_annotation_shading"),
              maxcolourLabel, maxColour);
      structureTab.setBorder(new TitledBorder(
              MessageManager.getString("label.structure_options")));
      structureTab.setLayout(null);
-     final int width = 400;
+     final int width = 420;
      final int height = 22;
      final int lineSpacing = 25;
      int ypos = 15;
        {
          boolean selected = structFromPdb.isSelected();
          // enable other options only when the first is checked
-         useRnaView.setEnabled(selected);
          addSecondaryStructure.setEnabled(selected);
          addTempFactor.setEnabled(selected);
        }
      structureTab.add(structFromPdb);
  
      // indent checkboxes that are conditional on the first one
-     ypos += lineSpacing;
-     useRnaView.setFont(LABEL_FONT);
-     useRnaView.setText(MessageManager.getString("label.use_rnaview"));
-     useRnaView.setBounds(new Rectangle(25, ypos, width, height));
-     structureTab.add(useRnaView);
 +
      ypos += lineSpacing;
      addSecondaryStructure.setFont(LABEL_FONT);
      addSecondaryStructure
      viewerLabel.setFont(LABEL_FONT);
      viewerLabel.setHorizontalAlignment(SwingConstants.LEFT);
      viewerLabel.setText(MessageManager.getString("label.structure_viewer"));
-     viewerLabel.setBounds(new Rectangle(10, ypos, 200, height));
+     viewerLabel.setBounds(new Rectangle(10, ypos, 220, height));
      structureTab.add(viewerLabel);
  
-     if (!Platform.isJS())
+     /*
+      * add all external viewers as options here - check 
+      * when selected whether the program is installed
+      */
+     structViewer.setFont(LABEL_FONT);
+     structViewer.setBounds(new Rectangle(190, ypos, 120, height));
+     structViewer.addItem(ViewerType.JMOL.name());
+     structViewer.addItem(ViewerType.CHIMERA.name());
+     structViewer.addItem(ViewerType.CHIMERAX.name());
+     structViewer.addItem(ViewerType.PYMOL.name());
+     structViewer.addActionListener(new ActionListener()
      {
-       structViewer.setFont(LABEL_FONT);
-       structViewer.setBounds(new Rectangle(160, ypos, 120, height));
-       structViewer.addItem(ViewerType.JMOL.name());
-       structViewer.addItem(ViewerType.CHIMERA.name());
-       structViewer.addActionListener(new ActionListener()
+       @Override
+       public void actionPerformed(ActionEvent e)
        {
-         @Override
-         public void actionPerformed(ActionEvent e)
-         {
-           structureViewer_actionPerformed(
-                   (String) structViewer.getSelectedItem());
-         }
-       });
-       structureTab.add(structViewer);
-     }
+         structureViewer_actionPerformed(
+                 (String) structViewer.getSelectedItem());
+       }
+     });
+     structureTab.add(structViewer);
  
      ypos += lineSpacing;
-     JLabel pathLabel = new JLabel();
-     pathLabel.setFont(new java.awt.Font("SansSerif", 0, 11));
-     pathLabel.setHorizontalAlignment(SwingConstants.LEFT);
-     pathLabel.setText(MessageManager.getString("label.chimera_path"));
-     pathLabel.setBounds(new Rectangle(10, ypos, 140, height));
-     structureTab.add(pathLabel);
-     chimeraPath.setFont(LABEL_FONT);
-     chimeraPath.setText("");
+     structureViewerPathLabel = new JLabel();
+     structureViewerPathLabel.setFont(LABEL_FONT);// new Font("SansSerif", 0,
+                                                  // 11));
+     structureViewerPathLabel.setHorizontalAlignment(SwingConstants.LEFT);
+     structureViewerPathLabel.setText(MessageManager
+             .formatMessage("label.viewer_path", "Chimera(X)"));
+     structureViewerPathLabel
+             .setBounds(new Rectangle(10, ypos, 170, height));
+     structureViewerPathLabel.setEnabled(false);
+     structureTab.add(structureViewerPathLabel);
+     structureViewerPath.setFont(LABEL_FONT);
+     structureViewerPath.setText("");
+     structureViewerPath.setEnabled(false);
      final String tooltip = JvSwingUtils.wrapTooltip(true,
-             MessageManager.getString("label.chimera_path_tip"));
-     chimeraPath.setToolTipText(tooltip);
-     chimeraPath.setBounds(new Rectangle(160, ypos, 300, height));
-     chimeraPath.addMouseListener(new MouseAdapter()
+             MessageManager.getString("label.viewer_path_tip"));
+     structureViewerPath.setToolTipText(tooltip);
+     structureViewerPath.setBounds(new Rectangle(190, ypos, 290, height));
+     structureViewerPath.addMouseListener(new MouseAdapter()
      {
        @Override
        public void mouseClicked(MouseEvent e)
        {
-         if (e.getClickCount() == 2)
+         if (structureViewerPath.isEnabled() && e.getClickCount() == 2)
          {
-           String chosen = openFileChooser(false);
++          structureViewer_actionPerformed(
++                  (String) structViewer.getSelectedItem());
++        }
++      });
++      structureTab.add(structViewer);
++      ypos += lineSpacing;
++      structureViewerPathLabel = new JLabel();
++      structureViewerPathLabel.setFont(LABEL_FONT);// new Font("SansSerif", 0,
++                                                   // 11));
++      structureViewerPathLabel.setHorizontalAlignment(SwingConstants.LEFT);
++      structureViewerPathLabel.setText(MessageManager
++              .formatMessage("label.viewer_path", "Chimera(X)"));
++      structureViewerPathLabel
++              .setBounds(new Rectangle(10, ypos, 170, height));
++      structureViewerPathLabel.setEnabled(false);
++      structureTab.add(structureViewerPathLabel);
++ 
++      structureViewerPath.setFont(LABEL_FONT);
++      structureViewerPath.setText("");
++      structureViewerPath.setEnabled(false);
++      final String tooltip = JvSwingUtils.wrapTooltip(true,
++              MessageManager.getString("label.viewer_path_tip"));
++      structureViewerPath.setToolTipText(tooltip);
++      structureViewerPath.setBounds(new Rectangle(190, ypos, 290, height));
++      structureViewerPath.addMouseListener(new MouseAdapter()
++      {
++        if (structureViewerPath.isEnabled() && e.getClickCount() == 2)
++        {
+           String chosen = openFileChooser();
            if (chosen != null)
            {
-             chimeraPath.setText(chosen);
+             structureViewerPath.setText(chosen);
            }
          }
        }
      });
-     structureTab.add(chimeraPath);
+     structureTab.add(structureViewerPath);
  
      ypos += lineSpacing;
      nwMapping.setFont(LABEL_FONT);
              MessageManager.getString("label.mapping_method"));
      mmTitledBorder.setTitleFont(LABEL_FONT);
      mappingPanel.setBorder(mmTitledBorder);
-     mappingPanel.setBounds(new Rectangle(10, ypos, 452, 45));
+     mappingPanel.setBounds(new Rectangle(10, ypos, 472, 45));
      // GridLayout mappingLayout = new GridLayout();
      mappingPanel.setLayout(new GridLayout());
      mappingPanel.add(nwMapping);
      ypos += lineSpacing;
      FTSDataColumnPreferences docFieldPref = new FTSDataColumnPreferences(
              PreferenceSource.PREFERENCES, PDBFTSRestClient.getInstance());
-     docFieldPref.setBounds(new Rectangle(10, ypos, 450, 120));
+     docFieldPref.setBounds(new Rectangle(10, ypos, 470, 120));
      structureTab.add(docFieldPref);
  
      /*
       */
      if (Platform.isJS())
      {
-       pathLabel.setVisible(false);
-       chimeraPath.setVisible(false);
+       structureViewerPathLabel.setVisible(false);
+       structureViewerPath.setVisible(false);
        viewerLabel.setVisible(false);
        structViewer.setVisible(false);
      }
--
      return structureTab;
    }
  
    }
  
    /**
-    * Show a dialog for the user to choose a file. Returns the chosen path, or
-    * null on Cancel.
+    * Show a dialog for the user to choose a file. Returns the chosen path, or null
+    * on Cancel.
     * 
     * @return
     */
 -  protected String openFileChooser()
 +  protected String openFileChooser(boolean forFolder)
    {
      String choice = null;
      JFileChooser chooser = new JFileChooser();
+     // Enable appBundleIsTraversable in macOS FileChooser to allow selecting
+     // hidden executables within .app dirs
+     if (Platform.isMac())
+     {
+       chooser.putClientProperty("JFileChooser.appBundleIsTraversable",
+               true);
+     }
 +    if (forFolder)
 +    {
 +      chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
 +    }
 +
      // chooser.setFileView(new JalviewFileView());
      chooser.setDialogTitle(
              MessageManager.getString("label.open_local_file"));
      return choice;
    }
  
+   /**
+    * Validate the structure tab preferences; if invalid, set focus on this tab.
+    * 
+    * @param e
+    */
+   protected boolean validateStructure(FocusEvent e)
+   {
+     if (!validateStructure())
+     {
+       e.getComponent().requestFocusInWindow();
+       return false;
+     }
+     return true;
+   }
    protected boolean validateStructure()
    {
      return false;
      visualTab.add(fontNameCB);
      visualTab.add(fontSizeCB);
      visualTab.add(fontStyleCB);
 -
 +    
      if (Platform.isJS())
      {
        startupCheckbox.setVisible(false);
        startupFileTextfield.setVisible(false);
      }
 -
 +    
      return visualTab;
    }
  
    {
      BackupFilesPresetEntry savedPreset = BackupFilesPresetEntry
              .getSavedBackupEntry();
 -    enableBackupFiles.setSelected(
 -            Cache.getDefault(BackupFiles.ENABLED, !Platform.isJS()));
 +    enableBackupFiles
 +            .setSelected(Cache.getDefault(BackupFiles.ENABLED, !Platform.isJS()));
  
      BackupFilesPresetEntry backupfilesCustomEntry = BackupFilesPresetEntry
              .createBackupFilesPresetEntry(Cache
        }
      });
  
 +
      // enable checkbox 1 col
      gbc.gridwidth = 1;
      gbc.gridheight = 1;
      presetsComboLabel = new JLabel(title + ":");
      presetsPanel.add(presetsComboLabel, gbc);
  
 -    List<Object> entries = Arrays.asList(
 -            (Object[]) BackupFilesPresetEntry.backupfilesPresetEntries);
 +    List<Object> entries = Arrays
 +            .asList((Object[]) BackupFilesPresetEntry.backupfilesPresetEntries);
      List<String> tooltips = Arrays.asList(
              BackupFilesPresetEntry.backupfilesPresetEntryDescriptions);
      backupfilesPresetsCombo = JvSwingUtils.buildComboWithTooltips(entries,
          {
            if (customiseCheckbox.isSelected())
            {
 -            // got here by clicking on customiseCheckbox so don't change the
 -            // values
 +            // got here by clicking on customiseCheckbox so don't change the values
              backupfilesCustomOptionsSetEnabled();
            }
            else
  
    private JPanel initBackupsTabFilenameExamplesPanel()
    {
 -    String title = MessageManager.getString("label.scheme_examples");
 +    String title = MessageManager
 +            .getString("label.scheme_examples");
      TitledBorder tb = new TitledBorder(title);
      exampleFilesPanel.setBorder(tb);
      exampleFilesPanel.setLayout(new GridBagLayout());
  
 +
      backupfilesExampleLabel.setEditable(false);
      backupfilesExampleLabel
              .setBackground(exampleFilesPanel.getBackground());
    }
  
    protected void setComboIntStringKey(
 -          JComboBox<Object> backupfilesPresetsCombo2, int key)
 +          JComboBox<Object> backupfilesPresetsCombo2,
 +          int key)
    {
      for (int i = 0; i < backupfilesPresetsCombo2.getItemCount(); i++)
      {
      boolean ret = false;
      String warningMessage = MessageManager
              .getString("label.warning_confirm_change_reverse");
 -    int confirm = JvOptionPane.showConfirmDialog(Desktop.desktop,
 +    int confirm = JvOptionPane.showConfirmDialog(Desktop.getDesktopPane(),
              warningMessage,
              MessageManager.getString("label.change_increment_decrement"),
              JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE);
  
      JPanel jp = new JPanel();
      jp.setLayout(new FlowLayout());
 -    oldBackupFilesLabel.setText(
 -            MessageManager.getString("label.autodelete_old_backup_files"));
 +    oldBackupFilesLabel
 +            .setText(MessageManager
 +                    .getString("label.autodelete_old_backup_files"));
      oldBackupFilesLabel.setFont(LABEL_FONT);
      oldBackupFilesLabel.setHorizontalAlignment(SwingConstants.LEFT);
      jp.add(oldBackupFilesLabel);
  
      }
  
 -    // add some extra empty lines to pad out the example files box. ugh, please
 -    // tell
 +    // 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)
    private void backupfilesKeepAllSetEnabled(boolean tryEnabled)
    {
      boolean enabled = tryEnabled && enableBackupFiles.isSelected()
 -            && customiseCheckbox.isSelected() && suffixTemplate.getText()
 +            && customiseCheckbox.isSelected()
 +            && suffixTemplate.getText()
                      .indexOf(BackupFiles.NUM_PLACEHOLDER) > -1;
      keepfilesPanel.setEnabled(enabled);
      backupfilesKeepAll.setEnabled(enabled);
     * DOCUMENT ME!
     * 
     * @param e
 -   *            DOCUMENT ME!
 +   *          DOCUMENT ME!
     */
    public void ok_actionPerformed(ActionEvent e)
    {
     * DOCUMENT ME!
     * 
     * @param e
 -   *            DOCUMENT ME!
 +   *          DOCUMENT ME!
     */
    public void cancel_actionPerformed(ActionEvent e)
    {
     * DOCUMENT ME!
     * 
     * @param e
 -   *            DOCUMENT ME!
 +   *          DOCUMENT ME!
     */
    public void annotations_actionPerformed(ActionEvent e)
    {
  
    }
  
-   public void useProxy_actionPerformed()
+   public void setProxyAuthEnabled()
    {
-     boolean enabled = useProxy.isSelected();
+     boolean enabled = proxyAuth.isSelected() && proxyAuth.isEnabled();
+     proxyAuthUsernameLabel.setEnabled(enabled);
+     proxyAuthPasswordLabel.setEnabled(enabled);
+     passwordNotStoredLabel.setEnabled(enabled);
+     proxyAuthUsernameTB.setEnabled(enabled);
+     proxyAuthPasswordPB.setEnabled(enabled);
+   }
+   public void setCustomProxyEnabled()
+   {
+     boolean enabled = customProxy.isSelected();
      portLabel.setEnabled(enabled);
      serverLabel.setEnabled(enabled);
-     proxyServerTB.setEnabled(enabled);
-     proxyPortTB.setEnabled(enabled);
+     portLabel2.setEnabled(enabled);
+     serverLabel2.setEnabled(enabled);
+     httpLabel.setEnabled(enabled);
+     httpsLabel.setEnabled(enabled);
+     proxyServerHttpTB.setEnabled(enabled);
+     proxyPortHttpTB.setEnabled(enabled);
+     proxyServerHttpsTB.setEnabled(enabled);
+     proxyPortHttpsTB.setEnabled(enabled);
+     proxyAuth.setEnabled(enabled);
+     setProxyAuthEnabled();
+   }
+   public void proxyType_actionPerformed()
+   {
+     setCustomProxyEnabled();
+     proxyAuthPasswordCheckHighlight(true);
+     applyProxyButtonEnabled(true);
+   }
+   public void proxyAuth_actionPerformed()
+   {
+     setProxyAuthEnabled();
+     proxyAuthPasswordCheckHighlight(true);
+     applyProxyButtonEnabled(true);
    }
  
    /**
      }
  
    }
 +
 +  protected void validateHmmerPath()
 +  {
 +  }
 +
 +  protected void validateCygwinPath()
 +  {
 +  }
 +
 +  /**
 +   * A helper method to add a panel containing a label and a component to a
 +   * panel
 +   * 
 +   * @param panel
 +   * @param tooltip
 +   * @param label
 +   * @param valBox
 +   */
 +  protected static void addtoLayout(JPanel panel, String tooltip,
 +          JComponent label, JComponent valBox)
 +  {
 +    JPanel laypanel = new JPanel(new GridLayout(1, 2));
 +    JPanel labPanel = new JPanel(new BorderLayout());
 +    JPanel valPanel = new JPanel();
 +    labPanel.setBounds(new Rectangle(7, 7, 158, 23));
 +    valPanel.setBounds(new Rectangle(172, 7, 270, 23));
 +    labPanel.add(label, BorderLayout.WEST);
 +    valPanel.add(valBox);
 +    laypanel.add(labPanel);
 +    laypanel.add(valPanel);
 +    valPanel.setToolTipText(tooltip);
 +    labPanel.setToolTipText(tooltip);
 +    valBox.setToolTipText(tooltip);
 +    panel.add(laypanel);
 +    panel.validate();
 +  }
  }
 +
   */
  package jalview.jbgui;
  
 +import jalview.api.structures.JalviewStructureDisplayI;
 +import jalview.gui.ColourMenuHelper.ColourChangeListener;
 +import jalview.util.ImageMaker.TYPE;
 +import jalview.util.MessageManager;
 +import jalview.util.Platform;
 +
  import java.awt.BorderLayout;
  import java.awt.GridLayout;
  import java.awt.event.ActionEvent;
@@@ -39,6 -33,11 +39,6 @@@ import javax.swing.JMenuItem
  import javax.swing.JPanel;
  import javax.swing.JRadioButtonMenuItem;
  
 -import jalview.api.structures.JalviewStructureDisplayI;
 -import jalview.gui.ColourMenuHelper.ColourChangeListener;
 -import jalview.util.ImageMaker.TYPE;
 -import jalview.util.MessageManager;
 -
  @SuppressWarnings("serial")
  public abstract class GStructureViewer extends JInternalFrame
          implements JalviewStructureDisplayI, ColourChangeListener
@@@ -90,7 -89,7 +90,7 @@@
    private void jbInit() throws Exception
    {
  
 -    setName("jalview-structureviewer");
 +    setName(Platform.getAppID("structureviewer"));
  
      JMenuBar menuBar = new JMenuBar();
      this.setJMenuBar(menuBar);
        @Override
        public void actionPerformed(ActionEvent actionEvent)
        {
-         pdbFile_actionPerformed(actionEvent);
+         pdbFile_actionPerformed();
        }
      });
  
        @Override
        public void actionPerformed(ActionEvent actionEvent)
        {
-         viewMapping_actionPerformed(actionEvent);
+         viewMapping_actionPerformed();
        }
      });
  
      JMenu helpMenu = new JMenu();
      helpMenu.setText(MessageManager.getString("action.help"));
      helpItem = new JMenuItem();
 +    helpItem.setText(MessageManager.getString("label.jmol_help"));
      helpItem.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent actionEvent)
        {
-         showHelp_actionPerformed(actionEvent);
+         showHelp_actionPerformed();
        }
      });
      alignStructs = new JMenuItem();
        @Override
        public void actionPerformed(ActionEvent actionEvent)
        {
-         alignStructs_actionPerformed(actionEvent);
+         alignStructsWithAllAlignPanels();
        }
      });
  
  
    protected void fitToWindow_actionPerformed()
    {
+     getBinding().focusView();
    }
  
    protected void highlightSelection_actionPerformed()
    {
    }
  
-   protected void viewerColour_actionPerformed(ActionEvent actionEvent)
+   protected void viewerColour_actionPerformed()
    {
    }
  
-   protected abstract String alignStructs_actionPerformed(
-           ActionEvent actionEvent);
+   protected abstract String alignStructsWithAllAlignPanels();
  
-   public void pdbFile_actionPerformed(ActionEvent actionEvent)
+   public void pdbFile_actionPerformed()
    {
  
    }
  
    }
  
-   public void viewMapping_actionPerformed(ActionEvent actionEvent)
+   public void viewMapping_actionPerformed()
    {
  
    }
  
-   public void seqColour_actionPerformed(ActionEvent actionEvent)
+   public void seqColour_actionPerformed()
    {
  
    }
  
-   public void chainColour_actionPerformed(ActionEvent actionEvent)
+   public void chainColour_actionPerformed()
    {
  
    }
  
-   public void chargeColour_actionPerformed(ActionEvent actionEvent)
+   public void chargeColour_actionPerformed()
    {
  
    }
  
-   public void background_actionPerformed(ActionEvent actionEvent)
+   public void background_actionPerformed()
    {
  
    }
  
-   public void showHelp_actionPerformed(ActionEvent actionEvent)
+   public void showHelp_actionPerformed()
    {
  
    }
   */
  package jalview.project;
  
 -import java.util.Locale;
 -
  import static jalview.math.RotatableMatrix.Axis.X;
  import static jalview.math.RotatableMatrix.Axis.Y;
  import static jalview.math.RotatableMatrix.Axis.Z;
  
 -import java.awt.Color;
 -import java.awt.Font;
 -import java.awt.Rectangle;
 -import java.io.BufferedReader;
 -import java.io.ByteArrayInputStream;
 -import java.io.File;
 -import java.io.FileInputStream;
 -import java.io.FileOutputStream;
 -import java.io.IOException;
 -import java.io.InputStream;
 -import java.io.InputStreamReader;
 -import java.io.OutputStream;
 -import java.io.OutputStreamWriter;
 -import java.io.PrintWriter;
 -import java.lang.reflect.InvocationTargetException;
 -import java.math.BigInteger;
 -import java.net.MalformedURLException;
 -import java.net.URL;
 -import java.util.ArrayList;
 -import java.util.Arrays;
 -import java.util.Collections;
 -import java.util.Enumeration;
 -import java.util.GregorianCalendar;
 -import java.util.HashMap;
 -import java.util.HashSet;
 -import java.util.Hashtable;
 -import java.util.IdentityHashMap;
 -import java.util.Iterator;
 -import java.util.LinkedHashMap;
 -import java.util.List;
 -import java.util.Map;
 -import java.util.Map.Entry;
 -import java.util.Set;
 -import java.util.Vector;
 -import java.util.jar.JarEntry;
 -import java.util.jar.JarInputStream;
 -import java.util.jar.JarOutputStream;
 -
 -import javax.swing.JInternalFrame;
 -import javax.swing.SwingUtilities;
 -import javax.xml.bind.JAXBContext;
 -import javax.xml.bind.JAXBElement;
 -import javax.xml.bind.Marshaller;
 -import javax.xml.datatype.DatatypeConfigurationException;
 -import javax.xml.datatype.DatatypeFactory;
 -import javax.xml.datatype.XMLGregorianCalendar;
 -import javax.xml.stream.XMLInputFactory;
 -import javax.xml.stream.XMLStreamReader;
 -
  import jalview.analysis.Conservation;
  import jalview.analysis.PCA;
  import jalview.analysis.scoremodels.ScoreModels;
  import jalview.analysis.scoremodels.SimilarityParams;
 +import jalview.api.AlignmentViewPanel;
  import jalview.api.FeatureColourI;
  import jalview.api.ViewStyleI;
  import jalview.api.analysis.ScoreModelI;
@@@ -42,7 -92,6 +42,7 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.GeneLocus;
  import jalview.datamodel.GraphLine;
 +import jalview.datamodel.HiddenMarkovModel;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.Point;
  import jalview.datamodel.RnaViewerModel;
@@@ -60,9 -109,7 +60,9 @@@ import jalview.gui.AlignFrame
  import jalview.gui.AlignViewport;
  import jalview.gui.AlignmentPanel;
  import jalview.gui.AppVarna;
 +import jalview.gui.ChimeraViewFrame;
  import jalview.gui.Desktop;
 +import jalview.gui.FeatureRenderer;
  import jalview.gui.JvOptionPane;
  import jalview.gui.OOMWarning;
  import jalview.gui.PCAPanel;
@@@ -75,7 -122,6 +75,7 @@@ import jalview.gui.TreePanel
  import jalview.io.BackupFiles;
  import jalview.io.DataSourceType;
  import jalview.io.FileFormat;
 +import jalview.io.HMMFile;
  import jalview.io.NewickFile;
  import jalview.math.Matrix;
  import jalview.math.MatrixI;
@@@ -86,8 -132,10 +86,10 @@@ import jalview.schemes.ColourSchemeProp
  import jalview.schemes.FeatureColour;
  import jalview.schemes.ResidueProperties;
  import jalview.schemes.UserColourScheme;
+ import jalview.structure.StructureSelectionManager;
  import jalview.structures.models.AAStructureBindingModel;
  import jalview.util.Format;
+ import jalview.util.HttpUtils;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
  import jalview.util.StringUtils;
@@@ -99,9 -147,9 +101,9 @@@ import jalview.viewmodel.ViewportRanges
  import jalview.viewmodel.seqfeatures.FeatureRendererModel;
  import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
  import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
 -import jalview.ws.jws2.Jws2Discoverer;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.jws2.PreferredServiceRegistry;
  import jalview.ws.jws2.dm.AAConSettings;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
  import jalview.ws.params.ArgumentI;
  import jalview.ws.params.AutoCalcSetting;
  import jalview.ws.params.WsParamSetI;
@@@ -153,56 -201,6 +155,56 @@@ import jalview.xml.binding.jalview.Sequ
  import jalview.xml.binding.jalview.ThresholdType;
  import jalview.xml.binding.jalview.VAMSAS;
  
 +import java.awt.Color;
 +import java.awt.Dimension;
 +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;
 +import java.io.FileInputStream;
 +import java.io.FileOutputStream;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.InputStreamReader;
 +import java.io.OutputStreamWriter;
 +import java.io.PrintWriter;
 +import java.math.BigInteger;
 +import java.net.MalformedURLException;
 +import java.net.URL;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collections;
 +import java.util.Enumeration;
 +import java.util.GregorianCalendar;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.Hashtable;
 +import java.util.IdentityHashMap;
 +import java.util.Iterator;
 +import java.util.LinkedHashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Map.Entry;
 +import java.util.Set;
 +import java.util.Vector;
 +import java.util.jar.JarEntry;
 +import java.util.jar.JarInputStream;
 +import java.util.jar.JarOutputStream;
 +
 +import javax.swing.JInternalFrame;
 +import javax.swing.SwingUtilities;
 +import javax.xml.bind.JAXBContext;
 +import javax.xml.bind.JAXBElement;
 +import javax.xml.bind.Marshaller;
 +import javax.xml.datatype.DatatypeConfigurationException;
 +import javax.xml.datatype.DatatypeFactory;
 +import javax.xml.datatype.XMLGregorianCalendar;
 +import javax.xml.stream.XMLInputFactory;
 +import javax.xml.stream.XMLStreamReader;
 +
  /**
   * Write out the current jalview desktop state as a Jalview XML stream.
   * 
@@@ -228,8 -226,6 +230,7 @@@ public class Jalview2XM
  
    private static final String RNA_PREFIX = "rna_";
  
 +  private static final String HMMER_PREFIX = "hmmer_";
    private static final String UTF_8 = "UTF-8";
  
    /**
    private Map<RnaModel, String> rnaSessions = new HashMap<>();
  
    /**
 +   * contains last error message (if any) encountered by XML loader.
 +   */
 +  String errorMessage = null;
 +
 +  /**
 +   * flag to control whether the Jalview2XML_V1 parser should be deferred to if
 +   * exceptions are raised during project XML parsing
 +   */
 +  public boolean attemptversion1parse = false;
 +
 +  /*
 +   * JalviewJS only -- to allow read file bytes to be saved in the
 +   * created AlignFrame, allowing File | Reload of a project file to work
 +   * 
 +   * BH 2019 JAL-3436
 +   */
 +  private File jarFile;
 +
 +  /**
     * A helper method for safely using the value of an optional attribute that
     * may be null if not present in the XML. Answers the boolean value, or false
     * if null.
     * @param _jmap
     * @return
     */
 -  public SeqFref newMappingRef(final String sref,
 +  protected SeqFref newMappingRef(final String sref,
            final jalview.datamodel.Mapping _jmap)
    {
      SeqFref fref = new SeqFref(sref, "Mapping")
      return fref;
    }
  
 -  public SeqFref newAlcodMapRef(final String sref,
 +  protected SeqFref newAlcodMapRef(final String sref,
            final AlignedCodonFrame _cf,
            final jalview.datamodel.Mapping _jmap)
    {
      return fref;
    }
  
 -  public void resolveFrefedSequences()
 +  protected void resolveFrefedSequences()
    {
      Iterator<SeqFref> nextFref = frefedSequence.iterator();
      int toresolve = frefedSequence.size();
     * core method for storing state for a set of AlignFrames.
     * 
     * @param frames
 -   *          - frames involving all data to be exported (including containing
 -   *          splitframes)
 +   *          - frames involving all data to be exported (including those
 +   *          contained in splitframes, though not the split frames themselves)
     * @param jout
     *          - project output stream
     */
    private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
    {
      Hashtable<String, AlignFrame> dsses = new Hashtable<>();
  
      /*
        for (int i = frames.size() - 1; i > -1; i--)
        {
          AlignFrame af = frames.get(i);
 +        AlignViewport vp = af.getViewport();
          // skip ?
          if (skipList != null && skipList
 -                .containsKey(af.getViewport().getSequenceSetId()))
 +                .containsKey(vp.getSequenceSetId()))
          {
            continue;
          }
  
          String shortName = makeFilename(af, shortNames);
  
 -        int apSize = af.getAlignPanels().size();
 +        AlignmentI alignment = vp.getAlignment();
 +        List<? extends AlignmentViewPanel> panels = af.getAlignPanels();
 +        int apSize = panels.size();
          for (int ap = 0; ap < apSize; ap++)
 -        {
 -          AlignmentPanel apanel = (AlignmentPanel) af.getAlignPanels()
 -                  .get(ap);
 +          {
 +          AlignmentPanel apanel = (AlignmentPanel) panels.get(ap);
            String fileName = apSize == 1 ? shortName : ap + shortName;
            if (!fileName.endsWith(".xml"))
            {
            }
  
            saveState(apanel, fileName, jout, viewIds);
 -          String dssid = getDatasetIdRef(
 -                  af.getViewport().getAlignment().getDataset());
 +        }
 +        if (apSize > 0)
 +        {
 +          // BH moved next bit out of inner loop, not that it really matters.
 +          // so we are testing to make sure we actually have an alignment,
 +          // apparently.
 +          String dssid = getDatasetIdRef(alignment.getDataset());
            if (!dsses.containsKey(dssid))
            {
 +            // We have not already covered this data by reference from another
 +            // frame.
              dsses.put(dssid, af);
            }
          }
        // create backupfiles object and get new temp filename destination
        boolean doBackup = BackupFiles.getEnabled();
        BackupFiles backupfiles = doBackup ? new BackupFiles(jarFile) : null;
 -      FileOutputStream fos = new FileOutputStream(
 -              doBackup ? backupfiles.getTempFilePath() : jarFile);
 +      FileOutputStream fos = new FileOutputStream(doBackup ? 
 +              backupfiles.getTempFilePath() : jarFile);
  
        JarOutputStream jout = new JarOutputStream(fos);
        List<AlignFrame> frames = new ArrayList<>();
      }
    }
  
 +  /**
 +   * Each AlignFrame has a single data set associated with it. Note that none of
 +   * these frames are split frames, because Desktop.getAlignFrames() collects
 +   * top and bottom separately here.
 +   * 
 +   * @param dsses
 +   * @param fileName
 +   * @param jout
 +   */
    private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
            String fileName, JarOutputStream jout)
    {
  
 +    // Note that in saveAllFrames we have associated each specific dataset to
 +    // ONE of its associated frames.
      for (String dssids : dsses.keySet())
      {
        AlignFrame _af = dsses.get(dssids);
     * @param out
     *          jar entry name
     */
 -  public JalviewModel saveState(AlignmentPanel ap, String fileName,
 +  protected JalviewModel saveState(AlignmentPanel ap, String fileName,
            JarOutputStream jout, List<String> viewIds)
    {
      return saveState(ap, fileName, false, jout, viewIds);
     * @param out
     *          jar entry name
     */
 -  public JalviewModel saveState(AlignmentPanel ap, String fileName,
 +  protected JalviewModel saveState(AlignmentPanel ap, String fileName,
            boolean storeDS, JarOutputStream jout, List<String> viewIds)
    {
      if (viewIds == null)
          else
          {
            vamsasSeq = createVamsasSequence(id, jds);
 -          // vamsasSet.addSequence(vamsasSeq);
 +//          vamsasSet.addSequence(vamsasSeq);
            vamsasSet.getSequence().add(vamsasSeq);
            vamsasSetIds.put(id, vamsasSeq);
            seqRefIds.put(id, jds);
          jseq.getFeatures().add(features);
        }
  
 +      /*
 +       * save PDB entries for sequence
 +       */
        if (jdatasq.getAllPDBEntries() != null)
        {
          Enumeration<PDBEntry> en = jdatasq.getAllPDBEntries().elements();
             * only view *should* be coped with sensibly.
             */
            // This must have been loaded, is it still visible?
 -          JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 +          JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
            String matchedFile = null;
            for (int f = frames.length - 1; f > -1; f--)
            {
              if (frames[f] instanceof StructureViewerBase)
              {
                StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
-               matchedFile = saveStructureState(ap, jds, pdb, entry, viewIds,
-                       matchedFile, viewFrame);
+               matchedFile = saveStructureViewer(ap, jds, pdb, entry,
+                       viewIds, matchedFile, viewFrame);
                /*
                 * Only store each structure viewer's state once in the project
                 * jar. First time through only (storeDS==false)
                 */
                String viewId = viewFrame.getViewId();
+               String viewerType = viewFrame.getViewerType().toString();
                if (!storeDS && !viewIds.contains(viewId))
                {
                  viewIds.add(viewId);
-                 try
+                 File viewerState = viewFrame.saveSession();
+                 if (viewerState != null)
                  {
-                   String viewerState = viewFrame.getStateInfo();
-                   writeJarEntry(jout, getViewerJarEntryName(viewId),
-                           viewerState.getBytes());
-                 } catch (IOException e)
+                   copyFileToJar(jout, viewerState.getPath(),
+                           getViewerJarEntryName(viewId), viewerType);
+                 }
+                 else
                  {
-                   System.err.println(
-                           "Error saving viewer state: " + e.getMessage());
+                   Cache.log.error(
+                           "Failed to save viewer state for " + viewerType);
                  }
                }
              }
              if (!pdbfiles.contains(pdbId))
              {
                pdbfiles.add(pdbId);
-               copyFileToJar(jout, matchedFile, pdbId);
+               copyFileToJar(jout, matchedFile, pdbId, pdbId);
              }
            }
  
  
        saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
  
 +      if (jds.hasHMMProfile())
 +      {
 +        saveHmmerProfile(jout, jseq, jds);
 +      }
        // jms.addJSeq(jseq);
        object.getJSeq().add(jseq);
      }
      {
        // FIND ANY ASSOCIATED TREES
        // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
 -      if (Desktop.desktop != null)
 +      if (Desktop.getDesktopPane() != null)
        {
 -        JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 +        JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
  
          for (int t = 0; t < frames.length; t++)
          {
      /*
       * save PCA viewers
       */
 -    if (!storeDS && Desktop.desktop != null)
 +    if (!storeDS && Desktop.getDesktopPane() != null)
      {
 -      for (JInternalFrame frame : Desktop.desktop.getAllFrames())
 +      for (JInternalFrame frame : Desktop.getDesktopPane().getAllFrames())
        {
          if (frame instanceof PCAPanel)
          {
  
              if (colourScheme instanceof jalview.schemes.UserColourScheme)
              {
 -              jGroup.setColour(setUserColourScheme(colourScheme,
 -                      userColours, object));
 +              jGroup.setColour(
 +                      setUserColourScheme(colourScheme, userColours,
 +                              object));
              }
              else
              {
          }
        }
  
 -      // jms.setJGroup(groups);
 +      //jms.setJGroup(groups);
        Object group;
        for (JGroup grp : groups)
        {
               * save any filter for the feature type
               */
              FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
 -            if (filter != null)
 -            {
 -              Iterator<FeatureMatcherI> filters = filter.getMatchers()
 -                      .iterator();
 +            if (filter != null)  {
 +              Iterator<FeatureMatcherI> filters = filter.getMatchers().iterator();
                FeatureMatcherI firstFilter = filters.next();
 -              setting.setMatcherSet(Jalview2XML.marshalFilter(firstFilter,
 -                      filters, filter.isAnded()));
 +              setting.setMatcherSet(Jalview2XML.marshalFilter(
 +                      firstFilter, filters, filter.isAnded()));
              }
  
              /*
  
              setting.setDisplay(
                      av.getFeaturesDisplayed().isVisible(featureType));
 -            float rorder = fr.getOrder(featureType);
 +            float rorder = fr
 +                    .getOrder(featureType);
              if (rorder > -1)
              {
                setting.setOrder(rorder);
            Group g = new Group();
            g.setName(grp);
            g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false))
 -                  .booleanValue());
 +                          .booleanValue());
            // fs.addGroup(g);
            fs.getGroup().add(g);
            groupsAdded.addElement(grp);
        // using save and then load
        try
        {
-       fileName = fileName.replace('\\', '/');
+         fileName = fileName.replace('\\', '/');
          System.out.println("Writing jar entry " + fileName);
          JarEntry entry = new JarEntry(fileName);
          jout.putNextEntry(entry);
      }
      return object;
    }
 +  /**
 +   * Saves the HMMER profile associated with the sequence as a file in the jar,
 +   * in HMMER format, and saves the name of the file as a child element of the
 +   * XML sequence element
 +   * 
 +   * @param jout
 +   * @param xmlSeq
 +   * @param seq
 +   */
 +  protected void saveHmmerProfile(JarOutputStream jout, JSeq xmlSeq,
 +          SequenceI seq)
 +  {
 +    HiddenMarkovModel profile = seq.getHMM();
 +    if (profile == null)
 +    {
 +      warn("Want to save HMM profile for " + seq.getName()
 +              + " but none found");
 +      return;
 +    }
 +    HMMFile hmmFile = new HMMFile(profile);
 +    String hmmAsString = hmmFile.print();
 +    String jarEntryName = HMMER_PREFIX + nextCounter();
 +    try
 +    {
 +      writeJarEntry(jout, jarEntryName, hmmAsString.getBytes());
 +      xmlSeq.setHmmerProfile(jarEntryName);
 +    } catch (IOException e)
 +    {
 +      warn("Error saving HMM profile: " + e.getMessage());
 +    }
 +  }
  
 +    
    /**
     * Writes PCA viewer attributes and computed values to an XML model object and
     * adds it to the JalviewModel. Any exceptions are reported by logging.
            final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
            boolean storeDataset)
    {
 -    if (Desktop.desktop == null)
 +    if (Desktop.getDesktopPane() == null)
      {
        return;
      }
 -    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 +    JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
      for (int f = frames.length - 1; f > -1; f--)
      {
        if (frames[f] instanceof AppVarna)
  
                  String varnaStateFile = varna.getStateInfo(model.rna);
                  jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
-                 copyFileToJar(jout, varnaStateFile, jarEntryName);
+                 copyFileToJar(jout, varnaStateFile, jarEntryName, "Varna");
                  rnaSessions.put(model, jarEntryName);
                }
                SecondaryStructure ss = new SecondaryStructure();
     * @param jout
     * @param infilePath
     * @param jarEntryName
+    * @param msg
+    *          additional identifying info to log to the console
     */
    protected void copyFileToJar(JarOutputStream jout, String infilePath,
-           String jarEntryName)
+           String jarEntryName, String msg)
    {
-     DataInputStream dis = null;
-     try
+     try (InputStream is = new FileInputStream(infilePath))
      {
        File file = new File(infilePath);
        if (file.exists() && jout != null)
        {
-         dis = new DataInputStream(new FileInputStream(file));
-         byte[] data = new byte[(int) file.length()];
-         dis.readFully(data);
-         writeJarEntry(jout, jarEntryName, data);
+         System.out.println(
+                 "Writing jar entry " + jarEntryName + " (" + msg + ")");
+         jout.putNextEntry(new JarEntry(jarEntryName));
+         copyAll(is, jout);
+         jout.closeEntry();
+         // dis = new DataInputStream(new FileInputStream(file));
+         // byte[] data = new byte[(int) file.length()];
+         // dis.readFully(data);
+         // writeJarEntry(jout, jarEntryName, data);
        }
      } catch (Exception ex)
      {
        ex.printStackTrace();
-     } finally
-     {
-       if (dis != null)
-       {
-         try
-         {
-           dis.close();
-         } catch (IOException e)
-         {
-           // ignore
-         }
-       }
      }
    }
  
    /**
-    * Write the data to a new entry of given name in the output jar file
+    * Copies input to output, in 4K buffers; handles any data (text or binary)
     * 
-    * @param jout
-    * @param jarEntryName
-    * @param data
+    * @param in
+    * @param out
     * @throws IOException
     */
-   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
-           byte[] data) throws IOException
+   protected void copyAll(InputStream in, OutputStream out)
+           throws IOException
    {
-     if (jout != null)
+     byte[] buffer = new byte[4096];
+     int bytesRead = 0;
+     while ((bytesRead = in.read(buffer)) != -1)
      {
-       jarEntryName = jarEntryName.replace('\\','/');
-       System.out.println("Writing jar entry " + jarEntryName);
-       jout.putNextEntry(new JarEntry(jarEntryName));
-       DataOutputStream dout = new DataOutputStream(jout);
-       dout.write(data, 0, data.length);
-       dout.flush();
-       jout.closeEntry();
+       out.write(buffer, 0, bytesRead);
      }
    }
  
     * @param viewFrame
     * @return
     */
-   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
+   protected String saveStructureViewer(AlignmentPanel ap, SequenceI jds,
            Pdbids pdb, PDBEntry entry, List<String> viewIds,
            String matchedFile, StructureViewerBase viewFrame)
    {
        final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
        final String pdbId = pdbentry.getId();
        if (!pdbId.equals(entry.getId())
-               && !(entry.getId().length() > 4 && entry.getId().toLowerCase()
-                       .startsWith(pdbId.toLowerCase())))
+               && !(entry.getId().length() > 4 && entry.getId().toLowerCase(Locale.ROOT)
+                       .startsWith(pdbId.toLowerCase(Locale.ROOT))))
        {
          /*
           * not interested in a binding to a different PDB entry here
        }
        else if (!matchedFile.equals(pdbentry.getFile()))
        {
-         Cache.log.warn(
-                   "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
-                           + pdbentry.getFile());
+         Cache.log.warn(
+                 "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
+                         + pdbentry.getFile());
        }
        // record the
        // file so we
            final String viewId = viewFrame.getViewId();
            state.setViewId(viewId);
            state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
-           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
+           state.setColourwithAlignPanel(viewFrame.isUsedForColourBy(ap));
            state.setColourByJmol(viewFrame.isColouredByViewer());
            state.setType(viewFrame.getViewerType().toString());
            // pdb.addStructureState(state);
    {
      if (calcIdParam.getVersion().equals("1.0"))
      {
 -      final String[] calcIds = calcIdParam.getServiceURL()
 -              .toArray(new String[0]);
 -      Jws2Instance service = Jws2Discoverer.getDiscoverer()
 +      final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]);
 +      ServiceWithParameters service = PreferredServiceRegistry.getRegistry()
                .getPreferredServiceFor(calcIds);
        if (service != null)
        {
            argList = parmSet.getArguments();
            parmSet = null;
          }
 -        AAConSettings settings = new AAConSettings(
 +        AutoCalcSetting settings = new AAConSettings(
                  calcIdParam.isAutoUpdate(), service, parmSet, argList);
          av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
                  calcIdParam.isNeedsUpdate());
        }
        else
        {
 -        warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
 +        warn("Cannot resolve a service for the parameters used in this project. Try configuring a server in the Web Services preferences tab.");
          return false;
        }
      }
          dbref.setSource(ref.getSource());
          dbref.setVersion(ref.getVersion());
          dbref.setAccessionId(ref.getAccessionId());
+         dbref.setCanonical(ref.isCanonical());
          if (ref instanceof GeneLocus)
          {
            dbref.setLocus(true);
          }
          if (ref.hasMap())
          {
 -          Mapping mp = createVamsasMapping(ref.getMap(), parentseq, jds,
 -                  recurse);
 +          Mapping mp = createVamsasMapping(ref.getMap(), parentseq,
 +                  jds, recurse);
            dbref.setMapping(mp);
          }
          vamsasSeq.getDBRef().add(dbref);
          for (int i = 0; i < colours.length; i++)
          {
            Colour col = new Colour();
-           col.setName(ResidueProperties.aa[i].toLowerCase());
+           col.setName(ResidueProperties.aa[i].toLowerCase(Locale.ROOT));
            col.setRGB(jalview.util.Format.getHexString(colours[i]));
            // jbucs.addColour(col);
            jbucs.getColour().add(col);
      return id;
    }
  
-   jalview.schemes.UserColourScheme getUserColourScheme(
-           JalviewModel jm, String id)
+   jalview.schemes.UserColourScheme getUserColourScheme(JalviewModel jm,
+           String id)
    {
      List<UserColours> uc = jm.getUserColours();
      UserColours colours = null;
- /*
+     /*
      for (int i = 0; i < uc.length; i++)
      {
        if (uc[i].getId().equals(id))
          break;
        }
      }
- */
+     */
      for (UserColours c : uc)
      {
        if (c.getId().equals(id))
        newColours = new java.awt.Color[23];
        for (int i = 0; i < 23; i++)
        {
-         newColours[i] = new java.awt.Color(Integer.parseInt(
-                 colours.getUserColourScheme().getColour().get(i + 24)
-                         .getRGB(),
-                 16));
+         newColours[i] = new java.awt.Color(
+                 Integer.parseInt(colours.getUserColourScheme().getColour()
+                         .get(i + 24).getRGB(), 16));
        }
        ucs.setLowerCaseColours(newColours);
      }
  
      return ucs;
    }
 -
+   /**
+    * contains last error message (if any) encountered by XML loader.
+    */
+   String errorMessage = null;
+   /**
+    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
+    * exceptions are raised during project XML parsing
+    */
+   public boolean attemptversion1parse = false;
  
    /**
     * Load a jalview project archive from a jar file
      {
        try
        {
 -        SwingUtilities.invokeAndWait(new Runnable()
 +// was invokeAndWait
 +        
 +        // BH 2019 -- can't wait
 +        SwingUtilities.invokeLater(new Runnable()
          {
            @Override
            public void run()
          System.err.println("Error loading alignment: " + x.getMessage());
        }
      }
 +    this.jarFile = null;
      return af;
    }
  
 -  @SuppressWarnings("unused")
 +      @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
                : ofile.toString());
        byte[] bytes = Platform.isJS() ? Platform.getFileBytes((File) ofile)
                : null;
 +      if (bytes != null)
 +      {
 +        this.jarFile = (File) ofile;
 +      }
+       URL url = null;
        errorMessage = null;
        uniqueSetSuffix = null;
        seqRefIds = null;
        viewportsAdded.clear();
        frefedSequence = null;
  
-       URL url = file.startsWith("http://") ? new URL(file) : null;
+       if (HttpUtils.startsWithHttpOrHttps(file))
+       {
+         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));
 -          }
 +          InputStream is = bytes != null ? new ByteArrayInputStream(bytes)
 +                  : (url != null ? url.openStream()
 +                          : new FileInputStream(file));
 +          return new JarInputStream(is);
 +        }
 +
 +        @Override
 +        public File getFile()
 +        {
 +          return jarFile;
          }
  
          @Override
      AlignFrame af = null, _af = null;
      IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
      Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
 -    final String file = jprovider.getFilename();
 +    String fileName = jprovider.getFilename();
 +    File file = jprovider.getFile();
 +    List<AlignFrame> alignFrames = new ArrayList<>();
      try
      {
        JarInputStream jin = null;
        JarEntry jarentry = null;
        int entryCount = 1;
  
 +      // Look for all the entry names ending with ".xml"
 +      // This includes all panels and at least one frame.
 +//      Platform.timeCheck(null, Platform.TIME_MARK);
        do
        {
          jin = jprovider.getJarInputStream();
          {
            jarentry = jin.getNextJarEntry();
          }
 -
 -        if (jarentry != null && jarentry.getName().endsWith(".xml"))
 -        {
 +        String name = (jarentry == null ? null : jarentry.getName());
 +
 +//        System.out.println("Jalview2XML opening " + name);
 +        if (name != null && name.endsWith(".xml"))
 +        {
 +          // DataSet for.... is read last.
 +          
 +          
 +          // The question here is what to do with the two
 +          // .xml files in the jvp file.
 +          // Some number of them, "...Dataset for...", will be the
 +          // Only AlignPanels and will have Viewport.
 +          // One or more will be the source data, with the DBRefs.
 +          //
 +          // JVP file writing (above) ensures tha the AlignPanels are written
 +          // first, then all relevant datasets (which are
 +          // Jalview.datamodel.Alignment).
 +          //
 +
 +//          Platform.timeCheck("Jalview2XML JAXB " + name, Platform.TIME_MARK);
            JAXBContext jc = JAXBContext
                    .newInstance("jalview.xml.binding.jalview");
            XMLStreamReader streamReader = XMLInputFactory.newInstance()
                    .createXMLStreamReader(jin);
            javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
 -          JAXBElement<JalviewModel> jbe = um.unmarshal(streamReader,
 -                  JalviewModel.class);
 -          JalviewModel object = jbe.getValue();
 +          JAXBElement<JalviewModel> jbe = um
 +                  .unmarshal(streamReader, JalviewModel.class);
 +          JalviewModel model = jbe.getValue();
  
            if (true) // !skipViewport(object))
            {
 -            _af = loadFromObject(object, file, true, jprovider);
 -            if (_af != null && object.getViewport().size() > 0)
 -            // getJalviewModelSequence().getViewportCount() > 0)
 +            // Q: Do we have to load from the model, even if it
 +            // does not have a viewport, could we discover that early on?
 +            // Q: Do we need to load this object?
 +            _af = loadFromObject(model, fileName, file, true, jprovider);
 +//            Platform.timeCheck("Jalview2XML.loadFromObject",
 +            // Platform.TIME_MARK);
 +
 +            if (_af != null)
              {
 +              alignFrames.add(_af);
 +            }
 +            if (_af != null && model.getViewport().size() > 0)
 +            {
 +
 +              // That is, this is one of the AlignmentPanel models
                if (af == null)
                {
                  // store a reference to the first view
      } catch (IOException ex)
      {
        ex.printStackTrace();
 -      errorMessage = "Couldn't locate Jalview XML file : " + file;
 +      errorMessage = "Couldn't locate Jalview XML file : " + fileName;
        System.err.println(
                "Exception whilst loading jalview XML file : " + ex + "\n");
      } catch (Exception ex)
        {
          // used to attempt to parse as V1 castor-generated xml
        }
 -      if (Desktop.instance != null)
 +      if (Desktop.getInstance() != null)
        {
 -        Desktop.instance.stopLoading();
 +        Desktop.getInstance().stopLoading();
        }
        if (af != null)
        {
        errorMessage = "Out of memory loading jalview XML file";
        System.err.println("Out of memory whilst loading jalview XML file");
        e.printStackTrace();
 +    } finally
 +    {
 +      for (AlignFrame alf : alignFrames)
 +      {
 +        alf.alignPanel.setHoldRepaint(false);
 +      }
      }
  
      /*
       */
      for (AlignFrame fr : gatherToThisFrame.values())
      {
 -      Desktop.instance.gatherViews(fr);
 +      Desktop.getInstance().gatherViews(fr);
      }
  
      restoreSplitFrames();
      {
        if (ds.getCodonFrames() != null)
        {
 -        StructureSelectionManager
 -                .getStructureSelectionManager(Desktop.instance)
 +        Desktop.getStructureSelectionManager()
                  .registerMappings(ds.getCodonFrames());
        }
      }
        reportErrors();
      }
  
 -    if (Desktop.instance != null)
 +    if (Desktop.getInstance() != null)
      {
 -      Desktop.instance.stopLoading();
 +      Desktop.getInstance().stopLoading();
      }
  
      return af;
       */
      for (SplitFrame sf : gatherTo)
      {
 -      Desktop.instance.gatherViews(sf);
 +      Desktop.getInstance().gatherViews(sf);
      }
  
      splitFrameCandidates.clear();
            @Override
            public void run()
            {
 -            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
 +            JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                      finalErrorMessage,
                      "Error " + (saving ? "saving" : "loading")
                              + " Jalview file",
     * @param prefix
     *          a prefix for the temporary file name, must be at least three
     *          characters long
 -   * @param suffixModel
 +   * @param origFile
     *          null or original file - so new file can be given the same suffix
     *          as the old one
     * @return
     */
    protected String copyJarEntry(jarInputStreamProvider jprovider,
 -          String jarEntryName, String prefix, String suffixModel)
 +          String jarEntryName, String prefix, String origFile)
    {
 +    BufferedReader in = null;
 +    PrintWriter out = null;
      String suffix = ".tmp";
 -    if (suffixModel == null)
 +    if (origFile == null)
      {
 -      suffixModel = jarEntryName;
 +      origFile = jarEntryName;
      }
 -    int sfpos = suffixModel.lastIndexOf(".");
 -    if (sfpos > -1 && sfpos < (suffixModel.length() - 1))
 +    int sfpos = origFile.lastIndexOf(".");
 +    if (sfpos > -1 && sfpos < (origFile.length() - 3))
      {
 -      suffix = "." + suffixModel.substring(sfpos + 1);
 +      suffix = "." + origFile.substring(sfpos + 1);
      }
-     try
 -
+     try (JarInputStream jin = jprovider.getJarInputStream())
      {
-       JarInputStream jin = jprovider.getJarInputStream();
-       /*
-        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
-        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
-        * FileInputStream(jprovider)); }
-        */
 +
        JarEntry entry = null;
        do
        {
          entry = jin.getNextJarEntry();
        } while (entry != null && !entry.getName().equals(jarEntryName));
 -
        if (entry != null)
        {
-         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
+         // in = new BufferedReader(new InputStreamReader(jin, UTF_8));
          File outFile = File.createTempFile(prefix, suffix);
          outFile.deleteOnExit();
-         out = new PrintWriter(new FileOutputStream(outFile));
-         String data;
-         while ((data = in.readLine()) != null)
+         try (OutputStream os = new FileOutputStream(outFile))
          {
-           out.println(data);
+           copyAll(jin, os);
          }
-         out.flush();
          String t = outFile.getAbsolutePath();
          return t;
        }
      } catch (Exception ex)
      {
        ex.printStackTrace();
-     } finally
-     {
-       if (in != null)
-       {
-         try
-         {
-           in.close();
-         } catch (IOException e)
-         {
-           // ignore
-         }
-       }
-       if (out != null)
-       {
-         out.close();
-       }
      }
  
      return null;
    }
  
    /**
 -   * Load alignment frame from jalview XML DOM object
 +   * Load alignment frame from jalview XML DOM object. For a DOM object that
 +   * includes one or more Viewport elements (one with a title that does NOT
 +   * contain "Dataset for"), create the frame.
     * 
     * @param jalviewModel
     *          DOM
 -   * @param file
 +   * @param fileName
     *          filename source string
 +   * @param file 
     * @param loadTreesAndStructures
     *          when false only create Viewport
     * @param jprovider
     *          data source provider
     * @return alignment frame created from view stored in DOM
     */
 -  AlignFrame loadFromObject(JalviewModel jalviewModel, String file,
 -          boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
 +  AlignFrame loadFromObject(JalviewModel jalviewModel, String fileName,
 +          File file, boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
    {
 -    SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet()
 -            .get(0);
 +    SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet().get(0);
      List<Sequence> vamsasSeqs = vamsasSet.getSequence();
  
      // JalviewModelSequence jms = object.getJalviewModelSequence();
  
      // Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
            if (tmpSeq.getStart() != jseq.getStart()
                    || tmpSeq.getEnd() != jseq.getEnd())
            {
 -            System.err.println(String.format(
 -                    "Warning JAL-2154 regression: updating start/end for sequence %s from %d/%d to %d/%d",
 -                    tmpSeq.getName(), tmpSeq.getStart(), tmpSeq.getEnd(),
 -                    jseq.getStart(), jseq.getEnd()));
 +            System.err.println(
 +                    String.format("Warning JAL-2154 regression: updating start/end for sequence %s from %d/%d to %d/%d",
 +                            tmpSeq.getName(), tmpSeq.getStart(),
 +                            tmpSeq.getEnd(), jseq.getStart(),
 +                            jseq.getEnd()));
            }
          }
          else
              {
                entry.setProperty(prop.getName(), prop.getValue());
              }
 -            StructureSelectionManager
 -                    .getStructureSelectionManager(Desktop.instance)
 +            Desktop.getStructureSelectionManager()
                      .registerPDBEntry(entry);
              // adds PDBEntry to datasequence's set (since Jalview 2.10)
              if (al.getSequenceAt(i).getDatasetSequence() != null)
              }
            }
          }
 +        /*
 +         * load any HMMER profile
 +         */
 +        // TODO fix this
 +
 +        String hmmJarFile = jseqs.get(i).getHmmerProfile();
 +        if (hmmJarFile != null && jprovider != null)
 +        {
 +          loadHmmerProfile(jprovider, hmmJarFile, al.getSequenceAt(i));
 +        }
        }
      } // end !multipleview
  
                else
                {
                  // defer to later
 -                frefedSequence
 -                        .add(newAlcodMapRef(map.getDnasq(), cf, mapping));
 +                frefedSequence.add(
 +                        newAlcodMapRef(map.getDnasq(), cf, mapping));
                }
              }
            }
              }
            }
          }
 +        // create the new AlignmentAnnotation
          jalview.datamodel.AlignmentAnnotation jaa = null;
  
          if (annotation.isGraph())
            jaa._linecolour = firstColour;
          }
          // register new annotation
 +        // Annotation graphs such as Conservation will not have id.
          if (annotation.getId() != null)
          {
            annotationIds.put(annotation.getId(), jaa);
          jaa.setCalcId(annotation.getCalcId());
          if (annotation.getProperty().size() > 0)
          {
 -          for (Annotation.Property prop : annotation.getProperty())
 +          for (Annotation.Property prop : annotation
 +                  .getProperty())
            {
              jaa.setProperty(prop.getName(), prop.getValue());
            }
          sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved()));
          sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold());
          // attributes with a default in the schema are never null
-           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
-           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
-           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
+         sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
+         sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
+         sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
          sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
          if (jGroup.getConsThreshold() != null
                  && jGroup.getConsThreshold().intValue() != 0)
          if (addAnnotSchemeGroup)
          {
            // reconstruct the annotation colourscheme
-           sg.setColourScheme(constructAnnotationColour(
-                   jGroup.getAnnotationColours(), null, al, jalviewModel, false));
+           sg.setColourScheme(
+                   constructAnnotationColour(jGroup.getAnnotationColours(),
+                           null, al, jalviewModel, false));
          }
        }
      }
      // ///////////////////////////////
      // LOAD VIEWPORT
  
 -    AlignFrame af = null;
 -    AlignViewport av = null;
      // now check to see if we really need to create a new viewport.
      if (multipleView && viewportsAdded.size() == 0)
      {
      boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
              jalviewModel.getVersion());
  
 +    AlignFrame af = null;
      AlignmentPanel ap = null;
 -    boolean isnewview = true;
 +    AlignViewport av = null;
      if (viewId != null)
      {
        // Check to see if this alignment already has a view id == viewId
        {
          for (int v = 0; v < views.length; v++)
          {
 -          if (views[v].av.getViewId().equalsIgnoreCase(viewId))
 +          ap = views[v];
 +          av = ap.av;
 +          if (av.getViewId().equalsIgnoreCase(viewId))
            {
              // recover the existing alignpanel, alignframe, viewport
 -            af = views[v].alignFrame;
 -            av = views[v].av;
 -            ap = views[v];
 +            af = ap.alignFrame;
 +            break;
              // TODO: could even skip resetting view settings if we don't want to
              // change the local settings from other jalview processes
 -            isnewview = false;
            }
          }
        }
      }
  
 -    if (isnewview)
 +    if (af == null)
      {
 -      af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
 +      af = loadViewport(fileName, file, jseqs, hiddenSeqs, al, jalviewModel, view,
                uniqueSeqSetId, viewId, autoAlan);
        av = af.getViewport();
 +      // note that this only retrieves the most recently accessed
 +      // tab of an AlignFrame.
        ap = af.alignPanel;
      }
  
       * 
       * Not done if flag is false (when this method is used for New View)
       */
 +    final AlignFrame af0 = af;
 +    final AlignViewport av0 = av;
 +    final AlignmentPanel ap0 = ap;
 +//    Platform.timeCheck("Jalview2XML.loadFromObject-beforetree",
 +//            Platform.TIME_MARK);
      if (loadTreesAndStructures)
      {
 -      loadTrees(jalviewModel, view, af, av, ap);
 -      loadPCAViewers(jalviewModel, ap);
 -      loadPDBStructures(jprovider, jseqs, af, ap);
 -      loadRnaViewers(jprovider, jseqs, ap);
 +      if (!jalviewModel.getTree().isEmpty())
 +      {
 +        SwingUtilities.invokeLater(new Runnable()
 +        {
 +          @Override
 +          public void run()
 +          {
 +//            Platform.timeCheck(null, Platform.TIME_MARK);
 +            loadTrees(jalviewModel, view, af0, av0, ap0);
 +//            Platform.timeCheck("Jalview2XML.loadTrees", Platform.TIME_MARK);
 +          }
 +        });
 +      }
 +      if (!jalviewModel.getPcaViewer().isEmpty())
 +      {
 +        SwingUtilities.invokeLater(new Runnable()
 +        {
 +          @Override
 +          public void run()
 +          {
 +//            Platform.timeCheck(null, Platform.TIME_MARK);
 +            loadPCAViewers(jalviewModel, ap0);
 +//            Platform.timeCheck("Jalview2XML.loadPCA", Platform.TIME_MARK);
 +          }
 +        });
 +      }
 +      SwingUtilities.invokeLater(new Runnable()
 +      {
 +        @Override
 +        public void run()
 +        {
 +//          Platform.timeCheck(null, Platform.TIME_MARK);
 +          loadPDBStructures(jprovider, jseqs, af0, ap0);
 +//          Platform.timeCheck("Jalview2XML.loadPDB", Platform.TIME_MARK);
 +        }
 +      });
 +      SwingUtilities.invokeLater(new Runnable()
 +      {
 +        @Override
 +        public void run()
 +        {
 +          loadRnaViewers(jprovider, jseqs, ap0);
 +        }
 +      });
      }
      // and finally return.
 +    // but do not set holdRepaint true just yet, because this could be the
 +    // initial frame with just its dataset.
      return af;
    }
  
    /**
 +   * Loads a HMMER profile from a file stored in the project, and associates it
 +   * with the specified sequence
 +   * 
 +   * @param jprovider
 +   * @param hmmJarFile
 +   * @param seq
 +   */
 +  protected void loadHmmerProfile(jarInputStreamProvider jprovider,
 +          String hmmJarFile, SequenceI seq)
 +  {
 +    try
 +    {
 +      String hmmFile = copyJarEntry(jprovider, hmmJarFile, "hmm", null);
 +      HMMFile parser = new HMMFile(hmmFile, DataSourceType.FILE);
 +      HiddenMarkovModel hmmModel = parser.getHMM();
 +      hmmModel = new HiddenMarkovModel(hmmModel, seq);
 +      seq.setHMM(hmmModel);
 +    } catch (IOException e)
 +    {
 +      warn("Error loading HMM profile for " + seq.getName() + ": "
 +              + e.getMessage());
 +    }
 +  }
 +
 +  /**
     * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
     * panel is restored from separate jar entries, two (gapped and trimmed) per
     * sequence and secondary structure.
     * @param jseqs
     * @param ap
     */
 -  private void loadRnaViewers(jarInputStreamProvider jprovider,
 +  protected void loadRnaViewers(jarInputStreamProvider jprovider,
            List<JSeq> jseqs, AlignmentPanel ap)
    {
      /*
     * @param av
     * @param ap
     */
 -  protected void loadTrees(JalviewModel jm, Viewport view, AlignFrame af,
 -          AlignViewport av, AlignmentPanel ap)
 +  protected void loadTrees(JalviewModel jm, Viewport view,
 +          AlignFrame af, AlignViewport av, AlignmentPanel ap)
    {
      // TODO result of automated refactoring - are all these parameters needed?
      try
                    tree.getTitle(), safeInt(tree.getWidth()),
                    safeInt(tree.getHeight()), safeInt(tree.getXpos()),
                    safeInt(tree.getYpos()));
 +          if (tp == null)
 +          {
 +            warn("There was a problem recovering stored Newick tree: \n"
 +                    + tree.getNewick());
 +            continue;
 +          }
            if (tree.getId() != null)
            {
              // perhaps bind the tree id to something ?
            tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
          }
          tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
 -        if (tp == null)
 -        {
 -          warn("There was a problem recovering stored Newick tree: \n"
 -                  + tree.getNewick());
 -          continue;
 -        }
          tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
          tp.fitToWindow_actionPerformed(null);
  
            for (int s = 0; s < structureStateCount; s++)
            {
              // check to see if we haven't already created this structure view
 -            final StructureState structureState = pdbid.getStructureState()
 -                    .get(s);
 +            final StructureState structureState = pdbid
 +                    .getStructureState().get(s);
              String sviewid = (structureState.getViewId() == null) ? null
                      : structureState.getViewId() + uniqueSetSuffix;
              jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
              int height = safeInt(structureState.getHeight());
  
              // Probably don't need to do this anymore...
 -            // Desktop.desktop.getComponentAt(x, y);
 +            // Desktop.getDesktop().getComponentAt(x, y);
              // TODO: NOW: check that this recovers the PDB file correctly.
              String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
                      pdbid.getFile());
              }
              if (!structureViewers.containsKey(sviewid))
              {
+               String viewerType = structureState.getType();
+               if (viewerType == null) // pre Jalview 2.9
+               {
+                 viewerType = ViewerType.JMOL.toString();
+               }
                structureViewers.put(sviewid,
                        new StructureViewerModel(x, y, width, height, false,
                                false, true, structureState.getViewId(),
-                               structureState.getType()));
+                               viewerType));
                // Legacy pre-2.7 conversion JAL-823 :
                // do not assume any view has to be linked for colour by
                // sequence
              colourByViewer &= structureState.isColourByJmol();
              jmoldat.setColourByViewer(colourByViewer);
  
 -            if (jmoldat.getStateData().length() < structureState.getValue()
 -                    /*Content()*/.length())
 +            if (jmoldat.getStateData().length() < structureState
 +                    .getValue()/*Content()*/.length())
              {
                jmoldat.setStateData(structureState.getValue());// Content());
              }
        return;
      }
  
-     /*
-      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
-      * "viewer_"+stateData.viewId
-      */
-     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
+     String type = stateData.getType();
+     try
      {
-       createChimeraViewer(viewerData, af, jprovider);
-     }
-     else
+       ViewerType viewerType = ViewerType.valueOf(type);
+       createStructureViewer(viewerType, viewerData, af, jprovider);
+     } catch (IllegalArgumentException | NullPointerException e)
      {
-       /*
-        * else Jmol (if pre-2.9, stateData contains JMOL state string)
-        */
-       createJmolViewer(viewerData, af, jprovider);
+       // TODO JAL-3619 show error dialog / offer an alternative viewer
+       Cache.log.error("Invalid structure viewer type: " + type);
      }
 -  }
    }
  
    /**
-    * Create a new Chimera viewer.
 -   * Returns any open frame that matches given structure viewer data. The match
 -   * is based on the unique viewId, or (for older project versions) the frame's
 -   * geometry.
++   * Creates a new structure viewer window
     * 
-    * @param data
++   * @param viewerType
+    * @param viewerData
 -   * @return
 +   * @param af
 +   * @param jprovider
     */
-   protected void createChimeraViewer(
-           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
-           jarInputStreamProvider jprovider)
 -  protected StructureViewerBase findMatchingViewer(
 -          Entry<String, StructureViewerModel> viewerData)
++  protected void createStructureViewer(ViewerType viewerType,
++          final Entry<String, StructureViewerModel> viewerData,
++          AlignFrame af, jarInputStreamProvider jprovider)
    {
-     StructureViewerModel data = viewerData.getValue();
-     String chimeraSessionFile = data.getStateData();
 -    final String sviewid = viewerData.getKey();
 -    final StructureViewerModel svattrib = viewerData.getValue();
 -    StructureViewerBase comp = null;
 -    JInternalFrame[] frames = getAllFrames();
 -    for (JInternalFrame frame : frames)
 -    {
 -      if (frame instanceof StructureViewerBase)
++    final StructureViewerModel viewerModel = viewerData.getValue();
++    String sessionFilePath = null;
 +
-     /*
-      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
-      * 
-      * NB this is the 'saved' viewId as in the project file XML, _not_ the
-      * 'uniquified' sviewid used to reconstruct the viewer here
-      */
-     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
-     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
-             "chimera", ".py");
-     Set<Entry<File, StructureData>> fileData = data.getFileData()
-             .entrySet();
-     List<PDBEntry> pdbs = new ArrayList<>();
-     List<SequenceI[]> allseqs = new ArrayList<>();
-     for (Entry<File, StructureData> pdb : fileData)
-     {
-       String filePath = pdb.getValue().getFilePath();
-       String pdbId = pdb.getValue().getPdbId();
-       // pdbs.add(new PDBEntry(filePath, pdbId));
-       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
-       final List<SequenceI> seqList = pdb.getValue().getSeqList();
-       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
-       allseqs.add(seqs);
-     }
-     boolean colourByChimera = data.isColourByViewer();
-     boolean colourBySequence = data.isColourWithAlignPanel();
-     // TODO use StructureViewer as a factory here, see JAL-1761
-     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
-     final SequenceI[][] seqsArray = allseqs
-             .toArray(new SequenceI[allseqs.size()][]);
-     String newViewId = viewerData.getKey();
-     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
-             af.alignPanel, pdbArray, seqsArray, colourByChimera,
-             colourBySequence, newViewId);
-     cvf.setSize(data.getWidth(), data.getHeight());
-     cvf.setLocation(data.getX(), data.getY());
++    if (viewerType == ViewerType.JMOL)
++    {
++      sessionFilePath = rewriteJmolSession(viewerModel, jprovider);
++    }
++    else
++    {
++      String viewerJarEntryName = getViewerJarEntryName(
++              viewerModel.getViewId());
++      sessionFilePath = copyJarEntry(jprovider, viewerJarEntryName,
++              "viewerSession", ".tmp");
++    }
++    final String sessionPath = sessionFilePath;
++    final String sviewid = viewerData.getKey();
++// BH again was invokeAndWait
++    // try
++    // {
++      javax.swing.SwingUtilities.invokeLater(new Runnable()
++      {
++        @Override
++        public void run()
++        {
++          JalviewStructureDisplayI sview = null;
++          try
++          {
++            sview = StructureViewer.createView(viewerType, af.alignPanel,
++                    viewerModel, sessionPath, sviewid);
++            addNewStructureViewer(sview);
++          } catch (OutOfMemoryError ex)
++          {
++            new OOMWarning("Restoring structure view for " + viewerType,
++                    (OutOfMemoryError) ex.getCause());
++            if (sview != null && sview.isVisible())
++            {
++              sview.closeViewer(false);
++              sview.setVisible(false);
++              sview.dispose();
++            }
++          }
++        }
++      });
++//    } catch (InvocationTargetException | InterruptedException ex)
++//    {
++//      warn("Unexpected error when opening " + viewerType
++//              + " structure viewer", ex);
++//    }
 +  }
 +
 +  /**
-    * Create a new Jmol window. First parse the Jmol state to translate filenames
-    * loaded into the view, and record the order in which files are shown in the
-    * Jmol view, so we can add the sequence mappings in same order.
++   * Rewrites a Jmol session script, saves it to a temporary file, and returns
++   * the path of the file. "load file" commands are rewritten to change the
++   * original PDB file names to those created as the Jalview project is loaded.
 +   * 
-    * @param viewerData
-    * @param af
++   * @param svattrib
 +   * @param jprovider
++   * @return
 +   */
-   protected void createJmolViewer(
-           final Entry<String, StructureViewerModel> viewerData,
-           AlignFrame af, jarInputStreamProvider jprovider)
++  private String rewriteJmolSession(StructureViewerModel svattrib,
++          jarInputStreamProvider jprovider)
 +  {
-     final StructureViewerModel svattrib = viewerData.getValue();
-     String state = svattrib.getStateData();
-     /*
-      * Pre-2.9: state element value is the Jmol state string
-      * 
-      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
-      * + viewId
-      */
-     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
++    String state = svattrib.getStateData(); // Jalview < 2.9
++    if (state == null || state.isEmpty()) // Jalview >= 2.9
 +    {
-       state = readJarEntry(jprovider,
-               getViewerJarEntryName(svattrib.getViewId()));
++      String jarEntryName = getViewerJarEntryName(svattrib.getViewId());
++      state = readJarEntry(jprovider, jarEntryName);
 +    }
-     List<String> pdbfilenames = new ArrayList<>();
-     List<SequenceI[]> seqmaps = new ArrayList<>();
-     List<String> pdbids = new ArrayList<>();
-     StringBuilder newFileLoc = new StringBuilder(64);
++    // TODO or simpler? for each key in oldFiles,
++    // replace key.getPath() in state with oldFiles.get(key).getFilePath()
++    // (allowing for different path escapings)
++    StringBuilder rewritten = new StringBuilder(state.length());
 +    int cp = 0, ncp, ecp;
 +    Map<File, StructureData> oldFiles = svattrib.getFileData();
 +    while ((ncp = state.indexOf("load ", cp)) > -1)
 +    {
 +      do
 +      {
 +        // look for next filename in load statement
-         newFileLoc.append(state.substring(cp,
++        rewritten.append(state.substring(cp,
 +                ncp = (state.indexOf("\"", ncp + 1) + 1)));
 +        String oldfilenam = state.substring(ncp,
 +                ecp = state.indexOf("\"", ncp));
 +        // recover the new mapping data for this old filename
 +        // have to normalize filename - since Jmol and jalview do
-         // filename
-         // translation differently.
++        // filename translation differently.
 +        StructureData filedat = oldFiles.get(new File(oldfilenam));
 +        if (filedat == null)
 +        {
 +          String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
 +          filedat = oldFiles.get(new File(reformatedOldFilename));
 +        }
-         newFileLoc
-                 .append(Platform.escapeBackslashes(filedat.getFilePath()));
-         pdbfilenames.add(filedat.getFilePath());
-         pdbids.add(filedat.getPdbId());
-         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
-         newFileLoc.append("\"");
++        rewritten.append(Platform.escapeBackslashes(filedat.getFilePath()));
++        rewritten.append("\"");
 +        cp = ecp + 1; // advance beyond last \" and set cursor so we can
 +                      // look for next file statement.
 +      } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
 +    }
 +    if (cp > 0)
 +    {
 +      // just append rest of state
-       newFileLoc.append(state.substring(cp));
++      rewritten.append(state.substring(cp));
 +    }
 +    else
 +    {
 +      System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
-       newFileLoc = new StringBuilder(state);
-       newFileLoc.append("; load append ");
++      rewritten = new StringBuilder(state);
++      rewritten.append("; load append ");
 +      for (File id : oldFiles.keySet())
 +      {
-         // add this and any other pdb files that should be present in
-         // the viewer
++        // add pdb files that should be present in the viewer
 +        StructureData filedat = oldFiles.get(id);
-         newFileLoc.append(filedat.getFilePath());
-         pdbfilenames.add(filedat.getFilePath());
-         pdbids.add(filedat.getPdbId());
-         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
-         newFileLoc.append(" \"");
-         newFileLoc.append(filedat.getFilePath());
-         newFileLoc.append("\"");
++        rewritten.append(" \"").append(filedat.getFilePath()).append("\"");
 +      }
-       newFileLoc.append(";");
++      rewritten.append(";");
 +    }
 +
-     if (newFileLoc.length() == 0)
++    if (rewritten.length() == 0)
 +    {
-       return;
++      return null;
 +    }
-     int histbug = newFileLoc.indexOf("history = ");
-     if (histbug > -1)
++    final String history = "history = ";
++    int historyIndex = rewritten.indexOf(history);
++    if (historyIndex > -1)
 +    {
 +      /*
 +       * change "history = [true|false];" to "history = [1|0];"
 +       */
-       histbug += 10;
-       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
-       String val = (diff == -1) ? null
-               : newFileLoc.substring(histbug, diff);
-       if (val != null && val.length() >= 4)
++      historyIndex += history.length();
++      String val = rewritten.substring(historyIndex, historyIndex + 5);
++      if (val.startsWith("true"))
 +      {
-         if (val.contains("e")) // eh? what can it be?
-         {
-           if (val.trim().equals("true"))
-           {
-             val = "1";
-           }
-           else
-           {
-             val = "0";
-           }
-           newFileLoc.replace(histbug, diff, val);
-         }
++        rewritten.replace(historyIndex, historyIndex + 4, "1");
++      }
++      else if (val.startsWith("false"))
++      {
++        rewritten.replace(historyIndex, historyIndex + 5, "0");
 +      }
 +    }
 +
-     final String[] pdbf = pdbfilenames
-             .toArray(new String[pdbfilenames.size()]);
-     final String[] id = pdbids.toArray(new String[pdbids.size()]);
-     final SequenceI[][] sq = seqmaps
-             .toArray(new SequenceI[seqmaps.size()][]);
-     final String fileloc = newFileLoc.toString();
-     final String sviewid = viewerData.getKey();
-     final AlignFrame alf = af;
-     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
-             svattrib.getWidth(), svattrib.getHeight());
-     
-     // BH again was invokeAndWait
-     // try
-     // {
-       javax.swing.SwingUtilities.invokeLater(new Runnable()
++    try
++    {
++      File tmp = File.createTempFile("viewerSession", ".tmp");
++      try (OutputStream os = new FileOutputStream(tmp))
 +      {
-         @Override
-         public void run()
-         {
-           JalviewStructureDisplayI sview = null;
-           try
-           {
-             sview = new StructureViewer(
-                     alf.alignPanel.getStructureSelectionManager())
-                             .createView(StructureViewer.ViewerType.JMOL,
-                                     pdbf, id, sq, alf.alignPanel, svattrib,
-                                     fileloc, rect, sviewid);
-             addNewStructureViewer(sview);
-           } catch (OutOfMemoryError ex)
-           {
-             new OOMWarning("restoring structure view for PDB id " + id,
-                     (OutOfMemoryError) ex.getCause());
-             if (sview != null && sview.isVisible())
-             {
-               sview.closeViewer(false);
-               sview.setVisible(false);
-               sview.dispose();
-             }
-           }
-         }
-       });
-     // } catch (InvocationTargetException ex)
-     // {
-     // warn("Unexpected error when opening Jmol view.", ex);
-     //
-     // } catch (InterruptedException e)
-     // {
-     // // e.printStackTrace();
-     // }
++        InputStream is = new ByteArrayInputStream(
++                rewritten.toString().getBytes());
++        copyAll(is, os);
++        return tmp.getAbsolutePath();
++      }
++    } catch (IOException e)
++    {
++      Cache.log.error("Error restoring Jmol session: " + e.toString());
++    }
++    return null;
 +  }
 +
 +  /**
 +   * Generates a name for the entry in the project jar file to hold state
 +   * information for a structure viewer
 +   * 
 +   * @param viewId
 +   * @return
 +   */
 +  protected String getViewerJarEntryName(String viewId)
 +  {
 +    return VIEWER_PREFIX + viewId;
 +  }
 +
 +  /**
 +   * Returns any open frame that matches given structure viewer data. The match
 +   * is based on the unique viewId, or (for older project versions) the frame's
 +   * geometry.
 +   * 
 +   * @param viewerData
 +   * @return
 +   */
 +  protected StructureViewerBase findMatchingViewer(
 +          Entry<String, StructureViewerModel> viewerData)
 +  {
 +    final String sviewid = viewerData.getKey();
 +    final StructureViewerModel svattrib = viewerData.getValue();
 +    StructureViewerBase comp = null;
 +    JInternalFrame[] frames = getAllFrames();
 +    for (JInternalFrame frame : frames)
 +    {
 +      if (frame instanceof StructureViewerBase)
        {
          /*
           * Post jalview 2.4 schema includes structure view id
      {
        try
        {
 -        frames = Desktop.desktop.getAllFrames();
 +        frames = Desktop.getDesktopPane().getAllFrames();
        } catch (ArrayIndexOutOfBoundsException e)
        {
          // occasional No such child exceptions are thrown here...
      }
    }
  
 -  AlignFrame loadViewport(String file, List<JSeq> JSEQ,
 +  AlignFrame loadViewport(String fileName, File file, List<JSeq> JSEQ,
            List<SequenceI> hiddenSeqs, AlignmentI al, JalviewModel jm,
            Viewport view, String uniqueSeqSetId, String viewId,
            List<JvAnnotRow> autoAlan)
      //
      // }
      ;
 -    af.setFileName(file, FileFormat.Jalview);
 +    af.alignPanel.setHoldRepaint(true);
 +    af.setFile(fileName, file, null, FileFormat.Jalview);
 +    af.setFileObject(jarFile); // BH 2019 JAL-3436
  
      final AlignViewport viewport = af.getViewport();
      for (int i = 0; i < JSEQ.size(); i++)
        viewport.setViewName(view.getViewName());
        af.setInitialTabVisible();
      }
 -    af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
 -            safeInt(view.getWidth()), safeInt(view.getHeight()));
 +    int x = safeInt(view.getXpos());
 +    int y = safeInt(view.getYpos());
 +    int w = safeInt(view.getWidth());
 +    int h = safeInt(view.getHeight());
 +    // // BH we cannot let the title bar go off the top
 +    // if (Platform.isJS())
 +    // {
 +    // x = Math.max(50 - w, x);
 +    // y = Math.max(0, y);
 +    // }
 +
 +    af.setBounds(x, y, w, h);
      // startSeq set in af.alignPanel.updateLayout below
      af.alignPanel.updateLayout();
      ColourSchemeI cs = null;
      // recover feature settings
      if (jm.getFeatureSettings() != null)
      {
-       FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
+       FeatureRendererModel fr = af.alignPanel.getSeqPanel().seqCanvas
                .getFeatureRenderer();
        FeaturesDisplayed fdi;
        viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
      String complementaryViewId = view.getComplementId();
      if (complementaryViewId == null)
      {
 -      Desktop.addInternalFrame(af, view.getTitle(),
 +      Dimension dim = Platform.getDimIfEmbedded(af,
                safeInt(view.getWidth()), safeInt(view.getHeight()));
 +      Desktop.addInternalFrame(af, view.getTitle(), dim.width, dim.height);
        // recompute any autoannotation
        af.alignPanel.updateAnnotation(false, true);
        reorderAutoannotation(af, al, autoAlan);
      String id = object.getViewport().get(0).getSequenceSetId();
      if (skipList.containsKey(id))
      {
 -      if (Cache.log != null && Cache.log.isDebugEnabled())
 -      {
 -        Cache.log.debug("Skipping seuqence set id " + id);
 -      }
 +      if (Cache.log != null && Cache.log.isDebugEnabled())
 +        {
 +          Cache.log.debug("Skipping seuqence set id " + id);
 +        }
        return true;
      }
      return false;
    }
  
 -  public void addToSkipList(AlignFrame af)
 +  protected void addToSkipList(AlignFrame af)
    {
      if (skipList == null)
      {
      skipList.put(af.getViewport().getSequenceSetId(), af);
    }
  
 -  public void clearSkipList()
 +  protected void clearSkipList()
    {
      if (skipList != null)
      {
          addDatasetRef(vamsasSet.getDatasetId(), ds);
        }
      }
 -    Vector<SequenceI> dseqs = null;
 +    Vector dseqs = null;
      if (!ignoreUnrefed)
      {
        // recovering an alignment View
        // try even harder to restore dataset
        AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
        // create a list of new dataset sequences
-       dseqs = new Vector();
+       dseqs = new Vector<>();
      }
      for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
      {
        SequenceI[] dsseqs = new SequenceI[dseqs.size()];
        dseqs.copyInto(dsseqs);
        ds = new jalview.datamodel.Alignment(dsseqs);
 -      debug("Created new dataset " + vamsasSet.getDatasetId()
 -              + " for alignment " + System.identityHashCode(al));
 +//      debug("Jalview2XML Created new dataset " + vamsasSet.getDatasetId()
 +//              + " for alignment " + System.identityHashCode(al));
        addDatasetRef(vamsasSet.getDatasetId(), ds);
      }
      // set the dataset for the newly imported alignment.
        }
      }
    }
 -
    /**
     * 
     * @param vamsasSeq
     *          vamsasSeq array ordering, to preserve ordering of dataset
     */
    private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
-           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
+           AlignmentI ds, Vector<SequenceI> dseqs, boolean ignoreUnrefed,
+           int vseqpos)
    {
      // JBP TODO: Check this is called for AlCodonFrames to support recovery of
      // xRef Codon Maps
        {
          entry.setMap(addMapping(dr.getMapping()));
        }
+       entry.setCanonical(dr.isCanonical());
        datasetSequence.addDBRef(entry);
      }
    }
          jmap.setTo(djs);
          incompleteSeqs.put(sqid, djs);
          seqRefIds.put(sqid, djs);
        }
        jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
        addDBRefs(djs, ms);
  
      viewportsAdded.clear();
  
 -    AlignFrame af = loadFromObject(jm, null, false, null);
 +    AlignFrame af = loadFromObject(jm, null, null, false, null);
      af.getAlignPanels().clear();
      af.closeMenuItem_actionPerformed(true);
 +    af.alignPanel.setHoldRepaint(false);
 +    this.jarFile = null;
  
      /*
       * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
        }
        else
        {
 -        Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
 +          Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
        }
      }
    }
                    axis.getXPos(), axis.getYPos(), axis.getZPos());
          }
  
 +        Dimension dim = Platform.getDimIfEmbedded(panel, 475, 450);
          Desktop.addInternalFrame(panel, MessageManager.formatMessage(
 -                "label.calc_title", "PCA", modelName), 475, 450);
 +                "label.calc_title", "PCA", modelName), dim.width,
 +                dim.height);
        }
      } catch (Exception ex)
      {
    }
  
    /**
 -   * Creates a new structure viewer window
 -   * 
 -   * @param viewerType
 -   * @param viewerData
 -   * @param af
 -   * @param jprovider
 -   */
 -  protected void createStructureViewer(ViewerType viewerType,
 -          final Entry<String, StructureViewerModel> viewerData,
 -          AlignFrame af, jarInputStreamProvider jprovider)
 -  {
 -    final StructureViewerModel viewerModel = viewerData.getValue();
 -    String sessionFilePath = null;
 -
 -    if (viewerType == ViewerType.JMOL)
 -    {
 -      sessionFilePath = rewriteJmolSession(viewerModel, jprovider);
 -    }
 -    else
 -    {
 -      String viewerJarEntryName = getViewerJarEntryName(
 -              viewerModel.getViewId());
 -      sessionFilePath = copyJarEntry(jprovider, viewerJarEntryName,
 -              "viewerSession", ".tmp");
 -    }
 -    final String sessionPath = sessionFilePath;
 -    final String sviewid = viewerData.getKey();
 -    try
 -    {
 -      SwingUtilities.invokeAndWait(new Runnable()
 -      {
 -        @Override
 -        public void run()
 -        {
 -          JalviewStructureDisplayI sview = null;
 -          try
 -          {
 -            sview = StructureViewer.createView(viewerType, af.alignPanel,
 -                    viewerModel, sessionPath, sviewid);
 -            addNewStructureViewer(sview);
 -          } catch (OutOfMemoryError ex)
 -          {
 -            new OOMWarning("Restoring structure view for " + viewerType,
 -                    (OutOfMemoryError) ex.getCause());
 -            if (sview != null && sview.isVisible())
 -            {
 -              sview.closeViewer(false);
 -              sview.setVisible(false);
 -              sview.dispose();
 -            }
 -          }
 -        }
 -      });
 -    } catch (InvocationTargetException | InterruptedException ex)
 -    {
 -      warn("Unexpected error when opening " + viewerType
 -              + " structure viewer", ex);
 -    }
 -  }
 -
 -  /**
 -   * Rewrites a Jmol session script, saves it to a temporary file, and returns
 -   * the path of the file. "load file" commands are rewritten to change the
 -   * original PDB file names to those created as the Jalview project is loaded.
 -   * 
 -   * @param svattrib
 -   * @param jprovider
 -   * @return
 -   */
 -  private String rewriteJmolSession(StructureViewerModel svattrib,
 -          jarInputStreamProvider jprovider)
 -  {
 -    String state = svattrib.getStateData(); // Jalview < 2.9
 -    if (state == null || state.isEmpty()) // Jalview >= 2.9
 -    {
 -      String jarEntryName = getViewerJarEntryName(svattrib.getViewId());
 -      state = readJarEntry(jprovider, jarEntryName);
 -    }
 -    // TODO or simpler? for each key in oldFiles,
 -    // replace key.getPath() in state with oldFiles.get(key).getFilePath()
 -    // (allowing for different path escapings)
 -    StringBuilder rewritten = new StringBuilder(state.length());
 -    int cp = 0, ncp, ecp;
 -    Map<File, StructureData> oldFiles = svattrib.getFileData();
 -    while ((ncp = state.indexOf("load ", cp)) > -1)
 -    {
 -      do
 -      {
 -        // look for next filename in load statement
 -        rewritten.append(state.substring(cp,
 -                ncp = (state.indexOf("\"", ncp + 1) + 1)));
 -        String oldfilenam = state.substring(ncp,
 -                ecp = state.indexOf("\"", ncp));
 -        // recover the new mapping data for this old filename
 -        // have to normalize filename - since Jmol and jalview do
 -        // filename translation differently.
 -        StructureData filedat = oldFiles.get(new File(oldfilenam));
 -        if (filedat == null)
 -        {
 -          String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
 -          filedat = oldFiles.get(new File(reformatedOldFilename));
 -        }
 -        rewritten.append(Platform.escapeBackslashes(filedat.getFilePath()));
 -        rewritten.append("\"");
 -        cp = ecp + 1; // advance beyond last \" and set cursor so we can
 -                      // look for next file statement.
 -      } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
 -    }
 -    if (cp > 0)
 -    {
 -      // just append rest of state
 -      rewritten.append(state.substring(cp));
 -    }
 -    else
 -    {
 -      System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
 -      rewritten = new StringBuilder(state);
 -      rewritten.append("; load append ");
 -      for (File id : oldFiles.keySet())
 -      {
 -        // add pdb files that should be present in the viewer
 -        StructureData filedat = oldFiles.get(id);
 -        rewritten.append(" \"").append(filedat.getFilePath()).append("\"");
 -      }
 -      rewritten.append(";");
 -    }
 -
 -    if (rewritten.length() == 0)
 -    {
 -      return null;
 -    }
 -    final String history = "history = ";
 -    int historyIndex = rewritten.indexOf(history);
 -    if (historyIndex > -1)
 -    {
 -      /*
 -       * change "history = [true|false];" to "history = [1|0];"
 -       */
 -      historyIndex += history.length();
 -      String val = rewritten.substring(historyIndex, historyIndex + 5);
 -      if (val.startsWith("true"))
 -      {
 -        rewritten.replace(historyIndex, historyIndex + 4, "1");
 -      }
 -      else if (val.startsWith("false"))
 -      {
 -        rewritten.replace(historyIndex, historyIndex + 5, "0");
 -      }
 -    }
 -
 -    try
 -    {
 -      File tmp = File.createTempFile("viewerSession", ".tmp");
 -      try (OutputStream os = new FileOutputStream(tmp))
 -      {
 -        InputStream is = new ByteArrayInputStream(
 -                rewritten.toString().getBytes());
 -        copyAll(is, os);
 -        return tmp.getAbsolutePath();
 -      }
 -    } catch (IOException e)
 -    {
 -      Cache.log.error("Error restoring Jmol session: " + e.toString());
 -    }
 -    return null;
 -  }
 -
 -  /**
     * Populates an XML model of the feature colour scheme for one feature type
     * 
     * @param featureType
     * @param fcol
     * @return
     */
 -  public static Colour marshalColour(String featureType,
 -          FeatureColourI fcol)
 +  public static Colour marshalColour(
 +          String featureType, FeatureColourI fcol)
    {
      Colour col = new Colour();
      if (fcol.isSimpleColour())
            boolean and)
    {
      jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
-   
 -
      if (filters.hasNext())
      {
        /*
        }
        result.setMatchCondition(matcherModel);
      }
-   
 -
      return result;
    }
  
     * @param matcherSetModel
     * @return
     */
 -  public static FeatureMatcherSetI parseFilter(String featureType,
 +  public static FeatureMatcherSetI parseFilter(
 +          String featureType,
            jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
    {
      FeatureMatcherSetI result = new FeatureMatcherSet();
                        featureType, e.getMessage()));
        // return as much as was parsed up to the error
      }
-   
 -
      return result;
    }
  
     * @throws IllegalStateException
     *           if AND and OR conditions are mixed
     */
 -  protected static void parseFilterConditions(FeatureMatcherSetI matcherSet,
 +  protected static void parseFilterConditions(
 +          FeatureMatcherSetI matcherSet,
            jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
            boolean and)
    {
        else if (filterBy == FilterBy.BY_SCORE)
        {
          matchCondition = FeatureMatcher.byScore(cond, pattern);
-   
 -
        }
        else if (filterBy == FilterBy.BY_ATTRIBUTE)
        {
          matchCondition = FeatureMatcher.byAttribute(cond, pattern,
                  attNames);
        }
-   
 -
        /*
         * note this throws IllegalStateException if AND-ing to a 
         * previously OR-ed compound condition, or vice versa
    public static FeatureColourI parseColour(Colour colourModel)
    {
      FeatureColourI colour = null;
-   
 -
      if (colourModel.getMax() != null)
      {
        Color mincol = null;
        Color maxcol = null;
        Color noValueColour = null;
-   
 -
        try
        {
          mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
          maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
        } catch (Exception e)
        {
 -        Cache.log.warn("Couldn't parse out graduated feature color.", e);
 +          Cache.log.warn("Couldn't parse out graduated feature color.", e);
        }
-   
 -
        NoValueColour noCol = colourModel.getNoValueColour();
        if (noCol == NoValueColour.MIN)
        {
        {
          noValueColour = maxcol;
        }
-   
 -
        colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
                safeFloat(colourModel.getMin()),
                safeFloat(colourModel.getMax()));
        Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
        colour = new FeatureColour(color);
      }
-   
 -
      return colour;
    }
  }
@@@ -29,14 -29,12 +29,14 @@@ import jalview.datamodel.AlignmentAnnot
  import jalview.datamodel.Annotation;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.HiddenColumns;
 +import jalview.datamodel.HiddenMarkovModel;
  import jalview.datamodel.ProfilesI;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.NucleotideColourScheme;
  import jalview.schemes.ResidueProperties;
  import jalview.schemes.ZappoColourScheme;
  import jalview.util.Platform;
 +import jalview.workers.InformationThread;
  
  import java.awt.BasicStroke;
  import java.awt.Color;
@@@ -71,14 -69,8 +71,14 @@@ public class AnnotationRendere
  
    private final boolean USE_FILL_ROUND_RECT = Platform.isAMacAndNotJS();
  
 -  boolean av_renderHistogram = true, av_renderProfile = true,
 -          av_normaliseProfile = false;
 +  // todo remove these flags, read from group/viewport where needed
 +  boolean av_renderHistogram = true;
 +
 +  boolean av_renderProfile = true;
 +
 +  boolean av_normaliseProfile = false;
 +
 +  boolean av_infoHeight = false;
  
    ResidueShaderI profcolour = null;
  
@@@ -94,8 -86,6 +94,8 @@@
  
    private boolean av_ignoreGapsConsensus;
  
 +  private boolean av_ignoreBelowBackground;
 +
    /**
     * attributes set from AwtRenderPanelI
     */
     */
    public void dispose()
    {
+     hiddenColumns = null;
      hconsensus = null;
      complementConsensus = null;
      hStrucConsensus = null;
      complementConsensus = av.getComplementConsensusHash();
      hStrucConsensus = av.getRnaStructureConsensusHash();
      av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
 +    av_ignoreBelowBackground = av.isIgnoreBelowBackground();
 +    av_infoHeight = av.isInfoLetterHeight();
    }
  
 +
 +
    /**
     * Returns profile data; the first element is the profile type, the second is
     * the number of distinct values, the third the total count, and the remainder
      // properties/rendering attributes as a global 'alignment group' which holds
      // all vis settings for the alignment as a whole rather than a subset
      //
 -    if (aa.autoCalculated && (aa.label.startsWith("Consensus")
 -            || aa.label.startsWith("cDNA Consensus")))
 +    if (InformationThread.HMM_CALC_ID.equals(aa.getCalcId()))
 +    {
 +      HiddenMarkovModel hmm = aa.sequenceRef.getHMM();
 +      return AAFrequency.extractHMMProfile(hmm, column,
 +              av_ignoreBelowBackground, av_infoHeight); // TODO check if this follows standard
 +                                         // pipeline
 +    }
 +    if (aa.autoCalculated
 +            && (aa.label.startsWith("Consensus") || aa.label
 +                    .startsWith("cDNA Consensus")))
      {
        boolean forComplement = aa.label.startsWith("cDNA Consensus");
 -      if (aa.groupRef != null && aa.groupRef.consensusData != null
 +      if (aa.groupRef != null && aa.groupRef.getConsensusData() != null
                && aa.groupRef.isShowSequenceLogo())
        {
          // TODO? group consensus for cDNA complement
          return AAFrequency.extractProfile(
 -                aa.groupRef.consensusData.get(column),
 -                aa.groupRef.getIgnoreGapsConsensus());
 +                aa.groupRef.getConsensusData().get(column),
 +                                              aa.groupRef.getIgnoreGapsConsensus());
        }
        // TODO extend annotation row to enable dynamic and static profile data to
        // be stored
          renderProfile = av_renderProfile;
          normaliseProfile = av_normaliseProfile;
        }
 +      else if (InformationThread.HMM_CALC_ID.equals(row.getCalcId()))
 +      {
 +        if (row.groupRef != null)
 +        {
 +          renderHistogram = row.groupRef.isShowInformationHistogram();
 +          renderProfile = row.groupRef.isShowHMMSequenceLogo();
 +          normaliseProfile = row.groupRef.isNormaliseHMMSequenceLogo();
 +        }
 +        else
 +        {
 +          renderHistogram = av.isShowInformationHistogram();
 +          renderProfile = av.isShowHMMSequenceLogo();
 +          normaliseProfile = av.isNormaliseHMMSequenceLogo();
 +        }
 +      }
 +      else if (row == consensusAnnot || row == structConsensusAnnot
 +              || row == complementConsensusAnnot)
 +      {
 +        renderHistogram = av_renderHistogram;
 +        renderProfile = av_renderProfile;
 +        normaliseProfile = av_normaliseProfile;
 +      }
  
        Annotation[] row_annotations = row.annotations;
        if (!row.visible)
                        .deriveFont(AffineTransform.getScaleInstance(sx, sy));
                g.setFont(font);
                g.drawChars(dc, 0, dc.length, x * charWidth, hght);
+               g.setFont(ofont);
                ht += newHeight;
              }
            }
   */
  package jalview.schemes;
  
+ import java.util.Locale;
  import jalview.api.AlignViewportI;
 +import jalview.bin.ApplicationSingletonProvider;
 +import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.datamodel.AnnotatedCollectionI;
  import jalview.datamodel.SequenceCollectionI;
  import jalview.datamodel.SequenceI;
 +import jalview.util.ColorUtils;
  
 +import java.awt.Color;
  import java.util.LinkedHashMap;
  import java.util.Map;
  
 -public class ColourSchemes
 +public class ColourSchemes implements ApplicationSingletonI
  {
 -  /*
 -   * singleton instance of this class
 -   */
 -  private static ColourSchemes instance = new ColourSchemes();
 -
 -  /*
 -   * a map from scheme name (lower-cased) to an instance of it
 -   */
 -  private Map<String, ColourSchemeI> schemes;
  
    /**
     * Returns the singleton instance of this class
@@@ -42,8 -49,7 +44,8 @@@
     */
    public static ColourSchemes getInstance()
    {
 -    return instance;
 +    return (ColourSchemes) ApplicationSingletonProvider
 +            .getInstance(ColourSchemes.class);
    }
  
    private ColourSchemes()
    }
  
    /**
 +   * ColourSchemeProperty "static"
 +   */
 +  public Color[] rnaHelices = null;
 +
 +  /**
 +   * delete the existing cached RNA helices colours
 +   */
 +  public static void resetRnaHelicesShading()
 +  {
 +    getInstance().rnaHelices = null;
 +  }
 +
 +  public static void initRnaHelicesShading(int n)
 +  {
 +    int i = 0;
 +    ColourSchemes j = getInstance();
 +
 +    if (j.rnaHelices == null)
 +    {
 +      j.rnaHelices = new Color[n + 1];
 +    }
 +    else if (j.rnaHelices != null && j.rnaHelices.length <= n)
 +    {
 +      Color[] t = new Color[n + 1];
 +      System.arraycopy(j.rnaHelices, 0, t, 0, j.rnaHelices.length);
 +      i = j.rnaHelices.length;
 +      j.rnaHelices = t;
 +    }
 +    else
 +    {
 +      return;
 +    }
 +    // Generate random colors and store
 +    for (; i <= n; i++)
 +    {
 +      j.rnaHelices[i] = ColorUtils.generateRandomColor(Color.white);
 +    }
 +  }
 +
 +  /**
 +   * a map from scheme name (lower-cased) to an instance of it
 +   */
 +  private Map<String, ColourSchemeI> schemes;
 +
 +  /**
     * Loads an instance of each standard or user-defined colour scheme
     * 
     * @return
       * name is lower-case for non-case-sensitive lookup
       * (name in the colour keeps its true case)
       */
-     String lower = name.toLowerCase();
+     String lower = name.toLowerCase(Locale.ROOT);
      if (schemes.containsKey(lower))
      {
        System.err
    {
      if (name != null)
      {
-       schemes.remove(name.toLowerCase());
+       schemes.remove(name.toLowerCase(Locale.ROOT));
      }
    }
  
      {
        return null;
      }
-     ColourSchemeI cs = schemes.get(name.toLowerCase());
+     ColourSchemeI cs = schemes.get(name.toLowerCase(Locale.ROOT));
      return cs == null ? null
              : cs.getInstance(viewport, forData);
    }
      {
        return false;
      }
-     return schemes.containsKey(name.toLowerCase());
+     return schemes.containsKey(name.toLowerCase(Locale.ROOT));
    }
  }
@@@ -22,7 -22,6 +22,7 @@@ package jalview.schemes
  
  import jalview.api.FeatureColourI;
  import jalview.api.FeatureSettingsModelI;
 +import jalview.datamodel.features.FeatureMatcherSetI;
  
  /**
   * An adapter class that may be extended to instantiate feature colour schemes
@@@ -37,6 -36,12 +37,12 @@@ public class FeatureSettingsAdapter imp
    }
  
    @Override
+   public boolean isFeatureHidden(String type)
+   {
+     return false;
+   }
+   @Override
    public boolean isGroupDisplayed(String group)
    {
      return true;
      return false;
    }
  
 +  @Override
 +  public FeatureMatcherSetI getFeatureFilters(String type)
 +  {
 +    return null;
 +  }
 +
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.schemes;
  
+ import java.util.Locale;
  import jalview.analysis.GeneticCodes;
  
  import java.awt.Color;
@@@ -34,13 -36,6 +36,13 @@@ import java.util.Vector
  
  public class ResidueProperties
  {
 +  // alphabet names used in Hidden Markov Model files
 +  public static final String ALPHABET_RNA = "RNA";
 +
 +  public static final String ALPHABET_DNA = "DNA";
 +
 +  public static final String ALPHABET_AMINO = "amino";
 +
    // Stores residue codes/names and colours and other things
    public static final int[] aaIndex; // aaHash version 2.1.1 and below
  
@@@ -57,9 -52,6 +59,9 @@@
    // lookup from modified amino acid (e.g. MSE) to canonical form (e.g. MET)
    public static final Map<String, String> modifications = new HashMap<>();
  
 +  // residue background frequencies across different alphabets
 +  public static final Map<String, Map<Character, Float>> backgroundFrequencies = new HashMap<>();
 +
    static
    {
      aaIndex = new int[255];
  
    }
  
 +  static
 +  {
 +    Map<Character, Float> amino = new HashMap<>();
 +    amino.put('A', 0.0826f);
 +    amino.put('Q', 0.0393f);
 +    amino.put('L', 0.0965f);
 +    amino.put('S', 0.0661f);
 +    amino.put('R', 0.0553f);
 +    amino.put('E', 0.0674f);
 +    amino.put('K', 0.0582f);
 +    amino.put('T', 0.0535f);
 +    amino.put('N', 0.0406f);
 +    amino.put('G', 0.0708f);
 +    amino.put('M', 0.0241f);
 +    amino.put('W', 0.0109f);
 +    amino.put('D', 0.0546f);
 +    amino.put('H', 0.0227f);
 +    amino.put('F', 0.0386f);
 +    amino.put('Y', 0.0292f);
 +    amino.put('C', 0.0137f);
 +    amino.put('I', 0.0593f);
 +    amino.put('P', 0.0472f);
 +    amino.put('V', 0.0686f);
 +    backgroundFrequencies.put(ALPHABET_AMINO, amino);
 +    // todo: these don't match https://www.ebi.ac.uk/uniprot/TrEMBLstats - what
 +    // are they?
 +  }
 +
 +  // TODO get correct frequencies
 +
 +  static
 +  {
 +    Map<Character, Float> dna = new HashMap<>();
 +    dna.put('A', 0.25f);
 +    dna.put('C', 0.25f);
 +    dna.put('T', 0.25f);
 +    dna.put('G', 0.25f);
 +    backgroundFrequencies.put(ALPHABET_DNA, dna);
 +
 +  }
 +
 +  static
 +  {
 +    Map<Character, Float> rna = new HashMap<>();
 +    rna.put('A', 0.25f);
 +    rna.put('C', 0.25f);
 +    rna.put('T', 0.25f);
 +    rna.put('G', 0.25f);
 +    backgroundFrequencies.put(ALPHABET_RNA, rna);
 +
 +  }
 +
    public static String getCanonicalAminoAcid(String aA)
    {
      String canonical = modifications.get(aA);
          {
            continue;
          }
-         nuc = nuc.toUpperCase();
+         nuc = nuc.toUpperCase(Locale.ROOT);
          if (!result.contains(nuc))
          {
            result.add(nuc);
          {
            continue;
          }
-         res = res.toUpperCase();
+         res = res.toUpperCase(Locale.ROOT);
          if (!result.contains(res))
          {
            result.add(res);
        return '0';
      }
      Integer index = ResidueProperties.aa3Hash
-             .get(threeLetterCode.toUpperCase());
+             .get(threeLetterCode.toUpperCase(Locale.ROOT));
      return index == null ? '0' : aa[index].charAt(0);
    }
  }
   */
  package jalview.structure;
  
++
+ import java.util.Locale;
 +import jalview.bin.ApplicationSingletonProvider;
 +import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
++
++
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.PDBEntry.Type;
  
   * @author tcofoegbu
   *
   */
 -public class StructureImportSettings
 +public class StructureImportSettings implements ApplicationSingletonI
  {
 +  private StructureImportSettings()
 +  {
 +    // private singleton
 +  }
 +
 +  private static StructureImportSettings getInstance()
 +  {
 +    return (StructureImportSettings) ApplicationSingletonProvider
 +            .getInstance(StructureImportSettings.class);
 +  }
    /**
     * set to true to add derived sequence annotations (temp factor read from
     * file, or computed secondary structure) to the alignment
     */
 -  private static boolean visibleChainAnnotation = false;
 +  private boolean visibleChainAnnotation = false;
  
    /**
     * Set true to predict secondary structure (using JMol for protein, Annotate3D
     * for RNA)
     */
-   private boolean processSecStr = false;
+   private static boolean processSecStr = false;
  
    /**
     * Set true (with predictSecondaryStructure=true) to predict secondary
     * structure using an external service (currently Annotate3D for RNA only)
     */
 -  private static boolean externalSecondaryStructure = false;
 +  private boolean externalSecondaryStructure = false;
  
-   private boolean showSeqFeatures = true;
+   private static boolean showSeqFeatures = true;
  
    public enum StructureParser
    {
     * Determines the default file format for structure files to be downloaded
     * from the PDB sequence fetcher. Possible options include: PDB|mmCIF
     */
-   private PDBEntry.Type defaultStructureFileFormat = Type.PDB;
 -  private static PDBEntry.Type defaultStructureFileFormat = Type.PDB;
++  private PDBEntry.Type defaultStructureFileFormat = Type.PDB; // TODO 2.12 should be mmCIF now ?
  
    /**
     * Determines the parser used for parsing PDB format file. Possible options
     * are : JMolParser|JalveiwParser
     */
 -  private static StructureParser defaultPDBFileParser = StructureParser.JMOL_PARSER;
 +  private StructureParser defaultPDBFileParser = StructureParser.JMOL_PARSER;
  
    public static void addSettings(boolean addAlignmentAnnotations,
            boolean processSecStr, boolean externalSecStr)
    {
 -    StructureImportSettings.visibleChainAnnotation = addAlignmentAnnotations;
 -    StructureImportSettings.processSecStr = processSecStr;
 -    StructureImportSettings.externalSecondaryStructure = externalSecStr;
 -    StructureImportSettings.showSeqFeatures = true;
 +    StructureImportSettings s = getInstance();
 +    s.visibleChainAnnotation = addAlignmentAnnotations;
 +    s.processSecStr = processSecStr;
 +    s.externalSecondaryStructure = externalSecStr;
 +    s.showSeqFeatures = true;
    }
  
    public static boolean isVisibleChainAnnotation()
    {
 -    return visibleChainAnnotation;
 +    return getInstance().visibleChainAnnotation;
    }
  
    public static void setVisibleChainAnnotation(
            boolean visibleChainAnnotation)
    {
 -    StructureImportSettings.visibleChainAnnotation = visibleChainAnnotation;
 +    getInstance().visibleChainAnnotation = visibleChainAnnotation;
    }
  
    public static boolean isProcessSecondaryStructure()
    {
 -    return processSecStr;
 +    return getInstance().processSecStr;
    }
  
    public static void setProcessSecondaryStructure(
            boolean processSecondaryStructure)
    {
 -    StructureImportSettings.processSecStr = processSecondaryStructure;
 +    getInstance().processSecStr = processSecondaryStructure;
    }
  
    public static boolean isExternalSecondaryStructure()
    {
 -    return externalSecondaryStructure;
 +    return getInstance().externalSecondaryStructure;
    }
  
    public static void setExternalSecondaryStructure(
            boolean externalSecondaryStructure)
    {
 -    StructureImportSettings.externalSecondaryStructure = externalSecondaryStructure;
 +    getInstance().externalSecondaryStructure = externalSecondaryStructure;
    }
  
    public static boolean isShowSeqFeatures()
    {
 -    return showSeqFeatures;
 +    return getInstance().showSeqFeatures;
    }
  
    public static void setShowSeqFeatures(boolean showSeqFeatures)
    {
 -    StructureImportSettings.showSeqFeatures = showSeqFeatures;
 +    getInstance().showSeqFeatures = showSeqFeatures;
    }
  
    public static PDBEntry.Type getDefaultStructureFileFormat()
    {
 -    return defaultStructureFileFormat;
 +    return getInstance().defaultStructureFileFormat;
    }
  
    public static void setDefaultStructureFileFormat(
            String defaultStructureFileFormat)
    {
 -    StructureImportSettings.defaultStructureFileFormat = PDBEntry.Type
 +    getInstance().defaultStructureFileFormat = PDBEntry.Type
-             .valueOf(defaultStructureFileFormat.toUpperCase());
+             .valueOf(defaultStructureFileFormat.toUpperCase(Locale.ROOT));
    }
  
    public static String getDefaultPDBFileParser()
    {
 -    return defaultPDBFileParser.toString();
 +    return getInstance().defaultPDBFileParser.toString();
    }
  
    public static void setDefaultPDBFileParser(
            StructureParser defaultPDBFileParser)
    {
 -    StructureImportSettings.defaultPDBFileParser = defaultPDBFileParser;
 +    getInstance().defaultPDBFileParser = defaultPDBFileParser;
    }
  
    public static void setDefaultPDBFileParser(String defaultPDBFileParser)
    {
 -    StructureImportSettings.defaultPDBFileParser = StructureParser
 +    getInstance().defaultPDBFileParser = StructureParser
-             .valueOf(defaultPDBFileParser.toUpperCase());
+             .valueOf(defaultPDBFileParser.toUpperCase(Locale.ROOT));
    }
  
  }
   */
  package jalview.structure;
  
 -import java.io.PrintStream;
 -import java.util.ArrayList;
 -import java.util.Arrays;
 -import java.util.Collections;
 -import java.util.Enumeration;
 -import java.util.HashMap;
 -import java.util.IdentityHashMap;
 -import java.util.List;
 -import java.util.Map;
 -import java.util.Vector;
 -
  import jalview.analysis.AlignSeq;
  import jalview.api.StructureSelectionManagerProvider;
 +import jalview.bin.ApplicationSingletonProvider;
 +import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.bin.Cache;
  import jalview.commands.CommandI;
  import jalview.commands.EditCommand;
@@@ -48,26 -57,16 +48,26 @@@ import jalview.util.Platform
  import jalview.ws.sifts.SiftsClient;
  import jalview.ws.sifts.SiftsException;
  import jalview.ws.sifts.SiftsSettings;
 +
 +import java.io.PrintStream;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collections;
 +import java.util.Enumeration;
 +import java.util.HashMap;
 +import java.util.IdentityHashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Vector;
 +
  import mc_view.Atom;
  import mc_view.PDBChain;
  import mc_view.PDBfile;
  
 -public class StructureSelectionManager
 +public class StructureSelectionManager implements ApplicationSingletonI
  {
    public final static String NEWLINE = System.lineSeparator();
  
 -  static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
 -
    private List<StructureMapping> mappings = new ArrayList<>();
  
    private boolean processSecondaryStructure = false;
  
    private List<SelectionListener> sel_listeners = new ArrayList<>();
  
 +  /*
 +   * instances of this class scoped by some context class
 +   */
 +  private IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> selectionManagers;
 +
 +  /**
 +   * Answers an instance of this class for the current application (Java or JS
 +   * 'applet') scope
 +   * 
 +   * @return
 +   */
 +  private static StructureSelectionManager getInstance()
 +  {
 +    return (StructureSelectionManager) ApplicationSingletonProvider
 +            .getInstance(StructureSelectionManager.class);
 +  }
 +
 +  /**
 +   * Private constructor as all 'singleton' instances are managed here or by
 +   * ApplicationSingletonProvider
 +   */
 +  private StructureSelectionManager()
 +  {
 +    selectionManagers = new IdentityHashMap<>();
 +  }
 +
 +  /**
 +   * Answers an instance of this class for the current application (Java or JS
 +   * 'applet') scope, and scoped to the specified context
 +   * 
 +   * @param context
 +   * @return
 +   */
 +  public static StructureSelectionManager getStructureSelectionManager(
 +          StructureSelectionManagerProvider context)
 +  {
 +    return getInstance().getInstanceForContext(context);
 +  }
 +
 +  /**
 +   * Answers an instance of this class scoped to the given context. The instance
 +   * is created on the first request for the context, thereafter the same
 +   * instance is returned. Note that the context may be null (this is the case
 +   * when running headless without a Desktop).
 +   * 
 +   * @param context
 +   * @return
 +   */
 +  StructureSelectionManager getInstanceForContext(
 +          StructureSelectionManagerProvider context)
 +  {
 +    StructureSelectionManager instance = selectionManagers.get(context);
 +    if (instance == null)
 +    {
 +      instance = new StructureSelectionManager();
 +      selectionManagers.put(context, instance);
 +    }
 +    return instance;
 +  }
 +
 +
    /**
     * @return true if will try to use external services for processing secondary
     *         structure
              || pdbIdFileName.containsKey(idOrFile);
    }
  
 -  private static StructureSelectionManager nullProvider = null;
 -
 -  public static StructureSelectionManager getStructureSelectionManager(
 -          StructureSelectionManagerProvider context)
 -  {
 -    if (context == null)
 -    {
 -      if (nullProvider == null)
 -      {
 -        if (instances != null)
 -        {
 -          throw new Error(MessageManager.getString(
 -                  "error.implementation_error_structure_selection_manager_null"),
 -                  new NullPointerException(MessageManager
 -                          .getString("exception.ssm_context_is_null")));
 -        }
 -        else
 -        {
 -          nullProvider = new StructureSelectionManager();
 -        }
 -        return nullProvider;
 -      }
 -    }
 -    if (instances == null)
 -    {
 -      instances = new java.util.IdentityHashMap<>();
 -    }
 -    StructureSelectionManager instance = instances.get(context);
 -    if (instance == null)
 -    {
 -      if (nullProvider != null)
 -      {
 -        instance = nullProvider;
 -      }
 -      else
 -      {
 -        instance = new StructureSelectionManager();
 -      }
 -      instances.put(context, instance);
 -    }
 -    return instance;
 -  }
 -
    /**
     * flag controlling whether SeqMappings are relayed from received sequence
     * mouse over events to other sequences
      return relaySeqMappings;
    }
  
 -  Vector listeners = new Vector();
 +  Vector<Object> listeners = new Vector<>();
  
    /**
     * register a listener for alignment sequence mouseover events
     * Import structure data and register a structure mapping for broadcasting
     * colouring, mouseovers and selection events (convenience wrapper).
     * 
 +   * This is the standard entry point.
 +   * 
     * @param sequence
     *          - one or more sequences to be mapped to pdbFile
     * @param targetChains
     * broadcasting colouring, mouseovers and selection events (convenience
     * wrapper).
     * 
 +   * 
 +   * 
     * @param forStructureView
 -   *          when true, record the mapping for use in mouseOvers
 +   *          when true (testng only), record the mapping for use in mouseOvers
 +   *          (testng only)
     * @param sequence
     *          - one or more sequences to be mapped to pdbFile
     * @param targetChains
     *          mapping operation
     * @return null or the structure data parsed as a pdb file
     */
 -  synchronized public StructureFile computeMapping(
 +  synchronized private StructureFile computeMapping(
            boolean forStructureView, SequenceI[] sequenceArray,
            String[] targetChainIds, String pdbFile, DataSourceType sourceType,
            IProgressIndicator progress)
          registerPDBFile(pdb.getId().trim(), pdbFile);
        }
        // if PDBId is unavailable then skip SIFTS mapping execution path
-       isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable();
+       // TODO: JAL-3868 need to know if structure is actually from 
+       // PDB (has valid PDB ID and has provenance suggesting it 
+       // actually came from PDB)
+       boolean isProtein = false;
+       for (SequenceI s:sequenceArray) {
+         if (s.isProtein()) {
+           isProtein = true;
+           break;
+         }
+       }
+       isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable() && !pdb.getId().startsWith("AF-") && isProtein;
  
      } catch (Exception ex)
      {
                      pdb, maxChain, sqmpping, maxAlignseq, siftsClient);
              seqToStrucMapping.add(siftsMapping);
              maxChain.makeExactMapping(siftsMapping, seq);
-             maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS");// FIXME: is this
-                                                        // "IEA:SIFTS" ?
+             maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS");
              maxChain.transferResidueAnnotation(siftsMapping, null);
              ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
  
            } catch (SiftsException e)
            {
              // fall back to NW alignment
-             System.err.println(e.getMessage());
+             Cache.log.error(e.getMessage());
              StructureMapping nwMapping = getNWMappings(seq, pdbFile,
                      targetChainId, maxChain, pdb, maxAlignseq);
              seqToStrucMapping.add(nwMapping);
          {
            ds = ds.getDatasetSequence();
          }
 -        ;
          if (ds.getAnnotation() != null)
          {
            for (AlignmentAnnotation ala : ds.getAnnotation())
          if (s != null)
          {
            result = s;
 -        }
        }
      }
 +  }
      return result;
    }
  
    }
  
    /**
 -   * Resets this object to its initial state by removing all registered
 -   * listeners, codon mappings, PDB file mappings
 +   * Reset this object to its initial state by removing all registered
 +   * listeners, codon mappings, PDB file mappings.
 +   * 
 +   * Called only by Desktop and testng.
 +   * 
     */
    public void resetAll()
    {
      }
    }
  
 -  public void addSelectionListener(SelectionListener selecter)
 +  public List<SelectionListener> getListeners() {
 +    return sel_listeners;
 +  }
 +  
 +   public void addSelectionListener(SelectionListener selecter)
    {
      if (!sel_listeners.contains(selecter))
      {
          {
            slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
          }
 -        ;
 +        
        }
      }
    }
  
 +  
    /**
 -   * release all references associated with this manager provider
 +   * Removes the instance associated with this provider
     * 
 -   * @param jalviewLite
 +   * @param provider
     */
 -  public static void release(StructureSelectionManagerProvider jalviewLite)
 +
 +  public static void release(StructureSelectionManagerProvider provider)
    {
 -    // synchronized (instances)
 -    {
 -      if (instances == null)
 -      {
 -        return;
 -      }
 -      StructureSelectionManager mnger = (instances.get(jalviewLite));
 -      if (mnger != null)
 -      {
 -        instances.remove(jalviewLite);
 -        try
 -        {
 -          /* bsoares 2019-03-20 finalize deprecated, no apparent external
 -           * resources to close
 -           */
 -          // mnger.finalize();
 -        } catch (Throwable x)
 -        {
 -        }
 -      }
 -    }
 +    getInstance().selectionManagers.remove(provider);
    }
 -
 +  
    public void registerPDBEntry(PDBEntry pdbentry)
    {
      if (pdbentry.getFile() != null
@@@ -24,6 -24,8 +24,8 @@@
  
  package jalview.util;
  
+ import java.util.Locale;
  import java.awt.Color;
  import java.util.HashMap;
  import java.util.Map;
@@@ -219,15 -221,12 +221,15 @@@ public class ColorUtil
      colour = colour.trim();
  
      Color col = null;
 -    try
 -    {
 -      int value = Integer.parseInt(colour, 16);
 -      col = new Color(value);
 -    } catch (NumberFormatException ex)
 +    if (StringUtils.isHexString(colour))
      {
 +      try
 +      {
 +        int value = Integer.parseInt(colour, 16);
 +        col = new Color(value);
 +      } catch (NumberFormatException ex)
 +      {
 +      }
      }
  
      if (col == null)
        return null;
      }
      Color col = null;
-     name = name.toLowerCase();
+     name = name.toLowerCase(Locale.ROOT);
  
      // or make a static map; or use reflection on the field name
      switch (name)
@@@ -256,19 -256,55 +256,55 @@@ public class Compariso
     */
    public static final boolean isGap(char c)
    {
 -    return (c == GAP_DASH || c == GAP_DOT || c == GAP_SPACE) ? true : false;
 +    return (c == GAP_DASH || c == GAP_DOT || c == GAP_SPACE);
    }
  
    /**
     * Overloaded method signature to test whether a single sequence is nucleotide
-    * (that is, more than 85% CGTA)
+    * (that is, more than 85% CGTAUNX)
     * 
     * @param seq
     * @return
     */
    public static final boolean isNucleotide(SequenceI seq)
    {
-     return isNucleotide(new SequenceI[] { seq });
+     if (seq==null)
+     {
+       return false;
+     }
+     long ntCount = 0;
+     long aaCount = 0;
+     long nCount = 0;
+     int len = seq.getLength();
+     for (int i = 0; i < len; i++)
+     {
+       char c = seq.getCharAt(i);
+       if (isNucleotide(c) || isX(c))
+       {
+         ntCount++;
+       }
+       else if (!isGap(c))
+       {
+         aaCount++;
+         if (isN(c))
+         {
+           nCount++;
+         }
+       }
+     }
+     /*
+      * Check for nucleotide count > 85% of total count (in a form that evades
+      * int / float conversion or divide by zero).
+      */
+     if ((ntCount+nCount) * 100 > EIGHTY_FIVE * (ntCount + aaCount))
+     {
+       return ntCount>0; // all N is considered protein. Could use a threshold here too
+     }
+     else
+     {
+       return false;
+     }
    }
  
    /**
      {
        return false;
      }
-     int ntCount = 0;
-     int aaCount = 0;
+     // true if we have seen a nucleotide sequence
+     boolean na = false;
      for (SequenceI seq : seqs)
      {
        if (seq == null)
        {
          continue;
        }
+       na = true;
        // TODO could possibly make an informed guess just from the first sequence
        // to save a lengthy calculation
-       int len = seq.getLength();
-       for (int i = 0; i < len; i++)
+       if (seq.isProtein())
        {
-         char c = seq.getCharAt(i);
-         if (isNucleotide(c))
-         {
-           ntCount++;
-         }
-         else if (!isGap(c))
-         {
-           aaCount++;
-         }
+         // if even one looks like protein, the alignment is protein
+         return false;
        }
      }
-     /*
-      * Check for nucleotide count > 85% of total count (in a form that evades
-      * int / float conversion or divide by zero).
-      */
-     if (ntCount * 100 > EIGHTY_FIVE * (ntCount + aaCount))
-     {
-       return true;
-     }
-     else
-     {
-       return false;
-     }
+     return na;
    }
  
    /**
      {
        c -= TO_UPPER_CASE;
      }
      switch (c)
      {
      case 'A':
      return false;
    }
  
+   public static boolean isN(char c)
+   {
+     switch (c)
+     {
+     case 'N':
+     case 'n':
+       return true;
+     }
+     return false;
+   }
+   public static boolean isX(char c)
+   {
+     switch (c)
+     {
+     case 'X':
+     case 'x':
+       return true;
+     }
+     return false;
+   }
    /**
     * Answers true if every character in the string is one of aAcCgGtTuU, or
     * (optionally) a gap character (dot, dash, space), else false
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.util;
  
+ import java.util.Locale;
  import java.util.ArrayList;
  import java.util.BitSet;
  import java.util.HashMap;
@@@ -73,7 -75,7 +75,7 @@@ public class DBRefUtil
            // guarantee we always have lowercase entries for canonical string lookups
            for (String k : canonicalSourceNameLookup.keySet())
            {
-             canonicalSourceNameLookup.put(k.toLowerCase(),
+             canonicalSourceNameLookup.put(k.toLowerCase(Locale.ROOT),
                      canonicalSourceNameLookup.get(k));
            }
     }
        HashSet<String> srcs = new HashSet<String>();
        for (String src : sources) 
        {
-         srcs.add(src.toUpperCase());
+         srcs.add(src.toUpperCase(Locale.ROOT));
        }
  
        int nrefs = dbrefs.size();
        {
          DBRefEntry dbr = dbrefs.get(ib);
          String source = getCanonicalName(dbr.getSource());
-         if (srcs.contains(source.toUpperCase())) 
+         if (srcs.contains(source.toUpperCase(Locale.ROOT))) 
          {
            res.add(dbr);
          }
          {
                return null;
          }
-         String canonical = canonicalSourceNameLookup.get(source.toLowerCase());
+         String canonical = canonicalSourceNameLookup.get(source.toLowerCase(Locale.ROOT));
          return canonical == null ? source : canonical;
        }
  
  
        };
  
 +  private static Regex PARSE_REGEX;
 +
 +  private static Regex getParseRegex()
 +  {
 +    return (PARSE_REGEX == null ? PARSE_REGEX = Platform.newRegex(
 +            "([0-9][0-9A-Za-z]{3})\\s*(.?)\\s*;\\s*([0-9]+)-([0-9]+)")
 +            : PARSE_REGEX);
 +  }
 +
    /**
     * Parses a DBRefEntry and adds it to the sequence, also a PDBEntry if the
     * database is PDB.
           * Check for PFAM style stockhom PDB accession id citation e.g.
           * "1WRI A; 7-80;"
           */
 -        Regex r = new com.stevesoft.pat.Regex(
 -                "([0-9][0-9A-Za-z]{3})\\s*(.?)\\s*;\\s*([0-9]+)-([0-9]+)");
 +        Regex r = getParseRegex();
          if (r.search(acn.trim()))
          {
            String pdbid = r.stringMatched(1);
   */
  package jalview.util;
  
 +import java.io.BufferedInputStream;
 +import java.io.File;
 +import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
+ import java.net.HttpURLConnection;
+ import java.net.ProtocolException;
  import java.net.URL;
+ import java.util.List;
+ import javax.ws.rs.HttpMethod;
  
  public class HttpUtils
  {
      }
      return false;
    }
 -
+   public static boolean startsWithHttpOrHttps(String file)
+   {
+     return file.startsWith("http://") || file.startsWith("https://");
+   }
+   
 -
+   /**
+    * wrapper to get/post to a URL or check headers
+    * @param url
+    * @param ids
+    * @param readTimeout
+    * @return
+    * @throws IOException
+    * @throws ProtocolException
+    */
+   public static boolean checkUrlAvailable(URL url,
+           int readTimeout) throws IOException, ProtocolException
+   {
+     // System.out.println(System.currentTimeMillis() + " " + url);
+     HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+     connection.setRequestMethod(HttpMethod.HEAD);
+     connection.setDoInput(true);
+     connection.setUseCaches(false);
+     connection.setConnectTimeout(300);
+     connection.setReadTimeout(readTimeout);
+     return connection.getResponseCode() == 200;
+   }
  
 +  /**
 +   * download from given URL and return a pointer to temporary file
 +   */
 +  public static File fetchURLToTemp(String url) throws OutOfMemoryError,
 +          IOException
 +  {
 +    long time = System.currentTimeMillis();
 +    URL rcall = new URL(url);
 +
 +    InputStream is = new BufferedInputStream(rcall.openStream());
 +    File outFile = null;
 +    try
 +    {
 +      outFile = File.createTempFile("jalview", ".xml");
 +      outFile.deleteOnExit();
 +      if (outFile.length() == 0)
 +      {
 +        outFile.delete();
 +        return null;
 +      }
 +    } catch (Exception ex)
 +    {
 +    }
 +
 +    if (outFile != null)
 +    {
 +      FileOutputStream fio = new FileOutputStream(outFile);
 +      byte[] bb = new byte[32 * 1024];
 +      int l;
 +      while ((l = is.read(bb)) > 0)
 +      {
 +        fio.write(bb, 0, l);
 +      }
 +      fio.close();
 +      is.close();
 +      return outFile;
 +    }
 +    else
 +    {
 +      return null;
 +    }
 +  }
  }
@@@ -22,6 -22,7 +22,7 @@@ package jalview.util
  
  import java.util.ArrayList;
  import java.util.Arrays;
+ import java.util.BitSet;
  import java.util.List;
  
  /**
@@@ -30,8 -31,6 +31,6 @@@
   * 
   * Use at your own risk!
   * 
-  * TODO: efficient implementation of private posMap method
-  * 
   * TODO: test/ensure that sense of from and to ratio start position is conserved
   * (codon start position recovery)
   */
@@@ -209,8 -208,7 +208,7 @@@ public class MapLis
  
    /**
     * Constructor given from and to ranges as [start1, end1, start2, end2,...].
-    * If any end is equal to the next start, the ranges will be merged. There is
-    * no validation check that the ranges do not overlap each other.
+    * There is no validation check that the ranges do not overlap each other.
     * 
     * @param from
     *          contiguous regions as [start1, end1, start2, end2, ...]
      this.toRatio = toRatio;
      fromLowest = Integer.MAX_VALUE;
      fromHighest = Integer.MIN_VALUE;
-     int added = 0;
  
      for (int i = 0; i < from.length; i += 2)
      {
         */
        fromLowest = Math.min(fromLowest, Math.min(from[i], from[i + 1]));
        fromHighest = Math.max(fromHighest, Math.max(from[i], from[i + 1]));
-       if (added > 0 && from[i] == fromShifts.get(added - 1)[1])
-       {
-         /*
-          * this range starts where the last ended - just extend it
-          */
-         fromShifts.get(added - 1)[1] = from[i + 1];
-       }
-       else
-       {
-         fromShifts.add(new int[] { from[i], from[i + 1] });
-         added++;
-       }
+       fromShifts.add(new int[] { from[i], from[i + 1] });
      }
  
      toLowest = Integer.MAX_VALUE;
      toHighest = Integer.MIN_VALUE;
-     added = 0;
      for (int i = 0; i < to.length; i += 2)
      {
        toLowest = Math.min(toLowest, Math.min(to[i], to[i + 1]));
        toHighest = Math.max(toHighest, Math.max(to[i], to[i + 1]));
-       if (added > 0 && to[i] == toShifts.get(added - 1)[1])
-       {
-         toShifts.get(added - 1)[1] = to[i + 1];
-       }
-       else
-       {
-         toShifts.add(new int[] { to[i], to[i + 1] });
-         added++;
-       }
+       toShifts.add(new int[] { to[i], to[i + 1] });
      }
    }
  
        if (range.length != 2)
        {
          // throw new IllegalArgumentException(range);
-         System.err.println(
-                 "Invalid format for fromRange " + Arrays.toString(range)
-                 + " may cause errors");
+         System.err.println("Invalid format for fromRange "
+                 + Arrays.toString(range) + " may cause errors");
        }
        fromLowest = Math.min(fromLowest, Math.min(range[0], range[1]));
        fromHighest = Math.max(fromHighest, Math.max(range[0], range[1]));
        {
          // throw new IllegalArgumentException(range);
          System.err.println("Invalid format for toRange "
-                 + Arrays.toString(range)
-                 + " may cause errors");
+                 + Arrays.toString(range) + " may cause errors");
        }
        toLowest = Math.min(toLowest, Math.min(range[0], range[1]));
        toHighest = Math.max(toHighest, Math.max(range[0], range[1]));
    /**
     * Consolidates a list of ranges so that any contiguous ranges are merged.
     * This assumes the ranges are already in start order (does not sort them).
+    * <p>
+    * The main use case for this method is when mapping cDNA sequence to its
+    * protein product, based on CDS feature ranges which derive from spliced
+    * exons, but are contiguous on the cDNA sequence. For example
+    * 
+    * <pre>
+    *   CDS 1-20  // from exon1
+    *   CDS 21-35 // from exon2
+    *   CDS 36-71 // from exon3
+    * 'coalesce' to range 1-71
+    * </pre>
     * 
     * @param ranges
     * @return the same list (if unchanged), else a new merged list, leaving the
          first = false;
          continue;
        }
-       if (range[0] == lastRange[0] && range[1] == lastRange[1])
-       {
-         // drop duplicate range
-         changed = true;
-         continue;
-       }
-       /*
-        * drop this range if it lies within the last range
-        */
-       if ((lastDirection == 1 && range[0] >= lastRange[0]
-               && range[0] <= lastRange[1] && range[1] >= lastRange[0]
-               && range[1] <= lastRange[1])
-               || (lastDirection == -1 && range[0] <= lastRange[0]
-                       && range[0] >= lastRange[1]
-                       && range[1] <= lastRange[0]
-                       && range[1] >= lastRange[1]))
-       {
-         changed = true;
-         continue;
-       }
  
        int direction = range[1] >= range[0] ? 1 : -1;
  
        boolean sameDirection = range[1] == range[0]
                || direction == lastDirection;
        boolean extending = range[0] == lastRange[1] + lastDirection;
-       boolean overlapping = (lastDirection == 1 && range[0] >= lastRange[0]
-               && range[0] <= lastRange[1])
-               || (lastDirection == -1 && range[0] <= lastRange[0]
-                       && range[0] >= lastRange[1]);
-       if (sameDirection && (overlapping || extending))
+       if (sameDirection && extending)
        {
          lastRange[1] = range[1];
          changed = true;
     */
    protected int[][] makeFromMap()
    {
-     // TODO not used - remove??
+     // TODO only used for test - remove??
      return posMap(fromShifts, fromRatio, toShifts, toRatio);
    }
  
     */
    protected int[][] makeToMap()
    {
-     // TODO not used - remove??
+     // TODO only used for test - remove??
      return posMap(toShifts, toRatio, fromShifts, fromRatio);
    }
  
     * @return int[] { from, to pos in range }, int[range.to-range.from+1]
     *         returning mapped position
     */
-   private int[][] posMap(List<int[]> shiftTo, int ratio,
-           List<int[]> shiftFrom, int toRatio)
+   private int[][] posMap(List<int[]> shiftTo, int sourceRatio,
+           List<int[]> shiftFrom, int targetRatio)
    {
-     // TODO not used - remove??
+     // TODO only used for test - remove??
      int iv = 0, ivSize = shiftTo.size();
      if (iv >= ivSize)
      {
      int mp[][] = new int[to - from + 2][];
      for (int i = 0; i < mp.length; i++)
      {
-       int[] m = shift(i + from, shiftTo, ratio, shiftFrom, toRatio);
+       int[] m = shift(i + from, shiftTo, sourceRatio, shiftFrom, targetRatio);
        if (m != null)
        {
          if (i == 0)
            List<int[]> shiftFrom, int toRatio)
    {
      // TODO: javadoc; tests
-     int[] fromCount = countPos(shiftTo, pos);
+     int[] fromCount = countPositions(shiftTo, pos);
      if (fromCount == null)
      {
        return null;
      }
      int fromRemainder = (fromCount[0] - 1) % fromRatio;
      int toCount = 1 + (((fromCount[0] - 1) / fromRatio) * toRatio);
-     int[] toPos = countToPos(shiftFrom, toCount);
+     int[] toPos = traverseToPosition(shiftFrom, toCount);
      if (toPos == null)
      {
-       return null; // throw new Error("Bad Mapping!");
+       return null;
      }
-     // System.out.println(fromCount[0]+" "+fromCount[1]+" "+toCount);
      return new int[] { toPos[0], fromRemainder, toPos[1] };
    }
  
    /**
-    * count how many positions pos is along the series of intervals.
+    * Counts how many positions pos is along the series of intervals. Returns an
+    * array of two values:
+    * <ul>
+    * <li>the number of positions traversed (inclusive) to reach {@code pos}</li>
+    * <li>+1 if the last interval traversed is forward, -1 if in a negative
+    * direction</li>
+    * </ul>
+    * Returns null if {@code pos} does not lie in any of the given intervals.
     * 
-    * @param shiftTo
+    * @param intervals
+    *          a list of start-end intervals
     * @param pos
-    * @return number of positions or null if pos is not within intervals
+    *          a position that may lie in one (or more) of the intervals
+    * @return
     */
-   protected static int[] countPos(List<int[]> shiftTo, int pos)
+   protected static int[] countPositions(List<int[]> intervals, int pos)
    {
-     int count = 0, intv[], iv = 0, ivSize = shiftTo.size();
+     int count = 0;
+     int iv = 0;
+     int ivSize = intervals.size();
      while (iv < ivSize)
      {
-       intv = shiftTo.get(iv++);
+       int[] intv = intervals.get(iv++);
        if (intv[0] <= intv[1])
        {
+         /*
+          * forwards interval
+          */
          if (pos >= intv[0] && pos <= intv[1])
          {
            return new int[] { count + pos - intv[0] + 1, +1 };
        }
        else
        {
+         /*
+          * reverse interval
+          */
          if (pos >= intv[1] && pos <= intv[0])
          {
            return new int[] { count + intv[0] - pos + 1, -1 };
    }
  
    /**
-    * count out pos positions into a series of intervals and return the position
+    * Reads through the given intervals until {@code count} positions have been
+    * traversed, and returns an array consisting of two values:
+    * <ul>
+    * <li>the value at the {@code count'th} position</li>
+    * <li>+1 if the last interval read is forwards, -1 if reverse direction</li>
+    * </ul>
+    * Returns null if the ranges include less than {@code count} positions, or if
+    * {@code count < 1}.
     * 
-    * @param shiftFrom
-    * @param pos
-    * @return position pos in interval set
+    * @param intervals
+    *          a list of [start, end] ranges
+    * @param count
+    *          the number of positions to traverse
+    * @return
     */
-   protected static int[] countToPos(List<int[]> shiftFrom, int pos)
+   protected static int[] traverseToPosition(List<int[]> intervals,
+           final int count)
    {
-     int count = 0, diff = 0, iv = 0, ivSize = shiftFrom.size();
-     int[] intv = { 0, 0 };
+     int traversed = 0;
+     int ivSize = intervals.size();
+     int iv = 0;
+     if (count < 1)
+     {
+       return null;
+     }
      while (iv < ivSize)
      {
-       intv = shiftFrom.get(iv++);
-       diff = intv[1] - intv[0];
+       int[] intv = intervals.get(iv++);
+       int diff = intv[1] - intv[0];
        if (diff >= 0)
        {
-         if (pos <= count + 1 + diff)
+         if (count <= traversed + 1 + diff)
          {
-           return new int[] { pos - count - 1 + intv[0], +1 };
+           return new int[] { intv[0] + (count - traversed - 1), +1 };
          }
          else
          {
-           count += 1 + diff;
+           traversed += 1 + diff;
          }
        }
        else
        {
-         if (pos <= count + 1 - diff)
+         if (count <= traversed + 1 - diff)
          {
-           return new int[] { intv[0] - (pos - count - 1), -1 };
+           return new int[] { intv[0] - (count - traversed - 1), -1 };
          }
          else
          {
-           count += 1 - diff;
+           traversed += 1 - diff;
          }
        }
      }
-     return null;// (diff<0) ? (intv[1]-1) : (intv[0]+1);
-   }
-   /**
-    * find series of intervals mapping from start-end in the From map.
-    * 
-    * @param start
-    *          position mapped 'to'
-    * @param end
-    *          position mapped 'to'
-    * @return series of [start, end] ranges in sequence mapped 'from'
-    */
-   public int[] locateInFrom(int start, int end)
-   {
-     // inefficient implementation
-     int fromStart[] = shiftTo(start);
-     // needs to be inclusive of end of symbol position
-     int fromEnd[] = shiftTo(end);
-     return getIntervals(fromShifts, fromStart, fromEnd, fromRatio);
-   }
-   /**
-    * find series of intervals mapping from start-end in the to map.
-    * 
-    * @param start
-    *          position mapped 'from'
-    * @param end
-    *          position mapped 'from'
-    * @return series of [start, end] ranges in sequence mapped 'to'
-    */
-   public int[] locateInTo(int start, int end)
-   {
-     int toStart[] = shiftFrom(start);
-     int toEnd[] = shiftFrom(end);
-     return getIntervals(toShifts, toStart, toEnd, toRatio);
+     return null;
    }
  
    /**
     */
    public int getToPosition(int mpos)
    {
-     // TODO not used - remove??
      int[] mp = shiftTo(mpos);
      if (mp != null)
      {
    }
  
    /**
-    * get range of positions in To frame for the mpos word in From
-    * 
-    * @param mpos
-    *          position in From
-    * @return null or int[] first position in To for mpos, last position in to
-    *         for Mpos
-    */
-   public int[] getToWord(int mpos)
-   {
-     int[] mp = shiftTo(mpos);
-     if (mp != null)
-     {
-       return new int[] { mp[0], mp[0] + mp[2] * (getFromRatio() - 1) };
-     }
-     return null;
-   }
-   /**
-    * get From position in the associated reference frame for position pos in the
-    * associated sequence.
-    * 
-    * @param pos
-    * @return
-    */
-   public int getMappedPosition(int pos)
-   {
-     // TODO not used - remove??
-     int[] mp = shiftFrom(pos);
-     if (mp != null)
-     {
-       return mp[0];
-     }
-     return pos;
-   }
-   public int[] getMappedWord(int pos)
-   {
-     // TODO not used - remove??
-     int[] mp = shiftFrom(pos);
-     if (mp != null)
-     {
-       return new int[] { mp[0], mp[0] + mp[2] * (getToRatio() - 1) };
-     }
-     return null;
-   }
-   /**
     * 
     * @return a MapList whose From range is this maplist's To Range, and vice
     *         versa
    }
  
    /**
-    * test for containment rather than equivalence to another mapping
-    * 
-    * @param map
-    *          to be tested for containment
-    * @return true if local or mapped range map contains or is contained by this
-    *         mapping
-    */
-   public boolean containsEither(boolean local, MapList map)
-   {
-     // TODO not used - remove?
-     if (local)
-     {
-       return ((getFromLowest() >= map.getFromLowest()
-               && getFromHighest() <= map.getFromHighest())
-               || (getFromLowest() <= map.getFromLowest()
-                       && getFromHighest() >= map.getFromHighest()));
-     }
-     else
-     {
-       return ((getToLowest() >= map.getToLowest()
-               && getToHighest() <= map.getToHighest())
-               || (getToLowest() <= map.getToLowest()
-                       && getToHighest() >= map.getToHighest()));
-     }
-   }
-   /**
     * String representation - for debugging, not guaranteed not to change
     */
    @Override
  
      for (int[] range : map.getFromRanges())
      {
 -      addRange(range, fromShifts);
 +      MappingUtils.addRange(range, fromShifts);
      }
      for (int[] range : map.getToRanges())
      {
 -      addRange(range, toShifts);
 +      MappingUtils.addRange(range, toShifts);
      }
    }
  
    /**
 -   * Adds the given range to a list of ranges. If the new range just extends
 -   * existing ranges, the current endpoint is updated instead.
 -   * 
 -   * @param range
 -   * @param addTo
 -   */
 -  static void addRange(int[] range, List<int[]> addTo)
 -  {
 -    /*
 -     * list is empty - add to it!
 -     */
 -    if (addTo.size() == 0)
 -    {
 -      addTo.add(range);
 -      return;
 -    }
 -
 -    int[] last = addTo.get(addTo.size() - 1);
 -    boolean lastForward = last[1] >= last[0];
 -    boolean newForward = range[1] >= range[0];
 -
 -    /*
 -     * contiguous range in the same direction - just update endpoint
 -     */
 -    if (lastForward == newForward && last[1] == range[0])
 -    {
 -      last[1] = range[1];
 -      return;
 -    }
 -
 -    /*
 -     * next range starts at +1 in forward sense - update endpoint
 -     */
 -    if (lastForward && newForward && range[0] == last[1] + 1)
 -    {
 -      last[1] = range[1];
 -      return;
 -    }
 -
 -    /*
 -     * next range starts at -1 in reverse sense - update endpoint
 -     */
 -    if (!lastForward && !newForward && range[0] == last[1] - 1)
 -    {
 -      last[1] = range[1];
 -      return;
 -    }
 -
 -    /*
 -     * just add the new range
 -     */
 -    addTo.add(range);
 -  }
 -
 -  /**
     * Returns true if mapping is from forward strand, false if from reverse
     * strand. Result is just based on the first 'from' range that is not a single
     * position. Default is true unless proven to be false. Behaviour is not well
    }
  
    /**
-    * A helper method that returns true unless at least one range has start > end.
-    * Behaviour is undefined for a mixture of forward and reverse ranges.
+    * A helper method that returns true unless at least one range has start >
+    * end. Behaviour is undefined for a mixture of forward and reverse ranges.
     * 
     * @param ranges
     * @return
      List<int[]> toRanges = new ArrayList<>();
      for (int[] range : getToRanges())
      {
+       int fromLength = Math.abs(range[1] - range[0]) + 1;
        int[] transferred = map.locateInTo(range[0], range[1]);
        if (transferred == null || transferred.length % 2 != 0)
        {
         *  convert [start1, end1, start2, end2, ...] 
         *  to [[start1, end1], [start2, end2], ...]
         */
+       int toLength = 0;
        for (int i = 0; i < transferred.length;)
        {
          toRanges.add(new int[] { transferred[i], transferred[i + 1] });
+         toLength += Math.abs(transferred[i + 1] - transferred[i]) + 1;
          i += 2;
        }
+       /*
+        * check we mapped the full range - if not, abort
+        */
+       if (fromLength * map.getToRatio() != toLength * map.getFromRatio())
+       {
+         return null;
+       }
      }
  
      return new MapList(getFromRanges(), toRanges, outFromRatio, outToRatio);
    {
      return fromShifts.size() == 1 && toShifts.size() == 1;
    }
+   /**
+    * Returns the [start1, end1, start2, end2, ...] positions in the 'from' range
+    * that map to positions between {@code start} and {@code end} in the 'to'
+    * range. Note that for a reverse strand mapping this will return ranges with
+    * end < start. Returns null if no mapped positions are found in start-end.
+    * 
+    * @param start
+    * @param end
+    * @return
+    */
+   public int[] locateInFrom(int start, int end)
+   {
+     return mapPositions(start, end, toShifts, fromShifts,
+             toRatio, fromRatio);
+   }
+   /**
+    * Returns the [start1, end1, start2, end2, ...] positions in the 'to' range
+    * that map to positions between {@code start} and {@code end} in the 'from'
+    * range. Note that for a reverse strand mapping this will return ranges with
+    * end < start. Returns null if no mapped positions are found in start-end.
+    * 
+    * @param start
+    * @param end
+    * @return
+    */
+   public int[] locateInTo(int start, int end)
+   {
+     return mapPositions(start, end, fromShifts, toShifts,
+             fromRatio, toRatio);
+   }
+   /**
+    * Helper method that returns the [start1, end1, start2, end2, ...] positions
+    * in {@code targetRange} that map to positions between {@code start} and
+    * {@code end} in {@code sourceRange}. Note that for a reverse strand mapping
+    * this will return ranges with end < start. Returns null if no mapped
+    * positions are found in start-end.
+    * 
+    * @param start
+    * @param end
+    * @param sourceRange
+    * @param targetRange
+    * @param sourceWordLength
+    * @param targetWordLength
+    * @return
+    */
+   final static int[] mapPositions(int start, int end,
+           List<int[]> sourceRange, List<int[]> targetRange,
+           int sourceWordLength, int targetWordLength)
+   {
+     if (end < start)
+     {
+       int tmp = end;
+       end = start;
+       start = tmp;
+     }
+     /*
+      * traverse sourceRange and mark offsets in targetRange 
+      * of any positions that lie in [start, end]
+      */
+     BitSet offsets = getMappedOffsetsForPositions(start, end, sourceRange,
+             sourceWordLength, targetWordLength);
+     /*
+      * traverse targetRange and collect positions at the marked offsets
+      */
+     List<int[]> mapped = getPositionsForOffsets(targetRange, offsets);
+     // TODO: or just return the List and adjust calling code to match
+     return mapped.isEmpty() ? null : MappingUtils.rangeListToArray(mapped);
+   }
+   /**
+    * Scans the list of {@code ranges} for any values (positions) that lie
+    * between start and end (inclusive), and records the <em>offsets</em> from
+    * the start of the list as a BitSet. The offset positions are converted to
+    * corresponding words in blocks of {@code wordLength2}.
+    * 
+    * <pre>
+    * For example:
+    * 1:1 (e.g. gene to CDS):
+    * ranges { [10-20], [31-40] }, wordLengthFrom = wordLength 2 = 1
+    *   for start = 1, end = 9, returns a BitSet with no bits set
+    *   for start = 1, end = 11, returns a BitSet with bits 0-1 set
+    *   for start = 15, end = 35, returns a BitSet with bits 5-15 set
+    * 1:3 (peptide to codon):
+    * ranges { [1-200] }, wordLengthFrom = 1, wordLength 2 = 3
+    *   for start = 9, end = 9, returns a BitSet with bits 24-26 set
+    * 3:1 (codon to peptide):
+    * ranges { [101-150], [171-180] }, wordLengthFrom = 3, wordLength 2 = 1
+    *   for start = 101, end = 102 (partial first codon), returns a BitSet with bit 0 set
+    *   for start = 150, end = 171 (partial 17th codon), returns a BitSet with bit 16 set
+    * 3:1 (circular DNA to peptide):
+    * ranges { [101-150], [21-30] }, wordLengthFrom = 3, wordLength 2 = 1
+    *   for start = 24, end = 40 (spans codons 18-20), returns a BitSet with bits 17-19 set
+    * </pre>
+    * 
+    * @param start
+    * @param end
+    * @param sourceRange
+    * @param sourceWordLength
+    * @param targetWordLength
+    * @return
+    */
+   protected final static BitSet getMappedOffsetsForPositions(int start,
+           int end, List<int[]> sourceRange, int sourceWordLength, int targetWordLength)
+   {
+     BitSet overlaps = new BitSet();
+     int offset = 0;
+     final int s1 = sourceRange.size();
+     for (int i = 0; i < s1; i++)
+     {
+       int[] range = sourceRange.get(i);
+       final int offset1 = offset;
+       int overlapStartOffset = -1;
+       int overlapEndOffset = -1;
+       if (range[1] >= range[0])
+       {
+         /*
+          * forward direction range
+          */
+         if (start <= range[1] && end >= range[0])
+         {
+           /*
+            * overlap
+            */
+           int overlapStart = Math.max(start, range[0]);
+           overlapStartOffset = offset1 + overlapStart - range[0];
+           int overlapEnd = Math.min(end, range[1]);
+           overlapEndOffset = offset1 + overlapEnd - range[0];
+         }
+       }
+       else
+       {
+         /*
+          * reverse direction range
+          */
+         if (start <= range[0] && end >= range[1])
+         {
+           /*
+            * overlap
+            */
+           int overlapStart = Math.max(start, range[1]);
+           int overlapEnd = Math.min(end, range[0]);
+           overlapStartOffset = offset1 + range[0] - overlapEnd;
+           overlapEndOffset = offset1 + range[0] - overlapStart;
+         }
+       }
+       if (overlapStartOffset > -1)
+       {
+         /*
+          * found an overlap
+          */
+         if (sourceWordLength != targetWordLength)
+         {
+           /*
+            * convert any overlap found to whole words in the target range
+            * (e.g. treat any partial codon overlap as if the whole codon)
+            */
+           overlapStartOffset -= overlapStartOffset % sourceWordLength;
+           overlapStartOffset = overlapStartOffset / sourceWordLength
+                   * targetWordLength;
+           /*
+            * similar calculation for range end, adding 
+            * (wordLength2 - 1) for end of mapped word
+            */
+           overlapEndOffset -= overlapEndOffset % sourceWordLength;
+           overlapEndOffset = overlapEndOffset / sourceWordLength
+                   * targetWordLength;
+           overlapEndOffset += targetWordLength - 1;
+         }
+         overlaps.set(overlapStartOffset, overlapEndOffset + 1);
+       }
+       offset += 1 + Math.abs(range[1] - range[0]);
+     }
+     return overlaps;
+   }
+   /**
+    * Returns a (possibly empty) list of the [start-end] values (positions) at
+    * offsets in the {@code targetRange} list that are marked by 'on' bits in the
+    * {@code offsets} bitset.
+    * 
+    * @param targetRange
+    * @param offsets
+    * @return
+    */
+   protected final static List<int[]> getPositionsForOffsets(
+           List<int[]> targetRange, BitSet offsets)
+   {
+     List<int[]> mapped = new ArrayList<>();
+     if (offsets.isEmpty())
+     {
+       return mapped;
+     }
+     /*
+      * count of positions preceding ranges[i]
+      */
+     int traversed = 0;
+     /*
+      * for each [from-to] range in ranges:
+      * - find subranges (if any) at marked offsets
+      * - add the start-end values at the marked positions
+      */
+     final int toAdd = offsets.cardinality();
+     int added = 0;
+     final int s2 = targetRange.size();
+     for (int i = 0; added < toAdd && i < s2; i++)
+     {
+       int[] range = targetRange.get(i);
+       added += addOffsetPositions(mapped, traversed, range, offsets);
+       traversed += Math.abs(range[1] - range[0]) + 1;
+     }
+     return mapped;
+   }
+   /**
+    * Helper method that adds any start-end subranges of {@code range} that are
+    * at offsets in {@code range} marked by set bits in overlaps.
+    * {@code mapOffset} is added to {@code range} offset positions. Returns the
+    * count of positions added.
+    * 
+    * @param mapped
+    * @param mapOffset
+    * @param range
+    * @param overlaps
+    * @return
+    */
+   final static int addOffsetPositions(List<int[]> mapped,
+           final int mapOffset, final int[] range, final BitSet overlaps)
+   {
+     final int rangeLength = 1 + Math.abs(range[1] - range[0]);
+     final int step = range[1] < range[0] ? -1 : 1;
+     int offsetStart = 0; // offset into range
+     int added = 0;
+     while (offsetStart < rangeLength)
+     {
+       /*
+        * find the start of the next marked overlap offset;
+        * if there is none, or it is beyond range, then finished
+        */
+       int overlapStart = overlaps.nextSetBit(mapOffset + offsetStart);
+       if (overlapStart == -1 || overlapStart - mapOffset >= rangeLength)
+       {
+         /*
+          * no more overlaps, or no more within range[]
+          */
+         return added;
+       }
+       overlapStart -= mapOffset;
+       /*
+        * end of the overlap range is just before the next clear bit;
+        * restrict it to end of range if necessary;
+        * note we may add a reverse strand range here (end < start)
+        */
+       int overlapEnd = overlaps.nextClearBit(mapOffset + overlapStart + 1);
+       overlapEnd = (overlapEnd == -1) ? rangeLength - 1
+               : Math.min(rangeLength - 1, overlapEnd - mapOffset - 1);
+       int startPosition = range[0] + step * overlapStart;
+       int endPosition = range[0] + step * overlapEnd;
+       mapped.add(new int[] { startPosition, endPosition });
+       offsetStart = overlapEnd + 1;
+       added += Math.abs(endPosition - startPosition) + 1;
+     }
+     return added;
+   }
  }
   */
  package jalview.util;
  
 -import java.util.ArrayList;
 -import java.util.Arrays;
 -import java.util.HashMap;
 -import java.util.Iterator;
 -import java.util.List;
 -import java.util.Map;
 -
  import jalview.analysis.AlignmentSorter;
  import jalview.api.AlignViewportI;
  import jalview.commands.CommandI;
@@@ -39,13 -46,6 +39,13 @@@ import jalview.datamodel.Sequence
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
  
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.HashMap;
 +import java.util.Iterator;
 +import java.util.List;
 +import java.util.Map;
 +
  /**
   * Helper methods for manipulations involving sequence mappings.
   * 
@@@ -546,8 -546,7 +546,8 @@@ public final class MappingUtil
      while (regions.hasNext())
      {
        mapHiddenColumns(regions.next(), codonFrames, newHidden,
 -              fromSequences, toSequences, fromGapChar);
 +              fromSequences,
 +              toSequences, fromGapChar);
      }
      return; // mappedColumns;
    }
  
      int min = Math.min(range[0], range[1]);
      int max = Math.max(range[0], range[1]);
 -
 +  
      return (min <= queryRange[0] && max >= queryRange[0]
              && min <= queryRange[1] && max >= queryRange[1]);
    }
     *          a list of (single) [start, end] ranges
     * @return
     */
 -  public static void removeEndPositions(int positions, List<int[]> ranges)
 +  public static void removeEndPositions(int positions,
 +          List<int[]> ranges)
    {
      int toRemove = positions;
      Iterator<int[]> it = new ReverseListIterator<>(ranges);
          /*
           * not coded for [start1, end1, start2, end2, ...]
           */
 -        System.err.println(
 -                "MappingUtils.removeEndPositions doesn't handle multiple  ranges");
 +        System.err
 +                .println("MappingUtils.removeEndPositions doesn't handle multiple  ranges");
          return;
        }
  
          /*
           * not coded for a reverse strand range (end < start)
           */
 -        System.err.println(
 -                "MappingUtils.removeEndPositions doesn't handle reverse strand");
 +        System.err
 +                .println("MappingUtils.removeEndPositions doesn't handle reverse strand");
          return;
        }
        if (length > toRemove)
        }
      }
    }
 +  /**
 +   * Adds the given range to a list of ranges. If the new range just extends
 +   * existing ranges, the current endpoint is updated instead.
 +   * 
 +   * @param range
 +   * @param addTo
 +   */
 +  public static void addRange(int[] range, List<int[]> addTo)
 +  {
 +    /*
 +     * list is empty - add to it!
 +     */
 +    if (addTo.size() == 0)
 +    {
 +      addTo.add(range);
 +      return;
 +    }
 +  
 +    int[] last = addTo.get(addTo.size() - 1);
 +    boolean lastForward = last[1] >= last[0];
 +    boolean newForward = range[1] >= range[0];
 +  
 +    /*
 +     * contiguous range in the same direction - just update endpoint
 +     */
 +    if (lastForward == newForward && last[1] == range[0])
 +    {
 +      last[1] = range[1];
 +      return;
 +    }
 +  
 +    /*
 +     * next range starts at +1 in forward sense - update endpoint
 +     */
 +    if (lastForward && newForward && range[0] == last[1] + 1)
 +    {
 +      last[1] = range[1];
 +      return;
 +    }
 +  
 +    /*
 +     * next range starts at -1 in reverse sense - update endpoint
 +     */
 +    if (!lastForward && !newForward && range[0] == last[1] - 1)
 +    {
 +      last[1] = range[1];
 +      return;
 +    }
 +  
 +    /*
 +     * just add the new range
 +     */
 +    addTo.add(range);
 +  }
+   /**
+    * Converts a list of {@code start-end} ranges to a single array of
+    * {@code start1, end1, start2, ... } ranges
+    * 
+    * @param ranges
+    * @return
+    */
+   public static int[] rangeListToArray(List<int[]> ranges)
+   {
+     int rangeCount = ranges.size();
+     int[] result = new int[rangeCount * 2];
+     int j = 0;
+     for (int i = 0; i < rangeCount; i++)
+     {
+       int[] range = ranges.get(i);
+       result[j++] = range[0];
+       result[j++] = range[1];
+     }
+     return result;
+   }
  }
@@@ -57,7 -57,8 +57,7 @@@ public class MessageManage
        // Locale.setDefault(loc);
        /* Getting messages for GV */
        log.info("Getting messages for lang: " + loc);
 -      Control control = Control.getControl(Control.FORMAT_PROPERTIES);
 -      rb = ResourceBundle.getBundle("lang.Messages", loc, control);
 +      rb = ResourceBundle.getBundle("lang.Messages", Platform.getLocaleOrNone(loc), Control.getControl(Control.FORMAT_PROPERTIES));
        // if (log.isLoggable(Level.FINEST))
        // {
        // // this might take a while, so we only do it if it will be shown
@@@ -93,7 -94,7 +93,7 @@@
      } catch (Exception e)
      {
        String msg = "I18N missing: " + loc + "\t" + key;
 -        logWarning(key, msg);
 +    logWarning(key, msg);
      }
      return value;
    }
     */
    public static String getStringOrReturn(String keyroot, String name)
    {
-     String smkey = keyroot + name.toLowerCase().replaceAll(" ", "");
+     String smkey = keyroot + name.toLowerCase(Locale.ROOT).replaceAll(" ", "");
      try
      {
        name = rb.getString(smkey);
      } catch (Exception x)
      {
        String msg = "I18N missing key with root " + keyroot + ": " + loc + "\t"
 -                        + smkey;
 -        logWarning(smkey, msg);
 +              + smkey;
 +    logWarning(smkey, msg);
      }
      return name;
    }
     */
    private static void logWarning(String key, String msg) 
    {
 -      if (!reportedMissing.contains(key))
 -      {
 +  if (!reportedMissing.contains(key))
 +  {
        reportedMissing.add(key);
 -        log.info(msg);
 -      }
 +    log.info(msg);
 +  }
    }
  }
   */
  package jalview.util;
  
 -import jalview.javascript.json.JSON;
 +import java.awt.Component;
 +import java.awt.Dimension;
 +import java.awt.GraphicsEnvironment;
  import java.awt.Toolkit;
 +import java.awt.event.KeyEvent;
  import java.awt.event.MouseEvent;
  import java.io.BufferedReader;
  import java.io.File;
@@@ -34,34 -32,14 +35,33 @@@ import java.io.IOException
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.Reader;
 +import java.lang.reflect.Method;
  import java.net.URL;
 +import java.nio.channels.Channels;
 +import java.nio.channels.ReadableByteChannel;
 +import java.nio.file.Files;
 +import java.nio.file.Path;
 +import java.nio.file.Paths;
 +import java.nio.file.StandardCopyOption;
 +import java.nio.file.attribute.BasicFileAttributes;
 +import java.util.Date;
 +import java.util.Locale;
 +import java.util.Map;
  import java.util.Properties;
 +import java.util.logging.ConsoleHandler;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
  
  import javax.swing.SwingUtilities;
  
  import org.json.simple.parser.JSONParser;
  import org.json.simple.parser.ParseException;
  
 +import com.stevesoft.pat.Regex;
 +
 +import jalview.bin.Jalview;
 +import jalview.javascript.json.JSON;
 +import swingjs.api.JSUtilI;
  /**
   * System platform information used by Applet and Application
   * 
@@@ -74,28 -52,10 +74,31 @@@ public class Platfor
            false;
  
    private static Boolean isNoJSMac = null, isNoJSWin = null, isMac = null,
-           isWin = null;
+           isWin = null, isLinux = null;
++
+   private static Boolean isHeadless = null;
  
 +  private static swingjs.api.JSUtilI jsutil;
 +
 +  static
 +  {
 +    if (isJS)
 +    {
 +      try
 +      {
 +        // this is ok - it's a highly embedded method in Java; the deprecation
 +        // is
 +        // really a recommended best practice.
 +        jsutil = ((JSUtilI) Class.forName("swingjs.JSUtil").newInstance());
 +      } catch (InstantiationException | IllegalAccessException
 +              | ClassNotFoundException e)
 +      {
 +        e.printStackTrace();
 +      }
 +    }
 +  }
 +
    /**
     * added to group mouse events into Windows and nonWindows (mac, unix, linux)
     * 
              : isMac);
    }
  
 +  public static int SHORTCUT_KEY_MASK = (Platform.isMac()
 +          ? KeyEvent.META_DOWN_MASK
 +          : KeyEvent.CTRL_DOWN_MASK);
 +
 +  static
 +  {
 +    if (!GraphicsEnvironment.isHeadless())
 +    {
 +      // Using non-deprecated Extended key mask modifiers, but Java 8 has no
 +      // getMenuShortcutKeyMaskEx method
 +      Toolkit tk = Toolkit.getDefaultToolkit();
 +      Method method = null;
 +      try
 +      {
 +        method = tk.getClass().getMethod("getMenuShortcutKeyMaskEx");
 +      } catch (Exception e)
 +      {
 +        System.err.println(
 +                "Could not find Toolkit method getMenuShortcutKeyMaskEx. Trying getMenuShortcutKeyMask.");
 +      }
 +      if (method == null)
 +      {
 +        try
 +        {
 +          method = tk.getClass().getMethod("getMenuShortcutKeyMask");
 +        } catch (Exception e)
 +        {
 +          System.err.println(
 +                  "Could not find Toolkit method getMenuShortcutKeyMaskEx or getMenuShortcutKeyMask.");
 +          e.printStackTrace();
 +        }
 +      }
 +      if (method != null)
 +      {
 +        try
 +        {
 +          method.setAccessible(true);
 +          SHORTCUT_KEY_MASK = ((int) method.invoke(tk, new Object[0]));
 +        } catch (Exception e)
 +        {
 +          e.printStackTrace();
 +        }
 +      }
 +      if (SHORTCUT_KEY_MASK <= 0xF)
 +      {
 +        // shift this into the extended region (was Java 8)
 +        SHORTCUT_KEY_MASK = SHORTCUT_KEY_MASK << 6;
 +      }
 +    }
 +  }
    /**
     * added to group mouse events into Windows and nonWindows (mac, unix, linux)
     * 
    }
  
    /**
+    * added to check LaF for Linux
+    * 
+    * @return
+    */
+   public static boolean isLinux()
+   {
+     return (isLinux == null
+             ? (isLinux = (System.getProperty("os.name").indexOf("Linux") >= 0))
+             : isLinux);
+   }
+   /**
     * 
     * @return true if HTML5 JavaScript
     */
    }
  
    /**
 -   * Check if we are on a Microsoft plaform...
 +   * Check if we are on a Microsoft platform...
     * 
     * @return true if we have to cope with another platform variation
     */
      return (isNoJSWin == null ? (isNoJSWin = !isJS && isWin()) : isNoJSWin);
    }
  
+   /**
+    * 
+    * @return true if we are running in non-interactive no UI mode
+    */
+   public static boolean isHeadless()
+   {
+     if (isHeadless == null)
+     {
+       isHeadless = "true".equals(System.getProperty("java.awt.headless"));
+     }
+     return isHeadless;
+   }
  
    /**
     * 
     */
    protected static boolean isControlDown(MouseEvent e, boolean aMac)
    {
 -    if (!aMac)
 -    {
 -      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?
 +    //
 +    // System.out.println(e.isPopupTrigger()
 +    // + " " + ((SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0)
 +    // + " " + e.isControlDown());
 +    return (aMac
 +            ? !e.isPopupTrigger()
 +                    && (SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0
 +            : e.isControlDown());
    }
  
    // BH: I don't know about that previous method. Here is what SwingJS uses.
  
    public static long time, mark, set, duration;
  
 +  /**
 +   * typical usage:
 +   * 
 +   * Platform.timeCheck(null, Platform.TIME_MARK);
 +   * 
 +   * ...
 +   * 
 +   * Platform.timeCheck("some message", Platform.TIME_MARK);
 +   * 
 +   * reset...[set/mark]n...get
 +   * 
 +   * @param msg
 +   * @param mode
 +   */
    public static void timeCheck(String msg, int mode)
    {
      long t = System.currentTimeMillis();
      {
      case TIME_RESET:
        time = mark = t;
 +      duration = 0;
        if (msg != null)
        {
          System.err.println("Platform: timer reset\t\t\t" + msg);
      case TIME_MARK:
        if (set > 0)
        {
 +        // total time between set/mark points
          duration += (t - set);
        }
        else
  
    public static void cacheFileData(String path, Object data)
    {
 -    if (!isJS() || data == null)
 +    if (isJS && data != null)
      {
 -      return;
 +      jsutil.cachePathData(path, data);
      }
 -    /**
 -     * @j2sNative
 -     * 
 -     *            swingjs.JSUtil.cacheFileData$S$O(path, data);
 -     * 
 -     */
    }
  
    public static void cacheFileData(File file)
    {
 -    byte[] data;
 -    if (!isJS() || (data = Platform.getFileBytes(file)) == null)
 +    if (isJS)
      {
 -      return;
 +      byte[] data = Platform.getFileBytes(file);
 +      {
 +        if (data != null)
 +        {
 +          cacheFileData(file.toString(), data);
 +        }
 +      }
      }
 -    cacheFileData(file.toString(), data);
    }
  
    public static byte[] getFileBytes(File f)
    {
 -    return /** @j2sNative f && swingjs.JSUtil.getFileAsBytes$O(f) || */
 -    null;
 +    return (isJS && f != null ? jsutil.getBytes(f) : 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;
 +    if (isJS && fileStr != null)
 +    {
 +      byte[] bytes = (byte[]) jsutil.getFile(fileStr, false);
 +      cacheFileData(fileStr, bytes);
 +      return bytes;
 +    }
 +    return null;
    }
  
 -  @SuppressWarnings("unused")
    public static String getFileAsString(String url)
    {
 -    String ret = null;
 -    /**
 -     * @j2sNative
 -     * 
 -     *            ret = swingjs.JSUtil.getFileAsString$S(url);
 -     * 
 -     * 
 -     */
 -    cacheFileData(url, ret);
 -    return ret;
 +    if (isJS && url != null)
 +    {
 +      String ret = (String) jsutil.getFile(url, true);
 +      cacheFileData(url, ret);
 +      return ret;
 +    }
 +    return null;
    }
  
    public static boolean setFileBytes(File f, String urlstring)
    {
 -    if (!isJS())
 +    if (isJS && f != null && urlstring != null)
      {
 -      return false;
 +      @SuppressWarnings("unused")
 +      byte[] bytes = getFileAsBytes(urlstring);
 +      jsutil.setFileBytes(f, bytes);
 +      return true;
      }
 -    @SuppressWarnings("unused")
 -    byte[] bytes = getFileAsBytes(urlstring);
 -    // TODO temporary doubling of ç§˜bytes and _bytes;
 -    // just remove _bytes when new transpiler has been installed
 -    /**
 -     * @j2sNative f.\u79d8bytes = f._bytes = bytes;
 -     */
 -    return true;
 +    return false;
    }
  
    public static void addJ2SBinaryType(String ext)
    {
 -    /**
 -     * @j2sNative
 -     * 
 -     *            J2S._binaryTypes.push("." + ext + "?");
 -     * 
 -     */
 +    if (isJS)
 +    {
 +      jsutil.addBinaryFileType(ext);
 +    }
    }
  
    /**
     * @param url
     * @return true if window has been opened
     */
 -  public static boolean openURL(String url)
 +  public static boolean openURL(String url) throws IOException
    {
      if (!isJS())
      {
  
    public static String getUniqueAppletID()
    {
 -    /**
 -     * @j2sNative return swingjs.JSUtil.getApplet$()._uniqueId;
 -     *
 -     */
 -    return null;
 +    return (isJS ? (String) jsutil.getAppletAttribute("_uniqueId") : null);
    }
  
    /**
     */
    public static void readInfoProperties(String prefix, Properties p)
    {
 -    if (!isJS())
 +    if (isJS)
      {
 -      return;
 -    }
 -    String id = getUniqueAppletID();
 -    String key = "", value = "";
 -    /**
 -     * @j2sNative var info = swingjs.JSUtil.getApplet$().__Info || {}; for (var
 -     *            key in info) { if (key.indexOf(prefix) == 0) { value = "" +
 -     *            info[key];
 -     */
 +      String id = getUniqueAppletID();
  
 -    System.out.println(
 -            "Platform id=" + id + " reading Info." + key + " = " + value);
 -    p.put(id + "_" + key, value);
 +      String key = "";
 +      String value = "";
 +      @SuppressWarnings("unused")
 +      Object info = jsutil.getAppletAttribute("__Info");
 +      /**
 +       * @j2sNative for (key in info) { value = info[key];
 +       */
  
 -    /**
 -     * @j2sNative
 -     * 
 -     * 
 -     *            } }
 -     */
 +      if (key.indexOf(prefix) == 0)
 +      {
 +        System.out.println("Platform id=" + id + " reading Info." + key
 +                + " = " + value);
 +        p.put(key, value);
 +
 +      }
 +
 +      /**
 +       * @j2sNative }
 +       */
 +    }
    }
  
    public static void setAjaxJSON(URL url)
  
    public static Object parseJSON(String json) throws ParseException
    {
 -    return (isJS() ? JSON.parse(json)
 -            : new JSONParser().parse(json));
 +    return (isJS() ? JSON.parse(json) : new JSONParser().parse(json));
    }
  
    public static Object parseJSON(Reader r)
                "StringJS does not support FileReader parsing for JSON -- but it could...");
      }
      return JSON.parse(r);
    }
  
    /**
     * @param is
     * @param outFile
     * @throws IOException
 -   *                       if the file cannot be created or there is a problem
 -   *                       reading the input stream.
 +   *           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
    {
 -    if (isJS() && /**
 -                   * @j2sNative outFile.setBytes$O && outFile.setBytes$O(is) &&
 -                   */
 -            true)
 +    if (isJS)
      {
 +      jsutil.setFileBytes(outFile, is);
        return;
      }
      FileOutputStream fio = new FileOutputStream(outFile);
      try
      {
    public static void addJ2SDirectDatabaseCall(String domain)
    {
  
 -    if (isJS())
 +    if (isJS)
      {
 +      jsutil.addDirectDatabaseCall(domain);
        System.out.println(
 -            "Platform adding known access-control-allow-origin * for domain "
 -                    + domain);
 -      /**
 -       * @j2sNative
 -       * 
 -       *            J2S.addDirectDatabaseCall(domain);
 -       */
 +              "Platform adding known access-control-allow-origin * for domain "
 +                      + domain);
      }
  
    }
  
 +  /**
 +   * Allow for URL-line command arguments. Untested.
 +   * 
 +   */
    public static void getURLCommandArguments()
    {
 +
-     try
-     {
+       try {
        /**
         * Retrieve the first query field as command arguments to Jalview. Include
         * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's
         * 
         * @j2sNative var a =
         *            decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
-        *            + "?").split("?")[1].split("#")[0]); a &&
-        *            (J2S.thisApplet.__Info.args = a.split(" "));
-        * 
-        *            System.out.println("URL arguments: " + a);
+        *            + "?").split("?")[1].split("#")[0]); a && (System.out.println("URL arguments detected were "+a)) &&
+        *            (J2S.thisApplet.__Info.urlargs = a.split(" ")); 
+        *            (!J2S.thisApplet.__Info.args || J2S.thisApplet.__Info.args == "" || J2S.thisApplet.__Info.args == "??") && (J2S.thisApplet.__Info.args = a) && (System.out.println("URL arguments were passed to J2S main."));
         */
      } catch (Throwable t)
      {
      }
++
    }
  
    /**
 -   * A (case sensitive) file path comparator that ignores the difference between /
 -   * and \
 +   * A (case sensitive) file path comparator that ignores the difference between
 +   * / and \
     * 
     * @param path1
     * @param path2
      String p2 = path2.replace('\\', '/');
      return p1.equals(p2);
    }
 +  ///////////// JAL-3253 Applet additions //////////////
 +
 +  /**
 +   * Retrieve the object's embedded size from a div's style on a page if
 +   * embedded in SwingJS.
 +   * 
 +   * @param frame
 +   *          JFrame or JInternalFrame
 +   * @param defaultWidth
 +   *          use -1 to return null (no default size)
 +   * @param defaultHeight
 +   * @return the embedded dimensions or null (no default size or not embedded)
 +   */
 +  public static Dimension getDimIfEmbedded(Component frame,
 +          int defaultWidth, int defaultHeight)
 +  {
 +    Dimension d = null;
 +    if (isJS)
 +    {
 +      d = (Dimension) getEmbeddedAttribute(frame, "dim");
 +    }
 +    return (d == null && defaultWidth >= 0
 +            ? new Dimension(defaultWidth, defaultHeight)
 +            : d);
 +
 +  }
 +
 +  public static Regex newRegex(String regex)
 +  {
 +    return newRegex(regex, null);
 +  }
 +
 +  public static Regex newRegex(String searchString, String replaceString)
 +  {
 +    ensureRegex();
 +    return (replaceString == null ? new Regex(searchString)
 +            : new Regex(searchString, replaceString));
 +  }
 +
 +  public static Regex newRegexPerl(String code)
 +  {
 +    ensureRegex();
 +    return Regex.perlCode(code);
 +  }
 +
 +  /**
 +   * Initialize Java debug logging. A representative sample -- adapt as desired.
 +   */
 +  public static void startJavaLogging()
 +  {
 +    /**
 +     * @j2sIgnore
 +     */
 +    {
 +      logClass("java.awt.EventDispatchThread", "java.awt.EventQueue",
 +              "java.awt.Component", "java.awt.focus.Component",
 +              "java.awt.event.Component",
 +              "java.awt.focus.DefaultKeyboardFocusManager");
 +    }
 +  }
 +
 +  /**
 +   * Initiate Java logging for a given class. Only for Java, not JavaScript;
 +   * Allows debugging of complex event processing.
 +   * 
 +   * @param className
 +   */
 +  public static void logClass(String... classNames)
 +  {
 +    /**
 +     * @j2sIgnore
 +     * 
 +     * 
 +     */
 +    {
 +      Logger rootLogger = Logger.getLogger("");
 +      rootLogger.setLevel(Level.ALL);
 +      ConsoleHandler consoleHandler = new ConsoleHandler();
 +      consoleHandler.setLevel(Level.ALL);
 +      for (int i = classNames.length; --i >= 0;)
 +      {
 +        Logger logger = Logger.getLogger(classNames[i]);
 +        logger.setLevel(Level.ALL);
 +        logger.addHandler(consoleHandler);
 +      }
 +    }
 +  }
 +
 +  /**
 +   * load a resource -- probably a core file -- if and only if a particular
 +   * class has not been instantialized. We use a String here because if we used
 +   * a .class object, that reference itself would simply load the class, and we
 +   * want the core package to include that as well.
 +   * 
 +   * @param resourcePath
 +   * @param className
 +   */
 +  public static void loadStaticResource(String resourcePath,
 +          String className)
 +  {
 +    if (isJS)
 +    {
 +      jsutil.loadResourceIfClassUnknown(resourcePath, className);
 +    }
 +  }
 +
 +  public static void ensureRegex()
 +  {
 +    if (isJS)
 +    {
 +      loadStaticResource("core/core_stevesoft.z.js",
 +              "com.stevesoft.pat.Regex");
 +    }
 +  }
 +
 +  /**
 +   * Set the "app" property of the HTML5 applet object, for example,
 +   * "testApplet.app", to point to the Jalview instance. This will be the object
 +   * that page developers use that is similar to the original Java applet object
 +   * that was accessed via LiveConnect.
 +   * 
 +   * @param j
 +   */
 +  public static void setAppClass(Object j)
 +  {
 +    if (isJS)
 +    {
 +      jsutil.setAppClass(j);
 +    }
 +  }
 +
 +  /**
 +   *
 +   * If this frame is embedded in a web page, return a known type.
 +   * 
 +   * @param frame
 +   *          a JFrame or JInternalFrame
 +   * @param type
 +   *          "name", "node", "init", "dim", or any DOM attribute, such as "id"
 +   * @return null if frame is not embedded.
 +   */
 +  public static Object getEmbeddedAttribute(Component frame, String type)
 +  {
 +    return (isJS ? jsutil.getEmbeddedAttribute(frame, type) : null);
 +  }
 +
 +  public static void stackTrace()
 +  {
 +    try
 +    {
 +      throw new NullPointerException();
 +    } catch (Exception e)
 +    {
 +      e.printStackTrace();
 +    }
 +
 +  }
 +
 +  public static URL getDocumentBase()
 +  {
 +    return (isJS ? jsutil.getDocumentBase() : null);
 +  }
 +
 +  public static URL getCodeBase()
 +  {
 +    return (isJS ? jsutil.getCodeBase() : null);
 +  }
 +
 +  public static String getUserPath(String subpath)
 +  {
 +    char sep = File.separatorChar;
 +    return System.getProperty("user.home") + sep
 +            + subpath.replace('/', sep);
 +  }
 +
 +  /**
 +   * This method enables checking if a cached file has exceeded a certain
 +   * threshold(in days)
 +   * 
 +   * @param file
 +   *          the cached file
 +   * @param noOfDays
 +   *          the threshold in days
 +   * @return
 +   */
 +  public static boolean isFileOlderThanThreshold(File file, int noOfDays)
 +  {
 +    if (isJS())
 +    {
 +      // not meaningful in SwingJS -- this is a session-specific temp file. It
 +      // doesn't have a timestamp.
 +      return false;
 +    }
 +    Path filePath = file.toPath();
 +    BasicFileAttributes attr;
 +    int diffInDays = 0;
 +    try
 +    {
 +      attr = Files.readAttributes(filePath, BasicFileAttributes.class);
 +      diffInDays = (int) ((new Date().getTime()
 +              - attr.lastModifiedTime().toMillis())
 +              / (1000 * 60 * 60 * 24));
 +      // System.out.println("Diff in days : " + diffInDays);
 +    } catch (IOException e)
 +    {
 +      e.printStackTrace();
 +    }
 +    return noOfDays <= diffInDays;
 +  }
 +
 +  /**
 +   * Get the leading integer part of a string that begins with an integer.
 +   * 
 +   * @param input
 +   *          - the string input to process
 +   * @param failValue
 +   *          - value returned if unsuccessful
 +   * @return
 +   */
 +  public static int getLeadingIntegerValue(String input, int failValue)
 +  {
 +    if (input == null)
 +    {
 +      return failValue;
 +    }
 +    if (isJS)
 +    {
 +      int val = /** @j2sNative 1 ? parseInt(input) : */
 +              0;
 +      return (val == val + 0 ? val : failValue);
 +    }
 +    // JavaScript does not support Regex ? lookahead
 +    String[] parts = input.split("(?=\\D)(?<=\\d)");
 +    if (parts != null && parts.length > 0 && parts[0].matches("[0-9]+"))
 +    {
 +      return Integer.valueOf(parts[0]);
 +    }
 +    return failValue;
 +  }
 +
 +  public static Map<String, Object> getAppletInfoAsMap()
 +  {
 +    return (isJS ? jsutil.getAppletInfoAsMap() : null);
 +  }
 +
 +  /**
 +   * Get the SwingJS applet ID and combine that with the frameType
 +   * 
 +   * @param frameType
 +   *          "alignment", "desktop", etc., or null
 +   * @return
 +   */
 +  public static String getAppID(String frameType)
 +  {
 +
 +    String id = Jalview.getInstance().j2sAppletID;
 +    if (id == null)
 +    {
 +      Jalview.getInstance().j2sAppletID = id = (isJS
 +              ? (String) jsutil.getAppletAttribute("_id")
 +              : "jalview");
 +    }
 +    return id + (frameType == null ? "" : "-" + frameType);
 +  }
 +
 +  /**
 +   * Option to avoid unnecessary seeking of nonexistent resources in JavaScript.
 +   * Works in Java as well.
 +   * 
 +   * @param loc
 +   * @return
 +   */
 +  public static Locale getLocaleOrNone(Locale loc)
 +  {
 +    return (isJS && loc.getLanguage() == "en" ? new Locale("") : loc);
 +  }
 +
 +  /**
 +   * From UrlDownloadClient; trivial in JavaScript; painful in Java.
 +   * 
 +   * @param urlstring
 +   * @param outfile
 +   * @throws IOException
 +   */
 +  public static void download(String urlstring, String outfile)
 +          throws IOException
 +  {
 +    Path temp = null;
 +    try (InputStream is = new URL(urlstring).openStream())
 +    {
 +      if (isJS)
 +      { // so much easier!
 +        streamToFile(is, new File(outfile));
 +        return;
 +      }
 +      temp = Files.createTempFile(".jalview_", ".tmp");
 +      try (FileOutputStream fos = new FileOutputStream(temp.toString());
 +              ReadableByteChannel rbc = Channels.newChannel(is))
 +      {
 +        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
 +        // copy tempfile to outfile once our download completes
 +        // incase something goes wrong
 +        Files.copy(temp, Paths.get(outfile),
 +                StandardCopyOption.REPLACE_EXISTING);
 +      }
 +    } catch (IOException e)
 +    {
 +      throw e;
 +    } finally
 +    {
 +      try
 +      {
 +        if (temp != null)
 +        {
 +          Files.deleteIfExists(temp);
 +        }
 +      } catch (IOException e)
 +      {
 +        System.out.println("Exception while deleting download temp file: "
 +                + e.getMessage());
 +      }
 +    }
 +  }
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.util;
  
+ import java.util.Locale;
  import java.io.UnsupportedEncodingException;
  import java.net.URLEncoder;
  import java.util.ArrayList;
@@@ -117,6 -119,29 +119,6 @@@ public class StringUtil
    }
  
    /**
 -   * Returns the last part of 'input' after the last occurrence of 'token'. For
 -   * example to extract only the filename from a full path or URL.
 -   * 
 -   * @param input
 -   * @param token
 -   *          a delimiter which must be in regular expression format
 -   * @return
 -   */
 -  public static String getLastToken(String input, String token)
 -  {
 -    if (input == null)
 -    {
 -      return null;
 -    }
 -    if (token == null)
 -    {
 -      return input;
 -    }
 -    String[] st = input.split(token);
 -    return st[st.length - 1];
 -  }
 -
 -  /**
     * Parses the input string into components separated by the delimiter. Unlike
     * String.split(), this method will ignore occurrences of the delimiter which
     * are nested within single quotes in name-value pair values, e.g. a='b,c'.
      }
      if (s.length() <= 1)
      {
-       return s.toUpperCase();
+       return s.toUpperCase(Locale.ROOT);
      }
-     return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
+     return s.substring(0, 1).toUpperCase(Locale.ROOT) + s.substring(1).toLowerCase(Locale.ROOT);
    }
  
    /**
      {
        return null;
      }
-     String tmp2up = text.toUpperCase();
+     String tmp2up = text.toUpperCase(Locale.ROOT);
      int startTag = tmp2up.indexOf("<HTML>");
      if (startTag > -1)
      {
      }
      return enc;
    }
 +
 +  /**
 +   * Answers true if the string is not empty and consists only of digits, or
 +   * characters 'a'-'f' or 'A'-'F', else false
 +   * 
 +   * @param s
 +   * @return
 +   */
 +  public static boolean isHexString(String s)
 +  {
 +    int j = s.length();
 +    if (j == 0)
 +    {
 +      return false;
 +    }
 +    for (int i = 0; i < j; i++)
 +    {
 +      int c = s.charAt(i);
 +      if (!(c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'))
 +      {
 +        return false;
 +      }
 +    }
 +    return true;
 +  }
  }
@@@ -24,8 -24,6 +24,8 @@@ import jalview.analysis.AnnotationSorte
  import jalview.analysis.Conservation;
  import jalview.analysis.TreeModel;
  import jalview.api.AlignCalcManagerI;
 +import jalview.api.AlignCalcManagerI2;
 +import jalview.api.AlignCalcWorkerI;
  import jalview.api.AlignExportSettingsI;
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
@@@ -59,10 -57,8 +59,10 @@@ import jalview.util.MappingUtils
  import jalview.util.MessageManager;
  import jalview.viewmodel.styles.ViewStyle;
  import jalview.workers.AlignCalcManager;
 +import jalview.workers.AlignCalcManager2;
  import jalview.workers.ComplementConsensusThread;
  import jalview.workers.ConsensusThread;
 +import jalview.workers.InformationThread;
  import jalview.workers.StrucConsensusThread;
  
  import java.awt.Color;
@@@ -87,9 -83,6 +87,8 @@@ import java.util.Map
  public abstract class AlignmentViewport
          implements AlignViewportI, CommandListener, VamsasSource
  {
 +  public static final String PROPERTY_ALIGNMENT = "alignment";
 +  public static final String PROPERTY_SEQUENCE = "sequence";
    protected ViewportRanges ranges;
  
    protected ViewStyleI viewStyle = new ViewStyle();
     * alignment displayed in the viewport. Please use get/setter
     */
    protected AlignmentI alignment;
 +  
 +  /*
 +   * probably unused indicator that view is of a dataset rather than an
 +   * alignment
 +   */
 +
 +  protected boolean ignoreBelowBackGroundFrequencyCalculation = false;
 +
 +  protected boolean infoLetterHeight = false;
 +
 +  protected AlignmentAnnotation occupancy;
 +  
 +  /**
 +   * results of alignment consensus analysis for visible portion of view
 +   */
 +  protected ProfilesI consensusProfiles;
 +
 +  /**
 +   * HMM profile for the alignment
 +   */
 +  protected ProfilesI hmmProfiles;
  
    public AlignmentViewport(AlignmentI al)
    {
     * alignment
     */
    protected boolean isDataset = false;
 +  
    public void setDataset(boolean b)
    {
      isDataset = b;
  
    protected ColumnSelection colSel = new ColumnSelection();
  
 -  public boolean autoCalculateConsensus = true;
 +  protected boolean autoCalculateConsensusAndConservation = true;
 +
 +  public boolean getAutoCalculateConsensusAndConservation()
 +  { // BH 2019.07.24
 +    return autoCalculateConsensusAndConservation;
 +  }
 +
 +  public void setAutoCalculateConsensusAndConservation(boolean b)
 +  {
 +    autoCalculateConsensusAndConservation = b;
 +  }
  
    protected boolean autoCalculateStrucConsensus = true;
  
 +  public boolean getAutoCalculateStrucConsensus()
 +  { // BH 2019.07.24
 +    return autoCalculateStrucConsensus;
 +  }
 +
 +  public void setAutoCalculateStrucConsensus(boolean b)
 +  {
 +    autoCalculateStrucConsensus = b;
 +  }
    protected boolean ignoreGapsInConsensusCalculation = false;
  
    protected ResidueShaderI residueShading = new ResidueShader();
 +  
    @Override
    public void setGlobalColourScheme(ColourSchemeI cs)
    {
    {
      return residueShading;
    }
 +  
    protected AlignmentAnnotation consensus;
  
    protected AlignmentAnnotation complementConsensus;
    protected Hashtable<String, Object>[] hStrucConsensus = null;
  
    protected Conservation hconservation = null;
 +  
    @Override
    public void setConservation(Conservation cons)
    {
    }
  
    @Override
 +  public void setHmmProfiles(ProfilesI info)
 +  {
 +    hmmProfiles = info;
 +  }
 +
 +  @Override
 +  public ProfilesI getHmmProfiles()
 +  {
 +    return hmmProfiles;
 +  }
 +
 +  @Override
    public Hashtable<String, Object>[] getComplementConsensusHash()
    {
      return hcomplementConsensus;
      return strucConsensus;
    }
  
 -  protected AlignCalcManagerI calculator = new AlignCalcManager();
 +  protected AlignCalcManagerI2 calculator = new AlignCalcManager2();
  
    /**
     * trigger update of conservation annotation
      // see note in mantis : issue number 8585
      if (alignment.isNucleotide()
              || (conservation == null && quality == null)
 -            || !autoCalculateConsensus)
 +            || !autoCalculateConsensusAndConservation)
      {
        return;
      }
 -    if (calculator.getRegisteredWorkersOfClass(
 -            jalview.workers.ConservationThread.class) == null)
 +    if (calculator.getWorkersOfClass(
 +            jalview.workers.ConservationThread.class).isEmpty())
      {
        calculator.registerWorker(
                new jalview.workers.ConservationThread(this, ap));
    public void updateConsensus(final AlignmentViewPanel ap)
    {
      // see note in mantis : issue number 8585
 -    if (consensus == null || !autoCalculateConsensus)
 +    if (consensus == null || !autoCalculateConsensusAndConservation)
      {
        return;
      }
 -    if (calculator
 -            .getRegisteredWorkersOfClass(ConsensusThread.class) == null)
 +    if (calculator.getWorkersOfClass(ConsensusThread.class).isEmpty())
      {
        calculator.registerWorker(new ConsensusThread(this, ap));
      }
        }
        if (doConsensus)
        {
 -        if (calculator.getRegisteredWorkersOfClass(
 -                ComplementConsensusThread.class) == null)
 +        if (calculator.getWorkersOfClass(ComplementConsensusThread.class).isEmpty())
          {
 -          calculator
 -                  .registerWorker(new ComplementConsensusThread(this, ap));
 +          calculator.registerWorker(new ComplementConsensusThread(this, ap));
          }
        }
      }
    }
  
 +  @Override
 +  public void initInformationWorker(final AlignmentViewPanel ap)
 +  {
 +    if (calculator.getWorkersOfClass(InformationThread.class).isEmpty())
 +    {
 +      calculator.registerWorker(new InformationThread(this, ap));
 +    }
 +  }
    // --------START Structure Conservation
    public void updateStrucConsensus(final AlignmentViewPanel ap)
    {
      {
        return;
      }
 -    if (calculator.getRegisteredWorkersOfClass(
 -            StrucConsensusThread.class) == null)
 +    if (calculator.getWorkersOfClass(StrucConsensusThread.class).isEmpty())
      {
        calculator.registerWorker(new StrucConsensusThread(this, ap));
      }
      {
        return false;
      }
 -    if (calculator.workingInvolvedWith(alignmentAnnotation))
 +    if (calculator.isWorkingWithAnnotation(alignmentAnnotation))
      {
        // System.err.println("grey out ("+alignmentAnnotation.label+")");
        return true;
      strucConsensus = null;
      conservation = null;
      quality = null;
 +    consensusProfiles = null;
      groupConsensus = null;
      groupConservation = null;
      hconsensus = null;
      hconservation = null;
      hcomplementConsensus = null;
      gapcounts = null;
 +    calculator.shutdown();
      calculator = null;
      residueShading = null; // may hold a reference to Consensus
      changeSupport = null;
      ranges = null;
      currentTree = null;
      selectionGroup = null;
 -    colSel = null;
      setAlignment(null);
    }
  
    }
  
    @Override
 -  public AlignCalcManagerI getCalcManager()
 +  public AlignCalcManagerI2 getCalcManager()
    {
      return calculator;
    }
    protected boolean showConsensusHistogram = true;
  
    /**
 +   * should hmm profile be rendered by default
 +   */
 +  protected boolean hmmShowSequenceLogo = false;
 +
 +  /**
 +   * should hmm profile be rendered normalised to row height
 +   */
 +  protected boolean hmmNormaliseSequenceLogo = false;
 +
 +  /**
 +   * should information histograms be rendered by default
 +   */
 +  protected boolean hmmShowHistogram = true;
 +
 +  /**
     * @return the showConsensusProfile
     */
    @Override
    }
  
    /**
 +   * @return the showInformationProfile
 +   */
 +  @Override
 +  public boolean isShowHMMSequenceLogo()
 +  {
 +    return hmmShowSequenceLogo;
 +  }
 +
 +  /**
     * @param showSequenceLogo
     *          the new value
     */
        // TODO: decouple settings setting from calculation when refactoring
        // annotation update method from alignframe to viewport
        this.showSequenceLogo = showSequenceLogo;
 -      calculator.updateAnnotationFor(ConsensusThread.class);
 -      calculator.updateAnnotationFor(ComplementConsensusThread.class);
 -      calculator.updateAnnotationFor(StrucConsensusThread.class);
 +      for (AlignCalcWorkerI worker : calculator.getWorkers())
 +      {
 +        if (worker.getClass().equals(ConsensusThread.class) ||
 +                worker.getClass().equals(ComplementConsensusThread.class) ||
 +                worker.getClass().equals(StrucConsensusThread.class))
 +        {
 +          worker.updateAnnotation();
 +        }
 +      }
      }
      this.showSequenceLogo = showSequenceLogo;
    }
  
 +  public void setShowHMMSequenceLogo(boolean showHMMSequenceLogo)
 +  {
 +    if (showHMMSequenceLogo != this.hmmShowSequenceLogo)
 +    {
 +      this.hmmShowSequenceLogo = showHMMSequenceLogo;
 +      // TODO: updateAnnotation if description (tooltip) will show
 +      // profile in place of information content?
 +      // calculator.updateAnnotationFor(InformationThread.class);
 +    }
 +    this.hmmShowSequenceLogo = showHMMSequenceLogo;
 +  }
    /**
     * @param showConsensusHistogram
     *          the showConsensusHistogram to set
    }
  
    /**
 +   * @param showInformationHistogram
 +   */
 +  public void setShowInformationHistogram(boolean showInformationHistogram)
 +  {
 +    this.hmmShowHistogram = showInformationHistogram;
 +  }
 +
 +  /**
     * @return the showGroupConservation
     */
    public boolean isShowGroupConservation()
    }
  
    /**
 +   * 
 +   * @return flag to indicate if the information content histogram should be
 +   *         rendered by default
 +   */
 +  @Override
 +  public boolean isShowInformationHistogram()
 +  {
 +    return this.hmmShowHistogram;
 +  }
 +
 +  /**
     * when set, updateAlignment will always ensure sequences are of equal length
     */
    private boolean padGaps = false;
                  ignoreGapsInConsensusCalculation);
        }
      }
 +  }
 +
 +  public void setIgnoreBelowBackground(boolean b, AlignmentViewPanel ap)
 +  {
 +    ignoreBelowBackGroundFrequencyCalculation = b;
 +  }
  
 +  public void setInfoLetterHeight(boolean b, AlignmentViewPanel ap)
 +  {
 +    infoLetterHeight = b;
    }
  
    private long sgrouphash = -1, colselhash = -1;
     * checks current colsel against record of last hash value, and optionally
     * updates record.
     * 
 -   * @param b
 +   * @param updateHash
     *          update the record of last hash value
     * @return true if colsel changed since last call (when b is true)
     */
 -  public boolean isColSelChanged(boolean b)
 +  public boolean isColSelChanged(boolean updateHash)
    {
      int hc = (colSel == null || colSel.isEmpty()) ? -1 : colSel.hashCode();
      if (hc != -1 && hc != colselhash)
      {
 -      if (b)
 +      if (updateHash)
        {
          colselhash = hc;
        }
        return true;
      }
 +    notifySequence();
      return false;
    }
  
      return ignoreGapsInConsensusCalculation;
    }
  
 +  @Override
 +  public boolean isIgnoreBelowBackground()
 +  {
 +    return ignoreBelowBackGroundFrequencyCalculation;
 +  }
 +
 +  @Override
 +  public boolean isInfoLetterHeight()
 +  {
 +    return infoLetterHeight;
 +  }
    // property change stuff
    // JBPNote Prolly only need this in the applet version.
    private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
      }
    }
  
 -  /**
 -   * Property change listener for changes in alignment
 -   * 
 -   * @param prop
 -   *          DOCUMENT ME!
 -   * @param oldvalue
 -   *          DOCUMENT ME!
 -   * @param newvalue
 -   *          DOCUMENT ME!
 -   */
 -  public void firePropertyChange(String prop, Object oldvalue,
 -          Object newvalue)
 -  {
 -    changeSupport.firePropertyChange(prop, oldvalue, newvalue);
 -  }
    // common hide/show column stuff
  
    public void hideSelectedColumns()
  
        ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
  
 -      firePropertyChange("alignment", null, alignment.getSequences());
        // used to set hasHiddenRows/hiddenRepSequences here, after the property
        // changed event
 +      notifySequence();
        sendSelection();
      }
    }
  
    public void showSequence(int index)
    {
      int startSeq = ranges.getStartSeq();
        }
  
        ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
 -      firePropertyChange("alignment", null, alignment.getSequences());
 +      notifyAlignment();
        sendSelection();
      }
    }
          setSequenceAnnotationsVisible(seq[i], false);
        }
        ranges.setStartSeq(startSeq);
 -      firePropertyChange("alignment", null, alignment.getSequences());
 +      notifyAlignment();
      }
    }
  
      {
        alignment.padGaps();
      }
 -    if (autoCalculateConsensus)
 +    if (autoCalculateConsensusAndConservation)
      {
        updateConsensus(ap);
      }
 -    if (hconsensus != null && autoCalculateConsensus)
 +    if (hconsensus != null && autoCalculateConsensusAndConservation)
      {
        updateConservation(ap);
      }
  
      updateAllColourSchemes();
      calculator.restartWorkers();
 -    // alignment.adjustSequenceAnnotations();
    }
  
    /**
                MessageManager.getString("label.consensus_descr"),
                new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
        initConsensus(consensus);
        initGapCounts();
  
        initComplementConsensus();
      boolean showprf = isShowSequenceLogo();
      boolean showConsHist = isShowConsensusHistogram();
      boolean normLogo = isNormaliseSequenceLogo();
 +    boolean showHMMPrf = isShowHMMSequenceLogo();
 +    boolean showInfoHist = isShowInformationHistogram();
 +    boolean normHMMLogo = isNormaliseHMMSequenceLogo();
  
      /**
       * TODO reorder the annotation rows according to group/sequence ordering on
            sg.setshowSequenceLogo(showprf);
            sg.setShowConsensusHistogram(showConsHist);
            sg.setNormaliseSequenceLogo(normLogo);
 +          sg.setShowHMMSequenceLogo(showHMMPrf);
 +          sg.setShowInformationHistogram(showInfoHist);
 +          sg.setNormaliseHMMSequenceLogo(normHMMLogo);
          }
          if (conv)
          {
      return sq;
    }
  
 +  public boolean hasReferenceAnnotation()
 +  {
 +    AlignmentAnnotation[] annots = this.alignment.getAlignmentAnnotation();
 +    for (AlignmentAnnotation annot : annots)
 +    {
 +      if ("RF".equals(annot.label) || annot.label.contains("Reference"))
 +      {
 +        return true;
 +      }
 +    }
 +    return false;
 +  }
    @Override
    public void setCurrentTree(TreeModel tree)
    {
      return ed;
    }
    
 +  @Override
 +  public boolean isNormaliseSequenceLogo()
 +  {
 +    return normaliseSequenceLogo;
 +  }
 +
 +  public void setNormaliseSequenceLogo(boolean state)
 +  {
 +    normaliseSequenceLogo = state;
 +  }
 +
 +  @Override
 +  public boolean isNormaliseHMMSequenceLogo()
 +  {
 +    return hmmNormaliseSequenceLogo;
 +  }
 +
 +  public void setNormaliseHMMSequenceLogo(boolean state)
 +  {
 +    hmmNormaliseSequenceLogo = state;
 +  }
    /**
     * flag set to indicate if structure views might be out of sync with sequences
     * in the alignment
        codingComplement.setUpdateStructures(needToUpdateStructureViews);
      }
    }
+   @Override
+   public Iterator<int[]> getViewAsVisibleContigs(boolean selectedRegionOnly)
+   {
+     int start = 0;
+     int end = 0;
+     if (selectedRegionOnly && selectionGroup != null)
+     {
+       start = selectionGroup.getStartRes();
+       end = selectionGroup.getEndRes() + 1;
+     }
+     else
+     {
+       end = alignment.getWidth();
+     }
+     return (alignment.getHiddenColumns().getVisContigsIterator(start, end,
+             false));
+   }
 +  /**
 +   * Filters out sequences with an eValue higher than the specified value. The
 +   * filtered sequences are hidden or deleted. Sequences with no eValues are also
 +   * filtered out.
 +   * 
 +   * @param eValue
 +   * @param delete
 +   */
 +  public void filterByEvalue(double eValue)
 +  {
 +    for (SequenceI seq : alignment.getSequencesArray())
 +    {
 +      if ((seq.getAnnotation("Search Scores") == null
 +              || seq.getAnnotation("Search Scores")[0].getEValue() > eValue)
 +              && seq.getHMM() == null)
 +      {
 +        hideSequence(new SequenceI[] { seq });
 +      }
 +    }
 +  }
 +
 +  /**
 +   * Filters out sequences with an score lower than the specified value. The
 +   * filtered sequences are hidden or deleted.
 +   * 
 +   * @param score
 +   * @param delete
 +   */
 +  public void filterByScore(double score)
 +  {
 +    for (SequenceI seq : alignment.getSequencesArray())
 +    {
 +      if ((seq.getAnnotation("Search Scores") == null
 +              || seq.getAnnotation("Search Scores")[0]
 +                      .getBitScore() < score)
 +              && seq.getHMM() == null)
 +      {
 +        hideSequence(new SequenceI[] { seq });
 +      }
 +    }
 +  }  
 +
 +  /**
 +   * Notify TreePanel and AlignmentPanel of some sort of alignment change.
 +   */
 +  public void notifyAlignment()
 +  {
 +    changeSupport.firePropertyChange(PROPERTY_ALIGNMENT, null, alignment.getSequences());
 +  }
 +  
 +  /**
 +   * Notify AlignmentPanel of a sequence column selection or visibility changes.
 +   */
 +  public void notifySequence()
 +  {
 +    changeSupport.firePropertyChange(PROPERTY_SEQUENCE, null, null);
 +  }
  }
@@@ -20,8 -20,6 +20,8 @@@
   */
  package jalview.ws;
  
 +import jalview.bin.ApplicationSingletonProvider;
 +import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
  import jalview.ext.ensembl.EnsemblGene;
  import jalview.ws.dbsources.EmblCdsSource;
  import jalview.ws.dbsources.EmblSource;
@@@ -29,6 -27,7 +29,7 @@@ import jalview.ws.dbsources.Pdb
  import jalview.ws.dbsources.PfamFull;
  import jalview.ws.dbsources.PfamSeed;
  import jalview.ws.dbsources.RfamSeed;
+ import jalview.ws.dbsources.TDBeacons;
  import jalview.ws.dbsources.Uniprot;
  import jalview.ws.seqfetcher.ASequenceFetcher;
  import jalview.ws.seqfetcher.DbSourceProxy;
@@@ -41,39 -40,8 +42,39 @@@ import java.util.List
   * This implements the run-time discovery of sequence database clients.
   * 
   */
 -public class SequenceFetcher extends ASequenceFetcher
 +public class SequenceFetcher extends ASequenceFetcher implements ApplicationSingletonI
  {
 +  /*
 +   * set a mock fetcher here for testing only - reset to null afterwards
 +   */
 +  private static SequenceFetcher mockFetcher;
 +
 +  /**
 +   * Set the instance object to use (intended for unit testing with mock
 +   * objects).
 +   * 
 +   * Be sure to reset to null in the tearDown method of any tests!
 +   * 
 +   * @param sf
 +   */
 +  public static void setMockFetcher(SequenceFetcher sf)
 +  {
 +    mockFetcher = sf;
 +  }
 +  
 +  /**
 +   * Returns a new SequenceFetcher singleton, or a mock object if one has been
 +   * set.
 +   * 
 +   * @return
 +   */
 +  public static SequenceFetcher getInstance()
 +  {
 +    return mockFetcher != null ? mockFetcher
 +            : (SequenceFetcher) ApplicationSingletonProvider
 +                    .getInstance(SequenceFetcher.class);
 +  }
 +
    /**
     * Thread safe construction of database proxies TODO: extend to a configurable
     * database plugin mechanism where classes are instantiated by reflection and
@@@ -87,6 -55,8 +88,8 @@@
      addDBRefSourceImpl(EmblSource.class);
      addDBRefSourceImpl(EmblCdsSource.class);
      addDBRefSourceImpl(Uniprot.class);
+     // not a sequence source yet
+     // addDBRefSourceImpl(TDBeacons.class);
      addDBRefSourceImpl(Pdb.class);
      addDBRefSourceImpl(PfamFull.class);
      addDBRefSourceImpl(PfamSeed.class);
@@@ -22,13 -22,9 +22,9 @@@ package jalview.ws.dbsources
  
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.DBRefSource;
- import jalview.util.Platform;
  
- import com.stevesoft.pat.Regex;
- public class EmblCdsSource extends EmblXmlSource
+ public class EmblCdsSource extends EmblFlatfileSource // was EmblXmlSource
  {
-   private Regex ACCESSION_REGEX = null;
  
    public EmblCdsSource()
    {
    }
  
    @Override
-   public String getAccessionSeparator()
-   {
-     return null;
-   }
-   @Override
-   public Regex getAccessionValidator()
-   {
-     if (ACCESSION_REGEX == null)
-     {
-       ACCESSION_REGEX = Platform.newRegex("^[A-Z]+[0-9]+");
-     }
-     return ACCESSION_REGEX;
-   }
-   @Override
    public String getDbSource()
    {
      return DBRefSource.EMBLCDS;
    }
  
    @Override
-   public String getDbVersion()
-   {
-     return "0"; // TODO : this is dynamically set for a returned record - not
-     // tied to proxy
-   }
-   @Override
    public AlignmentI getSequenceRecords(String queries) throws Exception
    {
      if (queries.indexOf(".") > -1)
      return getEmblSequenceRecords(DBRefSource.EMBLCDS, queries);
    }
  
-   @Override
-   public boolean isValidReference(String accession)
-   {
-     // most embl CDS refs look like ..
-     // TODO: improve EMBLCDS regex
-     return (accession == null || accession.length() < 2) ? false
-             : getAccessionValidator().search(accession);
-   }
 +
    /**
     * cDNA for LDHA_CHICK swissprot sequence
     */
      return "EMBL (CDS)";
    }
  
-   @Override
-   public int getTier()
-   {
-     return 0;
-   }
  }
index 0000000,2058800..6694a52
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,122 +1,126 @@@
+ package jalview.ws.dbsources;
+ import java.util.Locale;
+ import java.io.File;
+ import java.io.IOException;
+ import com.stevesoft.pat.Regex;
+ import jalview.bin.Cache;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.SequenceI;
+ import jalview.io.DataSourceType;
+ import jalview.io.EmblFlatFile;
+ import jalview.io.FileParse;
+ import jalview.ws.ebi.EBIFetchClient;
+ /**
+  * A class that does partial parsing of an EMBL flatfile.
+  * 
+  * @author gmcarstairs
+  *
+  */
+ public abstract class EmblFlatfileSource extends EbiFileRetrievedProxy
+ {
 -  private static final Regex ACCESSION_REGEX = new Regex("^[A-Z]+[0-9]+");
++  private static final Regex ACCESSION_REGEX = null;
+   @Override
+   public String getDbVersion()
+   {
+     return "0";
+   }
+   @Override
+   public String getAccessionSeparator()
+   {
+     return null;
+   }
+   @Override
+   public Regex getAccessionValidator()
+   {
++    if (ACCESSION_REGEX == null)
++    {
++      ACCESSION_REGEX = Platform.newRegex("^[A-Z]+[0-9]+");
++    }
+     return ACCESSION_REGEX;
+   }
+   @Override
+   public boolean isValidReference(String accession)
+   {
+     if (accession == null || accession.length() < 2)
+     {
+       return false;
+     }
+     return getAccessionValidator().search(accession);
+   }
+   @Override
+   public AlignmentI getSequenceRecords(String queries) throws Exception
+   {
+     return null;
+   }
+   @Override
+   public int getTier()
+   {
+     return 0;
+   }
+   protected AlignmentI getEmblSequenceRecords(String dbName, String query)
+           throws Exception
+   {
+     startQuery();
+     EBIFetchClient dbFetch = new EBIFetchClient();
+     File reply;
+     try
+     {
+       reply = dbFetch.fetchDataAsFile(
+               dbName.toLowerCase(Locale.ROOT) + ":" + query.trim(), null, "gz");
+     } catch (Exception e)
+     {
+       stopQuery();
+       throw new Exception(
+               String.format("EBI EMBL retrieval failed for %s:%s",
+                       dbName.toLowerCase(Locale.ROOT), query.trim()),
+               e);
+     }
+     return getEmblSequenceRecords(dbName, query, reply);
+   }
+   private AlignmentI getEmblSequenceRecords(String dbName, String query,
+           File reply) throws IOException
+   {
+     AlignmentI al = null;
+     if (reply != null && reply.exists())
+     {
+       file = reply.getAbsolutePath();
+       FileParse fp = new FileParse(file, DataSourceType.FILE);
+       EmblFlatFile emblParser = new EmblFlatFile(fp, getDbSource());
+       SequenceI[] seqs = emblParser.getSeqsAsArray();
+       if (seqs.length > 0)
+       {
+         al = new Alignment(seqs);
+       }
+       if (al == null)
+       {
+         Cache.log.error(
+                 "No record found for '" + dbName + ":" + query + "'");
+       }
+     }
+     stopQuery();
+     return al;
+   }
+   @Override
+   public boolean isDnaCoding()
+   {
+     return true;
+   }
+ }
@@@ -22,19 -22,14 +22,15 @@@ package jalview.ws.dbsources
  
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.DBRefSource;
- import jalview.util.Platform;
  
- import com.stevesoft.pat.Regex;
 +
  /**
   * @author JimP
   * 
   */
- public class EmblSource extends EmblXmlSource
+ public class EmblSource extends EmblFlatfileSource // was EmblXmlSource
  {
  
-   private static Regex ACCESSION_REGEX;
    public EmblSource()
    {
      super();
    /*
     * (non-Javadoc)
     * 
-    * @see jalview.ws.DbSourceProxy#getAccessionSeparator()
-    */
-   @Override
-   public String getAccessionSeparator()
-   {
-     // TODO Auto-generated method stub
-     return null;
-   }
-   /*
-    * (non-Javadoc)
-    * 
-    * @see jalview.ws.DbSourceProxy#getAccessionValidator()
-    */
-   @Override
-   public Regex getAccessionValidator()
-   {
-     if (ACCESSION_REGEX == null)
-     {
-       ACCESSION_REGEX = Platform.newRegex("^[A-Z]+[0-9]+");
-     }
-     return ACCESSION_REGEX;
-   }
-   /*
-    * (non-Javadoc)
-    * 
     * @see jalview.ws.DbSourceProxy#getDbSource()
     */
    @Override
    /*
     * (non-Javadoc)
     * 
-    * @see jalview.ws.DbSourceProxy#getDbVersion()
-    */
-   @Override
-   public String getDbVersion()
-   {
-     // TODO Auto-generated method stub
-     return "0";
-   }
-   /*
-    * (non-Javadoc)
-    * 
     * @see jalview.ws.DbSourceProxy#getSequenceRecords(java.lang.String[])
     */
    @Override
    public AlignmentI getSequenceRecords(String queries) throws Exception
    {
      return getEmblSequenceRecords(DBRefSource.EMBL, queries);
-   }
-   /*
-    * (non-Javadoc)
-    * 
-    * @see jalview.ws.DbSourceProxy#isValidReference(java.lang.String)
-    */
-   @Override
-   public boolean isValidReference(String accession)
-   {
-     // most embl refs look like ..
-     return (accession == null || accession.length() < 2) ? false
-             : getAccessionValidator().search(accession);
 +
    }
  
    /**
    {
      return "EMBL"; // getDbSource();
    }
-   @Override
-   public int getTier()
-   {
-     return 0;
-   }
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.ws.dbsources;
  
+ import java.util.Locale;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.InputStream;
@@@ -39,6 -41,8 +41,6 @@@ import javax.xml.stream.XMLInputFactory
  import javax.xml.stream.XMLStreamException;
  import javax.xml.stream.XMLStreamReader;
  
 -import com.stevesoft.pat.Regex;
 -
  import jalview.analysis.SequenceIdMatcher;
  import jalview.bin.Cache;
  import jalview.datamodel.Alignment;
@@@ -54,7 -58,6 +56,7 @@@ import jalview.util.DBRefUtils
  import jalview.util.DnaUtils;
  import jalview.util.MapList;
  import jalview.util.MappingUtils;
 +import jalview.util.MessageManager;
  import jalview.ws.ebi.EBIFetchClient;
  import jalview.xml.binding.embl.EntryType;
  import jalview.xml.binding.embl.EntryType.Feature;
@@@ -62,8 -65,16 +64,17 @@@ import jalview.xml.binding.embl.EntryTy
  import jalview.xml.binding.embl.ROOT;
  import jalview.xml.binding.embl.XrefType;
  
+ /**
+  * Provides XML binding and parsing of EMBL or EMBLCDS records retrieved from
+  * (e.g.) {@code https://www.ebi.ac.uk/ena/data/view/x53828&display=xml}.
+  * 
+  * @deprecated endpoint withdrawn August 2020 (JAL-3692), use EmblFlatfileSource
+  */
++
  public abstract class EmblXmlSource extends EbiFileRetrievedProxy
  {
++  // TODO: delete class or update tyhis validator for 2.12 style Platform.regex
+   private static final Regex ACCESSION_REGEX = new Regex("^[A-Z]+[0-9]+");
 -
    /*
     * JAL-1856 Embl returns this text for query not found
     */
      try
      {
        reply = dbFetch.fetchDataAsFile(
-               emprefx.toLowerCase() + ":" + query.trim(), "display=xml",
+               emprefx.toLowerCase(Locale.ROOT) + ":" + query.trim(), "display=xml",
                "xml");
      } catch (Exception e)
      {
        stopQuery();
-       throw new Exception(MessageManager.formatMessage(
-               "exception.ebiembl_retrieval_failed_on", new String[]
-               { emprefx.toLowerCase(), query.trim() }), e);
+       throw new Exception(
+               String.format("EBI EMBL XML retrieval failed for %s:%s",
+                       emprefx.toLowerCase(Locale.ROOT), query.trim()),
+               e);
      }
      return getEmblSequenceRecords(emprefx, query, reply);
    }
        XMLStreamReader streamReader = XMLInputFactory.newInstance()
                .createXMLStreamReader(is);
        javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
-       JAXBElement<ROOT> rootElement =  um.unmarshal(streamReader, ROOT.class);
+       JAXBElement<ROOT> rootElement = um.unmarshal(streamReader,
+               ROOT.class);
        ROOT root = rootElement.getValue();
  
        /*
                proteinSeq = new Sequence(proteinSeqName,
                        product.getSequenceAsString());
                matcher.add(proteinSeq);
+               proteinSeq.setDescription(product.getDescription());
                peptides.add(proteinSeq);
              }
              dnaToProteinMapping.setTo(proteinSeq);
                && dnaToProteinMapping.getTo() != null)
        {
          DBRefEntry dnaToEmblProteinRef = new DBRefEntry(
-                 DBRefSource.EMBLCDSProduct, sequenceVersion,
-                 proteinId);
+                 DBRefSource.EMBLCDSProduct, sequenceVersion, proteinId);
          dnaToEmblProteinRef.setMap(dnaToProteinMapping);
          dnaToProteinMapping.setMappedFromId(proteinId);
          dna.addDBRef(dnaToEmblProteinRef);
      {
        return new int[] {};
      }
-   
 -
      try
      {
        List<int[]> ranges = DnaUtils.parseLocation(location);
      return sf;
    }
  
+   @Override
+   public String getAccessionSeparator()
+   {
+     return null;
+   }
+   @Override
+   public Regex getAccessionValidator()
+   {
+     return ACCESSION_REGEX;
+   }
+   @Override
+   public String getDbVersion()
+   {
+     return "0";
+   }
+   @Override
+   public int getTier()
+   {
+     return 0;
+   }
+   @Override
+   public boolean isValidReference(String accession)
+   {
+     if (accession == null || accession.length() < 2)
+     {
+       return false;
+     }
+     return getAccessionValidator().search(accession);
+   }
 -
    /**
     * Truncates (if necessary) the exon intervals to match 3 times the length of
     * the protein; also accepts 3 bases longer (for stop codon not included in
      }
      int expectedCdsLength = proteinLength * 3;
      int exonLength = MappingUtils.getLength(Arrays.asList(exon));
-   
 -
      /*
       * if exon length matches protein, or is shorter, or longer by the 
       * length of a stop codon (3 bases), then leave it unchanged
      {
        return exon;
      }
-   
 -
      int origxon[];
      int sxpos = -1;
      int endxon = 0;
            // .println("Truncating final exon interval on region by "
            // + (cdspos - cdslength));
          }
-   
 -
          /*
           * shrink the final exon - reduce end position if forward
           * strand, increase it if reverse
          break;
        }
      }
-   
 -
      if (sxpos != -1)
      {
        // and trim the exon interval set if necessary
   */
  package jalview.ws.dbsources;
  
- import java.io.InputStream;
- import java.net.URL;
- import java.net.URLConnection;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Vector;
- import javax.xml.bind.JAXBContext;
- import javax.xml.bind.JAXBElement;
- import javax.xml.bind.JAXBException;
- import javax.xml.stream.FactoryConfigurationError;
- import javax.xml.stream.XMLInputFactory;
- import javax.xml.stream.XMLStreamException;
- import javax.xml.stream.XMLStreamReader;
- import com.stevesoft.pat.Regex;
+ import java.util.Locale;
 -
  import jalview.bin.Cache;
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentI;
@@@ -47,7 -32,6 +31,6 @@@ import jalview.datamodel.Sequence
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.schemes.ResidueProperties;
- import jalview.util.Platform;
  import jalview.util.StringUtils;
  import jalview.ws.seqfetcher.DbSourceProxyImpl;
  import jalview.xml.binding.uniprot.DbReferenceType;
@@@ -57,6 -41,23 +40,23 @@@ import jalview.xml.binding.uniprot.Loca
  import jalview.xml.binding.uniprot.PositionType;
  import jalview.xml.binding.uniprot.PropertyType;
  
+ import java.io.InputStream;
+ import java.net.HttpURLConnection;
+ import java.net.URL;
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Vector;
+ import javax.xml.bind.JAXBContext;
+ import javax.xml.bind.JAXBElement;
+ import javax.xml.bind.JAXBException;
+ import javax.xml.stream.FactoryConfigurationError;
+ import javax.xml.stream.XMLInputFactory;
+ import javax.xml.stream.XMLStreamException;
+ import javax.xml.stream.XMLStreamReader;
+ import com.stevesoft.pat.Regex;
  /**
   * This class queries the Uniprot database for sequence data, unmarshals the
   * returned XML, and converts it to Jalview Sequence records (including attached
@@@ -70,8 -71,6 +70,7 @@@ public class Uniprot extends DbSourcePr
    private static final String DEFAULT_UNIPROT_DOMAIN = "https://www.uniprot.org";
  
    private static final String BAR_DELIMITER = "|";
 +  private static Regex ACCESSION_REGEX;
  
    /**
     * Constructor
    @Override
    public Regex getAccessionValidator()
    {
 -    return new Regex("([A-Z]+[0-9]+[A-Z0-9]+|[A-Z0-9]+_[A-Z0-9]+)");
 +    if (ACCESSION_REGEX == null)
 +    {
 +      ACCESSION_REGEX = Platform
 +              .newRegex("([A-Z]+[0-9]+[A-Z0-9]+|[A-Z0-9]+_[A-Z0-9]+)");
 +    }
 +    return ACCESSION_REGEX;
    }
  
    /*
      startQuery();
      try
      {
-       queries = queries.toUpperCase().replaceAll(
+       queries = queries.toUpperCase(Locale.ROOT).replaceAll(
                "(UNIPROT\\|?|UNIPROT_|UNIREF\\d+_|UNIREF\\d+\\|?)", "");
        AlignmentI al = null;
  
                + ".xml";
  
        URL url = new URL(downloadstring);
-       URLConnection urlconn = url.openConnection();
-       InputStream istr = urlconn.getInputStream();
-       List<Entry> entries = getUniprotEntries(istr);
-       if (entries != null)
+       HttpURLConnection urlconn = (HttpURLConnection)url.openConnection();
+       // anything other than 200 means we don't have data
+       // TODO: JAL-3882 reuse the EnsemblRestClient's fair 
+       // use/backoff logic to retry when the server tells us to go away
+       if (urlconn.getResponseCode() == 200)
        {
 -        InputStream istr = urlconn.getInputStream();
 -        List<Entry> entries = getUniprotEntries(istr);
 -        if (entries != null)
 +        List<SequenceI> seqs = new ArrayList<>();
 +        for (Entry entry : entries)
          {
 -          List<SequenceI> seqs = new ArrayList<>();
 -          for (Entry entry : entries)
 -          {
 -            seqs.add(uniprotEntryToSequence(entry));
 -          }
 -          al = new Alignment(seqs.toArray(new SequenceI[seqs.size()]));
 +          seqs.add(uniprotEntryToSequence(entry));
          }
 +        al = new Alignment(seqs.toArray(new SequenceI[seqs.size()]));
        }
 +
        stopQuery();
        return al;
+       
      } catch (Exception e)
      {
        throw (e);
       */
      final String dbVersion = getDbVersion();
      List<DBRefEntry> dbRefs = new ArrayList<>();
+     boolean canonical=true;
      for (String accessionId : entry.getAccession())
      {
        DBRefEntry dbRef = new DBRefEntry(DBRefSource.UNIPROT, dbVersion,
-               accessionId);
+               accessionId,null,canonical);
+       canonical=false;
        dbRefs.add(dbRef);
      }
  
      } catch (JAXBException | XMLStreamException
              | FactoryConfigurationError e)
      {
+       if (e instanceof javax.xml.bind.UnmarshalException && e.getCause()!=null && e.getCause() instanceof XMLStreamException && e.getCause().getMessage().contains("[row,col]:[1,1]"))
+       {
+         // trying to parse an empty stream
+         return null;
+       }
        e.printStackTrace();
      }
      return entries;
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.ws.jws1;
  
+ import java.util.Locale;
  import jalview.analysis.AlignSeq;
  import jalview.bin.Cache;
  import jalview.datamodel.AlignmentView;
@@@ -183,7 -185,7 +185,7 @@@ public class JPredClient extends WS1Cli
  
    private String getPredictionName(String webServiceName)
    {
-     if (webServiceName.toLowerCase()
+     if (webServiceName.toLowerCase(Locale.ROOT)
              .indexOf("secondary structure prediction") > -1)
      {
        return webServiceName;
      WsURL = "http://www.compbio.dundee.ac.uk/JalviewWS/services/jpred";
  
      WebserviceInfo wsInfo = new WebserviceInfo(WebServiceJobTitle,
 -            WebServiceReference, true);
 +            WebServiceReference, Desktop.FRAME_MAKE_VISIBLE);
  
      return wsInfo;
    }
  
      } catch (Exception ex)
      {
 -      JvOptionPane.showMessageDialog(Desktop.desktop,
 +      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                MessageManager.formatMessage(
                        "label.secondary_structure_prediction_service_couldnt_be_located",
                        new String[]
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.ws.jws1;
  
+ import java.util.Locale;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.AlignmentView;
  import jalview.gui.AlignFrame;
@@@ -51,6 -53,8 +53,6 @@@ public class MsaWSClient extends WS1Cli
     */
    ext.vamsas.MuscleWS server;
  
 -  AlignFrame alignFrame;
 -
    /**
     * Creates a new MsaWSClient object that uses a service given by an externally
     * retrieved ServiceHandle
@@@ -76,7 -80,7 +78,7 @@@
      alignFrame = _alignFrame;
      if (!sh.getAbstractName().equals("MsaWS"))
      {
 -      JvOptionPane.showMessageDialog(Desktop.desktop,
 +      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
                MessageManager.formatMessage(
                        "label.service_called_is_not_msa_service",
                        new String[]
@@@ -89,7 -93,7 +91,7 @@@
  
      if ((wsInfo = setWebService(sh)) == null)
      {
 -      JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
 +      JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), MessageManager
                .formatMessage("label.msa_service_is_unknown", new String[]
                { sh.getName() }),
                MessageManager.getString("label.internal_jalview_error"),
  
      wsInfo.setProgressText(((submitGaps) ? "Re-alignment" : "Alignment")
              + " of " + altitle + "\nJob details\n");
-     String jobtitle = WebServiceName.toLowerCase();
+     String jobtitle = WebServiceName.toLowerCase(Locale.ROOT);
      if (jobtitle.endsWith("alignment"))
      {
        if (submitGaps && (!jobtitle.endsWith("realignment")
   */
  package jalview.ws.jws2;
  
 -import java.awt.event.ActionEvent;
 -import java.awt.event.ActionListener;
 +import jalview.gui.AlignFrame;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.WsParamSetI;
 +
  import java.util.List;
  
 -import javax.swing.JCheckBoxMenuItem;
  import javax.swing.JMenu;
 -import javax.swing.JMenuItem;
 -import javax.swing.event.MenuEvent;
 -import javax.swing.event.MenuListener;
 -
 -import compbio.metadata.Argument;
 -import jalview.api.AlignCalcWorkerI;
 -import jalview.bin.Cache;
 -import jalview.gui.AlignFrame;
 -import jalview.gui.Desktop;
 -import jalview.gui.JvSwingUtils;
 -import jalview.gui.WebserviceInfo;
 -import jalview.gui.WsJobParameters;
 -import jalview.util.MessageManager;
 -import jalview.ws.jws2.dm.AAConSettings;
 -import jalview.ws.jws2.dm.JabaWsParamSet;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 -import jalview.ws.params.WsParamSetI;
 -import jalview.ws.uimodel.AlignAnalysisUIText;
  
  /**
   * provides metadata for a jabaws2 service instance - resolves names, etc.
   */
  public abstract class Jws2Client extends jalview.ws.WSClient
  {
 -  protected AlignFrame alignFrame;
 -
 -  protected WsParamSetI preset;
 -
 -  protected List<Argument> paramset;
 +  /**
 +   * instantiate a new service client. preset and arguments are assumed to be
 +   * valid for the service
 +   * 
 +   * @param _alignFrame
 +   * @param preset
 +   * @param arguments
 +   */
    public Jws2Client(AlignFrame _alignFrame, WsParamSetI preset,
 -          List<Argument> arguments)
 +          List<ArgumentI> arguments)
    {
 -    alignFrame = _alignFrame;
 -    this.preset = preset;
 -    if (preset != null)
 -    {
 -      if (!((preset instanceof JabaPreset)
 -              || preset instanceof JabaWsParamSet))
 -      {
 -        /*
 -         * { this.preset = ((JabaPreset) preset).p; } else if (preset instanceof
 -         * JabaWsParamSet) { List<Argument> newargs = new ArrayList<Argument>();
 -         * JabaWsParamSet pset = ((JabaWsParamSet) preset); for (Option opt :
 -         * pset.getjabaArguments()) { newargs.add(opt); } if (arguments != null
 -         * && arguments.size() > 0) { // merge arguments with preset's own
 -         * arguments. for (Argument opt : arguments) { newargs.add(opt); } }
 -         * paramset = newargs; } else {
 -         */
 -        throw new Error(MessageManager.getString(
 -                "error.implementation_error_can_only_instantiate_jaba_param_sets"));
 -      }
 -    }
 -    else
 -    {
 -      // just provided with a bunch of arguments
 -      this.paramset = arguments;
 -    }
 -  }
 -
 -  boolean processParams(Jws2Instance sh, boolean editParams)
 -  {
 -    return processParams(sh, editParams, false);
 -  }
 -
 -  protected boolean processParams(Jws2Instance sh, boolean editParams,
 -          boolean adjustingExisting)
 -  {
 -
 -    if (editParams)
 -    {
 -      if (sh.paramStore == null)
 -      {
 -        sh.paramStore = new JabaParamStore(sh,
 -                Desktop.getUserParameterStore());
 -      }
 -      WsJobParameters jobParams = (preset == null && paramset != null
 -              && paramset.size() > 0)
 -                      ? new WsJobParameters(null, sh, null, paramset)
 -                      : new WsJobParameters(sh, preset);
 -      if (adjustingExisting)
 -      {
 -        jobParams.setName(MessageManager
 -                .getString("label.adjusting_parameters_for_calculation"));
 -      }
 -      if (!jobParams.showRunDialog())
 -      {
 -        return false;
 -      }
 -      WsParamSetI prset = jobParams.getPreset();
 -      if (prset == null)
 -      {
 -        paramset =
 -                /* JAL-3739 always take values from input form */
 -                /* jobParams.isServiceDefaults() ? null : */
 -                JabaParamStore.getJabafromJwsArgs(jobParams.getJobParams());
 -        this.preset = null;
 -      }
 -      else
 -      {
 -        this.preset = prset; // ((JabaPreset) prset).p;
 -        paramset = null; // no user supplied parameters.
 -      }
 -    }
 -    return true;
 +    super(_alignFrame, preset, arguments);
    }
  
    public Jws2Client()
      // anonymous constructor - used for headless method calls only
    }
  
 -  protected WebserviceInfo setWebService(Jws2Instance serv, boolean b)
 -  {
 -    // serviceHandle = serv;
 -    String serviceInstance = serv.action; // serv.service.getClass().getName();
 -    WebServiceName = serv.serviceType;
 -    WebServiceJobTitle = serv.getActionText();
 -    WsURL = serv.hosturl;
 -    if (!b)
 -    {
 -      return new WebserviceInfo(WebServiceJobTitle,
 -              WebServiceJobTitle + " using service hosted at "
 -                      + serv.hosturl + "\n"
 -                      + (serv.description != null ? serv.description : ""),
 -              false);
 -    }
 -    return null;
 -  }
    /*
     * Jws2Instance serviceHandle; (non-Javadoc)
     * 
     * @param service
     * @param alignFrame
     */
 -  abstract void attachWSMenuEntry(JMenu wsmenu, final Jws2Instance service,
 +  abstract void attachWSMenuEntry(JMenu wsmenu,
 +          final ServiceWithParameters service,
            final AlignFrame alignFrame);
 -  protected boolean registerAAConWSInstance(final JMenu wsmenu,
 -          final Jws2Instance service, final AlignFrame alignFrame)
 -  {
 -    final AlignAnalysisUIText aaui = service.getAlignAnalysisUI(); // null ; //
 -                                                                   // AlignAnalysisUIText.aaConGUI.get(service.serviceType.toString());
 -    if (aaui == null)
 -    {
 -      // not an instantaneous calculation GUI type service
 -      return false;
 -    }
 -    // create the instaneous calculation GUI bits and update state if existing
 -    // GUI elements already present
 -
 -    JCheckBoxMenuItem _aaConEnabled = null;
 -    for (int i = 0; i < wsmenu.getItemCount(); i++)
 -    {
 -      JMenuItem item = wsmenu.getItem(i);
 -      if (item instanceof JCheckBoxMenuItem
 -              && item.getText().equals(aaui.getAAconToggle()))
 -      {
 -        _aaConEnabled = (JCheckBoxMenuItem) item;
 -      }
 -    }
 -    // is there an aaCon worker already present - if so, set it to use the
 -    // given service handle
 -    {
 -      List<AlignCalcWorkerI> aaconClient = alignFrame.getViewport()
 -              .getCalcManager()
 -              .getRegisteredWorkersOfClass(aaui.getClient());
 -      if (aaconClient != null && aaconClient.size() > 0)
 -      {
 -        AbstractJabaCalcWorker worker = (AbstractJabaCalcWorker) aaconClient
 -                .get(0);
 -        if (!worker.service.hosturl.equals(service.hosturl))
 -        {
 -          // javax.swing.SwingUtilities.invokeLater(new Runnable()
 -          {
 -            // @Override
 -            // public void run()
 -            {
 -              removeCurrentAAConWorkerFor(aaui, alignFrame);
 -              buildCurrentAAConWorkerFor(aaui, alignFrame, service);
 -            }
 -          } // );
 -        }
 -      }
 -    }
 -
 -    // is there a service already registered ? there shouldn't be if we are
 -    // being called correctly
 -    if (_aaConEnabled == null)
 -    {
 -      final JCheckBoxMenuItem aaConEnabled = new JCheckBoxMenuItem(
 -              aaui.getAAconToggle());
 -
 -      aaConEnabled.setToolTipText(
 -              JvSwingUtils.wrapTooltip(true, aaui.getAAconToggleTooltip()));
 -      aaConEnabled.addActionListener(new ActionListener()
 -      {
 -        @Override
 -        public void actionPerformed(ActionEvent arg0)
 -        {
 -          List<AlignCalcWorkerI> aaconClient = alignFrame.getViewport()
 -                  .getCalcManager()
 -                  .getRegisteredWorkersOfClass(aaui.getClient());
 -          if (aaconClient != null && aaconClient.size() > 0)
 -          {
 -            removeCurrentAAConWorkerFor(aaui, alignFrame);
 -          }
 -          else
 -          {
 -            buildCurrentAAConWorkerFor(aaui, alignFrame);
 -
 -          }
 -        }
 -
 -      });
 -      wsmenu.add(aaConEnabled);
 -      final JMenuItem modifyParams = new JMenuItem(
 -              aaui.getAAeditSettings());
 -      modifyParams.setToolTipText(JvSwingUtils.wrapTooltip(true,
 -              aaui.getAAeditSettingsTooltip()));
 -      modifyParams.addActionListener(new ActionListener()
 -      {
 -
 -        @Override
 -        public void actionPerformed(ActionEvent arg0)
 -        {
 -          showAAConAnnotationSettingsFor(aaui, alignFrame);
 -        }
 -      });
 -      wsmenu.add(modifyParams);
 -      wsmenu.addMenuListener(new MenuListener()
 -      {
 -
 -        @Override
 -        public void menuSelected(MenuEvent arg0)
 -        {
 -          // TODO: refactor to the implementing class.
 -          if (alignFrame.getViewport().getAlignment().isNucleotide()
 -                  ? aaui.isNa()
 -                  : aaui.isPr())
 -          {
 -            aaConEnabled.setEnabled(true);
 -            modifyParams.setEnabled(true);
 -          }
 -          else
 -          {
 -            aaConEnabled.setEnabled(false);
 -            modifyParams.setEnabled(false);
 -          }
 -          List<AlignCalcWorkerI> aaconClient = alignFrame.getViewport()
 -                  .getCalcManager()
 -                  .getRegisteredWorkersOfClass(aaui.getClient());
 -          if (aaconClient != null && aaconClient.size() > 0)
 -          {
 -            aaConEnabled.setSelected(true);
 -          }
 -          else
 -          {
 -            aaConEnabled.setSelected(false);
 -          }
 -        }
 -
 -        @Override
 -        public void menuDeselected(MenuEvent arg0)
 -        {
 -          // TODO Auto-generated method stub
 -
 -        }
 -
 -        @Override
 -        public void menuCanceled(MenuEvent arg0)
 -        {
 -          // TODO Auto-generated method stub
 -
 -        }
 -      });
 -
 -    }
 -    return true;
 -  }
 -
 -  private static void showAAConAnnotationSettingsFor(
 -          final AlignAnalysisUIText aaui, AlignFrame alignFrame)
 -  {
 -    /*
 -     * preferred settings Whether AACon is automatically recalculated Which
 -     * AACon server to use What parameters to use
 -     */
 -    // could actually do a class search for this too
 -    AAConSettings fave = (AAConSettings) alignFrame.getViewport()
 -            .getCalcIdSettingsFor(aaui.getCalcId());
 -    if (fave == null)
 -    {
 -      fave = createDefaultAAConSettings(aaui);
 -    }
 -    new SequenceAnnotationWSClient(fave, alignFrame, true);
 -
 -  }
 -
 -  private static void buildCurrentAAConWorkerFor(
 -          final AlignAnalysisUIText aaui, AlignFrame alignFrame)
 -  {
 -    buildCurrentAAConWorkerFor(aaui, alignFrame, null);
 -  }
 -
 -  private static void buildCurrentAAConWorkerFor(
 -          final AlignAnalysisUIText aaui, AlignFrame alignFrame,
 -          Jws2Instance service)
 -  {
 -    /*
 -     * preferred settings Whether AACon is automatically recalculated Which
 -     * AACon server to use What parameters to use
 -     */
 -    AAConSettings fave = (AAConSettings) alignFrame.getViewport()
 -            .getCalcIdSettingsFor(aaui.getCalcId());
 -    if (fave == null)
 -    {
 -      fave = createDefaultAAConSettings(aaui, service);
 -    }
 -    else
 -    {
 -      if (service != null
 -              && !fave.getService().hosturl.equals(service.hosturl))
 -      {
 -        Cache.log.debug("Changing AACon service to " + service.hosturl
 -                + " from " + fave.getService().hosturl);
 -        fave.setService(service);
 -      }
 -    }
 -    new SequenceAnnotationWSClient(fave, alignFrame, false);
 -  }
 -
 -  private static AAConSettings createDefaultAAConSettings(
 -          AlignAnalysisUIText aaui)
 -  {
 -    return createDefaultAAConSettings(aaui, null);
 -  }
 -
 -  private static AAConSettings createDefaultAAConSettings(
 -          AlignAnalysisUIText aaui, Jws2Instance service)
 -  {
 -    if (service != null)
 -    {
 -      if (!service.serviceType.toString()
 -              .equals(compbio.ws.client.Services.AAConWS.toString()))
 -      {
 -        Cache.log.warn(
 -                "Ignoring invalid preferred service for AACon calculations (service type was "
 -                        + service.serviceType + ")");
 -        service = null;
 -      }
 -      else
 -      {
 -        // check service is actually in the list of currently avaialable
 -        // services
 -        if (!Jws2Discoverer.getDiscoverer().getServices().contains(service))
 -        {
 -          // it isn't ..
 -          service = null;
 -        }
 -      }
 -    }
 -    if (service == null)
 -    {
 -      // get the default service for AACon
 -      service = Jws2Discoverer.getDiscoverer().getPreferredServiceFor(null,
 -              aaui.getServiceType());
 -    }
 -    if (service == null)
 -    {
 -      // TODO raise dialog box explaining error, and/or open the JABA
 -      // preferences menu.
 -      throw new Error(
 -              MessageManager.getString("error.no_aacon_service_found"));
 -    }
 -    return new AAConSettings(true, service, null, null);
 -  }
 -
 -  private static void removeCurrentAAConWorkerFor(AlignAnalysisUIText aaui,
 -          AlignFrame alignFrame)
 -  {
 -    alignFrame.getViewport().getCalcManager()
 -            .removeRegisteredWorkersOfClass(aaui.getClient());
 -  }
  }
   */
  package jalview.ws.jws2;
  
 -import java.util.Locale;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.AlignmentView;
 +import jalview.gui.AlignFrame;
 +import jalview.gui.Desktop;
 +import jalview.gui.JvOptionPane;
 +import jalview.gui.JvSwingUtils;
 +import jalview.util.MessageManager;
 +import jalview.ws.WSMenuEntryProviderI;
 +import jalview.ws.api.JalviewServiceEndpointProviderI;
 +import jalview.ws.api.MultipleSequenceAlignmentI;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.gui.MsaWSThread;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.WsParamSetI;
  
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.util.List;
++import java.util.Locale;
  
  import javax.swing.JMenu;
  import javax.swing.JMenuItem;
  import javax.swing.ToolTipManager;
  
 -import compbio.data.msa.MsaWS;
 -import compbio.metadata.Argument;
 -import jalview.datamodel.AlignmentI;
 -import jalview.datamodel.AlignmentView;
 -import jalview.gui.AlignFrame;
 -import jalview.gui.Desktop;
 -import jalview.gui.JvOptionPane;
 -import jalview.gui.JvSwingUtils;
 -import jalview.util.MessageManager;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 -import jalview.ws.params.WsParamSetI;
  /**
 - * DOCUMENT ME!
 + * MsaWSClient
   * 
 - * @author $author$
 + * Instantiates web service menu items for multiple alignment services, and
 + * holds logic for constructing a web service thread.
 + * 
 + * TODO remove dependency on Jws2Client methods for creating AACon service UI
 + * elements.
 + * 
 + * @author Jim Procter et al
   * @version $Revision$
   */
 -public class MsaWSClient extends Jws2Client
 +public class MsaWSClient extends Jws2Client implements WSMenuEntryProviderI
  {
    /**
 -   * server is a WSDL2Java generated stub for an archetypal MsaWSI service.
 +   * server is a proxy class implementing the core methods for submitting,
 +   * monitoring and retrieving results from a multiple sequence alignment
 +   * service
     */
 -  MsaWS server;
 +  MultipleSequenceAlignmentI server;
  
 -  public MsaWSClient(Jws2Instance sh, String altitle,
 +  public MsaWSClient(ServiceWithParameters sh, String altitle,
            jalview.datamodel.AlignmentView msa, boolean submitGaps,
            boolean preserveOrder, AlignmentI seqdataset,
            AlignFrame _alignFrame)
@@@ -76,8 -67,7 +78,8 @@@
      // TODO Auto-generated constructor stub
    }
  
 -  public MsaWSClient(Jws2Instance sh, WsParamSetI preset, String altitle,
 +  public MsaWSClient(ServiceWithParameters sh, WsParamSetI preset,
 +          String altitle,
            jalview.datamodel.AlignmentView msa, boolean submitGaps,
            boolean preserveOrder, AlignmentI seqdataset,
            AlignFrame _alignFrame)
     *          DOCUMENT ME!
     */
  
 -  public MsaWSClient(Jws2Instance sh, WsParamSetI preset,
 -          List<Argument> arguments, boolean editParams, String altitle,
 +  public MsaWSClient(ServiceWithParameters sh, WsParamSetI preset,
 +          List<ArgumentI> arguments, boolean editParams, String altitle,
            jalview.datamodel.AlignmentView msa, boolean submitGaps,
            boolean preserveOrder, AlignmentI seqdataset,
            AlignFrame _alignFrame)
    {
      super(_alignFrame, preset, arguments);
 -    if (!processParams(sh, editParams))
 -    {
 -      return;
 -    }
 -
 -    if (!(sh.service instanceof MsaWS))
 -    {
 -      // redundant at mo - but may change
 -      JvOptionPane.showMessageDialog(Desktop.desktop,
 -              MessageManager.formatMessage(
 -                      "label.service_called_is_not_msa_service",
 -                      new String[]
 -                      { sh.serviceType }),
 -              MessageManager.getString("label.internal_jalview_error"),
 -              JvOptionPane.WARNING_MESSAGE);
 -
 -      return;
 -    }
 -    server = (MsaWS) sh.service;
 -    if ((wsInfo = setWebService(sh, false)) == null)
 -    {
 -      JvOptionPane.showMessageDialog(Desktop.desktop, MessageManager
 -              .formatMessage("label.msa_service_is_unknown", new String[]
 -              { sh.serviceType }),
 -              MessageManager.getString("label.internal_jalview_error"),
 -              JvOptionPane.WARNING_MESSAGE);
 -
 -      return;
 -    }
 -
 -    startMsaWSClient(altitle, msa, submitGaps, preserveOrder, seqdataset);
 +    processParams(sh, editParams).thenAccept((startJob) -> {
 +      if (!startJob)
 +        return;
 +      
 +      if (!(sh instanceof JalviewServiceEndpointProviderI
 +              && ((JalviewServiceEndpointProviderI) sh)
 +                      .getEndpoint() instanceof MultipleSequenceAlignmentI))
 +      {
 +        // redundant at mo - but may change
 +        JvOptionPane.showMessageDialog(Desktop.getDesktopPane(),
 +                MessageManager.formatMessage(
 +                        "label.service_called_is_not_msa_service",
 +                        new String[]
 +                        { sh.getName() }),
 +                MessageManager.getString("label.internal_jalview_error"),
 +                JvOptionPane.WARNING_MESSAGE);
 +  
 +        return;
 +      }
 +      serviceHandle = sh;
 +      server = (MultipleSequenceAlignmentI) ((JalviewServiceEndpointProviderI) sh)
 +              .getEndpoint();
 +      if ((wsInfo = setWebService(sh, false)) == null)
 +      {
 +        JvOptionPane.showMessageDialog(Desktop.getDesktopPane(), MessageManager
 +                .formatMessage("label.msa_service_is_unknown", new String[]
 +                { sh.getName() }),
 +                MessageManager.getString("label.internal_jalview_error"),
 +                JvOptionPane.WARNING_MESSAGE);
 +  
 +        return;
 +      }
 +  
 +      startMsaWSClient(altitle, msa, submitGaps, preserveOrder, seqdataset);
 +    });
    }
  
    public MsaWSClient()
  
      wsInfo.setProgressText(((submitGaps) ? "Re-alignment" : "Alignment")
              + " of " + altitle + "\nJob details\n");
-     String jobtitle = WebServiceName.toLowerCase();
+     String jobtitle = WebServiceName.toLowerCase(Locale.ROOT);
      if (jobtitle.endsWith("alignment"))
      {
        if (submitGaps && (!jobtitle.endsWith("realignment")
  
    @Override
    public void attachWSMenuEntry(JMenu rmsawsmenu,
-           final ServiceWithParameters service, final AlignFrame alignFrame)
 -          final Jws2Instance service, final AlignFrame af)
++          final Jws2Instance service, final AlignFrame alignFrame)
    {
-     if (Jws2ClientFactory.registerAAConWSInstance(rmsawsmenu,
-                     service, alignFrame))
 -    if (registerAAConWSInstance(rmsawsmenu, service, af))
++    if (registerAAConWSInstance(rmsawsmenu, service, alignFrame))
      {
        // Alignment dependent analysis calculation WS gui
        return;
      }
 +    serviceHandle = service;
      setWebService(service, true); // headless
 +    attachWSMenuEntry(rmsawsmenu, alignFrame);
 +  }
 +
 +  @Override
 +  public void attachWSMenuEntry(JMenu wsmenu, AlignFrame alignFrame)
 +  {
      boolean finished = true, submitGaps = false;
 -    JMenu msawsmenu = rmsawsmenu;
 +    /**
 +     * temp variables holding msa service submenu or root service menu
 +     */
 +    JMenu msawsmenu = wsmenu;
 +    JMenu rmsawsmenu = wsmenu;
      String svcname = WebServiceName;
      if (svcname.endsWith("WS"))
      {
        rmsawsmenu.add(msawsmenu);
        calcName = "";
      }
 -    boolean hasparams = service.hasParameters();
 +    boolean hasparams = serviceHandle.hasParameters();
 +    ServiceWithParameters service = (ServiceWithParameters) serviceHandle;
      do
      {
        String action = "Align ";
          @Override
          public void actionPerformed(ActionEvent e)
          {
 -          AlignmentView msa = af.gatherSequencesForAlignment();
 +          AlignmentView msa = alignFrame.gatherSequencesForAlignment();
  
            if (msa != null)
            {
 -            new MsaWSClient(service, af.getTitle(), msa, withGaps,
 +            new MsaWSClient(service, alignFrame.getTitle(), msa, withGaps,
                      true,
 -                    af.getViewport().getAlignment().getDataset(),
 -                    af);
 +                    alignFrame.getViewport().getAlignment().getDataset(),
 +                    alignFrame);
            }
  
          }
            @Override
            public void actionPerformed(ActionEvent e)
            {
 -            AlignmentView msa = af.gatherSequencesForAlignment();
 +            AlignmentView msa = alignFrame.gatherSequencesForAlignment();
              if (msa != null)
              {
 -              startJob(service, af, withGaps, msa);
 +              new MsaWSClient(service, null, null, true,
 +                      alignFrame.getTitle(), msa, withGaps, true,
 +                      alignFrame.getViewport().getAlignment().getDataset(),
 +                      alignFrame);
              }
  
            }
  
            final int showToolTipFor = ToolTipManager.sharedInstance()
                    .getDismissDelay();
 -          for (final WsParamSetI preSet : presets)
 +          for (final WsParamSetI preset : presets)
            {
 -            final JMenuItem methodR = new JMenuItem(preSet.getName());
 +            final JMenuItem methodR = new JMenuItem(preset.getName());
              final int QUICK_TOOLTIP = 1500;
              // JAL-1582 shorten tooltip display time in these menu items as
              // they can obscure other options
  
              });
              String tooltip = JvSwingUtils.wrapTooltip(true, "<strong>"
 -                    + (preSet.isModifiable()
 +                    + (preset.isModifiable()
                              ? MessageManager.getString("label.user_preset")
                              : MessageManager
                                      .getString("label.service_preset"))
 -                    + "</strong><br/>" + preSet.getDescription());
 +                    + "</strong><br/>" + preset.getDescription());
              methodR.setToolTipText(tooltip);
              methodR.addActionListener(new ActionListener()
              {
                @Override
                public void actionPerformed(ActionEvent e)
                {
 -                AlignmentView msa = af
 +                AlignmentView msa = alignFrame
                          .gatherSequencesForAlignment();
  
                  if (msa != null)
                  {
 -                  MsaWSClient msac = new MsaWSClient(service, preSet,
 -                          af.getTitle(), msa, false, true,
 -                          af.getViewport().getAlignment()
 +                  MsaWSClient msac = new MsaWSClient(service, preset,
 +                          alignFrame.getTitle(), msa, false, true,
 +                          alignFrame.getViewport().getAlignment()
                                    .getDataset(),
 -                          af);
 +                          alignFrame);
                  }
  
                }
        }
      } while (!finished);
    }
 -
 -  protected void startJob(final Jws2Instance service, final AlignFrame af,
 -          final boolean withGaps, AlignmentView msa)
 -  {
 -    try {
 -    new MsaWSClient(service, null, null, true,
 -            af.getTitle(), msa, withGaps, true,
 -            af.getViewport().getAlignment().getDataset(),
 -            af);
 -    } catch (Exception e) {
 -      JvOptionPane.showMessageDialog(alignFrame, e.getMessage(),
 -              MessageManager.getString("label.state_job_error"),
 -              JvOptionPane.WARNING_MESSAGE);
 -
 -    }
 -  }
  }
   */
  package jalview.ws.jws2;
  
 -import java.util.Locale;
 -
  import jalview.api.AlignCalcWorkerI;
  import jalview.gui.AlignFrame;
  import jalview.gui.Desktop;
  import jalview.gui.JvSwingUtils;
  import jalview.util.MessageManager;
 -import jalview.ws.jws2.dm.AAConSettings;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.params.AutoCalcSetting;
  import jalview.ws.params.WsParamSetI;
  import jalview.ws.uimodel.AlignAnalysisUIText;
  
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.util.List;
++import java.util.Locale;
++
  
  import javax.swing.JMenu;
  import javax.swing.JMenuItem;
@@@ -51,7 -53,7 +53,7 @@@ public class SequenceAnnotationWSClien
      // TODO Auto-generated constructor stub
    }
  
 -  public SequenceAnnotationWSClient(final Jws2Instance sh,
 +  public SequenceAnnotationWSClient(final ServiceWithParameters sh,
            AlignFrame alignFrame, WsParamSetI preset, boolean editParams)
    {
      super(alignFrame, preset, null);
@@@ -61,8 -63,8 +63,8 @@@
    // dan think. Do I need to change this method to run RNAalifold through the
    // GUI
  
 -  public void initSequenceAnnotationWSClient(final Jws2Instance sh,
 -          AlignFrame alignFrame, WsParamSetI preset, boolean editParams)
 +  private void initSequenceAnnotationWSClient(final ServiceWithParameters sh,
 +      AlignFrame alignFrame, final WsParamSetI preset, boolean editParams)
    {
      // dan changed! dan test. comment out if conditional
      // if (alignFrame.getViewport().getAlignment().isNucleotide())
        // columns
  
        List<AlignCalcWorkerI> clnts = alignFrame.getViewport()
 -              .getCalcManager().getRegisteredWorkersOfClass(clientClass);
 -      AbstractJabaCalcWorker worker;
 -      if (clnts == null || clnts.size() == 0)
 +          .getCalcManager()
 +          .getWorkersOfClass(SeqAnnotationServiceCalcWorker.class);
 +
 +      SeqAnnotationServiceCalcWorker tmpworker = null;
 +      if (clnts != null)
        {
 -        if (!processParams(sh, editParams))
 +        for (AlignCalcWorkerI _worker : clnts)
          {
 -          return;
 +          tmpworker = (SeqAnnotationServiceCalcWorker) _worker;
 +          if (tmpworker.hasService()
 +              && tmpworker.getService().getClass().equals(clientClass))
 +          {
 +            break;
 +          }
 +          tmpworker = null;
          }
 -        try
 -        {
 -          worker = (AbstractJabaCalcWorker) (clientClass
 -                  .getConstructor(new Class[]
 -                  { Jws2Instance.class, AlignFrame.class, WsParamSetI.class,
 -                      List.class })
 -                  .newInstance(new Object[]
 -                  { sh, alignFrame, this.preset, paramset }));
 -        } catch (Exception x)
 -        {
 -          x.printStackTrace();
 -          throw new Error(
 +      }
 +      final var worker = tmpworker;
 +      if (worker == null)
 +      {
 +        processParams(sh, editParams).thenAccept((startJob) -> {
 +          if (startJob)
 +          {
 +            final SeqAnnotationServiceCalcWorker worker_;
 +            try
 +            {
 +              worker_ = new SeqAnnotationServiceCalcWorker(sh, alignFrame, this.preset,
 +                  paramset);
 +            } catch (Exception x)
 +            {
 +              x.printStackTrace();
 +              throw new Error(
                    MessageManager.getString("error.implementation_error"),
                    x);
 -        }
 -        alignFrame.getViewport().getCalcManager().registerWorker(worker);
 -        alignFrame.getViewport().getCalcManager().startWorker(worker);
 +            }
 +            alignFrame.getViewport().getCalcManager().registerWorker(worker_);
 +                // also starts the worker
 +            startSeqAnnotationWorker(sh, alignFrame, preset, editParams);
 +          }
 +        });
        }
        else
        {
 -        worker = (AbstractJabaCalcWorker) clnts.get(0);
 +        WsParamSetI preset_;
          if (editParams)
          {
            paramset = worker.getArguments();
 -          preset = worker.getPreset();
 +          preset_ = worker.getPreset();
 +        }
 +        else
 +        {
 +          preset_ = preset;
          }
 +        processParams(sh, editParams, true).thenAccept((startJob) -> {
 +          if (startJob)
 +          {
 +            // reinstate worker if it was blacklisted (might have happened due
 +            // to
 +            // invalid parameters)
 +            alignFrame.getViewport().getCalcManager().enableWorker(worker);
 +            worker.updateParameters(this.preset, paramset);
 +            startSeqAnnotationWorker(sh, alignFrame, preset_, editParams);
 +          }
 +        });
  
 -        if (!processParams(sh, editParams, true))
 +        if (!processParams(sh, editParams, true).toCompletableFuture().join())
          {
            return;
          }
 -        // reinstate worker if it was blacklisted (might have happened due to
 -        // invalid parameters)
 -        alignFrame.getViewport().getCalcManager().enableWorker(worker);
 -        worker.updateParameters(this.preset, paramset);
        }
      }
-     else
-     {
-       startSeqAnnotationWorker(sh, alignFrame, preset, editParams);
-     }
-   }
-   private void startSeqAnnotationWorker(ServiceWithParameters sh,
-       AlignFrame alignFrame, WsParamSetI preset, boolean editParams)
-   {
-     if (!sh.isInteractiveUpdate())
+     if (sh.action.toLowerCase(Locale.ROOT).contains("disorder"))
      {
        // build IUPred style client. take sequences, returns annotation per
        // sequence.
 -      if (!processParams(sh, editParams))
 -      {
 -        return;
 -      }
 -
 -      alignFrame.getViewport().getCalcManager().startWorker(
 -              new AADisorderClient(sh, alignFrame, preset, paramset));
 +      processParams(sh, editParams).thenAccept((startJob) -> {
 +        if (startJob)
 +        {
 +          alignFrame.getViewport().getCalcManager().startWorker(
 +              new SeqAnnotationServiceCalcWorker(sh, alignFrame, preset, paramset));
 +        }
 +      });
      }
    }
  
 -  public SequenceAnnotationWSClient(AAConSettings fave,
 +  public SequenceAnnotationWSClient(AutoCalcSetting fave,
            AlignFrame alignFrame, boolean b)
    {
 -    super(alignFrame, fave.getPreset(), fave.getJobArgset());
 +    super(alignFrame, fave.getPreset(), fave.getArgumentSet());
      initSequenceAnnotationWSClient(fave.getService(), alignFrame,
              fave.getPreset(), b);
    }
     * @see jalview.ws.jws2.Jws2Client#attachWSMenuEntry(javax.swing.JMenu,
     * jalview.ws.jws2.jabaws2.Jws2Instance, jalview.gui.AlignFrame)
     */
 -  public void attachWSMenuEntry(JMenu wsmenu, final Jws2Instance service,
 +  @Override
 +  public void attachWSMenuEntry(JMenu wsmenu,
 +          final ServiceWithParameters service,
            final AlignFrame alignFrame)
    {
 -    if (registerAAConWSInstance(wsmenu, service, alignFrame))
 +    if (Jws2ClientFactory.registerAAConWSInstance(wsmenu,
 +            service, alignFrame))
      {
        // Alignment dependent analysis calculation WS gui
        return;
      }
      boolean hasparams = service.hasParameters();
 -    // Assume name ends in WS
 -    String calcName = service.serviceType.substring(0,
 -            service.serviceType.length() - 2);
 +    String calcName = service.getName();
 +    if (calcName.endsWith("WS"))
 +    {
 +      // Remove "WS" suffix
 +      calcName = calcName.substring(0, calcName.length() - 2);
 +    }
  
      JMenuItem annotservice = new JMenuItem(MessageManager.formatMessage(
              "label.calcname_with_default_settings", new String[]
        @Override
        public void actionPerformed(ActionEvent e)
        {
 -        new SequenceAnnotationWSClient(service, alignFrame, null, false);
 +        new SequenceAnnotationWSClient(service, alignFrame,
 +                null, false);
        }
      });
      wsmenu.add(annotservice);
  
        annotservice.addActionListener(new ActionListener()
        {
 +        @Override
          public void actionPerformed(ActionEvent e)
          {
 -          new SequenceAnnotationWSClient(service, alignFrame, null, true);
 +          new SequenceAnnotationWSClient(service, alignFrame,
 +                  null, true);
          }
        });
        wsmenu.add(annotservice);
                    + "</strong><br/>" + preset.getDescription()));
            methodR.addActionListener(new ActionListener()
            {
 +            @Override
              public void actionPerformed(ActionEvent e)
              {
 -              new SequenceAnnotationWSClient(service, alignFrame, preset,
 +              new SequenceAnnotationWSClient(service,
 +                      alignFrame, preset,
                        false);
              }
  
      {
        annotservice = new JMenuItem(
                MessageManager.getString("label.view_documentation"));
 -      if (service.docUrl != null)
 +      if (service != null && service.hasDocumentationUrl())
        {
          annotservice.addActionListener(new ActionListener()
          {
            @Override
            public void actionPerformed(ActionEvent arg0)
            {
 -            Desktop.instance.showUrl(service.docUrl);
 +            Desktop.getInstance().showUrl(service.getDocumentationUrl());
            }
          });
          annotservice.setToolTipText(
                  JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage(
                          "label.view_service_doc_url", new String[]
 -                        { service.docUrl, service.docUrl })));
 +                        { service.getDocumentationUrl(),
 +                            service.getDocumentationUrl() })));
          wsmenu.add(annotservice);
        }
      }
   */
  package jalview.ws.jws2.dm;
  
- import jalview.util.MessageManager;
- import jalview.ws.jws2.ParameterUtils;
- import jalview.ws.params.OptionI;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.List;
  
  import compbio.metadata.Option;
+ import jalview.util.MessageManager;
+ import jalview.ws.jws2.ParameterUtils;
+ import jalview.ws.params.OptionI;
  
  public class JabaOption implements jalview.ws.params.OptionI
  {
@@@ -94,9 -93,9 +93,9 @@@
        opt.setDefaultValue(selectedItem);
      } catch (Exception e)
      {
-       e.printStackTrace();
-       throw new Error(MessageManager.getString(
-               "error.implementation_error_cannot_set_jaba_option"));
+       throw new IllegalArgumentException(MessageManager
+               .formatMessage("error.invalid_value_for_option", new String[]
+               { selectedItem, opt.getName() }));
      }
    }
  
      return opt;
    }
  
 +  @Override
 +  public List<String> getDisplayNames()
 +  {
 +    return null; // not supported for Jaba options
 +  }
 +
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.ws.rest;
  
+ import java.util.Locale;
  import jalview.ws.params.ArgumentI;
  import jalview.ws.params.InvalidArgumentException;
  import jalview.ws.params.OptionI;
@@@ -30,6 -32,7 +32,6 @@@ import jalview.ws.params.simple.Option
  import java.io.UnsupportedEncodingException;
  import java.nio.charset.Charset;
  import java.util.ArrayList;
 -import java.util.Collection;
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
@@@ -57,9 -60,9 +59,9 @@@ public abstract class InputTyp
    {
      NUC, PROT, MIX;
  
 -    public static Collection<String> toStringValues()
 +    public static List<String> toStringValues()
      {
 -      Collection<String> c = new ArrayList<String>();
 +      List<String> c = new ArrayList<>();
        for (molType type : values())
        {
          c.add(type.toString());
@@@ -74,7 -77,7 +76,7 @@@
  
    public int max = 0; // unbounded
  
 -  protected ArrayList<Class> inputData = new ArrayList<Class>();
 +  protected List<Class> inputData = new ArrayList<>();
  
    /**
     * initialise the InputType with a list of jalview data classes that the
    public boolean validFor(RestJob restJob)
    {
      if (!validFor(restJob.rsd))
 +    {
        return false;
 +    }
      for (Class cl : inputData)
      {
        if (!restJob.hasDataOfType(cl))
    public boolean validFor(RestServiceDescription restServiceDescription)
    {
      if (!restServiceDescription.inputParams.values().contains(this))
 +    {
        return false;
 +    }
  
      return true;
    }
          {
            valid = false;
            warnings.append("Invalid value for parameter "
-                   + mtch.group(1).toLowerCase() + " '" + mtch.group(2)
+                   + mtch.group(1).toLowerCase(Locale.ROOT) + " '" + mtch.group(2)
                    + "' (expected an integer)\n");
          }
  
  
    public List<OptionI> getBaseOptions()
    {
 -    ArrayList<OptionI> opts = new ArrayList<OptionI>();
 +    ArrayList<OptionI> opts = new ArrayList<>();
      opts.add(new IntegerParameter("min",
              "Minimum number of data of this type", true, 1, min, 0, -1));
      opts.add(new IntegerParameter("max",
    public void configureFromArgumentI(List<ArgumentI> currentSettings)
            throws InvalidArgumentException
    {
 -    ArrayList<String> urltoks = new ArrayList<String>();
 +    List<String> urltoks = new ArrayList<>();
      String rg;
      for (ArgumentI arg : currentSettings)
      {
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.ws.rest;
  
+ import java.util.Locale;
  import jalview.bin.Cache;
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentAnnotation;
@@@ -59,6 -61,8 +61,6 @@@ import org.apache.http.client.methods.H
  import org.apache.http.entity.mime.HttpMultipartMode;
  import org.apache.http.entity.mime.MultipartEntity;
  import org.apache.http.impl.client.DefaultHttpClient;
 -import org.apache.http.protocol.BasicHttpContext;
 -import org.apache.http.protocol.HttpContext;
  import org.apache.http.util.EntityUtils;
  
  public class RestJobThread extends AWSThread
    protected void doHttpReq(Stage stg, RestJob rj, String postUrl)
            throws Exception
    {
 -    StringBuffer respText = new StringBuffer();
 -    // con.setContentHandlerFactory(new
 -    // jalview.ws.io.mime.HttpContentHandler());
      HttpRequestBase request = null;
      String messages = "";
      if (stg == Stage.SUBMIT)
      {
        DefaultHttpClient httpclient = new DefaultHttpClient();
  
 -      HttpContext localContext = new BasicHttpContext();
        HttpResponse response = null;
        try
        {
          Cache.log.debug("Processing result set.");
          processResultSet(rj, response, request);
          break;
 +
        case 202:
 -        rj.statMessage = "<br>Job submitted successfully. Results available at this URL:\n"
 -                + "<a href=" + rj.getJobId() + "\">" + rj.getJobId()
 -                + "</a><br>";
 -        rj.running = true;
 +        markJobAsRunning(rj);
          break;
 +
 +      case 201:
 +        // Created - redirect may be present. Fallthrough to 302
        case 302:
 -        Header[] loc;
 -        if (!rj.isSubmitted()
 -                && (loc = response
 -                        .getHeaders(HTTPConstants.HEADER_LOCATION)) != null
 -                && loc.length > 0)
 -        {
 -          if (loc.length > 1)
 -          {
 -            Cache.log.warn("Ignoring additional " + (loc.length - 1)
 -                    + " location(s) provided in response header ( next one is '"
 -                    + loc[1].getValue() + "' )");
 -          }
 -          rj.setJobId(loc[0].getValue());
 -          rj.setSubmitted(true);
 -        }
 +        extractJobId(rj, response);
          completeStatus(rj, response);
          break;
        case 500:
 -        // Failed.
 -        rj.setSubmitted(true);
 -        rj.setAllowedServerExceptions(0);
 -        rj.setSubjobComplete(true);
 -        rj.error = true;
 -        rj.running = false;
 -        completeStatus(rj, response,
 -                "" + getStage(stg) + "failed. Reason below:\n");
 +        markAsFailed(rj, response);
 +        completeStatus(rj, response, "" + getStage(stg)
 +                + "failed. Reason below:\n");
          break;
        default:
          // Some other response. Probably need to pop up the content in a window.
      }
    }
  
 +  private void markAsFailed(RestJob rj, HttpResponse response)
 +  {
 +    // Failed.
 +    rj.setSubmitted(true);
 +    rj.setAllowedServerExceptions(0);
 +    rj.setSubjobComplete(true);
 +    rj.error = true;
 +    rj.running = false;
 +  }
 +
 +  /**
 +   * set the jobRunning flag and post a link to the physical result page encoded
 +   * in rj.getJobId()
 +   * 
 +   * @param rj
 +   */
 +  private void markJobAsRunning(RestJob rj)
 +  {
 +    rj.statMessage = "<br>Job submitted successfully. Results available at this URL:\n"
 +            + "<a href="
 +            + rj.getJobId()
 +            + "\">"
 +            + rj.getJobId()
 +            + "</a><br>";
 +    rj.running = true;
 +  }
 +
 +  /**
 +   * extract the job ID URL from the redirect page. Does nothing if job is
 +   * already running.
 +   * 
 +   * @param rj
 +   * @param response
 +   */
 +  private void extractJobId(RestJob rj, HttpResponse response)
 +  {
 +    Header[] loc;
 +    if (!rj.isSubmitted())
 +    {
 +
 +      // redirect URL - typical for IBIVU type jobs.
 +      if ((loc = response.getHeaders(HTTPConstants.HEADER_LOCATION)) != null
 +              && loc.length > 0)
 +      {
 +        if (loc.length > 1)
 +        {
 +          Cache.log
 +                  .warn("Ignoring additional "
 +                          + (loc.length - 1)
 +                          + " location(s) provided in response header ( next one is '"
 +                          + loc[1].getValue() + "' )");
 +        }
 +        rj.setJobId(loc[0].getValue());
 +        rj.setSubmitted(true);
 +      }
 +    }
 +  }
 +
    /**
     * job has completed. Something valid should be available from con
     * 
       */
      String f;
      StringBuffer content = new StringBuffer(f = EntityUtils.toString(en));
-     f = f.toLowerCase();
+     f = f.toLowerCase(Locale.ROOT);
      int body = f.indexOf("<body");
      if (body > -1)
      {
      /**
       * alignment panels derived from each alignment set returned by service.
       */
 -    ArrayList<jalview.gui.AlignmentPanel> destPanels = new ArrayList<jalview.gui.AlignmentPanel>();
 +    ArrayList<jalview.gui.AlignmentPanel> destPanels = new ArrayList<>();
      /**
       * list of instructions for how to process each distinct alignment set
       * returned by the job set
       */
 -    ArrayList<AddDataTo> resultDest = new ArrayList<AddDataTo>();
 +    ArrayList<AddDataTo> resultDest = new ArrayList<>();
      /**
       * when false, zeroth pane is panel derived from input deta.
       */
      boolean vsepjobs = restClient.service.isVseparable();
      // total number of distinct alignment sets generated by job set.
      int numAlSets = 0, als = 0;
 -    List<AlignmentI> destAls = new ArrayList<AlignmentI>();
 -    List<jalview.datamodel.HiddenColumns> destColsel = new ArrayList<jalview.datamodel.HiddenColumns>();
 -    List<List<NewickFile>> trees = new ArrayList<List<NewickFile>>();
 +    List<AlignmentI> destAls = new ArrayList<>();
 +    List<jalview.datamodel.HiddenColumns> destColsel = new ArrayList<>();
 +    List<List<NewickFile>> trees = new ArrayList<>();
  
      do
      {
  
                if (alset.trees != null)
                {
 -                trees.add(new ArrayList<NewickFile>(alset.trees));
 +                trees.add(new ArrayList<>(alset.trees));
                }
                else
                {
         */
        int vrestjob = 0;
        // Destination alignments for all result data.
 -      ArrayList<SequenceGroup> visgrps = new ArrayList<SequenceGroup>();
 -      Hashtable<String, SequenceGroup> groupNames = new Hashtable<String, SequenceGroup>();
 +      ArrayList<SequenceGroup> visgrps = new ArrayList<>();
 +      Hashtable<String, SequenceGroup> groupNames = new Hashtable<>();
        ArrayList<AlignmentAnnotation> visAlAn = null;
        for (nvertsep = 0; nvertsep < nvertseps; nvertsep++)
        {
                  }
                  if (visAlAn == null)
                  {
 -                  visAlAn = new ArrayList<AlignmentAnnotation>();
 +                  visAlAn = new ArrayList<>();
                  }
                  AlignmentAnnotation visan = null;
                  for (AlignmentAnnotation v : visAlAn)
        HiddenColumns destcs;
        String alTitle = MessageManager
                .formatMessage("label.webservice_job_title_on", new String[]
 -              { restClient.service.details.Action,
 -                  restClient.service.details.Name, restClient.viewTitle });
 +              { restClient.service.details.getAction(),
 +                  restClient.service.details.getName(),
 +                  restClient.viewTitle });
        switch (action)
        {
        case newAlignment:
     */
    public boolean isValid()
    {
 -    ArrayList<String> _warnings = new ArrayList<String>();
 +    ArrayList<String> _warnings = new ArrayList<>();
      boolean validt = true;
      if (jobs != null)
      {
   */
  package jalview.ws.sifts;
  
 -import java.util.Locale;
 -
  import java.io.File;
  import java.io.FileInputStream;
+ import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.PrintStream;
  import java.net.URL;
  import java.net.URLConnection;
+ import java.nio.file.Files;
+ import java.nio.file.Path;
+ import java.nio.file.attribute.BasicFileAttributes;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
+ import java.util.Date;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
++import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  import java.util.TreeMap;
@@@ -53,6 -60,7 +59,7 @@@ import jalview.api.SiftsClientI
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.DBRefSource;
  import jalview.datamodel.SequenceI;
+ import jalview.io.BackupFiles;
  import jalview.io.StructureFile;
  import jalview.schemes.ResidueProperties;
  import jalview.structure.StructureMapping;
@@@ -96,6 -104,8 +103,8 @@@ public class SiftsClient implements Sif
     */
    private jalview.datamodel.Mapping seqFromPdbMapping;
  
+   private static final int BUFFER_SIZE = 4096;
    public static final int UNASSIGNED = Integer.MIN_VALUE;
  
    private static final int PDB_RES_POS = 0;
  
    private final static String NEWLINE = System.lineSeparator();
  
 +  private static final boolean GET_STREAM = false;
 +  private static final boolean CACHE_FILE = true;
    private String curSourceDBRef;
  
    private HashSet<String> curDBRefAccessionIdsString;
 +  private boolean doCache = false;
  
    private enum CoordinateSys
    {
      UNIPROT("UniProt"), PDB("PDBresnum"), PDBe("PDBe");
 -
      private String name;
  
      private CoordinateSys(String name)
    {
      NAME_SEC_STRUCTURE("nameSecondaryStructure"),
      CODE_SEC_STRUCTURE("codeSecondaryStructure"), ANNOTATION("Annotation");
 -
      private String code;
  
      private ResidueDetailType(String code)
    {
      this.pdb = pdb;
      this.pdbId = pdb.getId();
 -    File siftsFile = getSiftsFile(pdbId);
 -    siftsEntry = parseSIFTs(siftsFile);
 +    if (doCache) {
 +      File siftsFile = getSiftsFile(pdbId);
 +      siftsEntry = parseSIFTs(siftsFile);
 +    } else {
 +      siftsEntry = parseSIFTSStreamFor(pdbId);
 +    }
 +  }
 +
 +  /**
 +   * A more streamlined version of SIFT reading that allows for streaming of the data.
 +   * 
 +   * @param pdbId
 +   * @return
 +   * @throws SiftsException
 +   */
 +  private static Entry parseSIFTSStreamFor(String pdbId) throws SiftsException
 +  {
 +    try
 +    {
 +      InputStream is = (InputStream) downloadSifts(pdbId, GET_STREAM);
 +      return parseSIFTs(is);
 +    } catch (Exception e)
 +    {
 +      throw new SiftsException(e.getMessage());
 +    }
    }
  
    /**
     */
    private Entry parseSIFTs(File siftFile) throws SiftsException
    {
 -    try (InputStream in = new FileInputStream(siftFile);
 -            GZIPInputStream gzis = new GZIPInputStream(in);)
 +    try (InputStream in = new FileInputStream(siftFile)) {
 +      return parseSIFTs(in);
 +    } catch (Exception e)
 +    {
 +      e.printStackTrace();
 +      throw new SiftsException(e.getMessage());
 +    }
 +  }
 +  
 +  private static Entry parseSIFTs(InputStream in) throws Exception {
 +    try (GZIPInputStream gzis = new GZIPInputStream(in);)
      {
        // System.out.println("File : " + siftFile.getAbsolutePath());
        JAXBContext jc = JAXBContext.newInstance("jalview.xml.binding.sifts");
        Unmarshaller um = jc.createUnmarshaller();
        JAXBElement<Entry> jbe = um.unmarshal(streamReader, Entry.class);
        return jbe.getValue();
 -    } catch (Exception e)
 -    {
 -      e.printStackTrace();
 -      throw new SiftsException(e.getMessage());
      }
    }
  
      }
  
      String siftsFileName = SiftsSettings.getSiftDownloadDirectory()
-             + pdbId.toLowerCase() + ".xml.gz";
+             + pdbId.toLowerCase(Locale.ROOT) + ".xml.gz";
      File siftsFile = new File(siftsFileName);
      if (siftsFile.exists())
      {
        // The line below is required for unit testing... don't comment it out!!!
        System.out.println(">>> SIFTS File already downloaded for " + pdbId);
  
-       if (Platform.isFileOlderThanThreshold(siftsFile,
+       if (isFileOlderThanThreshold(siftsFile,
                SiftsSettings.getCacheThresholdInDays()))
        {
          File oldSiftsFile = new File(siftsFileName + "_old");
-         siftsFile.renameTo(oldSiftsFile);
+         BackupFiles.moveFileToFile(siftsFile, oldSiftsFile);
          try
          {
-           siftsFile = downloadSiftsFile(pdbId);
+           siftsFile = downloadSiftsFile(pdbId.toLowerCase(Locale.ROOT));
            oldSiftsFile.delete();
            return siftsFile;
          } catch (IOException e)
          {
            e.printStackTrace();
-           oldSiftsFile.renameTo(siftsFile);
+           BackupFiles.moveFileToFile(oldSiftsFile, siftsFile);
            return new File(siftsFileName);
          }
        }
      }
      try
      {
-       siftsFile = downloadSiftsFile(pdbId);
+       siftsFile = downloadSiftsFile(pdbId.toLowerCase(Locale.ROOT));
      } catch (IOException e)
      {
        throw new SiftsException(e.getMessage());
    }
  
    /**
 -   * This method enables checking if a cached file has exceeded a certain
 -   * threshold(in days)
 -   * 
 -   * @param file
 -   *          the cached file
 -   * @param noOfDays
 -   *          the threshold in days
 -   * @return
 -   */
 -  public static boolean isFileOlderThanThreshold(File file, int noOfDays)
 -  {
 -    Path filePath = file.toPath();
 -    BasicFileAttributes attr;
 -    int diffInDays = 0;
 -    try
 -    {
 -      attr = Files.readAttributes(filePath, BasicFileAttributes.class);
 -      diffInDays = (int) ((new Date().getTime()
 -              - attr.lastModifiedTime().toMillis())
 -              / (1000 * 60 * 60 * 24));
 -      // System.out.println("Diff in days : " + diffInDays);
 -    } catch (IOException e)
 -    {
 -      e.printStackTrace();
 -    }
 -    return noOfDays <= diffInDays;
 -  }
 -
 -  /**
     * Download a SIFTs XML file for a given PDB Id from an FTP repository
     * 
     * @param pdbId
     */
    public static File downloadSiftsFile(String pdbId)
            throws SiftsException, IOException
 +  {    
 +    return (File) downloadSifts(pdbId, CACHE_FILE);
 +  }
 +
 +  /**
 +   * Download SIFTs XML with the option to cache a file or to get a stream.
 +   * 
 +   * @param pdbId
 +   * @param asFile 
 +   * @return
 +   * @throws IOException
 +   */
 +  private static Object downloadSifts(String pdbId, boolean asFile) throws IOException
    {
-     pdbId = pdbId.toLowerCase();
++    pdbId = pdbId.toLowerCase(Locale.ROOT);
      if (pdbId.contains(".cif"))
      {
        pdbId = pdbId.replace(".cif", "");
      }
      String siftFile = pdbId + ".xml.gz";
 -    String siftsFileFTPURL = SIFTS_FTP_BASE_URL + siftFile;
 -
 -    /*
 -     * Download the file from URL to either
 -     * Java: directory of cached downloaded SIFTS files
 -     * Javascript: temporary 'file' (in-memory cache)
 -     */
      File downloadTo = null;
 -    if (Platform.isJS())
 -    {
 -      downloadTo = File.createTempFile(siftFile, ".xml.gz");
 -    }
 -    else
 +    if (asFile)
      {
        downloadTo = new File(
                SiftsSettings.getSiftDownloadDirectory() + siftFile);
 -      File siftsDownloadDir = new File(
 -              SiftsSettings.getSiftDownloadDirectory());
 +      File siftsDownloadDir = new File(SiftsSettings.getSiftDownloadDirectory());
        if (!siftsDownloadDir.exists())
        {
          siftsDownloadDir.mkdirs();
        }
      }
 -    // System.out.println(">> Download ftp url : " + siftsFileFTPURL);
 -    // long now = System.currentTimeMillis();
 +    String siftsFileFTPURL = SIFTS_FTP_BASE_URL + siftFile;
      URL url = new URL(siftsFileFTPURL);
      URLConnection conn = url.openConnection();
 -    InputStream inputStream = conn.getInputStream();
 -    FileOutputStream outputStream = new FileOutputStream(downloadTo);
 -    byte[] buffer = new byte[BUFFER_SIZE];
 -    int bytesRead = -1;
 -    while ((bytesRead = inputStream.read(buffer)) != -1)
 -    {
 -      outputStream.write(buffer, 0, bytesRead);
 -    }
 -    outputStream.close();
 -    inputStream.close();
 -    // System.out.println(">>> File downloaded : " + downloadedSiftsFile
 -    // + " took " + (System.currentTimeMillis() - now) + "ms");
 +    InputStream is = conn.getInputStream();
 +    if (!asFile)
 +      return is;
 +    // This is MUCH more efficent in JavaScript, as we already have the bytes
 +    Platform.streamToFile(is, downloadTo);
 +    is.close();
      return downloadTo;
    }
  
    public static boolean deleteSiftsFileByPDBId(String pdbId)
    {
      File siftsFile = new File(SiftsSettings.getSiftDownloadDirectory()
-             + pdbId.toLowerCase() + ".xml.gz");
+             + pdbId.toLowerCase(Locale.ROOT) + ".xml.gz");
      if (siftsFile.exists())
      {
        return siftsFile.delete();
          for (MapRegion mapRegion : mapRegions)
          {
            accessions
-                   .add(mapRegion.getDb().getDbAccessionId().toLowerCase());
+                   .add(mapRegion.getDb().getDbAccessionId().toLowerCase(Locale.ROOT));
          }
        }
      }
            SequenceI seq, java.io.PrintStream os) throws SiftsException
    {
      List<Integer> omitNonObserved = new ArrayList<>();
 -    int nonObservedShiftIndex = 0, pdbeNonObserved = 0;
 +    int nonObservedShiftIndex = 0,pdbeNonObserved=0;
      // System.out.println("Generating mappings for : " + entityId);
      Entity entity = null;
      entity = getEntityById(entityId);
      HashSet<String> dbRefAccessionIdsString = new HashSet<String>();
      for (DBRefEntry dbref : seq.getDBRefs())
      {
-       dbRefAccessionIdsString.add(dbref.getAccessionId().toLowerCase());
+       dbRefAccessionIdsString.add(dbref.getAccessionId().toLowerCase(Locale.ROOT));
      }
-     dbRefAccessionIdsString.add(sourceDBRef.getAccessionId().toLowerCase());
+     dbRefAccessionIdsString.add(sourceDBRef.getAccessionId().toLowerCase(Locale.ROOT));
  
      curDBRefAccessionIdsString = dbRefAccessionIdsString;
      curSourceDBRef = sourceDBRef.getAccessionId();
      TreeMap<Integer, String> resNumMap = new TreeMap<Integer, String>();
      List<Segment> segments = entity.getSegment();
      SegmentHelperPojo shp = new SegmentHelperPojo(seq, mapping, resNumMap,
 -            omitNonObserved, nonObservedShiftIndex, pdbeNonObserved);
 +            omitNonObserved, nonObservedShiftIndex,pdbeNonObserved);
      processSegments(segments, shp);
      try
      {
      {
        throw new SiftsException("SIFTS mapping failed");
      }
 -    // also construct a mapping object between the seq-coord sys and the PDB
 -    // seq's coord sys
 +    // also construct a mapping object between the seq-coord sys and the PDB seq's coord sys
  
      Integer[] keys = mapping.keySet().toArray(new Integer[0]);
      Arrays.sort(keys);
      seqStart = keys[0];
      seqEnd = keys[keys.length - 1];
 -    List<int[]> from = new ArrayList<>(), to = new ArrayList<>();
 -    int[] _cfrom = null, _cto = null;
 +    List<int[]> from=new ArrayList<>(),to=new ArrayList<>();
 +    int[]_cfrom=null,_cto=null;
      String matchedSeq = originalSeq;
 -    if (seqStart != UNASSIGNED) // fixme! seqStart can map to -1 for a pdb
 -                                // sequence that starts <-1
 +    if (seqStart != UNASSIGNED) // fixme! seqStart can map to -1 for a pdb sequence that starts <-1
      {
 -      for (int seqps : keys)
 +      for (int seqps:keys)
        {
          int pdbpos = mapping.get(seqps)[PDBE_POS];
          if (pdbpos == UNASSIGNED)
            // not correct - pdbpos might be -1, but leave it for now
            continue;
          }
 -        if (_cfrom == null || seqps != _cfrom[1] + 1)
 +        if (_cfrom==null || seqps!=_cfrom[1]+1)
          {
 -          _cfrom = new int[] { seqps, seqps };
 +          _cfrom = new int[] { seqps,seqps};
            from.add(_cfrom);
            _cto = null; // discontinuity
 +        } else {
 +          _cfrom[1]= seqps;
          }
 -        else
 -        {
 -          _cfrom[1] = seqps;
 -        }
 -        if (_cto == null || pdbpos != 1 + _cto[1])
 +        if (_cto==null || pdbpos!=1+_cto[1])
          {
 -          _cto = new int[] { pdbpos, pdbpos };
 +          _cto = new int[] { pdbpos,pdbpos};
            to.add(_cto);
 -        }
 -        else
 -        {
 +        } else {
            _cto[1] = pdbpos;
          }
        }
        ;
  
        seqFromPdbMapping = new jalview.datamodel.Mapping(null, _cto, _cfrom,
 -              1, 1);
 +              1,
 +              1);
        pdbStart = mapping.get(seqStart)[PDB_RES_POS];
        pdbEnd = mapping.get(seqEnd)[PDB_RES_POS];
        int orignalSeqStart = seq.getStart();
        for (Residue residue : residues)
        {
          boolean isObserved = isResidueObserved(residue);
 -        int pdbeIndex = getLeadingIntegerValue(residue.getDbResNum(),
 +        int pdbeIndex = Platform.getLeadingIntegerValue(residue.getDbResNum(),
                  UNASSIGNED);
          int currSeqIndex = UNASSIGNED;
          List<CrossRefDb> cRefDbs = residue.getCrossRefDb();
              pdbRefDb = cRefDb;
              if (firstPDBResNum == UNASSIGNED)
              {
 -              firstPDBResNum = getLeadingIntegerValue(cRefDb.getDbResNum(),
 +              firstPDBResNum = Platform.getLeadingIntegerValue(cRefDb.getDbResNum(),
                        UNASSIGNED);
              }
              else
            if (cRefDb.getDbCoordSys().equalsIgnoreCase(seqCoordSys.getName())
                    && isAccessionMatched(cRefDb.getDbAccessionId()))
            {
 -            currSeqIndex = getLeadingIntegerValue(cRefDb.getDbResNum(),
 +            currSeqIndex = Platform.getLeadingIntegerValue(cRefDb.getDbResNum(),
                      UNASSIGNED);
              if (pdbRefDb != null)
              {
          }
          // if (currSeqIndex >= seq.getStart() && currSeqIndex <= seqlength) //
          // true
 -        // numbering
 -        // is
 -        // not
 -        // up
 -        // to
 -        // seq.getEnd()
 +                                                                         // numbering
 +                                                                         // is
 +                                                                         // not
 +                                                                         // up
 +                                                                         // to
 +                                                                         // seq.getEnd()
          {
  
            int resNum = (pdbRefDb == null)
 -                  ? getLeadingIntegerValue(residue.getDbResNum(),
 +                  ? Platform.getLeadingIntegerValue(residue.getDbResNum(),
                            UNASSIGNED)
 -                  : getLeadingIntegerValue(pdbRefDb.getDbResNum(),
 +                  : Platform.getLeadingIntegerValue(pdbRefDb.getDbResNum(),
                            UNASSIGNED);
  
            if (isObserved)
    }
  
    /**
 -   * Get the leading integer part of a string that begins with an integer.
 -   * 
 -   * @param input
 -   *          - the string input to process
 -   * @param failValue
 -   *          - value returned if unsuccessful
 -   * @return
 -   */
 -  static int getLeadingIntegerValue(String input, int failValue)
 -  {
 -    if (input == null)
 -    {
 -      return failValue;
 -    }
 -    String[] parts = input.split("(?=\\D)(?<=\\d)");
 -    if (parts != null && parts.length > 0 && parts[0].matches("[0-9]+"))
 -    {
 -      return Integer.valueOf(parts[0]);
 -    }
 -    return failValue;
 -  }
 -
 -  /**
     * 
     * @param chainId
     *          Target chain to populate mapping of its atom positions.
    {
      boolean isStrictMatch = true;
      return isStrictMatch ? curSourceDBRef.equalsIgnoreCase(accession)
-             : curDBRefAccessionIdsString.contains(accession.toLowerCase());
+             : curDBRefAccessionIdsString.contains(accession.toLowerCase(Locale.ROOT));
    }
  
    private boolean isFoundInSiftsEntry(String accessionId)
    {
      Set<String> siftsDBRefs = getAllMappingAccession();
      return accessionId != null
-             && siftsDBRefs.contains(accessionId.toLowerCase());
+             && siftsDBRefs.contains(accessionId.toLowerCase(Locale.ROOT));
    }
  
    /**
      {
        return pdbeNonObserved;
      }
 -
      public SequenceI getSeq()
      {
        return seq;
@@@ -1,8 -1,8 +1,8 @@@
  //
 -// This file was generated by the Eclipse Implementation of JAXB, v2.3.3 
 -// See https://eclipse-ee4j.github.io/jaxb-ri 
 +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 
 +// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
  // Any modifications to this file will be lost upon recompilation of the source schema. 
- // Generated on: 2019.07.10 at 12:10:00 PM BST 
+ // Generated on: 2021.08.30 at 11:05:22 AM BST 
  //
  
  
@@@ -17,21 -17,21 +17,21 @@@ import javax.xml.bind.annotation.XmlTyp
  
  
  /**
 - * &lt;p&gt;Java class for DoubleVector complex type.
 + * <p>Java class for DoubleVector complex type.
   * 
 - * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
 + * <p>The following schema fragment specifies the expected content contained within this class.
   * 
 - * &lt;pre&gt;
 - * &amp;lt;complexType name="DoubleVector"&amp;gt;
 - *   &amp;lt;complexContent&amp;gt;
 - *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
 - *       &amp;lt;sequence&amp;gt;
 - *         &amp;lt;element name="v" type="{http://www.w3.org/2001/XMLSchema}double" maxOccurs="unbounded" minOccurs="0"/&amp;gt;
 - *       &amp;lt;/sequence&amp;gt;
 - *     &amp;lt;/restriction&amp;gt;
 - *   &amp;lt;/complexContent&amp;gt;
 - * &amp;lt;/complexType&amp;gt;
 - * &lt;/pre&gt;
 + * <pre>
 + * &lt;complexType name="DoubleVector">
 + *   &lt;complexContent>
 + *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 + *       &lt;sequence>
 + *         &lt;element name="v" type="{http://www.w3.org/2001/XMLSchema}double" maxOccurs="unbounded" minOccurs="0"/>
 + *       &lt;/sequence>
 + *     &lt;/restriction>
 + *   &lt;/complexContent>
 + * &lt;/complexType>
 + * </pre>
   * 
   * 
   */
@@@ -47,20 -47,20 +47,20 @@@ public class DoubleVector 
      /**
       * Gets the value of the v property.
       * 
 -     * &lt;p&gt;
 +     * <p>
       * This accessor method returns a reference to the live list,
       * not a snapshot. Therefore any modification you make to the
       * returned list will be present inside the JAXB object.
 -     * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the v property.
 +     * This is why there is not a <CODE>set</CODE> method for the v property.
       * 
 -     * &lt;p&gt;
 +     * <p>
       * For example, to add a new item, do as follows:
 -     * &lt;pre&gt;
 +     * <pre>
       *    getV().add(newItem);
 -     * &lt;/pre&gt;
 +     * </pre>
       * 
       * 
 -     * &lt;p&gt;
 +     * <p>
       * Objects of the following type(s) are allowed in the list
       * {@link Double }
       * 
@@@ -1,8 -1,8 +1,8 @@@
  //
- // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 
- // See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
+ // This file was generated by the Eclipse Implementation of JAXB, v2.3.3 
+ // See https://eclipse-ee4j.github.io/jaxb-ri 
  // Any modifications to this file will be lost upon recompilation of the source schema. 
- // Generated on: 2019.07.10 at 12:10:00 PM BST 
+ // Generated on: 2021.08.30 at 11:05:22 AM BST 
  //
  
  
@@@ -24,342 -24,341 +24,341 @@@ import javax.xml.datatype.XMLGregorianC
  
  
  /**
-  * <p>Java class for JalviewModel complex type.
+  * &lt;p&gt;Java class for JalviewModel complex type.
   * 
-  * <p>The following schema fragment specifies the expected content contained within this class.
+  * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
   * 
-  * <pre>
-  * &lt;complexType name="JalviewModel">
-  *   &lt;complexContent>
-  *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *       &lt;sequence>
-  *         &lt;element name="creationDate" type="{http://www.w3.org/2001/XMLSchema}dateTime"/>
-  *         &lt;element name="version" type="{http://www.w3.org/2001/XMLSchema}string"/>
-  *         &lt;element name="vamsasModel" type="{www.vamsas.ac.uk/jalview/version2}VAMSAS"/>
-  *         &lt;sequence>
-  *           &lt;element name="JSeq" maxOccurs="unbounded" minOccurs="0">
-  *             &lt;complexType>
-  *               &lt;complexContent>
-  *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                   &lt;sequence>
-  *                     &lt;element name="features" type="{www.jalview.org}feature" maxOccurs="unbounded" minOccurs="0"/>
-  *                     &lt;element name="pdbids" maxOccurs="unbounded" minOccurs="0">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;extension base="{www.jalview.org}pdbentry">
-  *                             &lt;sequence>
-  *                               &lt;element name="structureState" maxOccurs="unbounded" minOccurs="0">
-  *                                 &lt;complexType>
-  *                                   &lt;simpleContent>
-  *                                     &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
-  *                                       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-  *                                       &lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                                       &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                                       &lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-  *                                       &lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                                       &lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-  *                                       &lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                                     &lt;/extension>
-  *                                   &lt;/simpleContent>
-  *                                 &lt;/complexType>
-  *                               &lt;/element>
-  *                             &lt;/sequence>
-  *                           &lt;/extension>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                     &lt;element name="hiddenSequences" type="{http://www.w3.org/2001/XMLSchema}int" maxOccurs="unbounded" minOccurs="0"/>
-  *                     &lt;element name="rnaViewer" maxOccurs="unbounded" minOccurs="0">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                             &lt;sequence>
-  *                               &lt;element name="secondaryStructure" maxOccurs="unbounded">
-  *                                 &lt;complexType>
-  *                                   &lt;complexContent>
-  *                                     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                                       &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                                       &lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                                       &lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                                       &lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                                     &lt;/restriction>
-  *                                   &lt;/complexContent>
-  *                                 &lt;/complexType>
-  *                               &lt;/element>
-  *                             &lt;/sequence>
-  *                             &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-  *                             &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                             &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                             &lt;attribute name="dividerLocation" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                             &lt;attribute name="selectedRna" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                           &lt;/restriction>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                     &lt;element name="hmmerProfile" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
-  *                   &lt;/sequence>
-  *                   &lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="start" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="end" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="id" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="hidden" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="viewreference" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                 &lt;/restriction>
-  *               &lt;/complexContent>
-  *             &lt;/complexType>
-  *           &lt;/element>
-  *           &lt;element name="JGroup" maxOccurs="unbounded" minOccurs="0">
-  *             &lt;complexType>
-  *               &lt;complexContent>
-  *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                   &lt;sequence>
-  *                     &lt;element name="seq" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="unbounded"/>
-  *                     &lt;element name="annotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/>
-  *                   &lt;/sequence>
-  *                   &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="name" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="outlineColour" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="displayBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="displayText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="colourText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-  *                   &lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-  *                   &lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                   &lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                   &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                 &lt;/restriction>
-  *               &lt;/complexContent>
-  *             &lt;/complexType>
-  *           &lt;/element>
-  *           &lt;element name="Viewport" maxOccurs="unbounded" minOccurs="0">
-  *             &lt;complexType>
-  *               &lt;complexContent>
-  *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                   &lt;sequence>
-  *                     &lt;element name="AnnotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/>
-  *                     &lt;element name="hiddenColumns" maxOccurs="unbounded" minOccurs="0">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                             &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                             &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                           &lt;/restriction>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                     &lt;element name="calcIdParam" maxOccurs="unbounded" minOccurs="0">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;extension base="{www.jalview.org/xml/wsparamset}WebServiceParameterSet">
-  *                             &lt;attribute name="calcId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                             &lt;attribute name="needsUpdate" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                             &lt;attribute name="autoUpdate" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                           &lt;/extension>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                   &lt;/sequence>
-  *                   &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-  *                   &lt;attribute name="conservationSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="pidSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="showFullId" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="rightAlignIds" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="showText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="showColourText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                   &lt;attribute name="showBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="wrapAlignment" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="renderGaps" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="showSequenceFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="showNPfeatureTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="showDbRefTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="followHighlight" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-  *                   &lt;attribute name="followSelection" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-  *                   &lt;attribute name="showAnnotation" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="centreColumnLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                   &lt;attribute name="showGroupConservation" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                   &lt;attribute name="showGroupConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                   &lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-  *                   &lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                   &lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                   &lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-  *                   &lt;attribute name="startRes" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="startSeq" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="scaleProteinAsCdna" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-  *                   &lt;attribute name="viewName" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="sequenceSetId" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="gatheredViews" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" />
-  *                   &lt;attribute name="complementId" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="showComplementFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                   &lt;attribute name="showComplementFeaturesOnTop" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                 &lt;/restriction>
-  *               &lt;/complexContent>
-  *             &lt;/complexType>
-  *           &lt;/element>
-  *           &lt;element name="UserColours" maxOccurs="unbounded" minOccurs="0">
-  *             &lt;complexType>
-  *               &lt;complexContent>
-  *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                   &lt;sequence>
-  *                     &lt;element name="UserColourScheme" type="{www.jalview.org/colours}JalviewUserColours"/>
-  *                   &lt;/sequence>
-  *                   &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                 &lt;/restriction>
-  *               &lt;/complexContent>
-  *             &lt;/complexType>
-  *           &lt;/element>
-  *           &lt;element name="tree" maxOccurs="unbounded" minOccurs="0">
-  *             &lt;complexType>
-  *               &lt;complexContent>
-  *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                   &lt;sequence minOccurs="0">
-  *                     &lt;element name="title" type="{http://www.w3.org/2001/XMLSchema}string"/>
-  *                     &lt;element name="newick" type="{http://www.w3.org/2001/XMLSchema}string"/>
-  *                   &lt;/sequence>
-  *                   &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-  *                   &lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
-  *                   &lt;attribute name="showBootstrap" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="showDistances" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="markUnlinked" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="fitToWindow" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="currentTree" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" />
-  *                   &lt;attribute name="linkToAllViews" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-  *                 &lt;/restriction>
-  *               &lt;/complexContent>
-  *             &lt;/complexType>
-  *           &lt;/element>
-  *           &lt;element name="PcaViewer" maxOccurs="unbounded" minOccurs="0">
-  *             &lt;complexType>
-  *               &lt;complexContent>
-  *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                   &lt;sequence>
-  *                     &lt;element name="sequencePoint" maxOccurs="unbounded">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                             &lt;attGroup ref="{www.jalview.org}position"/>
-  *                             &lt;attribute name="sequenceRef" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                           &lt;/restriction>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                     &lt;element name="axis" maxOccurs="3" minOccurs="3">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                             &lt;attGroup ref="{www.jalview.org}position"/>
-  *                           &lt;/restriction>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                     &lt;element name="seqPointMin">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                             &lt;attGroup ref="{www.jalview.org}position"/>
-  *                           &lt;/restriction>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                     &lt;element name="seqPointMax">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                             &lt;attGroup ref="{www.jalview.org}position"/>
-  *                           &lt;/restriction>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                     &lt;element name="pcaData" type="{www.jalview.org}PcaDataType"/>
-  *                   &lt;/sequence>
-  *                   &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-  *                   &lt;attGroup ref="{www.jalview.org}SimilarityParams"/>
-  *                   &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="scoreModelName" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                   &lt;attribute name="xDim" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="yDim" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="zDim" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                   &lt;attribute name="scaleFactor" type="{http://www.w3.org/2001/XMLSchema}float" />
-  *                   &lt;attribute name="showLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                   &lt;attribute name="linkToAllViews" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                 &lt;/restriction>
-  *               &lt;/complexContent>
-  *             &lt;/complexType>
-  *           &lt;/element>
-  *           &lt;element name="FeatureSettings" minOccurs="0">
-  *             &lt;complexType>
-  *               &lt;complexContent>
-  *                 &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                   &lt;sequence>
-  *                     &lt;element name="setting" maxOccurs="unbounded" minOccurs="0">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                             &lt;sequence>
-  *                               &lt;element name="attributeName" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="2" minOccurs="0"/>
-  *                               &lt;element name="matcherSet" type="{www.jalview.org/colours}FeatureMatcherSet" minOccurs="0"/>
-  *                             &lt;/sequence>
-  *                             &lt;attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                             &lt;attribute name="colour" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                             &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                             &lt;attribute name="order" type="{http://www.w3.org/2001/XMLSchema}float" />
-  *                             &lt;attribute name="mincolour" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                             &lt;attribute name="noValueColour" type="{www.jalview.org/colours}NoValueColour" default="Min" />
-  *                             &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
-  *                             &lt;attribute name="threshstate" type="{http://www.w3.org/2001/XMLSchema}int" />
-  *                             &lt;attribute name="max" type="{http://www.w3.org/2001/XMLSchema}float" />
-  *                             &lt;attribute name="min" type="{http://www.w3.org/2001/XMLSchema}float" />
-  *                             &lt;attribute name="colourByLabel" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                             &lt;attribute name="autoScale" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                           &lt;/restriction>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                     &lt;element name="group" maxOccurs="unbounded" minOccurs="0">
-  *                       &lt;complexType>
-  *                         &lt;complexContent>
-  *                           &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-  *                             &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-  *                             &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-  *                           &lt;/restriction>
-  *                         &lt;/complexContent>
-  *                       &lt;/complexType>
-  *                     &lt;/element>
-  *                   &lt;/sequence>
-  *                 &lt;/restriction>
-  *               &lt;/complexContent>
-  *             &lt;/complexType>
-  *           &lt;/element>
-  *         &lt;/sequence>
-  *       &lt;/sequence>
-  *     &lt;/restriction>
-  *   &lt;/complexContent>
-  * &lt;/complexType>
-  * </pre>
+  * &lt;pre&gt;
+  * &amp;lt;complexType name="JalviewModel"&amp;gt;
+  *   &amp;lt;complexContent&amp;gt;
+  *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *       &amp;lt;sequence&amp;gt;
+  *         &amp;lt;element name="creationDate" type="{http://www.w3.org/2001/XMLSchema}dateTime"/&amp;gt;
+  *         &amp;lt;element name="version" type="{http://www.w3.org/2001/XMLSchema}string"/&amp;gt;
+  *         &amp;lt;element name="vamsasModel" type="{www.vamsas.ac.uk/jalview/version2}VAMSAS"/&amp;gt;
+  *         &amp;lt;sequence&amp;gt;
+  *           &amp;lt;element name="JSeq" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *             &amp;lt;complexType&amp;gt;
+  *               &amp;lt;complexContent&amp;gt;
+  *                 &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                   &amp;lt;sequence&amp;gt;
+  *                     &amp;lt;element name="features" type="{www.jalview.org}feature" maxOccurs="unbounded" minOccurs="0"/&amp;gt;
+  *                     &amp;lt;element name="pdbids" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;extension base="{www.jalview.org}pdbentry"&amp;gt;
+  *                             &amp;lt;sequence&amp;gt;
+  *                               &amp;lt;element name="structureState" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *                                 &amp;lt;complexType&amp;gt;
+  *                                   &amp;lt;simpleContent&amp;gt;
+  *                                     &amp;lt;extension base="&amp;lt;http://www.w3.org/2001/XMLSchema&amp;gt;string"&amp;gt;
+  *                                       &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+  *                                       &amp;lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                                       &amp;lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                                       &amp;lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+  *                                       &amp;lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                                       &amp;lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+  *                                       &amp;lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                                     &amp;lt;/extension&amp;gt;
+  *                                   &amp;lt;/simpleContent&amp;gt;
+  *                                 &amp;lt;/complexType&amp;gt;
+  *                               &amp;lt;/element&amp;gt;
+  *                             &amp;lt;/sequence&amp;gt;
+  *                           &amp;lt;/extension&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                     &amp;lt;element name="hiddenSequences" type="{http://www.w3.org/2001/XMLSchema}int" maxOccurs="unbounded" minOccurs="0"/&amp;gt;
+  *                     &amp;lt;element name="rnaViewer" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                             &amp;lt;sequence&amp;gt;
+  *                               &amp;lt;element name="secondaryStructure" maxOccurs="unbounded"&amp;gt;
+  *                                 &amp;lt;complexType&amp;gt;
+  *                                   &amp;lt;complexContent&amp;gt;
+  *                                     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                                       &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                                       &amp;lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                                       &amp;lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                                       &amp;lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                                     &amp;lt;/restriction&amp;gt;
+  *                                   &amp;lt;/complexContent&amp;gt;
+  *                                 &amp;lt;/complexType&amp;gt;
+  *                               &amp;lt;/element&amp;gt;
+  *                             &amp;lt;/sequence&amp;gt;
+  *                             &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+  *                             &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                             &amp;lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                             &amp;lt;attribute name="dividerLocation" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                             &amp;lt;attribute name="selectedRna" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                           &amp;lt;/restriction&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                   &amp;lt;/sequence&amp;gt;
+  *                   &amp;lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="start" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="end" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="id" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="hidden" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="viewreference" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                 &amp;lt;/restriction&amp;gt;
+  *               &amp;lt;/complexContent&amp;gt;
+  *             &amp;lt;/complexType&amp;gt;
+  *           &amp;lt;/element&amp;gt;
+  *           &amp;lt;element name="JGroup" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *             &amp;lt;complexType&amp;gt;
+  *               &amp;lt;complexContent&amp;gt;
+  *                 &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                   &amp;lt;sequence&amp;gt;
+  *                     &amp;lt;element name="seq" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="unbounded"/&amp;gt;
+  *                     &amp;lt;element name="annotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/&amp;gt;
+  *                   &amp;lt;/sequence&amp;gt;
+  *                   &amp;lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="name" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="outlineColour" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="displayBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="displayText" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="colourText" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+  *                   &amp;lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+  *                   &amp;lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                   &amp;lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                   &amp;lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                 &amp;lt;/restriction&amp;gt;
+  *               &amp;lt;/complexContent&amp;gt;
+  *             &amp;lt;/complexType&amp;gt;
+  *           &amp;lt;/element&amp;gt;
+  *           &amp;lt;element name="Viewport" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *             &amp;lt;complexType&amp;gt;
+  *               &amp;lt;complexContent&amp;gt;
+  *                 &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                   &amp;lt;sequence&amp;gt;
+  *                     &amp;lt;element name="AnnotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/&amp;gt;
+  *                     &amp;lt;element name="hiddenColumns" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                             &amp;lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                             &amp;lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                           &amp;lt;/restriction&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                     &amp;lt;element name="calcIdParam" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;extension base="{www.jalview.org/xml/wsparamset}WebServiceParameterSet"&amp;gt;
+  *                             &amp;lt;attribute name="calcId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                             &amp;lt;attribute name="needsUpdate" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                             &amp;lt;attribute name="autoUpdate" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                           &amp;lt;/extension&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                   &amp;lt;/sequence&amp;gt;
+  *                   &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+  *                   &amp;lt;attribute name="conservationSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="pidSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="showFullId" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="rightAlignIds" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="showText" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="showColourText" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                   &amp;lt;attribute name="showBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="wrapAlignment" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="renderGaps" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="showSequenceFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="showNPfeatureTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="showDbRefTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="followHighlight" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+  *                   &amp;lt;attribute name="followSelection" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+  *                   &amp;lt;attribute name="showAnnotation" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="centreColumnLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                   &amp;lt;attribute name="showGroupConservation" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                   &amp;lt;attribute name="showGroupConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                   &amp;lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+  *                   &amp;lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                   &amp;lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                   &amp;lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+  *                   &amp;lt;attribute name="startRes" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="startSeq" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="scaleProteinAsCdna" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+  *                   &amp;lt;attribute name="viewName" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="sequenceSetId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="gatheredViews" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" /&amp;gt;
+  *                   &amp;lt;attribute name="complementId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="showComplementFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                   &amp;lt;attribute name="showComplementFeaturesOnTop" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                 &amp;lt;/restriction&amp;gt;
+  *               &amp;lt;/complexContent&amp;gt;
+  *             &amp;lt;/complexType&amp;gt;
+  *           &amp;lt;/element&amp;gt;
+  *           &amp;lt;element name="UserColours" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *             &amp;lt;complexType&amp;gt;
+  *               &amp;lt;complexContent&amp;gt;
+  *                 &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                   &amp;lt;sequence&amp;gt;
+  *                     &amp;lt;element name="UserColourScheme" type="{www.jalview.org/colours}JalviewUserColours"/&amp;gt;
+  *                   &amp;lt;/sequence&amp;gt;
+  *                   &amp;lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                 &amp;lt;/restriction&amp;gt;
+  *               &amp;lt;/complexContent&amp;gt;
+  *             &amp;lt;/complexType&amp;gt;
+  *           &amp;lt;/element&amp;gt;
+  *           &amp;lt;element name="tree" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *             &amp;lt;complexType&amp;gt;
+  *               &amp;lt;complexContent&amp;gt;
+  *                 &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                   &amp;lt;sequence minOccurs="0"&amp;gt;
+  *                     &amp;lt;element name="title" type="{http://www.w3.org/2001/XMLSchema}string"/&amp;gt;
+  *                     &amp;lt;element name="newick" type="{http://www.w3.org/2001/XMLSchema}string"/&amp;gt;
+  *                   &amp;lt;/sequence&amp;gt;
+  *                   &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+  *                   &amp;lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+  *                   &amp;lt;attribute name="showBootstrap" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="showDistances" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="markUnlinked" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="fitToWindow" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="currentTree" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" /&amp;gt;
+  *                   &amp;lt;attribute name="linkToAllViews" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+  *                 &amp;lt;/restriction&amp;gt;
+  *               &amp;lt;/complexContent&amp;gt;
+  *             &amp;lt;/complexType&amp;gt;
+  *           &amp;lt;/element&amp;gt;
+  *           &amp;lt;element name="PcaViewer" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *             &amp;lt;complexType&amp;gt;
+  *               &amp;lt;complexContent&amp;gt;
+  *                 &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                   &amp;lt;sequence&amp;gt;
+  *                     &amp;lt;element name="sequencePoint" maxOccurs="unbounded"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                             &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+  *                             &amp;lt;attribute name="sequenceRef" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                           &amp;lt;/restriction&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                     &amp;lt;element name="axis" maxOccurs="3" minOccurs="3"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                             &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+  *                           &amp;lt;/restriction&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                     &amp;lt;element name="seqPointMin"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                             &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+  *                           &amp;lt;/restriction&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                     &amp;lt;element name="seqPointMax"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                             &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+  *                           &amp;lt;/restriction&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                     &amp;lt;element name="pcaData" type="{www.jalview.org}PcaDataType"/&amp;gt;
+  *                   &amp;lt;/sequence&amp;gt;
+  *                   &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+  *                   &amp;lt;attGroup ref="{www.jalview.org}SimilarityParams"/&amp;gt;
+  *                   &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="scoreModelName" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                   &amp;lt;attribute name="xDim" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="yDim" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="zDim" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                   &amp;lt;attribute name="scaleFactor" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+  *                   &amp;lt;attribute name="showLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                   &amp;lt;attribute name="linkToAllViews" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                 &amp;lt;/restriction&amp;gt;
+  *               &amp;lt;/complexContent&amp;gt;
+  *             &amp;lt;/complexType&amp;gt;
+  *           &amp;lt;/element&amp;gt;
+  *           &amp;lt;element name="FeatureSettings" minOccurs="0"&amp;gt;
+  *             &amp;lt;complexType&amp;gt;
+  *               &amp;lt;complexContent&amp;gt;
+  *                 &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                   &amp;lt;sequence&amp;gt;
+  *                     &amp;lt;element name="setting" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                             &amp;lt;sequence&amp;gt;
+  *                               &amp;lt;element name="attributeName" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="2" minOccurs="0"/&amp;gt;
+  *                               &amp;lt;element name="matcherSet" type="{www.jalview.org/colours}FeatureMatcherSet" minOccurs="0"/&amp;gt;
+  *                             &amp;lt;/sequence&amp;gt;
+  *                             &amp;lt;attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                             &amp;lt;attribute name="colour" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                             &amp;lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                             &amp;lt;attribute name="order" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+  *                             &amp;lt;attribute name="mincolour" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                             &amp;lt;attribute name="noValueColour" type="{www.jalview.org/colours}NoValueColour" default="Min" /&amp;gt;
+  *                             &amp;lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+  *                             &amp;lt;attribute name="threshstate" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+  *                             &amp;lt;attribute name="max" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+  *                             &amp;lt;attribute name="min" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+  *                             &amp;lt;attribute name="colourByLabel" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                             &amp;lt;attribute name="autoScale" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                           &amp;lt;/restriction&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                     &amp;lt;element name="group" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+  *                       &amp;lt;complexType&amp;gt;
+  *                         &amp;lt;complexContent&amp;gt;
+  *                           &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+  *                             &amp;lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+  *                             &amp;lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+  *                           &amp;lt;/restriction&amp;gt;
+  *                         &amp;lt;/complexContent&amp;gt;
+  *                       &amp;lt;/complexType&amp;gt;
+  *                     &amp;lt;/element&amp;gt;
+  *                   &amp;lt;/sequence&amp;gt;
+  *                 &amp;lt;/restriction&amp;gt;
+  *               &amp;lt;/complexContent&amp;gt;
+  *             &amp;lt;/complexType&amp;gt;
+  *           &amp;lt;/element&amp;gt;
+  *         &amp;lt;/sequence&amp;gt;
+  *       &amp;lt;/sequence&amp;gt;
+  *     &amp;lt;/restriction&amp;gt;
+  *   &amp;lt;/complexContent&amp;gt;
+  * &amp;lt;/complexType&amp;gt;
+  * &lt;/pre&gt;
   * 
   * 
   */
@@@ -474,20 -473,20 +473,20 @@@ public class JalviewModel 
      /**
       * Gets the value of the jSeq property.
       * 
-      * <p>
+      * &lt;p&gt;
       * This accessor method returns a reference to the live list,
       * not a snapshot. Therefore any modification you make to the
       * returned list will be present inside the JAXB object.
-      * This is why there is not a <CODE>set</CODE> method for the jSeq property.
+      * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the jSeq property.
       * 
-      * <p>
+      * &lt;p&gt;
       * For example, to add a new item, do as follows:
-      * <pre>
+      * &lt;pre&gt;
       *    getJSeq().add(newItem);
-      * </pre>
+      * &lt;/pre&gt;
       * 
       * 
-      * <p>
+      * &lt;p&gt;
       * Objects of the following type(s) are allowed in the list
       * {@link JalviewModel.JSeq }
       * 
      /**
       * Gets the value of the jGroup property.
       * 
-      * <p>
+      * &lt;p&gt;
       * This accessor method returns a reference to the live list,
       * not a snapshot. Therefore any modification you make to the
       * returned list will be present inside the JAXB object.
-      * This is why there is not a <CODE>set</CODE> method for the jGroup property.
+      * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the jGroup property.
       * 
-      * <p>
+      * &lt;p&gt;
       * For example, to add a new item, do as follows:
-      * <pre>
+      * &lt;pre&gt;
       *    getJGroup().add(newItem);
-      * </pre>
+      * &lt;/pre&gt;
       * 
       * 
-      * <p>
+      * &lt;p&gt;
       * Objects of the following type(s) are allowed in the list
       * {@link JalviewModel.JGroup }
       * 
      /**
       * Gets the value of the viewport property.
       * 
-      * <p>
+      * &lt;p&gt;
       * This accessor method returns a reference to the live list,
       * not a snapshot. Therefore any modification you make to the
       * returned list will be present inside the JAXB object.
-      * This is why there is not a <CODE>set</CODE> method for the viewport property.
+      * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the viewport property.
       * 
-      * <p>
+      * &lt;p&gt;
       * For example, to add a new item, do as follows:
-      * <pre>
+      * &lt;pre&gt;
       *    getViewport().add(newItem);
-      * </pre>
+      * &lt;/pre&gt;
       * 
       * 
-      * <p>
+      * &lt;p&gt;
       * Objects of the following type(s) are allowed in the list
       * {@link JalviewModel.Viewport }
       * 
      /**
       * Gets the value of the userColours property.
       * 
-      * <p>
+      * &lt;p&gt;
       * This accessor method returns a reference to the live list,
       * not a snapshot. Therefore any modification you make to the
       * returned list will be present inside the JAXB object.
-      * This is why there is not a <CODE>set</CODE> method for the userColours property.
+      * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the userColours property.
       * 
-      * <p>
+      * &lt;p&gt;
       * For example, to add a new item, do as follows:
-      * <pre>
+      * &lt;pre&gt;
       *    getUserColours().add(newItem);
-      * </pre>
+      * &lt;/pre&gt;
       * 
       * 
-      * <p>
+      * &lt;p&gt;
       * Objects of the following type(s) are allowed in the list
       * {@link JalviewModel.UserColours }
       * 
      /**
       * Gets the value of the tree property.
       * 
-      * <p>
+      * &lt;p&gt;
       * This accessor method returns a reference to the live list,
       * not a snapshot. Therefore any modification you make to the
       * returned list will be present inside the JAXB object.
-      * This is why there is not a <CODE>set</CODE> method for the tree property.
+      * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the tree property.
       * 
-      * <p>
+      * &lt;p&gt;
       * For example, to add a new item, do as follows:
-      * <pre>
+      * &lt;pre&gt;
       *    getTree().add(newItem);
-      * </pre>
+      * &lt;/pre&gt;
       * 
       * 
-      * <p>
+      * &lt;p&gt;
       * Objects of the following type(s) are allowed in the list
       * {@link JalviewModel.Tree }
       * 
      /**
       * Gets the value of the pcaViewer property.
       * 
-      * <p>
+      * &lt;p&gt;
       * This accessor method returns a reference to the live list,
       * not a snapshot. Therefore any modification you make to the
       * returned list will be present inside the JAXB object.
-      * This is why there is not a <CODE>set</CODE> method for the pcaViewer property.
+      * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the pcaViewer property.
       * 
-      * <p>
+      * &lt;p&gt;
       * For example, to add a new item, do as follows:
-      * <pre>
+      * &lt;pre&gt;
       *    getPcaViewer().add(newItem);
-      * </pre>
+      * &lt;/pre&gt;
       * 
       * 
-      * <p>
+      * &lt;p&gt;
       * Objects of the following type(s) are allowed in the list
       * {@link JalviewModel.PcaViewer }
       * 
  
  
      /**
-      * <p>Java class for anonymous complex type.
+      * &lt;p&gt;Java class for anonymous complex type.
       * 
-      * <p>The following schema fragment specifies the expected content contained within this class.
+      * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
       * 
-      * <pre>
-      * &lt;complexType>
-      *   &lt;complexContent>
-      *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *       &lt;sequence>
-      *         &lt;element name="setting" maxOccurs="unbounded" minOccurs="0">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *                 &lt;sequence>
-      *                   &lt;element name="attributeName" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="2" minOccurs="0"/>
-      *                   &lt;element name="matcherSet" type="{www.jalview.org/colours}FeatureMatcherSet" minOccurs="0"/>
-      *                 &lt;/sequence>
-      *                 &lt;attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                 &lt;attribute name="colour" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *                 &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *                 &lt;attribute name="order" type="{http://www.w3.org/2001/XMLSchema}float" />
-      *                 &lt;attribute name="mincolour" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *                 &lt;attribute name="noValueColour" type="{www.jalview.org/colours}NoValueColour" default="Min" />
-      *                 &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
-      *                 &lt;attribute name="threshstate" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *                 &lt;attribute name="max" type="{http://www.w3.org/2001/XMLSchema}float" />
-      *                 &lt;attribute name="min" type="{http://www.w3.org/2001/XMLSchema}float" />
-      *                 &lt;attribute name="colourByLabel" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *                 &lt;attribute name="autoScale" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *               &lt;/restriction>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *         &lt;element name="group" maxOccurs="unbounded" minOccurs="0">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *                 &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                 &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *               &lt;/restriction>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *       &lt;/sequence>
-      *     &lt;/restriction>
-      *   &lt;/complexContent>
-      * &lt;/complexType>
-      * </pre>
+      * &lt;pre&gt;
+      * &amp;lt;complexType&amp;gt;
+      *   &amp;lt;complexContent&amp;gt;
+      *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *       &amp;lt;sequence&amp;gt;
+      *         &amp;lt;element name="setting" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *                 &amp;lt;sequence&amp;gt;
+      *                   &amp;lt;element name="attributeName" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="2" minOccurs="0"/&amp;gt;
+      *                   &amp;lt;element name="matcherSet" type="{www.jalview.org/colours}FeatureMatcherSet" minOccurs="0"/&amp;gt;
+      *                 &amp;lt;/sequence&amp;gt;
+      *                 &amp;lt;attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                 &amp;lt;attribute name="colour" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *                 &amp;lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *                 &amp;lt;attribute name="order" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+      *                 &amp;lt;attribute name="mincolour" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *                 &amp;lt;attribute name="noValueColour" type="{www.jalview.org/colours}NoValueColour" default="Min" /&amp;gt;
+      *                 &amp;lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+      *                 &amp;lt;attribute name="threshstate" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *                 &amp;lt;attribute name="max" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+      *                 &amp;lt;attribute name="min" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+      *                 &amp;lt;attribute name="colourByLabel" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *                 &amp;lt;attribute name="autoScale" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *               &amp;lt;/restriction&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *         &amp;lt;element name="group" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *                 &amp;lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                 &amp;lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *               &amp;lt;/restriction&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *       &amp;lt;/sequence&amp;gt;
+      *     &amp;lt;/restriction&amp;gt;
+      *   &amp;lt;/complexContent&amp;gt;
+      * &amp;lt;/complexType&amp;gt;
+      * &lt;/pre&gt;
       * 
       * 
       */
          /**
           * Gets the value of the setting property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the setting property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the setting property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getSetting().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link JalviewModel.FeatureSettings.Setting }
           * 
          /**
           * Gets the value of the group property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the group property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the group property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getGroup().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link JalviewModel.FeatureSettings.Group }
           * 
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-          *       &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *       &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-          *     &lt;/restriction>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+          *       &amp;lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *       &amp;lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+          *     &amp;lt;/restriction&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-          *       &lt;sequence>
-          *         &lt;element name="attributeName" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="2" minOccurs="0"/>
-          *         &lt;element name="matcherSet" type="{www.jalview.org/colours}FeatureMatcherSet" minOccurs="0"/>
-          *       &lt;/sequence>
-          *       &lt;attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *       &lt;attribute name="colour" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
-          *       &lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-          *       &lt;attribute name="order" type="{http://www.w3.org/2001/XMLSchema}float" />
-          *       &lt;attribute name="mincolour" type="{http://www.w3.org/2001/XMLSchema}int" />
-          *       &lt;attribute name="noValueColour" type="{www.jalview.org/colours}NoValueColour" default="Min" />
-          *       &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
-          *       &lt;attribute name="threshstate" type="{http://www.w3.org/2001/XMLSchema}int" />
-          *       &lt;attribute name="max" type="{http://www.w3.org/2001/XMLSchema}float" />
-          *       &lt;attribute name="min" type="{http://www.w3.org/2001/XMLSchema}float" />
-          *       &lt;attribute name="colourByLabel" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-          *       &lt;attribute name="autoScale" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-          *     &lt;/restriction>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+          *       &amp;lt;sequence&amp;gt;
+          *         &amp;lt;element name="attributeName" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="2" minOccurs="0"/&amp;gt;
+          *         &amp;lt;element name="matcherSet" type="{www.jalview.org/colours}FeatureMatcherSet" minOccurs="0"/&amp;gt;
+          *       &amp;lt;/sequence&amp;gt;
+          *       &amp;lt;attribute name="type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *       &amp;lt;attribute name="colour" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+          *       &amp;lt;attribute name="display" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+          *       &amp;lt;attribute name="order" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+          *       &amp;lt;attribute name="mincolour" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+          *       &amp;lt;attribute name="noValueColour" type="{www.jalview.org/colours}NoValueColour" default="Min" /&amp;gt;
+          *       &amp;lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+          *       &amp;lt;attribute name="threshstate" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+          *       &amp;lt;attribute name="max" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+          *       &amp;lt;attribute name="min" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+          *       &amp;lt;attribute name="colourByLabel" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+          *       &amp;lt;attribute name="autoScale" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+          *     &amp;lt;/restriction&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
              /**
               * Gets the value of the attributeName property.
               * 
-              * <p>
+              * &lt;p&gt;
               * This accessor method returns a reference to the live list,
               * not a snapshot. Therefore any modification you make to the
               * returned list will be present inside the JAXB object.
-              * This is why there is not a <CODE>set</CODE> method for the attributeName property.
+              * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the attributeName property.
               * 
-              * <p>
+              * &lt;p&gt;
               * For example, to add a new item, do as follows:
-              * <pre>
+              * &lt;pre&gt;
               *    getAttributeName().add(newItem);
-              * </pre>
+              * &lt;/pre&gt;
               * 
               * 
-              * <p>
+              * &lt;p&gt;
               * Objects of the following type(s) are allowed in the list
               * {@link String }
               * 
  
  
      /**
-      * <p>Java class for anonymous complex type.
+      * &lt;p&gt;Java class for anonymous complex type.
       * 
-      * <p>The following schema fragment specifies the expected content contained within this class.
+      * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
       * 
-      * <pre>
-      * &lt;complexType>
-      *   &lt;complexContent>
-      *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *       &lt;sequence>
-      *         &lt;element name="seq" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="unbounded"/>
-      *         &lt;element name="annotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/>
-      *       &lt;/sequence>
-      *       &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="name" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="outlineColour" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="displayBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="displayText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="colourText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-      *       &lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-      *       &lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *       &lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *       &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *     &lt;/restriction>
-      *   &lt;/complexContent>
-      * &lt;/complexType>
-      * </pre>
+      * &lt;pre&gt;
+      * &amp;lt;complexType&amp;gt;
+      *   &amp;lt;complexContent&amp;gt;
+      *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *       &amp;lt;sequence&amp;gt;
+      *         &amp;lt;element name="seq" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="unbounded"/&amp;gt;
+      *         &amp;lt;element name="annotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/&amp;gt;
+      *       &amp;lt;/sequence&amp;gt;
+      *       &amp;lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="name" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="outlineColour" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="displayBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="displayText" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="colourText" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+      *       &amp;lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+      *       &amp;lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *       &amp;lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *       &amp;lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *     &amp;lt;/restriction&amp;gt;
+      *   &amp;lt;/complexContent&amp;gt;
+      * &amp;lt;/complexType&amp;gt;
+      * &lt;/pre&gt;
       * 
       * 
       */
          /**
           * Gets the value of the seq property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the seq property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the seq property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getSeq().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link String }
           * 
  
  
      /**
-      * <p>Java class for anonymous complex type.
+      * &lt;p&gt;Java class for anonymous complex type.
       * 
-      * <p>The following schema fragment specifies the expected content contained within this class.
+      * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
       * 
-      * <pre>
-      * &lt;complexType>
-      *   &lt;complexContent>
-      *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *       &lt;sequence>
-      *         &lt;element name="features" type="{www.jalview.org}feature" maxOccurs="unbounded" minOccurs="0"/>
-      *         &lt;element name="pdbids" maxOccurs="unbounded" minOccurs="0">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;extension base="{www.jalview.org}pdbentry">
-      *                 &lt;sequence>
-      *                   &lt;element name="structureState" maxOccurs="unbounded" minOccurs="0">
-      *                     &lt;complexType>
-      *                       &lt;simpleContent>
-      *                         &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
-      *                           &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-      *                           &lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *                           &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                           &lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-      *                           &lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *                           &lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-      *                           &lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                         &lt;/extension>
-      *                       &lt;/simpleContent>
-      *                     &lt;/complexType>
-      *                   &lt;/element>
-      *                 &lt;/sequence>
-      *               &lt;/extension>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *         &lt;element name="hiddenSequences" type="{http://www.w3.org/2001/XMLSchema}int" maxOccurs="unbounded" minOccurs="0"/>
-      *         &lt;element name="rnaViewer" maxOccurs="unbounded" minOccurs="0">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *                 &lt;sequence>
-      *                   &lt;element name="secondaryStructure" maxOccurs="unbounded">
-      *                     &lt;complexType>
-      *                       &lt;complexContent>
-      *                         &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *                           &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                           &lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                           &lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *                           &lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                         &lt;/restriction>
-      *                       &lt;/complexContent>
-      *                     &lt;/complexType>
-      *                   &lt;/element>
-      *                 &lt;/sequence>
-      *                 &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-      *                 &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                 &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                 &lt;attribute name="dividerLocation" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *                 &lt;attribute name="selectedRna" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *               &lt;/restriction>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *         &lt;element name="hmmerProfile" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
-      *       &lt;/sequence>
-      *       &lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="start" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="end" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="id" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="hidden" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="viewreference" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *     &lt;/restriction>
-      *   &lt;/complexContent>
-      * &lt;/complexType>
-      * </pre>
+      * &lt;pre&gt;
+      * &amp;lt;complexType&amp;gt;
+      *   &amp;lt;complexContent&amp;gt;
+      *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *       &amp;lt;sequence&amp;gt;
+      *         &amp;lt;element name="features" type="{www.jalview.org}feature" maxOccurs="unbounded" minOccurs="0"/&amp;gt;
+      *         &amp;lt;element name="pdbids" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;extension base="{www.jalview.org}pdbentry"&amp;gt;
+      *                 &amp;lt;sequence&amp;gt;
+      *                   &amp;lt;element name="structureState" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+      *                     &amp;lt;complexType&amp;gt;
+      *                       &amp;lt;simpleContent&amp;gt;
+      *                         &amp;lt;extension base="&amp;lt;http://www.w3.org/2001/XMLSchema&amp;gt;string"&amp;gt;
+      *                           &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+      *                           &amp;lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *                           &amp;lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                           &amp;lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+      *                           &amp;lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *                           &amp;lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+      *                           &amp;lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                         &amp;lt;/extension&amp;gt;
+      *                       &amp;lt;/simpleContent&amp;gt;
+      *                     &amp;lt;/complexType&amp;gt;
+      *                   &amp;lt;/element&amp;gt;
+      *                 &amp;lt;/sequence&amp;gt;
+      *               &amp;lt;/extension&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *         &amp;lt;element name="hiddenSequences" type="{http://www.w3.org/2001/XMLSchema}int" maxOccurs="unbounded" minOccurs="0"/&amp;gt;
+      *         &amp;lt;element name="rnaViewer" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *                 &amp;lt;sequence&amp;gt;
+      *                   &amp;lt;element name="secondaryStructure" maxOccurs="unbounded"&amp;gt;
+      *                     &amp;lt;complexType&amp;gt;
+      *                       &amp;lt;complexContent&amp;gt;
+      *                         &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *                           &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                           &amp;lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                           &amp;lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *                           &amp;lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                         &amp;lt;/restriction&amp;gt;
+      *                       &amp;lt;/complexContent&amp;gt;
+      *                     &amp;lt;/complexType&amp;gt;
+      *                   &amp;lt;/element&amp;gt;
+      *                 &amp;lt;/sequence&amp;gt;
+      *                 &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+      *                 &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                 &amp;lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                 &amp;lt;attribute name="dividerLocation" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *                 &amp;lt;attribute name="selectedRna" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *               &amp;lt;/restriction&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *       &amp;lt;/sequence&amp;gt;
+      *       &amp;lt;attribute name="colour" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="start" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="end" use="required" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="id" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="hidden" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="viewreference" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *     &amp;lt;/restriction&amp;gt;
+      *   &amp;lt;/complexContent&amp;gt;
+      * &amp;lt;/complexType&amp;gt;
+      * &lt;/pre&gt;
       * 
       * 
       */
          "features",
          "pdbids",
          "hiddenSequences",
 -        "rnaViewer"
 +        "rnaViewer",
 +        "hmmerProfile"
      })
      public static class JSeq {
  
          protected List<Integer> hiddenSequences;
          @XmlElement(namespace = "www.jalview.org")
          protected List<JalviewModel.JSeq.RnaViewer> rnaViewer;
 +        @XmlElement(namespace = "www.jalview.org")
 +        protected String hmmerProfile;
          @XmlAttribute(name = "colour")
          protected Integer colour;
          @XmlAttribute(name = "start", required = true)
          /**
           * Gets the value of the features property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the features property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the features property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getFeatures().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link Feature }
           * 
          /**
           * Gets the value of the pdbids property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the pdbids property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the pdbids property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getPdbids().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link JalviewModel.JSeq.Pdbids }
           * 
          /**
           * Gets the value of the hiddenSequences property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the hiddenSequences property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the hiddenSequences property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getHiddenSequences().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link Integer }
           * 
          /**
           * Gets the value of the rnaViewer property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the rnaViewer property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the rnaViewer property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getRnaViewer().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link JalviewModel.JSeq.RnaViewer }
           * 
          }
  
          /**
 +         * Gets the value of the hmmerProfile property.
 +         * 
 +         * @return
 +         *     possible object is
 +         *     {@link String }
 +         *     
 +         */
 +        public String getHmmerProfile() {
 +            return hmmerProfile;
 +        }
 +
 +        /**
 +         * Sets the value of the hmmerProfile property.
 +         * 
 +         * @param value
 +         *     allowed object is
 +         *     {@link String }
 +         *     
 +         */
 +        public void setHmmerProfile(String value) {
 +            this.hmmerProfile = value;
 +        }
 +
 +        /**
           * Gets the value of the colour property.
           * 
           * @return
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;extension base="{www.jalview.org}pdbentry">
-          *       &lt;sequence>
-          *         &lt;element name="structureState" maxOccurs="unbounded" minOccurs="0">
-          *           &lt;complexType>
-          *             &lt;simpleContent>
-          *               &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
-          *                 &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-          *                 &lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-          *                 &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *                 &lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-          *                 &lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-          *                 &lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-          *                 &lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *               &lt;/extension>
-          *             &lt;/simpleContent>
-          *           &lt;/complexType>
-          *         &lt;/element>
-          *       &lt;/sequence>
-          *     &lt;/extension>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;extension base="{www.jalview.org}pdbentry"&amp;gt;
+          *       &amp;lt;sequence&amp;gt;
+          *         &amp;lt;element name="structureState" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+          *           &amp;lt;complexType&amp;gt;
+          *             &amp;lt;simpleContent&amp;gt;
+          *               &amp;lt;extension base="&amp;lt;http://www.w3.org/2001/XMLSchema&amp;gt;string"&amp;gt;
+          *                 &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+          *                 &amp;lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+          *                 &amp;lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *                 &amp;lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+          *                 &amp;lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+          *                 &amp;lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+          *                 &amp;lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *               &amp;lt;/extension&amp;gt;
+          *             &amp;lt;/simpleContent&amp;gt;
+          *           &amp;lt;/complexType&amp;gt;
+          *         &amp;lt;/element&amp;gt;
+          *       &amp;lt;/sequence&amp;gt;
+          *     &amp;lt;/extension&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
              /**
               * Gets the value of the structureState property.
               * 
-              * <p>
+              * &lt;p&gt;
               * This accessor method returns a reference to the live list,
               * not a snapshot. Therefore any modification you make to the
               * returned list will be present inside the JAXB object.
-              * This is why there is not a <CODE>set</CODE> method for the structureState property.
+              * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the structureState property.
               * 
-              * <p>
+              * &lt;p&gt;
               * For example, to add a new item, do as follows:
-              * <pre>
+              * &lt;pre&gt;
               *    getStructureState().add(newItem);
-              * </pre>
+              * &lt;/pre&gt;
               * 
               * 
-              * <p>
+              * &lt;p&gt;
               * Objects of the following type(s) are allowed in the list
               * {@link JalviewModel.JSeq.Pdbids.StructureState }
               * 
  
  
              /**
-              * <p>Java class for anonymous complex type.
+              * &lt;p&gt;Java class for anonymous complex type.
               * 
-              * <p>The following schema fragment specifies the expected content contained within this class.
+              * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
               * 
-              * <pre>
-              * &lt;complexType>
-              *   &lt;simpleContent>
-              *     &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
-              *       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-              *       &lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-              *       &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
-              *       &lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-              *       &lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-              *       &lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-              *       &lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" />
-              *     &lt;/extension>
-              *   &lt;/simpleContent>
-              * &lt;/complexType>
-              * </pre>
+              * &lt;pre&gt;
+              * &amp;lt;complexType&amp;gt;
+              *   &amp;lt;simpleContent&amp;gt;
+              *     &amp;lt;extension base="&amp;lt;http://www.w3.org/2001/XMLSchema&amp;gt;string"&amp;gt;
+              *       &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+              *       &amp;lt;attribute name="visible" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+              *       &amp;lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+              *       &amp;lt;attribute name="alignwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+              *       &amp;lt;attribute name="colourwithAlignPanel" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+              *       &amp;lt;attribute name="colourByJmol" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+              *       &amp;lt;attribute name="type" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+              *     &amp;lt;/extension&amp;gt;
+              *   &amp;lt;/simpleContent&amp;gt;
+              * &amp;lt;/complexType&amp;gt;
+              * &lt;/pre&gt;
               * 
               * 
               */
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-          *       &lt;sequence>
-          *         &lt;element name="secondaryStructure" maxOccurs="unbounded">
-          *           &lt;complexType>
-          *             &lt;complexContent>
-          *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-          *                 &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *                 &lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *                 &lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-          *                 &lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *               &lt;/restriction>
-          *             &lt;/complexContent>
-          *           &lt;/complexType>
-          *         &lt;/element>
-          *       &lt;/sequence>
-          *       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-          *       &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *       &lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *       &lt;attribute name="dividerLocation" type="{http://www.w3.org/2001/XMLSchema}int" />
-          *       &lt;attribute name="selectedRna" type="{http://www.w3.org/2001/XMLSchema}int" />
-          *     &lt;/restriction>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+          *       &amp;lt;sequence&amp;gt;
+          *         &amp;lt;element name="secondaryStructure" maxOccurs="unbounded"&amp;gt;
+          *           &amp;lt;complexType&amp;gt;
+          *             &amp;lt;complexContent&amp;gt;
+          *               &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+          *                 &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *                 &amp;lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *                 &amp;lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+          *                 &amp;lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *               &amp;lt;/restriction&amp;gt;
+          *             &amp;lt;/complexContent&amp;gt;
+          *           &amp;lt;/complexType&amp;gt;
+          *         &amp;lt;/element&amp;gt;
+          *       &amp;lt;/sequence&amp;gt;
+          *       &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+          *       &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *       &amp;lt;attribute name="viewId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *       &amp;lt;attribute name="dividerLocation" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+          *       &amp;lt;attribute name="selectedRna" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+          *     &amp;lt;/restriction&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
              /**
               * Gets the value of the secondaryStructure property.
               * 
-              * <p>
+              * &lt;p&gt;
               * This accessor method returns a reference to the live list,
               * not a snapshot. Therefore any modification you make to the
               * returned list will be present inside the JAXB object.
-              * This is why there is not a <CODE>set</CODE> method for the secondaryStructure property.
+              * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the secondaryStructure property.
               * 
-              * <p>
+              * &lt;p&gt;
               * For example, to add a new item, do as follows:
-              * <pre>
+              * &lt;pre&gt;
               *    getSecondaryStructure().add(newItem);
-              * </pre>
+              * &lt;/pre&gt;
               * 
               * 
-              * <p>
+              * &lt;p&gt;
               * Objects of the following type(s) are allowed in the list
               * {@link JalviewModel.JSeq.RnaViewer.SecondaryStructure }
               * 
  
  
              /**
-              * <p>Java class for anonymous complex type.
+              * &lt;p&gt;Java class for anonymous complex type.
               * 
-              * <p>The following schema fragment specifies the expected content contained within this class.
+              * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
               * 
-              * <pre>
-              * &lt;complexType>
-              *   &lt;complexContent>
-              *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-              *       &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-              *       &lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-              *       &lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-              *       &lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" />
-              *     &lt;/restriction>
-              *   &lt;/complexContent>
-              * &lt;/complexType>
-              * </pre>
+              * &lt;pre&gt;
+              * &amp;lt;complexType&amp;gt;
+              *   &amp;lt;complexContent&amp;gt;
+              *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+              *       &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+              *       &amp;lt;attribute name="annotationId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+              *       &amp;lt;attribute name="gapped" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+              *       &amp;lt;attribute name="viewerState" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+              *     &amp;lt;/restriction&amp;gt;
+              *   &amp;lt;/complexContent&amp;gt;
+              * &amp;lt;/complexType&amp;gt;
+              * &lt;/pre&gt;
               * 
               * 
               */
  
  
      /**
-      * <p>Java class for anonymous complex type.
+      * &lt;p&gt;Java class for anonymous complex type.
       * 
-      * <p>The following schema fragment specifies the expected content contained within this class.
+      * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
       * 
-      * <pre>
-      * &lt;complexType>
-      *   &lt;complexContent>
-      *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *       &lt;sequence>
-      *         &lt;element name="sequencePoint" maxOccurs="unbounded">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *                 &lt;attGroup ref="{www.jalview.org}position"/>
-      *                 &lt;attribute name="sequenceRef" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *               &lt;/restriction>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *         &lt;element name="axis" maxOccurs="3" minOccurs="3">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *                 &lt;attGroup ref="{www.jalview.org}position"/>
-      *               &lt;/restriction>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *         &lt;element name="seqPointMin">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *                 &lt;attGroup ref="{www.jalview.org}position"/>
-      *               &lt;/restriction>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *         &lt;element name="seqPointMax">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *                 &lt;attGroup ref="{www.jalview.org}position"/>
-      *               &lt;/restriction>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *         &lt;element name="pcaData" type="{www.jalview.org}PcaDataType"/>
-      *       &lt;/sequence>
-      *       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-      *       &lt;attGroup ref="{www.jalview.org}SimilarityParams"/>
-      *       &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="scoreModelName" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="xDim" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="yDim" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="zDim" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="scaleFactor" type="{http://www.w3.org/2001/XMLSchema}float" />
-      *       &lt;attribute name="showLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="linkToAllViews" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *     &lt;/restriction>
-      *   &lt;/complexContent>
-      * &lt;/complexType>
-      * </pre>
+      * &lt;pre&gt;
+      * &amp;lt;complexType&amp;gt;
+      *   &amp;lt;complexContent&amp;gt;
+      *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *       &amp;lt;sequence&amp;gt;
+      *         &amp;lt;element name="sequencePoint" maxOccurs="unbounded"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *                 &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+      *                 &amp;lt;attribute name="sequenceRef" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *               &amp;lt;/restriction&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *         &amp;lt;element name="axis" maxOccurs="3" minOccurs="3"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *                 &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+      *               &amp;lt;/restriction&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *         &amp;lt;element name="seqPointMin"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *                 &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+      *               &amp;lt;/restriction&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *         &amp;lt;element name="seqPointMax"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *                 &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+      *               &amp;lt;/restriction&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *         &amp;lt;element name="pcaData" type="{www.jalview.org}PcaDataType"/&amp;gt;
+      *       &amp;lt;/sequence&amp;gt;
+      *       &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+      *       &amp;lt;attGroup ref="{www.jalview.org}SimilarityParams"/&amp;gt;
+      *       &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="scoreModelName" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="xDim" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="yDim" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="zDim" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="scaleFactor" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+      *       &amp;lt;attribute name="showLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="linkToAllViews" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *     &amp;lt;/restriction&amp;gt;
+      *   &amp;lt;/complexContent&amp;gt;
+      * &amp;lt;/complexType&amp;gt;
+      * &lt;/pre&gt;
       * 
       * 
       */
          /**
           * Gets the value of the sequencePoint property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the sequencePoint property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the sequencePoint property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getSequencePoint().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link JalviewModel.PcaViewer.SequencePoint }
           * 
          /**
           * Gets the value of the axis property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the axis property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the axis property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getAxis().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link JalviewModel.PcaViewer.Axis }
           * 
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-          *       &lt;attGroup ref="{www.jalview.org}position"/>
-          *     &lt;/restriction>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+          *       &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+          *     &amp;lt;/restriction&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-          *       &lt;attGroup ref="{www.jalview.org}position"/>
-          *     &lt;/restriction>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+          *       &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+          *     &amp;lt;/restriction&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-          *       &lt;attGroup ref="{www.jalview.org}position"/>
-          *     &lt;/restriction>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+          *       &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+          *     &amp;lt;/restriction&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-          *       &lt;attGroup ref="{www.jalview.org}position"/>
-          *       &lt;attribute name="sequenceRef" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *     &lt;/restriction>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+          *       &amp;lt;attGroup ref="{www.jalview.org}position"/&amp;gt;
+          *       &amp;lt;attribute name="sequenceRef" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *     &amp;lt;/restriction&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
  
  
      /**
-      * <p>Java class for anonymous complex type.
+      * &lt;p&gt;Java class for anonymous complex type.
       * 
-      * <p>The following schema fragment specifies the expected content contained within this class.
+      * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
       * 
-      * <pre>
-      * &lt;complexType>
-      *   &lt;complexContent>
-      *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *       &lt;sequence minOccurs="0">
-      *         &lt;element name="title" type="{http://www.w3.org/2001/XMLSchema}string"/>
-      *         &lt;element name="newick" type="{http://www.w3.org/2001/XMLSchema}string"/>
-      *       &lt;/sequence>
-      *       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-      *       &lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" />
-      *       &lt;attribute name="showBootstrap" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="showDistances" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="markUnlinked" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="fitToWindow" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="currentTree" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" />
-      *       &lt;attribute name="linkToAllViews" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *     &lt;/restriction>
-      *   &lt;/complexContent>
-      * &lt;/complexType>
-      * </pre>
+      * &lt;pre&gt;
+      * &amp;lt;complexType&amp;gt;
+      *   &amp;lt;complexContent&amp;gt;
+      *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *       &amp;lt;sequence minOccurs="0"&amp;gt;
+      *         &amp;lt;element name="title" type="{http://www.w3.org/2001/XMLSchema}string"/&amp;gt;
+      *         &amp;lt;element name="newick" type="{http://www.w3.org/2001/XMLSchema}string"/&amp;gt;
+      *       &amp;lt;/sequence&amp;gt;
+      *       &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+      *       &amp;lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="threshold" type="{http://www.w3.org/2001/XMLSchema}float" /&amp;gt;
+      *       &amp;lt;attribute name="showBootstrap" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="showDistances" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="markUnlinked" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="fitToWindow" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="currentTree" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" /&amp;gt;
+      *       &amp;lt;attribute name="linkToAllViews" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *     &amp;lt;/restriction&amp;gt;
+      *   &amp;lt;/complexContent&amp;gt;
+      * &amp;lt;/complexType&amp;gt;
+      * &lt;/pre&gt;
       * 
       * 
       */
  
  
      /**
-      * <p>Java class for anonymous complex type.
+      * &lt;p&gt;Java class for anonymous complex type.
       * 
-      * <p>The following schema fragment specifies the expected content contained within this class.
+      * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
       * 
-      * <pre>
-      * &lt;complexType>
-      *   &lt;complexContent>
-      *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *       &lt;sequence>
-      *         &lt;element name="UserColourScheme" type="{www.jalview.org/colours}JalviewUserColours"/>
-      *       &lt;/sequence>
-      *       &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *     &lt;/restriction>
-      *   &lt;/complexContent>
-      * &lt;/complexType>
-      * </pre>
+      * &lt;pre&gt;
+      * &amp;lt;complexType&amp;gt;
+      *   &amp;lt;complexContent&amp;gt;
+      *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *       &amp;lt;sequence&amp;gt;
+      *         &amp;lt;element name="UserColourScheme" type="{www.jalview.org/colours}JalviewUserColours"/&amp;gt;
+      *       &amp;lt;/sequence&amp;gt;
+      *       &amp;lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *     &amp;lt;/restriction&amp;gt;
+      *   &amp;lt;/complexContent&amp;gt;
+      * &amp;lt;/complexType&amp;gt;
+      * &lt;/pre&gt;
       * 
       * 
       */
  
  
      /**
-      * <p>Java class for anonymous complex type.
+      * &lt;p&gt;Java class for anonymous complex type.
       * 
-      * <p>The following schema fragment specifies the expected content contained within this class.
+      * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
       * 
-      * <pre>
-      * &lt;complexType>
-      *   &lt;complexContent>
-      *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *       &lt;sequence>
-      *         &lt;element name="AnnotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/>
-      *         &lt;element name="hiddenColumns" maxOccurs="unbounded" minOccurs="0">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-      *                 &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *                 &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *               &lt;/restriction>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *         &lt;element name="calcIdParam" maxOccurs="unbounded" minOccurs="0">
-      *           &lt;complexType>
-      *             &lt;complexContent>
-      *               &lt;extension base="{www.jalview.org/xml/wsparamset}WebServiceParameterSet">
-      *                 &lt;attribute name="calcId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *                 &lt;attribute name="needsUpdate" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *                 &lt;attribute name="autoUpdate" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *               &lt;/extension>
-      *             &lt;/complexContent>
-      *           &lt;/complexType>
-      *         &lt;/element>
-      *       &lt;/sequence>
-      *       &lt;attGroup ref="{www.jalview.org}swingwindow"/>
-      *       &lt;attribute name="conservationSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="pidSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="showFullId" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="rightAlignIds" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="showText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="showColourText" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *       &lt;attribute name="showBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="wrapAlignment" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="renderGaps" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="showSequenceFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="showNPfeatureTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="showDbRefTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="followHighlight" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-      *       &lt;attribute name="followSelection" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-      *       &lt;attribute name="showAnnotation" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="centreColumnLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *       &lt;attribute name="showGroupConservation" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *       &lt;attribute name="showGroupConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *       &lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-      *       &lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *       &lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *       &lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-      *       &lt;attribute name="startRes" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="startSeq" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="scaleProteinAsCdna" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" />
-      *       &lt;attribute name="viewName" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="sequenceSetId" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="gatheredViews" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-      *       &lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" />
-      *       &lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" />
-      *       &lt;attribute name="complementId" type="{http://www.w3.org/2001/XMLSchema}string" />
-      *       &lt;attribute name="showComplementFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *       &lt;attribute name="showComplementFeaturesOnTop" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-      *     &lt;/restriction>
-      *   &lt;/complexContent>
-      * &lt;/complexType>
-      * </pre>
+      * &lt;pre&gt;
+      * &amp;lt;complexType&amp;gt;
+      *   &amp;lt;complexContent&amp;gt;
+      *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *       &amp;lt;sequence&amp;gt;
+      *         &amp;lt;element name="AnnotationColours" type="{www.jalview.org}AnnotationColourScheme" minOccurs="0"/&amp;gt;
+      *         &amp;lt;element name="hiddenColumns" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+      *                 &amp;lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *                 &amp;lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *               &amp;lt;/restriction&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *         &amp;lt;element name="calcIdParam" maxOccurs="unbounded" minOccurs="0"&amp;gt;
+      *           &amp;lt;complexType&amp;gt;
+      *             &amp;lt;complexContent&amp;gt;
+      *               &amp;lt;extension base="{www.jalview.org/xml/wsparamset}WebServiceParameterSet"&amp;gt;
+      *                 &amp;lt;attribute name="calcId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *                 &amp;lt;attribute name="needsUpdate" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *                 &amp;lt;attribute name="autoUpdate" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *               &amp;lt;/extension&amp;gt;
+      *             &amp;lt;/complexContent&amp;gt;
+      *           &amp;lt;/complexType&amp;gt;
+      *         &amp;lt;/element&amp;gt;
+      *       &amp;lt;/sequence&amp;gt;
+      *       &amp;lt;attGroup ref="{www.jalview.org}swingwindow"/&amp;gt;
+      *       &amp;lt;attribute name="conservationSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="pidSelected" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="bgColour" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="consThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="pidThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="title" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="showFullId" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="rightAlignIds" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="showText" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="showColourText" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="showUnconserved" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *       &amp;lt;attribute name="showBoxes" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="wrapAlignment" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="renderGaps" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="showSequenceFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="showNPfeatureTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="showDbRefTooltip" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="followHighlight" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+      *       &amp;lt;attribute name="followSelection" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+      *       &amp;lt;attribute name="showAnnotation" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="centreColumnLabels" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *       &amp;lt;attribute name="showGroupConservation" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *       &amp;lt;attribute name="showGroupConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *       &amp;lt;attribute name="showConsensusHistogram" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+      *       &amp;lt;attribute name="showSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *       &amp;lt;attribute name="normaliseSequenceLogo" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *       &amp;lt;attribute name="ignoreGapsinConsensus" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+      *       &amp;lt;attribute name="startRes" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="startSeq" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="fontName" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="fontSize" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="fontStyle" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="scaleProteinAsCdna" type="{http://www.w3.org/2001/XMLSchema}boolean" default="true" /&amp;gt;
+      *       &amp;lt;attribute name="viewName" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="sequenceSetId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="gatheredViews" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+      *       &amp;lt;attribute name="textCol1" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="textCol2" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="textColThreshold" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+      *       &amp;lt;attribute name="id" type="{http://www.w3.org/2001/XMLSchema}ID" /&amp;gt;
+      *       &amp;lt;attribute name="complementId" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+      *       &amp;lt;attribute name="showComplementFeatures" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *       &amp;lt;attribute name="showComplementFeaturesOnTop" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+      *     &amp;lt;/restriction&amp;gt;
+      *   &amp;lt;/complexContent&amp;gt;
+      * &amp;lt;/complexType&amp;gt;
+      * &lt;/pre&gt;
       * 
       * 
       */
          /**
           * Gets the value of the hiddenColumns property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the hiddenColumns property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the hiddenColumns property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getHiddenColumns().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link JalviewModel.Viewport.HiddenColumns }
           * 
          /**
           * Gets the value of the calcIdParam property.
           * 
-          * <p>
+          * &lt;p&gt;
           * This accessor method returns a reference to the live list,
           * not a snapshot. Therefore any modification you make to the
           * returned list will be present inside the JAXB object.
-          * This is why there is not a <CODE>set</CODE> method for the calcIdParam property.
+          * This is why there is not a &lt;CODE&gt;set&lt;/CODE&gt; method for the calcIdParam property.
           * 
-          * <p>
+          * &lt;p&gt;
           * For example, to add a new item, do as follows:
-          * <pre>
+          * &lt;pre&gt;
           *    getCalcIdParam().add(newItem);
-          * </pre>
+          * &lt;/pre&gt;
           * 
           * 
-          * <p>
+          * &lt;p&gt;
           * Objects of the following type(s) are allowed in the list
           * {@link JalviewModel.Viewport.CalcIdParam }
           * 
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;extension base="{www.jalview.org/xml/wsparamset}WebServiceParameterSet">
-          *       &lt;attribute name="calcId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
-          *       &lt;attribute name="needsUpdate" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" />
-          *       &lt;attribute name="autoUpdate" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" />
-          *     &lt;/extension>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;extension base="{www.jalview.org/xml/wsparamset}WebServiceParameterSet"&amp;gt;
+          *       &amp;lt;attribute name="calcId" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /&amp;gt;
+          *       &amp;lt;attribute name="needsUpdate" type="{http://www.w3.org/2001/XMLSchema}boolean" default="false" /&amp;gt;
+          *       &amp;lt;attribute name="autoUpdate" use="required" type="{http://www.w3.org/2001/XMLSchema}boolean" /&amp;gt;
+          *     &amp;lt;/extension&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
  
  
          /**
-          * <p>Java class for anonymous complex type.
+          * &lt;p&gt;Java class for anonymous complex type.
           * 
-          * <p>The following schema fragment specifies the expected content contained within this class.
+          * &lt;p&gt;The following schema fragment specifies the expected content contained within this class.
           * 
-          * <pre>
-          * &lt;complexType>
-          *   &lt;complexContent>
-          *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
-          *       &lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" />
-          *       &lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" />
-          *     &lt;/restriction>
-          *   &lt;/complexContent>
-          * &lt;/complexType>
-          * </pre>
+          * &lt;pre&gt;
+          * &amp;lt;complexType&amp;gt;
+          *   &amp;lt;complexContent&amp;gt;
+          *     &amp;lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&amp;gt;
+          *       &amp;lt;attribute name="start" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+          *       &amp;lt;attribute name="end" type="{http://www.w3.org/2001/XMLSchema}int" /&amp;gt;
+          *     &amp;lt;/restriction&amp;gt;
+          *   &amp;lt;/complexContent&amp;gt;
+          * &amp;lt;/complexType&amp;gt;
+          * &lt;/pre&gt;
           * 
           * 
           */
   */
  package jalview.analysis;
  
+ import java.util.Locale;
 -import jalview.datamodel.Alignment;
 -import jalview.datamodel.AlignmentI;
 -import jalview.datamodel.Sequence;
 -import jalview.datamodel.SequenceI;
 -import jalview.gui.JvOptionPane;
 -import jalview.io.FastaFile;
 -
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.PrintStream;
  import java.util.Arrays;
  import java.util.Random;
  
 -import org.testng.annotations.BeforeClass;
 +import jalview.datamodel.Alignment;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.Sequence;
 +import jalview.datamodel.SequenceI;
 +import jalview.io.FastaFile;
  
  /**
   * Generates, and outputs in Fasta format, a random peptide or nucleotide alignment for given
@@@ -88,7 -93,7 +90,7 @@@ public class AlignmentGenerato
        ps = new PrintStream(new File(args[6]));
      }
  
-     boolean nucleotide = args[0].toLowerCase().startsWith("n");
+     boolean nucleotide = args[0].toLowerCase(Locale.ROOT).startsWith("n");
      int width = Integer.parseInt(args[1]);
      int height = Integer.parseInt(args[2]);
      long randomSeed = Long.valueOf(args[3]);
@@@ -68,11 -68,16 +68,16 @@@ public class FinderTes
    public void setUp()
    {
      Cache.loadProperties("test/jalview/io/testProps.jvprops");
 -    Cache.applicationProperties.setProperty("PAD_GAPS",
 +    Cache.setPropertyNoSave("PAD_GAPS",
              Boolean.FALSE.toString());
  
-     String seqData = "seq1seq1/8-18 ABCD--EF-GHIJI\n" + "seq2 A--BCDefHI\n"
-             + "seq3 --bcdEFH\n" + "seq4 aa---aMMMMMaaa\n";
+     //@formatter:off
+     String seqData = 
+         "seq1/8-18 ABCD--EF-GHIJI\n" + 
+         "seq2      A--BCDefHI\n" + 
+         "seq3      --bcdEFH\n" + 
+         "seq4      aa---aMMMMMaaa\n";
+     //@formatter:on
      af = new FileLoader().LoadFileWaitTillLoaded(seqData,
              DataSourceType.PASTE);
      av = af.getViewport();
       * find next match only
       */
      Finder f = new Finder(av);
-     f.findNext("E.H", false, false); // 'E, any character, H'
+     f.findNext("E.H", false, false, false); // 'E, any character, H'
      // should match seq2 efH only
      SearchResultsI sr = f.getSearchResults();
-     assertEquals(sr.getSize(), 1);
+     assertEquals(sr.getCount(), 1);
      List<SearchResultMatchI> matches = sr.getResults();
      assertSame(matches.get(0).getSequence(), al.getSequenceAt(1));
      assertEquals(matches.get(0).getStart(), 5);
      assertEquals(matches.get(0).getEnd(), 7);
  
      f = new Finder(av);
-     f.findAll("E.H", false, false); // 'E, any character, H'
+     f.findAll("E.H", false, false, false); // 'E, any character, H'
      // should match seq2 efH and seq3 EFH
      sr = f.getSearchResults();
-     assertEquals(sr.getSize(), 2);
+     assertEquals(sr.getCount(), 2);
      matches = sr.getResults();
      assertSame(matches.get(0).getSequence(), al.getSequenceAt(1));
      assertSame(matches.get(1).getSequence(), al.getSequenceAt(2));
      assertEquals(matches.get(1).getEnd(), 6);
    }
  
+   @Test(groups = "Functional")
+   public void testFind_findAll()
+   {
+     /*
+      * simple JAL-3765 test
+      * single symbol should find *all* matching symbols 
+      */
+     Finder f = new Finder(av);
+     f.findAll("M", false,false,false);
+     SearchResultsI sr = f.getSearchResults();
+     assertEquals(sr.getCount(),5);
+     
+   }
    /**
     * Test for (undocumented) find residue by position
     */
      /*
       * find first match should return seq1 residue 9
       */
-     f.findNext("9", false, false);
+     f.findNext("9", false, false, false);
      SearchResultsI sr = f.getSearchResults();
-     assertEquals(sr.getSize(), 1);
+     assertEquals(sr.getCount(), 1);
      List<SearchResultMatchI> matches = sr.getResults();
      assertSame(matches.get(0).getSequence(), al.getSequenceAt(0));
      assertEquals(matches.get(0).getStart(), 9);
  
      /*
       * find all matches should return seq1 and seq4 (others are too short)
+      * (and not matches in sequence ids)
       */
      f = new Finder(av);
-     f.findAll("9", false, false);
+     String name = al.getSequenceAt(0).getName();
+     al.getSequenceAt(0).setName("Q9XA0");
+     f.findAll("9", false, false, false);
      sr = f.getSearchResults();
-     assertEquals(sr.getSize(), 2);
+     assertEquals(sr.getCount(), 2);
      matches = sr.getResults();
      assertSame(matches.get(0).getSequence(), al.getSequenceAt(0));
      assertSame(matches.get(1).getSequence(), al.getSequenceAt(3));
      assertEquals(matches.get(0).getEnd(), 9);
      assertEquals(matches.get(1).getStart(), 9);
      assertEquals(matches.get(1).getEnd(), 9);
+     al.getSequenceAt(0).setName(name);
  
      /*
       * parsing of search string as integer is strict
       */
      f = new Finder(av);
-     f.findNext(" 9", false, false);
+     f.findNext(" 9", false, false, false);
      assertTrue(f.getSearchResults().isEmpty());
    }
  
    public void testFindNext()
    {
      /*
-      * start at second sequence; colIndex of -1
+      * start at second sequence; residueIndex of -1
       * means sequence id / description is searched
       */
      Finder f = new Finder(av);
      PA.setValue(f, "sequenceIndex", 1);
-     PA.setValue(f, "columnIndex", -1);
-     f.findNext("e", false, false); // matches id
+     PA.setValue(f, "residueIndex", -1);
+     f.findNext("e", false, false, false); // matches id
  
      assertTrue(f.getSearchResults().isEmpty());
      assertEquals(f.getIdMatches().size(), 1);
      assertSame(f.getIdMatches().get(0), al.getSequenceAt(1));
  
-     // colIndex is now 0 - for use in next find next
+     // residueIndex is now 0 - for use in next find next
      // searching A--BCDefHI
-     assertEquals(PA.getValue(f, "columnIndex"), 0);
+     assertEquals(PA.getValue(f, "residueIndex"), 0);
      f = new Finder(av);
      PA.setValue(f, "sequenceIndex", 1);
-     PA.setValue(f, "columnIndex", 0);
-     f.findNext("e", false, false); // matches in sequence
+     PA.setValue(f, "residueIndex", 0);
+     f.findNext("e", false, false, false); // matches in sequence
      assertTrue(f.getIdMatches().isEmpty());
-     assertEquals(f.getSearchResults().getSize(), 1);
+     assertEquals(f.getSearchResults().getCount(), 1);
      List<SearchResultMatchI> matches = f.getSearchResults().getResults();
      assertEquals(matches.get(0).getStart(), 5);
      assertEquals(matches.get(0).getEnd(), 5);
      assertSame(matches.get(0).getSequence(), al.getSequenceAt(1));
      // still in the second sequence
      assertEquals(PA.getValue(f, "sequenceIndex"), 1);
-     // next column position to search from is 7
-     assertEquals(PA.getValue(f, "columnIndex"), 7);
+     // next residue offset to search from is 5
+     assertEquals(PA.getValue(f, "residueIndex"), 5);
  
      // find next from end of sequence - finds next sequence id
      f = new Finder(av);
      PA.setValue(f, "sequenceIndex", 1);
-     PA.setValue(f, "columnIndex", 7);
-     f.findNext("e", false, false);
+     PA.setValue(f, "residueIndex", 7);
+     f.findNext("e", false, false, false);
      assertEquals(f.getIdMatches().size(), 1);
      assertSame(f.getIdMatches().get(0), al.getSequenceAt(2));
      assertTrue(f.getSearchResults().isEmpty());
       * find first match only
       */
      Finder f = new Finder(av2);
-     f.findNext("rAF", false, true);
+     f.findNext("rAF", false, true, false);
      assertEquals(f.getIdMatches().size(), 1);
      assertSame(f.getIdMatches().get(0), al2.getSequenceAt(0));
      assertTrue(f.getSearchResults().isEmpty());
       * find all matches
       */
      f = new Finder(av2);
-     f.findAll("rAF", false, true);
+     f.findAll("rAF", false, true, false);
      assertEquals(f.getIdMatches().size(), 2);
      assertSame(f.getIdMatches().get(0), al2.getSequenceAt(0));
      assertSame(f.getIdMatches().get(1), al2.getSequenceAt(1));
       * case sensitive
       */
      f = new Finder(av2);
-     f.findAll("RAF", true, true);
+     f.findAll("RAF", true, true, false);
      assertEquals(f.getIdMatches().size(), 1);
      assertSame(f.getIdMatches().get(0), al2.getSequenceAt(0));
      assertTrue(f.getSearchResults().isEmpty());
      /*
       * sequence matches should have no duplicates
       */
-     f.findAll("EFH", false, true);
+     f.findAll("EFH", false, true, false);
      assertEquals(f.getIdMatches().size(), 2);
      assertSame(f.getIdMatches().get(0), al2.getSequenceAt(0));
      assertSame(f.getIdMatches().get(1), al2.getSequenceAt(1));
  
-     assertEquals(f.getSearchResults().getSize(), 2);
+     assertEquals(f.getSearchResults().getCount(), 2);
      SearchResultMatchI match = f.getSearchResults().getResults().get(0);
      assertSame(match.getSequence(), al2.getSequenceAt(1));
      assertEquals(match.getStart(), 5);
       * case insensitive; seq1 occurs twice in sequence id but
       * only one match should be returned
       */
-     f.findAll("SEQ1", false, false);
+     f.findAll("SEQ1", false, false, false);
      assertEquals(f.getIdMatches().size(), 1);
      assertSame(f.getIdMatches().get(0), al.getSequenceAt(0));
      SearchResultsI searchResults = f.getSearchResults();
       * case sensitive
       */
      f = new Finder(av);
-     f.findAll("SEQ1", true, false);
+     f.findAll("SEQ1", true, false, false);
      searchResults = f.getSearchResults();
      assertTrue(searchResults.isEmpty());
  
      AlignViewportI av2 = new AlignViewport(al2);
      al2.addSequence(new Sequence("aBz", "xyzabZpqrAbZ"));
      f = new Finder(av2);
-     f.findAll("ABZ", false, false);
+     f.findAll("ABZ", false, false, false);
      assertEquals(f.getIdMatches().size(), 1);
      assertSame(f.getIdMatches().get(0), al2.getSequenceAt(4));
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 2);
+     assertEquals(searchResults.getCount(), 2);
      SearchResultMatchI match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al2.getSequenceAt(4));
      assertEquals(match.getStart(), 4);
    @Test(groups = "Functional")
    public void testFind_findNext()
    {
+     // "seq1/8-18 ABCD--EF-GHIJI\n" +
+     // "seq2 A--BCDefHI\n" +
+     // "seq3 --bcdEFH\n" +
+     // "seq4 aa---aMMMMMaaa\n";
      /*
       * efh should be matched in seq2 only
       */
      FinderI f = new Finder(av);
-     f.findNext("EfH", false, false);
+     f.findNext("EfH", false, false, false);
      SearchResultsI searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      SearchResultMatchI match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(1));
      assertEquals(match.getStart(), 5);
       * I should be found in seq1 (twice) and seq2 (once)
       */
      f = new Finder(av);
-     f.findNext("I", false, false); // find next: seq1/16
+     f.findNext("I", false, false, false); // find next: seq1/16
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(0));
      assertEquals(match.getStart(), 16);
      assertEquals(match.getEnd(), 16);
  
-     f.findNext("I", false, false); // find next: seq1/18
+     f.findNext("I", false, false, false); // find next: seq1/18
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(0));
      assertEquals(match.getStart(), 18);
      assertEquals(match.getEnd(), 18);
  
-     f.findNext("I", false, false); // find next: seq2/8
+     f.findNext("I", false, false, false); // find next: seq2/8
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(1));
      assertEquals(match.getStart(), 8);
      assertEquals(match.getEnd(), 8);
  
-     f.findNext("I", false, false);
+     f.findNext("I", false, false, false);
      assertTrue(f.getSearchResults().isEmpty());
  
      /*
       * find should reset to start of alignment after a failed search
       */
-     f.findNext("I", false, false); // find next: seq1/16
+     f.findNext("I", false, false, false); // find next: seq1/16
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(0));
      assertEquals(match.getStart(), 16);
     * result
     */
    @Test(groups = "Functional")
-   public void testFind_maximalResultOnly()
+   public void testFindAll_maximalResultOnly()
    {
      Finder f = new Finder(av);
-     f.findAll("M+", false, false);
+     f.findAll("M+", false, false, false);
      SearchResultsI searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      SearchResultMatchI match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(3));
      assertEquals(match.getStart(), 4); // dataset sequence positions
     * Test finding all matches of a sequence pattern in an alignment
     */
    @Test(groups = "Functional")
-   public void testFind_findAll()
+   public void testFindAll()
    {
      Finder f = new Finder(av);
-     f.findAll("EfH", false, false);
+     f.findAll("EfH", false, false, false);
      SearchResultsI searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 2);
+     assertEquals(searchResults.getCount(), 2);
      SearchResultMatchI match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(1));
      assertEquals(match.getStart(), 5);
      /*
       * find all I should find 2 positions in seq1, 1 in seq2
       */
-     f.findAll("I", false, false);
+     f.findAll("I", false, false, false);
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 3);
+     assertEquals(searchResults.getCount(), 3);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(0));
      assertEquals(match.getStart(), 16);
     * Test finding all matches, case-sensitive
     */
    @Test(groups = "Functional")
-   public void testFind_findAllCaseSensitive()
+   public void testFindAll_caseSensitive()
    {
      Finder f = new Finder(av);
  
      /*
       * BC should match seq1/9-10 and seq2/2-3
       */
-     f.findAll("BC", true, false);
+     f.findAll("BC", true, false, false);
      SearchResultsI searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 2);
+     assertEquals(searchResults.getCount(), 2);
      SearchResultMatchI match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(0));
      assertEquals(match.getStart(), 9);
       * bc should match seq3/1-2
       */
      f = new Finder(av);
-     f.findAll("bc", true, false);
+     f.findAll("bc", true, false, false);
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(2));
      assertEquals(match.getStart(), 1);
      assertEquals(match.getEnd(), 2);
  
-     f.findAll("bC", true, false);
+     f.findAll("bC", true, false, false);
      assertTrue(f.getSearchResults().isEmpty());
    }
  
     * Test finding next match of a sequence pattern in a selection group
     */
    @Test(groups = "Functional")
-   public void testFind_inSelection()
+   public void testFindNext_inSelection()
    {
      /*
       * select sequences 2 and 3, columns 4-6 which contains
      av.setSelectionGroup(sg);
  
      FinderI f = new Finder(av);
-     f.findNext("b", false, false);
+     f.findNext("b", false, false, false);
      assertTrue(f.getIdMatches().isEmpty());
      SearchResultsI searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      SearchResultMatchI match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(1));
      assertEquals(match.getStart(), 2);
      /*
       * a second Find should not return the 'b' in seq3 as outside the selection
       */
-     f.findNext("b", false, false);
+     f.findNext("b", false, false, false);
      assertTrue(f.getSearchResults().isEmpty());
      assertTrue(f.getIdMatches().isEmpty());
  
      f = new Finder(av);
-     f.findNext("d", false, false);
+     f.findNext("d", false, false, false);
      assertTrue(f.getIdMatches().isEmpty());
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(1));
      assertEquals(match.getStart(), 4);
      assertEquals(match.getEnd(), 4);
-     f.findNext("d", false, false);
+     f.findNext("d", false, false, false);
      assertTrue(f.getIdMatches().isEmpty());
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(2));
      assertEquals(match.getStart(), 3);
     * Test finding all matches of a search pattern in a selection group
     */
    @Test(groups = "Functional")
-   public void testFind_findAllInSelection()
+   public void testFindAll_inSelection()
    {
      /*
       * select sequences 2 and 3, columns 4-6 which contains
       * search for 'e' should match two sequence ids and one residue
       */
      Finder f = new Finder(av);
-     f.findAll("e", false, false);
+     f.findAll("e", false, false, false);
      assertEquals(f.getIdMatches().size(), 2);
      assertSame(f.getIdMatches().get(0), al.getSequenceAt(1));
      assertSame(f.getIdMatches().get(1), al.getSequenceAt(2));
      SearchResultsI searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      SearchResultMatchI match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(2));
      assertEquals(match.getStart(), 4);
       * search for 'Q' should match two sequence ids only
       */
      f = new Finder(av);
-     f.findAll("Q", false, false);
+     f.findAll("Q", false, false, false);
      assertEquals(f.getIdMatches().size(), 2);
      assertSame(f.getIdMatches().get(0), al.getSequenceAt(1));
      assertSame(f.getIdMatches().get(1), al.getSequenceAt(2));
       * search for 'I' should match two sequence positions
       */
      Finder f = new Finder(av);
-     f.findAll("I", false, false);
+     f.findAll("I", false, false, false);
      assertTrue(f.getIdMatches().isEmpty());
      SearchResultsI searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 2);
+     assertEquals(searchResults.getCount(), 2);
      SearchResultMatchI match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(0));
      assertEquals(match.getStart(), 16);
    }
  
    /**
-    * Test that find does not report hidden positions
+    * Test that find does not report hidden positions, but does report matches that
+    * span hidden gaps
     */
    @Test(groups = "Functional")
    public void testFind_withHiddenColumns()
       */
  
      /*
-      * hide 2-4 (CD- -BC bcd ---)
+      * hide column 3 only, search for aaa
+      * should find two matches: aa-[-]-aa and trailing aaa
       */
      HiddenColumns hc = new HiddenColumns();
-     hc.hideColumns(2, 4);
+     hc.hideColumns(3, 3);
      al.setHiddenColumns(hc);
+     Finder f = new Finder(av);
+     f.findAll("aaa", false, false, false);
+     SearchResultsI searchResults = f.getSearchResults();
+     assertEquals(searchResults.getCount(), 2);
+     SearchResultMatchI match = searchResults.getResults().get(0);
+     assertSame(match.getSequence(), al.getSequenceAt(3));
+     assertEquals(match.getStart(), 1);
+     assertEquals(match.getEnd(), 3);
+     match = searchResults.getResults().get(1);
+     assertSame(match.getSequence(), al.getSequenceAt(3));
+     assertEquals(match.getStart(), 9);
+     assertEquals(match.getEnd(), 11);
+     /*
+      * hide 2-4 (CD- -BC bcd ---)
+      */
+     hc.hideColumns(2, 4);
  
      /*
       * find all search for D should ignore hidden positions in seq1 and seq3,
       * find the visible D in seq2
       */
-     Finder f = new Finder(av);
-     f.findAll("D", false, false);
-     SearchResultsI searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
-     SearchResultMatchI match = searchResults.getResults().get(0);
+     f = new Finder(av);
+     f.findAll("D", false, false, false);
+     searchResults = f.getSearchResults();
+     assertEquals(searchResults.getCount(), 1);
+     match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(1));
      assertEquals(match.getStart(), 4);
      assertEquals(match.getEnd(), 4);
  
      /*
+      * search for AD should fail although these are now
+      * consecutive in the visible columns
+      */
+     f = new Finder(av);
+     f.findAll("AD", false, false, false);
+     searchResults = f.getSearchResults();
+     assertTrue(searchResults.isEmpty());
+     /*
+      * find all 'aaa' should find both start and end of seq4
+      * (first run includes hidden gaps)
+      */
+     f = new Finder(av);
+     f.findAll("aaa", false, false, false);
+     searchResults = f.getSearchResults();
+     assertEquals(searchResults.getCount(), 2);
+     match = searchResults.getResults().get(0);
+     assertSame(match.getSequence(), al.getSequenceAt(3));
+     assertEquals(match.getStart(), 1);
+     assertEquals(match.getEnd(), 3);
+     match = searchResults.getResults().get(1);
+     assertSame(match.getSequence(), al.getSequenceAt(3));
+     assertEquals(match.getStart(), 9);
+     assertEquals(match.getEnd(), 11);
+     /*
       * hide columns 2-5:
-      * find all 'aaa' should find end of seq4 only
+      * find all 'aaa' should match twice in seq4
+      * (first match partly hidden, second all visible)
       */
      hc.hideColumns(2, 5);
      f = new Finder(av);
-     f.findAll("aaa", false, false);
+     f.findAll("aaa", false, false, false);
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 2);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(3));
+     assertEquals(match.getStart(), 1);
+     assertEquals(match.getEnd(), 3);
+     match = searchResults.getResults().get(1);
+     assertSame(match.getSequence(), al.getSequenceAt(3));
      assertEquals(match.getStart(), 9);
      assertEquals(match.getEnd(), 11);
  
      /*
       * find all 'BE' should not match across hidden columns in seq1
       */
-     f.findAll("BE", false, false);
+     f.findAll("BE", false, false, false);
      assertTrue(f.getSearchResults().isEmpty());
  
      /*
      hc.revealAllHiddenColumns(new ColumnSelection());
      hc.hideColumns(8, 13);
      f = new Finder(av);
-     f.findNext("H", false, false);
+     f.findNext("H", false, false, false);
      searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 1);
+     assertEquals(searchResults.getCount(), 1);
      match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(2));
      assertEquals(match.getStart(), 6);
       * should match seq2/1, seq2/7, not seq3/6
       */
      Finder f = new Finder(av);
-     f.findAll("[AH]", false, false);
+     f.findAll("[AH]", false, false, false);
      SearchResultsI searchResults = f.getSearchResults();
-     assertEquals(searchResults.getSize(), 2);
+     assertEquals(searchResults.getCount(), 2);
      SearchResultMatchI match = searchResults.getResults().get(0);
      assertSame(match.getSequence(), al.getSequenceAt(1));
      assertEquals(match.getStart(), 1);
      assertEquals(match.getStart(), 7);
      assertEquals(match.getEnd(), 7);
    }
+   @Test(groups = "Functional")
+   public void testFind_ignoreHiddenColumns()
+   {
+     /*
+      * 0    5   9
+      * ABCD--EF-GHI
+      * A--BCDefHI
+      * --bcdEFH
+      * aa---aMMMMMaaa
+      */
+     HiddenColumns hc = new HiddenColumns();
+     hc.hideColumns(2, 4);
+     hc.hideColumns(7, 7);
+     al.setHiddenColumns(hc);
+     /*
+      * now have
+      * 015689
+      * AB-E-GHI
+      * A-DeHI
+      * --EF
+      * aaaMMMMaaa
+      */
+     Finder f = new Finder(av);
+     f.findAll("abe", false, false, true); // true = ignore hidden
+     SearchResultsI searchResults = f.getSearchResults();
+     /*
+      * match of seq1 ABE made up of AB and E
+      * note only one match is counted
+      */
+     assertEquals(searchResults.getCount(), 1);
+     assertEquals(searchResults.getResults().size(), 2);
+     SearchResultMatchI match = searchResults.getResults().get(0);
+     assertSame(match.getSequence(), al.getSequenceAt(0));
+     assertEquals(match.getStart(), 8); // A
+     assertEquals(match.getEnd(), 9); // B
+     match = searchResults.getResults().get(1);
+     assertSame(match.getSequence(), al.getSequenceAt(0));
+     assertEquals(match.getStart(), 12); // E
+     assertEquals(match.getEnd(), 12);
+     f = new Finder(av);
+     f.findNext("a.E", false, false, true);
+     searchResults = f.getSearchResults();
+     assertEquals(searchResults.getCount(), 1);
+     assertEquals(searchResults.getResults().size(), 2);
+     match = searchResults.getResults().get(0);
+     assertSame(match.getSequence(), al.getSequenceAt(0));
+     assertEquals(match.getStart(), 8); // A
+     assertEquals(match.getEnd(), 9); // B
+     match = searchResults.getResults().get(1);
+     assertSame(match.getSequence(), al.getSequenceAt(0));
+     assertEquals(match.getStart(), 12); // E
+     assertEquals(match.getEnd(), 12);
+     f.findNext("a.E", false, false, true);
+     searchResults = f.getSearchResults();
+     assertEquals(searchResults.getCount(), 1);
+     assertEquals(searchResults.getResults().size(), 2);
+     match = searchResults.getResults().get(0);
+     assertSame(match.getSequence(), al.getSequenceAt(1));
+     assertEquals(match.getStart(), 1); // a
+     assertEquals(match.getEnd(), 1);
+     match = searchResults.getResults().get(1);
+     assertSame(match.getSequence(), al.getSequenceAt(1));
+     assertEquals(match.getStart(), 4); // D
+     assertEquals(match.getEnd(), 5); // e
+     /*
+      * find all matching across two hidden column regions
+      * note one 'match' is returned as three contiguous matches
+      */
+     f.findAll("BEG", false, false, true);
+     searchResults = f.getSearchResults();
+     assertEquals(searchResults.getCount(), 1);
+     assertEquals(searchResults.getResults().size(), 3);
+     match = searchResults.getResults().get(0);
+     assertSame(match.getSequence(), al.getSequenceAt(0));
+     assertEquals(match.getStart(), 9); // B
+     assertEquals(match.getEnd(), 9);
+     match = searchResults.getResults().get(1);
+     assertSame(match.getSequence(), al.getSequenceAt(0));
+     assertEquals(match.getStart(), 12); // E
+     assertEquals(match.getEnd(), 12);
+     match = searchResults.getResults().get(2);
+     assertSame(match.getSequence(), al.getSequenceAt(0));
+     assertEquals(match.getStart(), 14); // G
+     assertEquals(match.getEnd(), 14);
+     /*
+      * now select columns 0-9 and search for A.*H
+      * this should match in the second sequence (split as 3 matches)
+      * but not the first (as H is outside the selection)
+      */
+     SequenceGroup selection = new SequenceGroup();
+     selection.setStartRes(0);
+     selection.setEndRes(9);
+     al.getSequences().forEach(seq -> selection.addSequence(seq, false));
+     av.setSelectionGroup(selection);
+     f.findAll("A.*H", false, false, true);
+     searchResults = f.getSearchResults();
+     assertEquals(searchResults.getCount(), 1);
+     assertEquals(searchResults.getResults().size(), 3);
+     // match made of contiguous matches A, DE, H
+     match = searchResults.getResults().get(0);
+     assertSame(match.getSequence(), al.getSequenceAt(1));
+     assertEquals(match.getStart(), 1); // A
+     assertEquals(match.getEnd(), 1);
+     match = searchResults.getResults().get(1);
+     assertSame(match.getSequence(), al.getSequenceAt(1));
+     assertEquals(match.getStart(), 4); // D
+     assertEquals(match.getEnd(), 5); // E
+     match = searchResults.getResults().get(2);
+     assertSame(match.getSequence(), al.getSequenceAt(1));
+     assertEquals(match.getStart(), 7); // H (there is no G)
+     assertEquals(match.getEnd(), 7);
+   }
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.datamodel;
  
+ import java.util.Locale;
  import static org.testng.Assert.assertEquals;
  import static org.testng.Assert.assertFalse;
  import static org.testng.Assert.assertTrue;
@@@ -27,8 -29,6 +29,8 @@@
  import jalview.datamodel.ResidueCount.SymbolCounts;
  import jalview.gui.JvOptionPane;
  
 +import java.util.Arrays;
 +
  import org.junit.Assert;
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
@@@ -71,7 -71,6 +73,7 @@@ public class ResidueCountTes
      assertEquals(rc.getCount('N'), 1);
      assertEquals(rc.getCount('?'), 0);
      assertEquals(rc.getCount('-'), 0);
 +    assertEquals(rc.getTotalResidueCount(), 11);
  
      assertFalse(rc.isCountingInts());
      assertFalse(rc.isUsingOtherData());
      assertEquals(rc.getCount(' '), 4);
      assertEquals(rc.getCount('-'), 4);
      assertEquals(rc.getCount('.'), 4);
 +    assertEquals(rc.getTotalResidueCount(), 0);
      assertFalse(rc.isUsingOtherData());
      assertFalse(rc.isCountingInts());
+     
+     rc.set(ResidueCount.GAP_COUNT, Short.MAX_VALUE-2);
+     assertEquals(rc.getGapCount(), Short.MAX_VALUE-2);
+     assertFalse(rc.isCountingInts());
+     rc.addGap();
+     assertEquals(rc.getGapCount(), Short.MAX_VALUE-1);
+     assertFalse(rc.isCountingInts());
+     rc.addGap();
+     assertEquals(rc.getGapCount(), Short.MAX_VALUE);
+     rc.addGap();
+     assertTrue(rc.isCountingInts());
+     assertEquals(rc.getGapCount(), Short.MAX_VALUE+1);
    }
  
    @Test(groups = "Functional")
      assertEquals(rc.getCount('m'), 13);
      assertEquals(rc.getCount('G'), 0);
      assertEquals(rc.getCount('-'), 0);
 +    assertEquals(rc.getTotalResidueCount(), 27);
  
      assertFalse(rc.isCountingInts());
      assertFalse(rc.isUsingOtherData());
      ResidueCount rc = new ResidueCount(false);
      // expected characters (upper or lower case):
      String aas = "ACDEFGHIKLMNPQRSTVWXY";
-     String lower = aas.toLowerCase();
+     String lower = aas.toLowerCase(Locale.ROOT);
      for (int i = 0; i < aas.length(); i++)
      {
        rc.put(aas.charAt(i), i);
      ResidueCount rc = new ResidueCount(true);
      // expected characters (upper or lower case):
      String nucs = "ACGTUN";
-     String lower = nucs.toLowerCase();
+     String lower = nucs.toLowerCase(Locale.ROOT);
      for (int i = 0; i < nucs.length(); i++)
      {
        rc.put(nucs.charAt(i), i);
      assertEquals(rc.getCount('?'), 6);
      assertEquals(rc.getCount('!'), 7);
    }
 +
 +  @Test(groups = "Functional")
 +  public void testConstructor_forSequences()
 +  {
 +    SequenceI seq1 = new Sequence("seq1", "abcde--. FCD");
 +    SequenceI seq2 = new Sequence("seq2", "ab.kKqBd-.");
 +    ResidueCount rc = new ResidueCount(Arrays.asList(seq1, seq2));
 +
 +    assertEquals(rc.getGapCount(), 7);
 +    assertEquals(rc.getTotalResidueCount(), 15); // excludes gaps
 +    assertEquals(rc.getCount('a'), 2);
 +    assertEquals(rc.getCount('A'), 2);
 +    assertEquals(rc.getCount('B'), 3);
 +    assertEquals(rc.getCount('c'), 2);
 +    assertEquals(rc.getCount('D'), 3);
 +    assertEquals(rc.getCount('f'), 1);
 +    assertEquals(rc.getCount('K'), 2);
 +    assertEquals(rc.getCount('Q'), 1);
 +  }
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.datamodel;
  
+ import java.util.Locale;
  import static org.testng.AssertJUnit.assertEquals;
  import static org.testng.AssertJUnit.assertFalse;
  import static org.testng.AssertJUnit.assertNotNull;
@@@ -28,6 -30,14 +30,6 @@@ import static org.testng.AssertJUnit.as
  import static org.testng.AssertJUnit.assertSame;
  import static org.testng.AssertJUnit.assertTrue;
  
 -import jalview.analysis.AlignmentGenerator;
 -import jalview.commands.EditCommand;
 -import jalview.commands.EditCommand.Action;
 -import jalview.datamodel.PDBEntry.Type;
 -import jalview.gui.JvOptionPane;
 -import jalview.util.MapList;
 -import jalview.ws.params.InvalidArgumentException;
 -
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Arrays;
@@@ -41,13 -51,6 +43,13 @@@ import org.testng.annotations.BeforeCla
  import org.testng.annotations.BeforeMethod;
  import org.testng.annotations.Test;
  
 +import jalview.analysis.AlignmentGenerator;
 +import jalview.commands.EditCommand;
 +import jalview.commands.EditCommand.Action;
 +import jalview.datamodel.PDBEntry.Type;
 +import jalview.gui.JvOptionPane;
 +import jalview.util.MapList;
 +
  import junit.extensions.PA;
  
  public class SequenceTest
      assertTrue(sq.isProtein());
    }
  
+   @Test(groups = ("Functional"))
+   public void testIsProteinWithXorNAmbiguityCodes()
+   {
+     // test Protein with N - poly asparagine 
+     assertTrue(new Sequence("prot", "ASDFASDFASDFNNNNNNNNN").isProtein());
+     assertTrue(new Sequence("prot", "NNNNNNNNNNNNNNNNNNNNN").isProtein());
+     // test Protein with X
+     assertTrue(new Sequence("prot", "ASDFASDFASDFXXXXXXXXX").isProtein());
+     // test DNA with X
+     assertFalse(new Sequence("prot", "ACGTACGTACGTXXXXXXXX").isProtein());
+     // test DNA with N
+     assertFalse(new Sequence("prot", "ACGTACGTACGTNNNNNNNN").isProtein());
+     // test RNA with X
+     assertFalse(new Sequence("prot", "ACGUACGUACGUXXXXXXXXX").isProtein());
+     assertFalse(new Sequence("prot", "ACGUACGUACGUNNNNNNNNN").isProtein());
+   }
    @Test(groups = { "Functional" })
    public void testGetAnnotation()
    {
      assertTrue(seq.getAlignmentAnnotations(null, null).isEmpty());
    }
  
+   @Test(groups = { "Functional" })
+   public void testGetAlignmentAnnotations_forCalcIdLabelAndDescription()
+   {
+     addAnnotation("label1", "desc1", "calcId1", 1f);
+     AlignmentAnnotation ann2 = addAnnotation("label2", "desc2", "calcId2",
+             1f);
+     addAnnotation("label2", "desc3", "calcId3", 1f);
+     AlignmentAnnotation ann4 = addAnnotation("label2", "desc3", "calcId2",
+             1f);
+     addAnnotation("label5", "desc3", null, 1f);
+     addAnnotation(null, "desc3", "calcId3", 1f);
+     List<AlignmentAnnotation> anns = seq.getAlignmentAnnotations("calcId2",
+             "label2", "desc3");
+     assertEquals(1, anns.size());
+     assertSame(ann4, anns.get(0));
+     /**
+      * null matching should fail
+      */
+     assertTrue(seq.getAlignmentAnnotations("calcId3", "label2",null).isEmpty());
+     
+     assertTrue(seq.getAlignmentAnnotations("calcId2", "label3",null).isEmpty());
+     assertTrue(seq.getAlignmentAnnotations("calcId3", "label5",null).isEmpty());
+     assertTrue(seq.getAlignmentAnnotations("calcId2", null,null).isEmpty());
+     assertTrue(seq.getAlignmentAnnotations(null, "label3",null).isEmpty());
+     assertTrue(seq.getAlignmentAnnotations(null, null,null).isEmpty());
+   }
    /**
     * Tests for addAlignmentAnnotation. Note this method has the side-effect of
     * setting the sequenceRef on the annotation. Adding the same annotation twice
       * invalid inputs
       */
      assertNull(sq.findPositions(6, 5));
 -    assertNull(sq.findPositions(0, 5));
 -    assertNull(sq.findPositions(-1, 5));
  
      /*
       * all gapped ranges
      assertEquals(new Range(11, 12), sq.findPositions(5, 10)); // DE
      assertEquals(new Range(8, 13), sq.findPositions(1, 13)); // the lot
      assertEquals(new Range(8, 13), sq.findPositions(1, 99));
 +
 +    /**
 +     * now try on a sequence with no gaps
 +     */
 +    sq.createDatasetSequence();
 +    assertEquals(new Range(8, 13),
 +            sq.getDatasetSequence().findPositions(1, 99));
 +    assertEquals(new Range(8, 13),
 +            sq.getDatasetSequence().findPositions(0, 99));
 +
    }
  
    /**
      } catch (IllegalArgumentException e)
      {
        // TODO Jalview error/exception class for raising implementation errors
-       assertTrue(e.getMessage().toLowerCase()
+       assertTrue(e.getMessage().toLowerCase(Locale.ROOT)
                .contains("implementation error"));
      }
      assertTrue(sq.getSequenceFeatures().isEmpty());
      seq.addPDBId(pdbe5);
      assertEquals(4, seq.getAllPDBEntries().size());
      assertSame(pdbe5, seq.getAllPDBEntries().get(3));
+     
+     // add with a fake pdbid 
+     // (models don't have an embedded ID)
+     String realId = "RealIDQ";
+     PDBEntry pdbe6 = new PDBEntry(realId,null,Type.PDB,"real/localpath");
+     PDBEntry pdbe7 = new PDBEntry("RealID/real/localpath","C",Type.MMCIF,"real/localpath");
+     pdbe7.setFakedPDBId(true);
+     seq.addPDBId(pdbe6);
+     assertEquals(5,seq.getAllPDBEntries().size());
+     seq.addPDBId(pdbe7);
+     assertEquals(5,seq.getAllPDBEntries().size());
+     assertFalse(pdbe6.fakedPDBId());
+     assertSame(pdbe6,seq.getAllPDBEntries().get(4));
+     assertEquals("C",pdbe6.getChainCode());
+     assertEquals(realId, pdbe6.getId());
    }
  
    @Test(
      assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
  
    }
+   @Test(groups= {"Functional"})
+   public void testTransferAnnotation() {
+     Sequence origSeq = new Sequence("MYSEQ","THISISASEQ");
+     Sequence toSeq = new Sequence("MYSEQ","THISISASEQ");
+     origSeq.addDBRef(new DBRefEntry("UNIPROT", "0", "Q12345", null, true));
+     toSeq.transferAnnotation(origSeq, null);
+     assertTrue(toSeq.getDBRefs().size()==1);
+     
+     assertTrue(toSeq.getDBRefs().get(0).isCanonical());
+     
+     // check for promotion of non-canonical 
+     // to canonical (e.g. fetch-db-refs on a jalview project pre 2.11.2)
+     toSeq.setDBRefs(null);
+     toSeq.addDBRef(new DBRefEntry("UNIPROT", "0", "Q12345", null, false));
+     toSeq.transferAnnotation(origSeq, null);
+     assertTrue(toSeq.getDBRefs().size()==1);
+     
+     assertTrue("Promotion of non-canonical DBRefEntry failed",toSeq.getDBRefs().get(0).isCanonical());
+     
+     
+   }
  }
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.ext.ensembl;
  
+ import java.util.Locale;
  import static org.testng.AssertJUnit.assertEquals;
  import static org.testng.AssertJUnit.assertFalse;
  import static org.testng.AssertJUnit.assertNull;
@@@ -54,13 -56,13 +56,13 @@@ public class EnsemblCdnaTes
    @BeforeClass(alwaysRun = true)
    public void setUp()
    {
 -    SequenceOntologyFactory.setInstance(new SequenceOntologyLite());
 +    SequenceOntologyFactory.setSequenceOntology(new SequenceOntologyLite());
    }
  
    @AfterClass(alwaysRun = true)
    public void tearDown()
    {
 -    SequenceOntologyFactory.setInstance(null);
 +    SequenceOntologyFactory.setSequenceOntology(null);
    }
  
    /**
      assertTrue(testee.retainFeature(sf, accId));
  
      // test is not case-sensitive
-     assertTrue(testee.retainFeature(sf, accId.toLowerCase()));
+     assertTrue(testee.retainFeature(sf, accId.toLowerCase(Locale.ROOT)));
  
      // feature with wrong parent is not retained
      sf.setValue("Parent", "XYZ");
@@@ -20,6 -20,8 +20,8 @@@
   */
  package jalview.ext.ensembl;
  
+ import java.util.Locale;
  import static org.testng.AssertJUnit.assertEquals;
  import static org.testng.AssertJUnit.assertFalse;
  import static org.testng.AssertJUnit.assertTrue;
@@@ -56,13 -58,13 +58,13 @@@ public class EnsemblGeneTes
    public void setUp()
    {
      Cache.loadProperties("test/jalview/io/testProps.jvprops");
 -    SequenceOntologyFactory.setInstance(new SequenceOntologyLite());
 +    SequenceOntologyFactory.setSequenceOntology(new SequenceOntologyLite());
    }
  
    @AfterClass(alwaysRun = true)
    public void tearDown()
    {
 -    SequenceOntologyFactory.setInstance(null);
 +    SequenceOntologyFactory.setSequenceOntology(null);
    }
  
    /**
      SequenceFeature sf3 = new SequenceFeature("NMD_transcript_variant", "",
              22000, 22500, 0f, null);
      // id matching should not be case-sensitive
-     sf3.setValue("Parent", geneId.toLowerCase());
+     sf3.setValue("Parent", geneId.toLowerCase(Locale.ROOT));
      sf3.setValue("id", "transcript3");
      genomic.addSequenceFeature(sf3);
  
  
    /**
     * Check behaviour of feature colour scheme for EnsemblGene sequences.
-    * Currently coded to display exon and sequence_variant (or sub-types) only,
-    * with sequence_variant in red above exon coloured by label.
+    * Currently coded to hide all except exon and sequence_variant (or sub-types)
+    * only, with sequence_variant in red above exon coloured by label.
     */
    @Test(groups = "Functional")
    public void testGetFeatureColourScheme()
    {
      FeatureSettingsModelI fc = new EnsemblGene().getFeatureColourScheme();
-     assertTrue(fc.isFeatureDisplayed("exon"));
-     assertTrue(fc.isFeatureDisplayed("coding_exon")); // subtype of exon
-     assertTrue(fc.isFeatureDisplayed("sequence_variant"));
-     assertTrue(fc.isFeatureDisplayed("feature_variant")); // subtype
-     assertFalse(fc.isFeatureDisplayed("transcript"));
+     assertFalse(fc.isFeatureDisplayed("exon"));
+     assertFalse(fc.isFeatureHidden("exon"));
+     assertFalse(fc.isFeatureDisplayed("coding_exon")); // subtype of exon
+     assertFalse(fc.isFeatureHidden("coding_exon")); // subtype of exon
+     assertFalse(fc.isFeatureDisplayed("sequence_variant"));
+     assertFalse(fc.isFeatureHidden("sequence_variant"));
+     assertFalse(fc.isFeatureDisplayed("feature_variant")); // subtype
+     assertFalse(fc.isFeatureHidden("feature_variant")); // subtype
+     assertTrue(fc.isFeatureHidden("transcript"));
+     assertTrue(fc.isFeatureHidden("CDS"));
      assertEquals(Color.RED, fc.getFeatureColour("sequence_variant")
              .getColour());
      assertEquals(Color.RED, fc.getFeatureColour("feature_variant")
@@@ -23,50 -23,36 +23,36 @@@ package jalview.ext.jmol
  import static org.testng.Assert.assertEquals;
  import static org.testng.Assert.assertTrue;
  
+ import java.awt.Color;
+ import java.util.HashMap;
+ import java.util.LinkedHashMap;
+ import java.util.List;
+ import java.util.Map;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.Test;
 -
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceI;
  import jalview.gui.AlignFrame;
- import jalview.gui.JvOptionPane;
  import jalview.gui.SequenceRenderer;
  import jalview.schemes.JalviewColourScheme;
+ import jalview.structure.AtomSpecModel;
+ import jalview.structure.StructureCommandI;
  import jalview.structure.StructureMapping;
- import jalview.structure.StructureMappingcommandSet;
  import jalview.structure.StructureSelectionManager;
  
- import java.util.HashMap;
- import org.testng.annotations.BeforeClass;
- import org.testng.annotations.Test;
 +
  public class JmolCommandsTest
  {
+   private JmolCommands testee;
  
    @BeforeClass(alwaysRun = true)
-   public void setUpJvOptionPane()
+   public void setUp()
    {
-     JvOptionPane.setInteractiveMode(false);
-     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
-   }
-   @Test(groups = { "Functional" })
-   public void testGetColourBySequenceCommand_noFeatures()
-   {
-     SequenceI seq1 = new Sequence("seq1", "MHRSQTRALK");
-     SequenceI seq2 = new Sequence("seq2", "MRLEITQSGD");
-     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
-     AlignFrame af = new AlignFrame(al, 800, 500);
-     SequenceRenderer sr = new SequenceRenderer(af.getViewport());
-     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
-     String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-     StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(null);
-     // need some mappings!
-     StructureMappingcommandSet[] commands = JmolCommands
-             .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+     testee = new JmolCommands();
    }
  
    @Test(groups = { "Functional" })
      SequenceRenderer sr = new SequenceRenderer(af.getViewport());
      SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
      String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-     StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(null);
-   
+     StructureSelectionManager ssm = new StructureSelectionManager();
      /*
       * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
       */
-     HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
+     HashMap<Integer, int[]> map = new HashMap<>();
      for (int pos = 1; pos <= seq1.getLength(); pos++)
      {
        map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
      StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
              "B", map, null);
      ssm.addStructureMapping(sm2);
-   
-     StructureMappingcommandSet[] commands = JmolCommands
-             .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
 -
+     String[] commands = testee.colourBySequence(ssm,
+             files,
+             seqs, sr, af.alignPanel);
      assertEquals(commands.length, 2);
-     assertEquals(commands[0].commands.length, 1);
++    assertEquals(commands[0].commands.length, 1); // from 2.12 merge from 2.11.2
  
-     String chainACommand = commands[0].commands[0];
+     String chainACommand = commands[0];
      // M colour is #82827d == (130, 130, 125) (see strand.html help page)
-     assertTrue(chainACommand
-             .contains("select 21:A/1.1;color[130,130,125]")); // first one
+     assertTrue(
+             chainACommand.contains("select 21:A/1.1;color[130,130,125]")); // first
+                                                                            // one
      // H colour is #60609f == (96, 96, 159)
      assertTrue(chainACommand.contains(";select 22:A/1.1;color[96,96,159]"));
      // hidden columns are Gray (128, 128, 128)
      assertTrue(chainACommand
              .contains(";select 23-25:A/1.1;color[128,128,128]"));
      // S and G are both coloured #4949b6 == (73, 73, 182)
-     assertTrue(chainACommand
-             .contains(";select 26-30:A/1.1;color[73,73,182]"));
+     assertTrue(
+             chainACommand.contains(";select 26-30:A/1.1;color[73,73,182]"));
  
-     String chainBCommand = commands[1].commands[0];
+     String chainBCommand = commands[1];
      // M colour is #82827d == (130, 130, 125)
-     assertTrue(chainBCommand
-             .contains("select 21:B/2.1;color[130,130,125]"));
+     assertTrue(
+             chainBCommand.contains("select 21:B/2.1;color[130,130,125]"));
      // V colour is #ffff00 == (255, 255, 0)
-     assertTrue(chainBCommand
- .contains(";select 22:B/2.1;color[255,255,0]"));
+     assertTrue(chainBCommand.contains(";select 22:B/2.1;color[255,255,0]"));
      // hidden columns are Gray (128, 128, 128)
      assertTrue(chainBCommand
              .contains(";select 23-25:B/2.1;color[128,128,128]"));
      // S and G are both coloured #4949b6 == (73, 73, 182)
-     assertTrue(chainBCommand
-             .contains(";select 26-30:B/2.1;color[73,73,182]"));
+     assertTrue(
+             chainBCommand.contains(";select 26-30:B/2.1;color[73,73,182]"));
+   }
+   @Test(groups = "Functional")
+   public void testGetAtomSpec()
+   {
+     AtomSpecModel model = new AtomSpecModel();
+     assertEquals(testee.getAtomSpec(model, false), "");
+     model.addRange("1", 2, 4, "A");
+     assertEquals(testee.getAtomSpec(model, false), "2-4:A/1.1");
+     model.addRange("1", 8, 8, "A");
+     assertEquals(testee.getAtomSpec(model, false), "2-4:A/1.1|8:A/1.1");
+     model.addRange("1", 5, 7, "B");
+     assertEquals(testee.getAtomSpec(model, false),
+             "2-4:A/1.1|8:A/1.1|5-7:B/1.1");
+     model.addRange("1", 3, 5, "A");
+     assertEquals(testee.getAtomSpec(model, false),
+             "2-5:A/1.1|8:A/1.1|5-7:B/1.1");
+     model.addRange("2", 1, 4, "B");
+     assertEquals(testee.getAtomSpec(model, false),
+             "2-5:A/1.1|8:A/1.1|5-7:B/1.1|1-4:B/2.1");
+     model.addRange("2", 5, 9, "C");
+     assertEquals(testee.getAtomSpec(model, false),
+             "2-5:A/1.1|8:A/1.1|5-7:B/1.1|1-4:B/2.1|5-9:C/2.1");
+     model.addRange("1", 8, 10, "B");
+     assertEquals(testee.getAtomSpec(model, false),
+             "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|5-9:C/2.1");
+     model.addRange("1", 8, 9, "B");
+     assertEquals(testee.getAtomSpec(model, false),
+             "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|5-9:C/2.1");
+     model.addRange("2", 3, 10, "C"); // subsumes 5-9
+     assertEquals(testee.getAtomSpec(model, false),
+             "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|3-10:C/2.1");
+     model.addRange("5", 25, 35, " ");
+     assertEquals(testee.getAtomSpec(model, false),
+             "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|3-10:C/2.1|25-35:/5.1");
+   }
+   @Test(groups = { "Functional" })
+   public void testColourBySequence()
+   {
+     Map<Object, AtomSpecModel> map = new LinkedHashMap<>();
+     JmolCommands.addAtomSpecRange(map, Color.blue, "1", 2, 5, "A");
+     JmolCommands.addAtomSpecRange(map, Color.blue, "1", 7, 7, "B");
+     JmolCommands.addAtomSpecRange(map, Color.blue, "1", 9, 23, "A");
+     JmolCommands.addAtomSpecRange(map, Color.blue, "2", 1, 1, "A");
+     JmolCommands.addAtomSpecRange(map, Color.blue, "2", 4, 7, "B");
+     JmolCommands.addAtomSpecRange(map, Color.yellow, "2", 8, 8, "A");
+     JmolCommands.addAtomSpecRange(map, Color.yellow, "2", 3, 5, "A");
+     JmolCommands.addAtomSpecRange(map, Color.red, "1", 3, 5, "A");
+     JmolCommands.addAtomSpecRange(map, Color.red, "1", 6, 9, "A");
+     // Colours should appear in the Jmol command in the order in which
+     // they were added; within colour, by model, by chain, ranges in start order
+     List<StructureCommandI> commands = testee.colourBySequence(map);
+     assertEquals(commands.size(), 1);
+     String expected1 = "select 2-5:A/1.1|9-23:A/1.1|7:B/1.1|1:A/2.1|4-7:B/2.1;color[0,0,255]";
+     String expected2 = "select 3-5:A/2.1|8:A/2.1;color[255,255,0]";
+     String expected3 = "select 3-9:A/1.1;color[255,0,0]";
+     assertEquals(commands.get(0).getCommand(),
+             expected1 + ";" + expected2 + ";" + expected3);
+   }
+   @Test(groups = { "Functional" })
+   public void testSuperposeStructures()
+   {
+     AtomSpecModel ref = new AtomSpecModel();
+     ref.addRange("1", 12, 14, "A");
+     ref.addRange("1", 18, 18, "B");
+     ref.addRange("1", 22, 23, "B");
+     AtomSpecModel toAlign = new AtomSpecModel();
+     toAlign.addRange("2", 15, 17, "B");
+     toAlign.addRange("2", 20, 21, "B");
+     toAlign.addRange("2", 22, 22, "C");
+     List<StructureCommandI> command = testee.superposeStructures(ref,
+             toAlign);
+     assertEquals(command.size(), 1);
+     String refSpec = "12-14:A/1.1|18:B/1.1|22-23:B/1.1";
+     String toAlignSpec = "15-17:B/2.1|20-21:B/2.1|22:C/2.1";
+     String expected = String.format(
+             "compare {2.1} {1.1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS {%s}{%s} ROTATE TRANSLATE ;select %s|%s;cartoons",
+             toAlignSpec, refSpec, toAlignSpec, refSpec);
+     assertEquals(command.get(0).getCommand(), expected);
+   }
+   @Test(groups = "Functional")
+   public void testGetModelStartNo()
+   {
+     assertEquals(testee.getModelStartNo(), 1);
+   }
+   @Test(groups = "Functional")
+   public void testColourByChain()
+   {
+     StructureCommandI cmd = testee.colourByChain();
+     assertEquals(cmd.getCommand(), "select *;color chain");
+   }
+   @Test(groups = "Functional")
+   public void testColourByCharge()
+   {
+     List<StructureCommandI> cmds = testee.colourByCharge();
+     assertEquals(cmds.size(), 1);
+     assertEquals(cmds.get(0).getCommand(),
+             "select *;color white;select ASP,GLU;color red;"
+                     + "select LYS,ARG;color blue;select CYS;color yellow");
+   }
+   @Test(groups = "Functional")
+   public void testSetBackgroundColour()
+   {
+     StructureCommandI cmd = testee.setBackgroundColour(Color.PINK);
+     assertEquals(cmd.getCommand(), "background [255,175,175]");
+   }
+   @Test(groups = "Functional")
+   public void testFocusView()
+   {
+     StructureCommandI cmd = testee.focusView();
+     assertEquals(cmd.getCommand(), "zoom 0");
+   }
+   @Test(groups = "Functional")
+   public void testSaveSession()
+   {
+     StructureCommandI cmd = testee.saveSession("/some/filepath");
+     assertEquals(cmd.getCommand(), "write STATE \"/some/filepath\"");
+   }
+   @Test(groups = "Functional")
+   public void testShowBackbone()
+   {
+     List<StructureCommandI> cmds = testee.showBackbone();
+     assertEquals(cmds.size(), 1);
+     assertEquals(cmds.get(0).getCommand(),
+             "select *; cartoons off; backbone");
+   }
+   @Test(groups = "Functional")
+   public void testLoadFile()
+   {
+     StructureCommandI cmd = testee.loadFile("/some/filepath");
+     assertEquals(cmd.getCommand(), "load FILES \"/some/filepath\"");
+     // single backslash gets escaped to double
+     cmd = testee.loadFile("\\some\\filepath");
+     assertEquals(cmd.getCommand(), "load FILES \"\\\\some\\\\filepath\"");
+   }
+   @Test(groups = "Functional")
+   public void testOpenSession()
+   {
+     StructureCommandI cmd = testee.openSession("/some/filepath");
+     assertEquals(cmd.getCommand(), "load FILES \"/some/filepath\"");
+     // single backslash gets escaped to double
+     cmd = testee.openSession("\\some\\filepath");
+     assertEquals(cmd.getCommand(), "load FILES \"\\\\some\\\\filepath\"");
    }
  }
@@@ -98,11 -98,11 +98,11 @@@ public class JmolParserTes
    public void setUp()
    {
      Cache.loadProperties("test/jalview/io/testProps.jvprops");
 -    Cache.applicationProperties.setProperty("STRUCT_FROM_PDB",
 +    Cache.setPropertyNoSave("STRUCT_FROM_PDB",
              Boolean.TRUE.toString());
 -    Cache.applicationProperties.setProperty("ADD_TEMPFACT_ANN",
 +    Cache.setPropertyNoSave("ADD_TEMPFACT_ANN",
              Boolean.FALSE.toString());
 -    Cache.applicationProperties.setProperty("ADD_SS_ANN",
 +    Cache.setPropertyNoSave("ADD_SS_ANN",
              Boolean.TRUE.toString());
      StructureImportSettings.setDefaultStructureFileFormat("PDB");
      StructureImportSettings
      assertEquals(structureData.getId(), "localstruct");
      assertNotNull(structureData.getSeqs());
      /*
+      * local structures have a fake ID
+      */
+     assertTrue(structureData.getSeqs().get(0).getAllPDBEntries().get(0).fakedPDBId());
+     /*
       * the ID is also the group for features derived from structure data 
       */
      String featureGroup = structureData.getSeqs().get(0)
@@@ -23,19 -23,6 +23,7 @@@ package jalview.ext.rbvi.chimera
  import static org.testng.Assert.assertEquals;
  import static org.testng.Assert.assertTrue;
  
- import jalview.datamodel.Alignment;
- import jalview.datamodel.AlignmentI;
- import jalview.datamodel.ColumnSelection;
- import jalview.datamodel.Sequence;
- import jalview.datamodel.SequenceI;
- import jalview.gui.AlignFrame;
- import jalview.gui.JvOptionPane;
- import jalview.gui.SequenceRenderer;
- import jalview.schemes.JalviewColourScheme;
- import jalview.structure.StructureMapping;
- import jalview.structure.StructureMappingcommandSet;
- import jalview.structure.StructureSelectionManager;
 +
  import java.awt.Color;
  import java.util.HashMap;
  import java.util.LinkedHashMap;
@@@ -45,176 -32,365 +33,362 @@@ import java.util.Map
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
  
+ import jalview.structure.AtomSpecModel;
+ import jalview.structure.StructureCommand;
+ import jalview.structure.StructureCommandI;
 -
  public class ChimeraCommandsTest
  {
+   private ChimeraCommands testee;
  
    @BeforeClass(alwaysRun = true)
-   public void setUpJvOptionPane()
+   public void setUp()
    {
-     JvOptionPane.setInteractiveMode(false);
-     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+     testee = new ChimeraCommands();
    }
  
    @Test(groups = { "Functional" })
-   public void testBuildColourCommands()
+   public void testColourBySequence()
    {
  
-     Map<Object, AtomSpecModel> map = new LinkedHashMap<Object, AtomSpecModel>();
-     ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 2, 5, "A");
-     ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 7, 7, "B");
-     ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 9, 23, "A");
-     ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 1, 1, "A");
-     ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 4, 7, "B");
-     ChimeraCommands.addAtomSpecRange(map, Color.yellow, 1, 8, 8, "A");
-     ChimeraCommands.addAtomSpecRange(map, Color.yellow, 1, 3, 5, "A");
-     ChimeraCommands.addAtomSpecRange(map, Color.red, 0, 3, 5, "A");
-     ChimeraCommands.addAtomSpecRange(map, Color.red, 0, 6, 9, "A");
+     Map<Object, AtomSpecModel> map = new LinkedHashMap<>();
+     ChimeraCommands.addAtomSpecRange(map, Color.blue, "0", 2, 5, "A");
+     ChimeraCommands.addAtomSpecRange(map, Color.blue, "0", 7, 7, "B");
+     ChimeraCommands.addAtomSpecRange(map, Color.blue, "0", 9, 23, "A");
+     ChimeraCommands.addAtomSpecRange(map, Color.blue, "1", 1, 1, "A");
+     ChimeraCommands.addAtomSpecRange(map, Color.blue, "1", 4, 7, "B");
+     ChimeraCommands.addAtomSpecRange(map, Color.yellow, "1", 8, 8, "A");
+     ChimeraCommands.addAtomSpecRange(map, Color.yellow, "1", 3, 5, "A");
+     ChimeraCommands.addAtomSpecRange(map, Color.red, "0", 3, 5, "A");
+     ChimeraCommands.addAtomSpecRange(map, Color.red, "0", 6, 9, "A");
  
      // Colours should appear in the Chimera command in the order in which
      // they were added; within colour, by model, by chain, ranges in start order
-     String command = ChimeraCommands.buildColourCommands(map).get(0);
-     assertEquals(
-             command,
-             "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:3-5.A,8.A; color #ff0000 #0:3-9.A");
+     List<StructureCommandI> commands = testee.colourBySequence(map);
+     assertEquals(commands.size(), 1);
+     assertEquals(commands.get(0).getCommand(),
+             "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B;color #ffff00 #1:3-5.A,8.A;color #ff0000 #0:3-9.A");
    }
  
    @Test(groups = { "Functional" })
-   public void testBuildSetAttributeCommands()
+   public void testSetAttributes()
    {
      /*
       * make a map of { featureType, {featureValue, {residue range specification } } }
       */
-     Map<String, Map<Object, AtomSpecModel>> featuresMap = new LinkedHashMap<String, Map<Object, AtomSpecModel>>();
-     Map<Object, AtomSpecModel> featureValues = new HashMap<Object, AtomSpecModel>();
-     
+     Map<String, Map<Object, AtomSpecModel>> featuresMap = new LinkedHashMap<>();
+     Map<Object, AtomSpecModel> featureValues = new HashMap<>();
 -
      /*
       * start with just one feature/value...
       */
      featuresMap.put("chain", featureValues);
-     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 8, 20, "A");
-   
-     List<String> commands = ChimeraCommands
-             .buildSetAttributeCommands(featuresMap);
+     ChimeraCommands.addAtomSpecRange(featureValues, "X", "0", 8, 20, "A");
+     List<StructureCommandI> commands = testee.setAttributes(featuresMap);
      assertEquals(1, commands.size());
  
      /*
       * feature name gets a jv_ namespace prefix
       * feature value is quoted in case it contains spaces
       */
-     assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:8-20.A");
+     assertEquals(commands.get(0).getCommand(),
+             "setattr res jv_chain 'X' #0:8-20.A");
  
      // add same feature value, overlapping range
-     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 3, 9, "A");
+     ChimeraCommands.addAtomSpecRange(featureValues, "X", "0", 3, 9, "A");
      // same feature value, contiguous range
-     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "A");
-     commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+     ChimeraCommands.addAtomSpecRange(featureValues, "X", "0", 21, 25, "A");
+     commands = testee.setAttributes(featuresMap);
      assertEquals(1, commands.size());
-     assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:3-25.A");
+     assertEquals(commands.get(0).getCommand(),
+             "setattr res jv_chain 'X' #0:3-25.A");
  
      // same feature value and model, different chain
-     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "B");
+     ChimeraCommands.addAtomSpecRange(featureValues, "X", "0", 21, 25, "B");
      // same feature value and chain, different model
-     ChimeraCommands.addAtomSpecRange(featureValues, "X", 1, 26, 30, "A");
-     commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+     ChimeraCommands.addAtomSpecRange(featureValues, "X", "1", 26, 30, "A");
+     commands = testee.setAttributes(featuresMap);
      assertEquals(1, commands.size());
-     assertEquals(commands.get(0),
-             "setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A");
+     String expected1 = "setattr res jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A";
+     assertEquals(commands.get(0).getCommand(), expected1);
  
      // same feature, different value
-     ChimeraCommands.addAtomSpecRange(featureValues, "Y", 0, 40, 50, "A");
-     commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
+     ChimeraCommands.addAtomSpecRange(featureValues, "Y", "0", 40, 50, "A");
+     commands = testee.setAttributes(featuresMap);
      assertEquals(2, commands.size());
      // commands are ordered by feature type but not by value
-     // so use contains to test for the expected command:
-     assertTrue(commands
-             .contains("setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A"));
-     assertTrue(commands.contains("setattr r jv_chain 'Y' #0:40-50.A"));
+     // so test for the expected command in either order
+     String cmd1 = commands.get(0).getCommand();
+     String cmd2 = commands.get(1).getCommand();
+     assertTrue(cmd1.equals(expected1) || cmd2.equals(expected1));
+     String expected2 = "setattr res jv_chain 'Y' #0:40-50.A";
+     assertTrue(cmd1.equals(expected2) || cmd2.equals(expected2));
  
      featuresMap.clear();
      featureValues.clear();
      featuresMap.put("side-chain binding!", featureValues);
      ChimeraCommands.addAtomSpecRange(featureValues,
-             "<html>metal <a href=\"http:a.b.c/x\"> 'ion!", 0, 7, 15,
-             "A");
+             "<html>metal <a href=\"http:a.b.c/x\"> 'ion!", "0", 7, 15, "A");
      // feature names are sanitised to change non-alphanumeric to underscore
      // feature values are sanitised to encode single quote characters
-     commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
-     assertTrue(commands
-             .contains("setattr r jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A"));
+     commands = testee.setAttributes(featuresMap);
+     assertEquals(commands.size(), 1);
+     String expected3 = "setattr res jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A";
+     assertTrue(commands.get(0).getCommand().equals(expected3));
    }
  
    /**
     * Tests for the method that prefixes and sanitises a feature name so it can
-    * be used as a valid, namespaced attribute name in Chimera
+    * be used as a valid, namespaced attribute name in Chimera or PyMol
     */
    @Test(groups = { "Functional" })
    public void testMakeAttributeName()
    {
-     assertEquals(ChimeraCommands.makeAttributeName(null), "jv_");
-     assertEquals(ChimeraCommands.makeAttributeName(""), "jv_");
-     assertEquals(ChimeraCommands.makeAttributeName("helix"), "jv_helix");
-     assertEquals(ChimeraCommands.makeAttributeName("Hello World 24"),
+     assertEquals(testee.makeAttributeName(null), "jv_");
+     assertEquals(testee.makeAttributeName(""), "jv_");
+     assertEquals(testee.makeAttributeName("helix"), "jv_helix");
+     assertEquals(testee.makeAttributeName(
+             "Hello World 24"),
              "jv_Hello_World_24");
-     assertEquals(
-             ChimeraCommands.makeAttributeName("!this is-a_very*{odd(name"),
+     assertEquals(testee.makeAttributeName(
+             "!this is-a_very*{odd(name"),
              "jv__this_is_a_very__odd_name");
      // name ending in color gets underscore appended
-     assertEquals(ChimeraCommands.makeAttributeName("helixColor"),
-             "jv_helixColor_");
+     assertEquals(testee.makeAttributeName("helixColor"), "jv_helixColor_");
+   }
+   @Test(groups = "Functional")
+   public void testGetAtomSpec()
+   {
+     AtomSpecModel model = new AtomSpecModel();
+     assertEquals(testee.getAtomSpec(model, false), "");
+     model.addRange("1", 2, 4, "A");
+     assertEquals(testee.getAtomSpec(model, false), "#1:2-4.A");
+     model.addRange("1", 8, 8, "A");
+     assertEquals(testee.getAtomSpec(model, false), "#1:2-4.A,8.A");
+     model.addRange("1", 5, 7, "B");
+     assertEquals(testee.getAtomSpec(model, false), "#1:2-4.A,8.A,5-7.B");
+     model.addRange("1", 3, 5, "A");
+     assertEquals(testee.getAtomSpec(model, false), "#1:2-5.A,8.A,5-7.B");
+     model.addRange("0", 1, 4, "B");
+     assertEquals(testee.getAtomSpec(model, false),
+             "#0:1-4.B|#1:2-5.A,8.A,5-7.B");
+     model.addRange("0", 5, 9, "C");
+     assertEquals(testee.getAtomSpec(model, false),
+             "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-7.B");
+     model.addRange("1", 8, 10, "B");
+     assertEquals(testee.getAtomSpec(model, false),
+             "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
+     model.addRange("1", 8, 9, "B");
+     assertEquals(testee.getAtomSpec(model, false),
+             "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
+     model.addRange("0", 3, 10, "C"); // subsumes 5-9
+     assertEquals(testee.getAtomSpec(model, false),
+             "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B");
+     model.addRange("5", 25, 35, " ");
+     assertEquals(testee.getAtomSpec(model, false),
+             "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B|#5:25-35.");
    }
  
    @Test(groups = { "Functional" })
-   public void testGetColourBySequenceCommands_hiddenColumns()
+   public void testSuperposeStructures()
    {
-     /*
-      * load these sequences, coloured by Strand propensity,
-      * with columns 2-4 hidden
-      */
-     SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
-     SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
-     AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
-     AlignFrame af = new AlignFrame(al, 800, 500);
-     af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
-     ColumnSelection cs = new ColumnSelection();
-     cs.addElement(2);
-     cs.addElement(3);
-     cs.addElement(4);
-     af.getViewport().setColumnSelection(cs);
-     af.hideSelColumns_actionPerformed(null);
-     SequenceRenderer sr = new SequenceRenderer(af.getViewport());
-     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
-     String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-     StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(null);
+     AtomSpecModel ref = new AtomSpecModel();
+     ref.addRange("1", 12, 14, "A");
+     ref.addRange("1", 18, 18, "B");
+     ref.addRange("1", 22, 23, "B");
+     AtomSpecModel toAlign = new AtomSpecModel();
+     toAlign.addRange("2", 15, 17, "B");
+     toAlign.addRange("2", 20, 21, "B");
+     toAlign.addRange("2", 22, 22, "C");
+     List<StructureCommandI> command = testee.superposeStructures(ref,
+             toAlign);
+     // qualifier to restrict match to CA and no altlocs
+     String carbonAlphas = "@CA&~@.B-Z&~@.2-9";
+     String refSpec = "#1:12-14.A,18.B,22-23.B";
+     String toAlignSpec = "#2:15-17.B,20-21.B,22.C";
+     String expected = String.format("match %s%s %s%s; ribbon %s|%s; focus",
+             toAlignSpec, carbonAlphas, refSpec, carbonAlphas, toAlignSpec,
+             refSpec);
+     assertEquals(command.get(0).getCommand(), expected);
+   }
  
-     /*
-      * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
-      */
-     HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
-     for (int pos = 1; pos <= seq1.getLength(); pos++)
-     {
-       map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
-     }
-     StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
-             "A", map, null);
-     ssm.addStructureMapping(sm1);
-     StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
-             "B", map, null);
-     ssm.addStructureMapping(sm2);
-     StructureMappingcommandSet[] commands = ChimeraCommands
-             .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
-     assertEquals(1, commands.length);
-     assertEquals(1, commands[0].commands.length);
-     String theCommand = commands[0].commands[0];
-     // M colour is #82827d (see strand.html help page)
-     assertTrue(theCommand.contains("color #82827d #0:21.A|#1:21.B"));
-     // H colour is #60609f
-     assertTrue(theCommand.contains("color #60609f #0:22.A"));
-     // V colour is #ffff00
-     assertTrue(theCommand.contains("color #ffff00 #1:22.B"));
-     // hidden columns are Gray (128, 128, 128)
-     assertTrue(theCommand.contains("color #808080 #0:23-25.A|#1:23-25.B"));
-     // S and G are both coloured #4949b6
-     assertTrue(theCommand.contains("color #4949b6 #0:26-30.A|#1:26-30.B"));
+   @Test(groups = "Functional")
+   public void testGetAtomSpec_alphaOnly()
+   {
+     AtomSpecModel model = new AtomSpecModel();
+     assertEquals(testee.getAtomSpec(model, true), "");
+     model.addRange("1", 2, 4, "A");
+     assertEquals(testee.getAtomSpec(model, true),
+             "#1:2-4.A@CA&~@.B-Z&~@.2-9");
+     model.addRange("1", 8, 8, "A");
+     assertEquals(testee.getAtomSpec(model, true),
+             "#1:2-4.A,8.A@CA&~@.B-Z&~@.2-9");
+     model.addRange("1", 5, 7, "B");
+     assertEquals(testee.getAtomSpec(model, true),
+             "#1:2-4.A,8.A,5-7.B@CA&~@.B-Z&~@.2-9");
+     model.addRange("1", 3, 5, "A");
+     assertEquals(testee.getAtomSpec(model, true),
+             "#1:2-5.A,8.A,5-7.B@CA&~@.B-Z&~@.2-9");
+     model.addRange("0", 1, 4, "B");
+     assertEquals(testee.getAtomSpec(model, true),
+             "#0:1-4.B@CA&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-7.B@CA&~@.B-Z&~@.2-9");
+     model.addRange("0", 5, 9, "C");
+     assertEquals(testee.getAtomSpec(model, true),
+             "#0:1-4.B,5-9.C@CA&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-7.B@CA&~@.B-Z&~@.2-9");
+     model.addRange("1", 8, 10, "B");
+     assertEquals(testee.getAtomSpec(model, true),
+             "#0:1-4.B,5-9.C@CA&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA&~@.B-Z&~@.2-9");
+     model.addRange("1", 8, 9, "B");
+     assertEquals(testee.getAtomSpec(model, true),
+             "#0:1-4.B,5-9.C@CA&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA&~@.B-Z&~@.2-9");
+     model.addRange("0", 3, 10, "C"); // subsumes 5-9
+     assertEquals(testee.getAtomSpec(model, true),
+             "#0:1-4.B,3-10.C@CA&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA&~@.B-Z&~@.2-9");
+     model.addRange("5", 25, 35, " "); // empty chain code
+     assertEquals(testee.getAtomSpec(model, true),
+             "#0:1-4.B,3-10.C@CA&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA&~@.B-Z&~@.2-9|#5:25-35.@CA&~@.B-Z&~@.2-9");
+   }
+   @Test(groups = "Functional")
+   public void testGetModelStartNo()
+   {
+     assertEquals(testee.getModelStartNo(), 0);
+   }
+   @Test(groups = "Functional")
+   public void testGetResidueSpec()
+   {
+     assertEquals(testee.getResidueSpec("ALA"), "::ALA");
+   }
+   @Test(groups = "Functional")
+   public void testShowBackbone()
+   {
+     List<StructureCommandI> cmds = testee.showBackbone();
+     assertEquals(cmds.size(), 1);
+     assertEquals(cmds.get(0).getCommand(),
+             "~display all;~ribbon;chain @CA|P");
+   }
+   @Test(groups = "Functional")
+   public void testOpenCommandFile()
+   {
+     assertEquals(testee.openCommandFile("nowhere").getCommand(),
+             "open cmd:nowhere");
+   }
+   @Test(groups = "Functional")
+   public void testSaveSession()
+   {
+     assertEquals(testee.saveSession("somewhere").getCommand(),
+             "save somewhere");
+   }
+   @Test(groups = "Functional")
+   public void testColourByChain()
+   {
+     assertEquals(testee.colourByChain().getCommand(), "rainbow chain");
+   }
+   @Test(groups = { "Functional" })
+   public void testSetBackgroundColour()
+   {
+     StructureCommandI cmd = testee.setBackgroundColour(Color.PINK);
+     assertEquals(cmd.getCommand(), "set bgColor #ffafaf");
+   }
+   @Test(groups = { "Functional" })
+   public void testLoadFile()
+   {
+     StructureCommandI cmd = testee.loadFile("/some/filepath");
+     assertEquals(cmd.getCommand(), "open /some/filepath");
+   }
+   @Test(groups = { "Functional" })
+   public void testOpenSession()
+   {
+     StructureCommandI cmd = testee.openSession("/some/filepath");
+     assertEquals(cmd.getCommand(), "open chimera:/some/filepath");
+   }
+   @Test(groups = "Functional")
+   public void testColourByCharge()
+   {
+     List<StructureCommandI> cmds = testee.colourByCharge();
+     assertEquals(cmds.size(), 1);
+     assertEquals(cmds.get(0)
+             .getCommand(),
+             "color white;color red ::ASP,GLU;color blue ::LYS,ARG;color yellow ::CYS");
+   }
+   @Test(groups = "Functional")
+   public void testGetColourCommand()
+   {
+     assertEquals(testee.colourResidues("something", Color.MAGENTA)
+             .getCommand(),
+             "color #ff00ff something");
+   }
+   @Test(groups = "Functional")
+   public void testFocusView()
+   {
+     assertEquals(testee.focusView().getCommand(), "focus");
+   }
+   @Test(groups = "Functional")
+   public void testSetAttribute()
+   {
+     AtomSpecModel model = new AtomSpecModel();
+     model.addRange("1", 89, 92, "A");
+     model.addRange("2", 12, 20, "B");
+     model.addRange("2", 8, 9, "B");
+     assertEquals(testee.setAttribute("jv_kd", "27.3", model).getCommand(),
+             "setattr res jv_kd '27.3' #1:89-92.A|#2:8-9.B,12-20.B");
+   }
+   @Test(groups = "Functional")
+   public void testCloseViewer()
+   {
+     assertEquals(testee.closeViewer(), new StructureCommand("stop really"));
+   }
+   @Test(groups = "Functional")
+   public void testGetSelectedResidues()
+   {
+     assertEquals(testee.getSelectedResidues(),
+             new StructureCommand("list selection level residue"));
+   }
+   @Test(groups = "Functional")
+   public void testListResidueAttributes()
+   {
+     assertEquals(testee.listResidueAttributes(),
+             new StructureCommand("list resattr"));
+   }
+   @Test(groups = "Functional")
+   public void testGetResidueAttributes()
+   {
+     assertEquals(testee.getResidueAttributes("binding site"),
+             new StructureCommand("list residues attr 'binding site'"));
+   }
+   @Test(groups = "Functional")
+   public void testStartNotifications()
+   {
+     List<StructureCommandI> cmds = testee.startNotifications("to here");
+     assertEquals(cmds.size(), 2);
+     assertEquals(cmds.get(0), new StructureCommand("listen start models url to here"));
+     assertEquals(cmds.get(1), new StructureCommand("listen start select prefix SelectionChanged url to here"));
+   }
+   @Test(groups = "Functional")
+   public void testStopNotifications()
+   {
+     List<StructureCommandI> cmds = testee.stopNotifications();
+     assertEquals(cmds.size(), 2);
+     assertEquals(cmds.get(0), new StructureCommand("listen stop models"));
+     assertEquals(cmds.get(1), new StructureCommand("listen stop selection"));
    }
  }
@@@ -25,12 -25,24 +25,24 @@@ import static org.testng.Assert.assertF
  import static org.testng.Assert.assertNotNull;
  import static org.testng.Assert.assertTrue;
  
+ import java.io.File;
+ import java.io.IOException;
+ import java.util.List;
+ import java.util.Vector;
+ import org.testng.annotations.AfterClass;
+ import org.testng.annotations.AfterMethod;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.Test;
  import jalview.api.FeatureRenderer;
  import jalview.api.structures.JalviewStructureDisplayI;
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
+ import jalview.datamodel.AlignmentI;
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.gui.AlignFrame;
@@@ -41,22 -53,13 +53,13 @@@ import jalview.gui.StructureViewer
  import jalview.gui.StructureViewer.ViewerType;
  import jalview.io.DataSourceType;
  import jalview.io.FileLoader;
+ import jalview.structure.StructureCommand;
  import jalview.structure.StructureMapping;
  import jalview.structure.StructureSelectionManager;
  import jalview.ws.sifts.SiftsClient;
  import jalview.ws.sifts.SiftsException;
  import jalview.ws.sifts.SiftsSettings;
  
- import java.io.File;
- import java.io.IOException;
- import java.util.List;
- import java.util.Vector;
- import org.testng.annotations.AfterClass;
- import org.testng.annotations.AfterMethod;
- import org.testng.annotations.BeforeClass;
- import org.testng.annotations.Test;
  @Test(singleThreaded = true)
  public class JalviewChimeraView
  {
@@@ -95,7 -98,7 +98,7 @@@
    @AfterClass(alwaysRun = true)
    public static void tearDownAfterClass() throws Exception
    {
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
    }
  
    @AfterMethod(alwaysRun = true)
    @Test(groups = { "External" })
    public void testSingleSeqViewChimera()
    {
      String inFile = "examples/1gaq.txt";
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
              DataSourceType.FILE);
      /*
       * Wait for viewer load thread to complete
       */
-     while (!binding.isFinishedInit())
+     do
      {
        try
        {
        } catch (InterruptedException e)
        {
        }
-     }
+     } while (!binding.isFinishedInit()  ||  !chimeraViewer.isVisible());
  
-     assertTrue(binding.isChimeraRunning(), "Failed to start Chimera");
+     assertTrue(binding.isViewerRunning(), "Failed to start Chimera");
  
      assertEquals(chimeraViewer.getBinding().getPdbCount(), 1);
+     assertTrue(chimeraViewer.hasViewerActionsMenu());
+     // now add another sequence and bind to view
+     // 
+     AlignmentI al = af.getViewport().getAlignment();
+     PDBEntry xpdb = al.getSequenceAt(0).getPDBEntry("1GAQ");
+     sq = new Sequence("1GAQ", al.getSequenceAt(0).getSequence(25, 95).toString());
+     al.addSequence(sq);
+     structureViewer.viewStructures(new PDBEntry[] { xpdb }, new SequenceI[] { sq }, af.getCurrentView().getAlignPanel());
+     /*
+      * Wait for viewer load thread to complete
+      */
+     do 
+     {
+       try {
+         Thread.sleep(1500);
+       } catch (InterruptedException q) {};
+     } while (!binding.isLoadingFinished());
+     
+     // still just one PDB structure shown
+     assertEquals(chimeraViewer.getBinding().getPdbCount(), 1);
+     // and the viewer action menu should still be visible
+     assertTrue(chimeraViewer.hasViewerActionsMenu());
      chimeraViewer.closeViewer(true);
      chimeraViewer = null;
      return;
        }
      } while (!binding.isFinishedInit());
  
-     assertTrue(binding.isChimeraRunning(), "Failed to launch Chimera");
+     assertTrue(binding.isViewerRunning(), "Failed to launch Chimera");
  
      assertEquals(binding.getPdbCount(), 1);
  
      /*
       * ask Chimera for its residue attribute names
       */
-     List<String> reply = binding.sendChimeraCommand("list resattr", true);
+     List<String> reply = binding
+             .executeCommand(new StructureCommand("list resattr"), true);
      // prefixed and sanitised attribute names for Jalview features:
      assertTrue(reply.contains("resattr jv_domain"));
      assertTrue(reply.contains("resattr jv_metal_ion_binding_site"));
       * ask Chimera for residues with an attribute
       * 91 and 96 on sequence --> residues 40 and 45 on chains A and B
       */
-     reply = binding.sendChimeraCommand(
-             "list resi att jv_metal_ion_binding_site", true);
+     reply = binding.executeCommand(
+             new StructureCommand("list resi att jv_metal_ion_binding_site"),
+             true);
      assertEquals(reply.size(), 4);
      assertTrue(reply
              .contains("residue id #0:40.A jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 40"));
       * check attributes with score values
       * sequence positions 62 and 65 --> residues 11 and 14 on chains A and B
       */
-     reply = binding.sendChimeraCommand("list resi att jv_kd", true);
+     reply = binding.executeCommand(
+             new StructureCommand("list resi att jv_kd"), true);
      assertEquals(reply.size(), 4);
      assertTrue(reply.contains("residue id #0:11.A jv_kd -2.1 index 11"));
      assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
      /*
       * list residues with positive kd score 
       */
-     reply = binding.sendChimeraCommand(
-             "list resi spec :*/jv_kd>0 attr jv_kd", true);
+     reply = binding.executeCommand(
+             new StructureCommand("list resi spec :*/jv_kd>0 attr jv_kd"),
+             true);
      assertEquals(reply.size(), 2);
      assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
      assertTrue(reply.contains("residue id #0:14.B jv_kd 3.6 index 14"));
        }
      } while (!binding.isFinishedInit());
    
-     assertTrue(binding.isChimeraRunning(), "Failed to launch Chimera");
+     assertTrue(binding.isViewerRunning(), "Failed to launch Chimera");
    
      assertEquals(binding.getPdbCount(), 1);
    
      /*
-      * 'perform' menu action to copy visible features to
-      * attributes in Chimera
+      * 'perform' menu action to copy Chimera attributes
+      * to features in Jalview
       */
      // TODO rename and pull up method to binding interface
      // once functionality is added for Jmol as well
      binding.copyStructureAttributesToFeatures("phi", af.getViewport()
              .getAlignPanel());
      fr.setVisible("phi");
-     List<SequenceFeature> fs = fer2Arath.getFeatures().findFeatures(54, 54);
-     assertEquals(fs.size(), 3);
-     /*
-      * order of returned features is not guaranteed
-      */
-     assertTrue("RESNUM".equals(fs.get(0).getType())
-             || "RESNUM".equals(fs.get(1).getType())
-             || "RESNUM".equals(fs.get(2).getType()));
+     List<SequenceFeature> fs = fer2Arath.getFeatures().findFeatures(54, 54,
+             "phi");
+     assertEquals(fs.size(), 2);
      assertTrue(fs.contains(new SequenceFeature("phi", "A", 54, 54,
              -131.0713f, "Chimera")));
      assertTrue(fs.contains(new SequenceFeature("phi", "B", 54, 54,
            int res, String featureType)
    {
      String where = "at position " + res;
-     List<SequenceFeature> fs = seq.getFeatures().findFeatures(res, res);
+     List<SequenceFeature> fs = seq.getFeatures().findFeatures(res, res,
+             featureType);
  
-     assertEquals(fs.size(), 2, where);
-     assertEquals(fs.get(0).getType(), "RESNUM", where);
-     SequenceFeature sf = fs.get(1);
+     assertEquals(fs.size(), 1, where);
+     SequenceFeature sf = fs.get(0);
      assertEquals(sf.getType(), featureType, where);
      assertEquals(sf.getFeatureGroup(), "Chimera", where);
      assertEquals(sf.getDescription(), "True", where);
@@@ -23,6 -23,9 +23,6 @@@ package jalview.fts.service.pdb
  import static org.testng.AssertJUnit.assertEquals;
  import static org.testng.AssertJUnit.assertTrue;
  
 -import jalview.gui.JvOptionPane;
 -
 -import javax.swing.JComboBox;
  import javax.swing.JInternalFrame;
  
  import org.testng.annotations.AfterMethod;
@@@ -30,7 -33,6 +30,7 @@@ import org.testng.annotations.BeforeCla
  import org.testng.annotations.BeforeMethod;
  import org.testng.annotations.Test;
  
 +import jalview.gui.JvOptionPane;
  import junit.extensions.PA;
  
  public class PDBFTSPanelTest
@@@ -67,7 -69,7 +67,7 @@@
      String expectedString = "1xyz OR text:2xyz OR text:3xyz";
      String outcome = PDBFTSPanel.decodeSearchTerm("1xyz:A;2xyz;3xyz",
              "text");
-     // System.out.println("1 >>>>>>>>>>> " + outcome);
+     System.out.println("1 >>>>>>>>>>> " + outcome);
      assertEquals(expectedString, outcome);
  
      expectedString = "1xyz";
      assertTrue(!mainFrame.getTitle().equalsIgnoreCase(
              "PDB Sequence Fetcher"));
    }
+   
+   @Test
+   public void getFTSframeTitleTest() {
+     PDBFTSPanel searchPanel = new PDBFTSPanel(null);
+     String outcome = searchPanel.getFTSFrameTitle();
+     //System.out.println("FTS Frame title :" + outcome);
+     assertEquals(outcome, "PDB Sequence Fetcher");
+   }
  
  }
@@@ -1,17 -1,8 +1,9 @@@
  package jalview.gui;
  
  import static org.testng.Assert.assertNotNull;
  import static org.testng.Assert.assertTrue;
  
- import jalview.analysis.AlignmentGenerator;
- import jalview.bin.Cache;
- import jalview.bin.Jalview;
- import jalview.datamodel.AlignmentI;
- import jalview.datamodel.SequenceGroup;
- import jalview.io.DataSourceType;
- import jalview.io.FileLoader;
 +
  import java.awt.event.MouseEvent;
  import java.io.File;
  import java.io.IOException;
@@@ -20,120 -11,177 +12,201 @@@ import java.io.PrintStream
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
  
+ import jalview.analysis.AlignmentGenerator;
+ import jalview.bin.Cache;
+ import jalview.bin.Jalview;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.SequenceGroup;
+ import jalview.io.DataSourceType;
+ import jalview.io.FileLoader;
  import junit.extensions.PA;
  
+ /**
+  * Provides a simple test that memory is released when all windows are closed.
+  * <ul>
+  * <li>generates a reasonably large alignment and loads it</li>
+  * <li>performs various operations on the alignment</li>
+  * <li>closes all windows</li>
+  * <li>requests garbage collection</li>
+  * <li>asserts that the remaining memory footprint (heap usage) is 'not large'
+  * </li>
+  * </ul>
+  * If the test fails, this means that reference(s) to large object(s) have
+  * failed to be garbage collected. In this case:
+  * <ul>
+  * <li>set a breakpoint just before the test assertion in
+  * {@code checkUsedMemory}</li>
+  * <li>if the test fails intermittently, make this breakpoint conditional on
+  * {@code usedMemory > expectedMax}</li>
+  * <li>run the test to this point (and check that it is about to fail i.e.
+  * {@code usedMemory > expectedMax})</li>
+  * <li>use <a href="https://visualvm.github.io/">visualvm</a> to obtain a heap
+  * dump from the suspended process (and kill the test or let it fail)</li>
+  * <li>inspect the heap dump using visualvm for large objects and their
+  * referers</li>
+  * <li>Tips:</li>
+  * <ul>
+  * <li>Perform GC from the Monitor view in visualvm before requesting the heap
+  * dump - test failure might be simply a delay to GC</li>
+  * <li>View 'Objects' and filter classes to {@code jalview}. Sort columns by
+  * Count, or Size, and look for anything suspicious. For example, if the object
+  * count for {@code Sequence} is non-zero (it shouldn't be), pick any instance,
+  * and follow the chain of {@code references} to find which class(es) still hold
+  * references to sequence objects</li>
+  * <li>If this chain is impracticably long, re-run the test with a smaller
+  * alignment (set width=100, height=10 in {@code generateAlignment()}), to
+  * capture a heap which is qualitatively the same, but much smaller, so easier
+  * to analyse; note this requires an unconditional breakpoint</li>
+  * </ul>
+  * </ul>
+  * <p>
+  * <h2>Fixing memory leaks</h2>
+  * <p>
+  * Experience shows that often a reference is retained (directly or indirectly)
+  * by a Swing (or related) component (for example a {@code MouseListener} or
+  * {@code ActionListener}). There are two possible approaches to fixing:
+  * <ul>
+  * <li>Purist: ensure that all listeners and similar objects are removed when no
+  * longer needed. May be difficult, to achieve and to maintain as code
+  * changes.</li>
+  * <li>Pragmatic: null references to potentially large objects from Jalview
+  * application classes when no longer needed, typically when a panel is closed.
+  * This ensures that even if the JVM keeps a reference to a panel or viewport,
+  * it does not retain a large heap footprint. This is the approach taken in, for
+  * example, {@code AlignmentPanel.closePanel()} and
+  * {@code AnnotationPanel.dispose()}.</li>
+  * <li>Adjust code if necessary; for example an {@code ActionListener} should
+  * act on {@code av.getAlignment()} and not directly on {@code alignment}, as
+  * the latter pattern could leave persistent references to the alignment</li>
+  * </ul>
+  * Add code to 'null unused large object references' until the test passes. For
+  * a final sanity check, capture the heap dump for a passing test, and satisfy
+  * yourself that only 'small' or 'harmless' {@code jalview} object instances
+  * (such as enums or singletons) are left in the heap.
+  */
  public class FreeUpMemoryTest
  {
    private static final int ONE_MB = 1000 * 1000;
  
+   /*
+    * maximum retained heap usage (in MB) for a passing test
+    */
+   private static int MAX_RESIDUAL_HEAP = 45;
 -
    /**
     * Configure (read-only) Jalview property settings for test
     */
    @BeforeClass(alwaysRun = true)
    public void setUp()
    {
-     Jalview.main(new String[] { "-nonews", "-props",
-         "test/jalview/testProps.jvprops" });
+     Jalview.main(
+             new String[]
+             { "-nonews", "-props", "test/jalview/testProps.jvprops" });
      String True = Boolean.TRUE.toString();
 -    Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", True);
 -    Cache.applicationProperties.setProperty("SHOW_QUALITY", True);
 -    Cache.applicationProperties.setProperty("SHOW_CONSERVATION", True);
 -    Cache.applicationProperties.setProperty("SHOW_OCCUPANCY", True);
 -    Cache.applicationProperties.setProperty("SHOW_IDENTITY", True);
 +    Cache.setPropertyNoSave("SHOW_ANNOTATIONS", True);
 +    Cache.setPropertyNoSave("SHOW_QUALITY", True);
 +    Cache.setPropertyNoSave("SHOW_CONSERVATION", True);
 +    Cache.setPropertyNoSave("SHOW_OCCUPANCY", True);
 +    Cache.setPropertyNoSave("SHOW_IDENTITY", True);
    }
  
 +  /**
 +   * A simple test that memory is released when all windows are closed.
 +   * <ul>
 +   * <li>generates a reasonably large alignment and loads it</li>
 +   * <li>performs various operations on the alignment</li>
 +   * <li>closes all windows</li>
 +   * <li>requests garbage collection</li>
 +   * <li>asserts that the remaining memory footprint (heap usage) is 'not large'
 +   * </li>
 +   * </ul>
 +   * If the test fails, this suggests that a reference to some large object
 +   * (perhaps the alignment data, or some annotation / Tree / PCA data) has
 +   * failed to be garbage collected. If this is the case, the heap will need to
 +   * be inspected manually (suggest using jvisualvm) in order to track down
 +   * where large objects are still referenced. The code (for example
 +   * AlignmentViewport.dispose()) should then be updated to ensure references to
 +   * large objects are set to null when they are no longer required.
 +   * 
 +   * @throws IOException
 +   */
    @Test(groups = "Memory")
    public void testFreeMemoryOnClose() throws IOException
    {
      File f = generateAlignment();
      f.deleteOnExit();
  
-     long expectedMin = 35L;
++    long expectedMin = MAX_RESIDUAL_HEAP;
 +    long usedMemoryAtStart=getUsedMemory();
 +    if (usedMemoryAtStart>expectedMin)
 +    {
 +      System.err.println("used memory before test is "+usedMemoryAtStart+" > "+expectedMin+"MB .. adjusting minimum.");
 +      expectedMin = usedMemoryAtStart;
 +    }
      doStuffInJalview(f);
  
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
  
 -    checkUsedMemory(MAX_RESIDUAL_HEAP);
 +    checkUsedMemory(expectedMin);
    }
  
-   private static long getUsedMemory()
+   /**
+    * Returns the current total used memory (available memory - free memory),
+    * rounded down to the nearest MB
+    * 
+    * @return
+    */
+   private static int getUsedMemory()
    {
-     long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB;
-     long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB;
+     long availableMemory = Runtime.getRuntime().totalMemory();
+     long freeMemory = Runtime.getRuntime().freeMemory();
      long usedMemory = availableMemory - freeMemory;
-     return usedMemory;
 -
+     return (int) (usedMemory / ONE_MB);
    }
 -
    /**
     * Requests garbage collection and then checks whether remaining memory in use
     * is less than the expected value (in Megabytes)
     * 
     * @param expectedMax
     */
-   protected void checkUsedMemory(long expectedMax)
+   protected void checkUsedMemory(int expectedMax)
    {
      /*
-      * request garbage collection and wait for it to run;
+      * request garbage collection and wait for it to run (up to 3 times);
       * NB there is no guarantee when, or whether, it will do so
-      * wait time depends on JRE/processor, generous allowance here  
-      */
-     System.gc();
-     waitFor(1500);
-     /*
-      * a second gc() call should not be necessary - but it is!
-      * the test passes with it, and fails without it
       */
-     System.gc();
-     waitFor(1500);
-     /*
-      * check used memory is 'reasonably low'
-      */
-     long usedMemory = getUsedMemory();
-     /*
-      * sanity check - fails if any frame was added after
-      * closeAll_actionPerformed
-      */
-     assertEquals(Desktop.getInstance().getAllFrames().length, 0);
+     long usedMemory = 0L;
+     Long minUsedMemory = null;
+     int gcCount = 0;
+     while (gcCount < 3)
+     {
+       gcCount++;
+       System.gc();
+       waitFor(1500);
+       usedMemory = getUsedMemory();
+       if (minUsedMemory == null || usedMemory < minUsedMemory)
+       {
+         minUsedMemory = usedMemory;
+       }
+       if (usedMemory < expectedMax)
+       {
+         break;
+       }
+     }
  
      /*
-      * if this assertion fails
-      * - set a breakpoint here
-      * - run jvisualvm to inspect a heap dump of Jalview
-      * - identify large objects in the heap and their referers
+      * if this assertion fails (reproducibly!)
+      * - set a breakpoint here, conditional on (usedMemory > expectedMax)
+      * - run VisualVM to inspect the heap usage, and run GC from VisualVM to check 
+      *   it is not simply delayed garbage collection causing the test failure 
+      * - take a heap dump and identify large objects in the heap and their referers
       * - fix code as necessary to null the references on close
       */
-     System.out.println("Used memory after gc = " + usedMemory + "MB");
-     assertTrue(usedMemory < expectedMax, String.format(
+     System.out.println("(Minimum) Used memory after " + gcCount
+             + " call(s) to gc() = " + minUsedMemory + "MB (should be <="
+             + expectedMax + ")");
+     assertTrue(usedMemory <= expectedMax, String.format(
              "Used memory %d should be less than %d (Recommend running test manually to verify)",
-             usedMemory,
-             expectedMax));
+             usedMemory, expectedMax));
    }
  
    /**
       * wait until Tree and PCA have been computed
       */
      while (af.viewport.getCurrentTree() == null
-             && dialog.getPcaPanel().isWorking())
+             || dialog.getPcaPanel().isWorking())
      {
        waitFor(10);
      }
      int width = 100000;
      int height = 100;
      ag.generate(width, height, 0, 10, 15);
+     ps.close();
      return f;
    }
  }
   */
  package jalview.gui;
  
 -import java.awt.Font;
 -import java.awt.FontMetrics;
 -
 -import org.testng.annotations.BeforeMethod;
 -import org.testng.annotations.Test;
  import static org.testng.Assert.assertEquals;
  import static org.testng.Assert.assertFalse;
  import static org.testng.Assert.assertNotNull;
  import static org.testng.Assert.assertNull;
  import static org.testng.Assert.assertTrue;
  
 +import java.awt.Font;
 +import java.awt.FontMetrics;
 +
- import org.testng.annotations.BeforeClass;
++import org.testng.annotations.BeforeMethod;
 +import org.testng.annotations.Test;
 +
  import jalview.bin.Cache;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.SearchResults;
  import jalview.datamodel.SearchResultsI;
  import jalview.io.DataSourceType;
 -import jalview.io.DataSourceType;
  import jalview.io.FileLoader;
 -
 +import jalview.util.Platform;
 +import jalview.viewmodel.ViewportRanges;
  import junit.extensions.PA;
  
  public class SeqCanvasTest
  {
 -  private AlignFrame af;
 +  @BeforeClass(alwaysRun = true)
 +  public void setUp()
 +  {
 +    Cache.loadProperties(null);
 +    Cache.initLogger();
 +    Desktop.getInstance().setVisible(false);
 +  }
  
    /**
     * Test the method that computes wrapped width in residues, height of wrapped
@@@ -59,9 -53,6 +60,6 @@@
    @Test(groups = "Functional")
    public void testCalculateWrappedGeometry_noAnnotations()
    {
-     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
-             "examples/uniref50.fa", DataSourceType.FILE);
-     
      AlignViewport av = af.getViewport();
      AlignmentI al = av.getAlignment();
      assertEquals(al.getWidth(), 157);
@@@ -74,8 -65,8 +72,8 @@@
      av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
      int charHeight = av.getCharHeight();
      int charWidth = av.getCharWidth();
 -    assertEquals(charHeight, 17);
 -    assertEquals(charWidth, 12);
 +    assertEquals(charHeight, !Platform.isWin() ? 17 : 19);
 +    assertEquals(charWidth, !Platform.isWin() ? 12 : 11);
  
      /*
       * first with scales above, left, right
@@@ -86,8 -77,7 +84,8 @@@
      av.setScaleRightWrapped(true);
      FontMetrics fm = testee.getFontMetrics(av.getFont());
      int labelWidth = fm.stringWidth("000") + charWidth;
 -    assertEquals(labelWidth, 39); // 3 x 9 + charWidth
 +    assertEquals(labelWidth,
 +            !Platform.isWin() ? 3 * 9 + charWidth : 3 * 8 + charWidth);
  
      /*
       * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
      canvasWidth += 2;
      wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
              canvasHeight);
 -    assertEquals(wrappedWidth, 24); // 2px not enough
 +    assertEquals(wrappedWidth, !Platform.isWin() ? 24 : 25); // 2px not enough
      canvasWidth += 1;
      wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
              canvasHeight);
    @Test(groups = "Functional")
    public void testCalculateWrappedGeometry_withAnnotations()
    {
-     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
-             "examples/uniref50.fa", DataSourceType.FILE);
      AlignViewport av = af.getViewport();
      AlignmentI al = av.getAlignment();
      assertEquals(al.getWidth(), 157);
      av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
      int charHeight = av.getCharHeight();
      int charWidth = av.getCharWidth();
 -    assertEquals(charHeight, 17);
 -    assertEquals(charWidth, 12);
 -  
 +    assertEquals(charHeight, !Platform.isWin() ? 17 : 19);
 +    assertEquals(charWidth, !Platform.isWin() ? 12 : 11);
      SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
    
      /*
      av.setScaleAboveWrapped(true);
      av.setScaleLeftWrapped(true);
      av.setScaleRightWrapped(true);
      FontMetrics fm = testee.getFontMetrics(av.getFont());
      int labelWidth = fm.stringWidth("000") + charWidth;
 -    assertEquals(labelWidth, 39); // 3 x 9 + charWidth
 +    assertEquals(labelWidth,
 +            !Platform.isWin() ? 3 * 9 + charWidth : 3 * 8 + charWidth);
      int annotationHeight = testee.getAnnotationHeight();
  
      /*
     * endSeq should be unchanged, but the vertical repeat height should include
     * all sequences.
     */
-   @Test(groups = "Functional")
+   @Test(groups = "Functional_Failing")
    public void testCalculateWrappedGeometry_fromScrolled()
    {
-     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
-             "examples/uniref50.fa", DataSourceType.FILE);
-     // 
-     System.out.println("ap dim = " + af.alignPanel.getSize());
-     System.out.println("seqpan dim = " + af.alignPanel.getSeqPanel().getSize());
-     System.out.println("seqcan dim = " + af.alignPanel.getSeqPanel().seqCanvas.getSize());
      AlignViewport av = af.getViewport();
      AlignmentI al = av.getAlignment();
      assertEquals(al.getWidth(), 157);
      assertEquals(al.getHeight(), 15);
-     ViewportRanges ranges = av.getRanges();
-     System.out.println(ranges + " before setting to 3");
-     ranges.setStartEndSeq(0, 3);
-     int endSeq = ranges.getEndSeq();
-     System.out.println(ranges + " after setting to 3");
+     av.getRanges().setStartEndSeq(0, 3);
+     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
+     av.setWrapAlignment(true);
      av.setShowAnnotation(false);
      av.setScaleAboveWrapped(true);
  
      SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
-     av.setWrapAlignment(true);
-     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
      int charHeight = av.getCharHeight();
      int charWidth = av.getCharWidth();
-     // Windows h=19, w=11.
-     assertEquals(charHeight, !Platform.isWin() ? 17 : 19);
-     assertEquals(charWidth, !Platform.isWin() ? 12 : 11);
-     
+     assertEquals(charHeight, 17);
+     assertEquals(charWidth, 12);
      int canvasWidth = 400;
      int canvasHeight = 300;
      testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
-     System.out.println(ranges);
-     assertEquals(ranges.getEndSeq(), endSeq); // unchanged
+     assertEquals(av.getRanges().getEndSeq(), 3); // unchanged
      int repeatingHeight = (int) PA.getValue(testee,
              "wrappedRepeatHeightPx");
-     int h = charHeight * (2 + al.getHeight());
-     assertEquals(repeatingHeight, h);
+     assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
+   }
+   @BeforeMethod(alwaysRun = true)
+   public void setUp()
+   {
+     Cache.loadProperties("test/jalview/io/testProps.jvprops");
+     Cache.applicationProperties.setProperty("SHOW_IDENTITY",
+             Boolean.TRUE.toString());
+     af = new FileLoader().LoadFileWaitTillLoaded("examples/uniref50.fa",
+             DataSourceType.FILE);
+   
+     /*
+      * wait for Consensus thread to complete
+      */
+     do
+     {
+       try
+       {
+         Thread.sleep(50);
+       } catch (InterruptedException x)
+       {
+       }
+     } while (af.getViewport().getCalcManager().isWorking());
    }
    @Test(groups = "Functional")
    public void testClear_HighlightAndSelection()
    {
 +    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
 +            "examples/uniref50.fa", DataSourceType.FILE);
      AlignViewport av = af.getViewport();
      SearchResultsI highlight = new SearchResults();
      highlight.addResult(
@@@ -24,6 -24,17 +24,16 @@@ import static org.testng.Assert.assertE
  import static org.testng.Assert.assertNull;
  import static org.testng.Assert.assertTrue;
  
+ import java.awt.EventQueue;
+ import java.awt.FontMetrics;
+ import java.awt.event.MouseEvent;
+ import java.lang.reflect.InvocationTargetException;
+ import javax.swing.JLabel;
+ import org.testng.annotations.AfterMethod;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.Test;
 -
  import jalview.api.AlignViewportI;
  import jalview.bin.Cache;
  import jalview.bin.Jalview;
@@@ -33,6 -44,8 +43,8 @@@ import jalview.commands.EditCommand.Edi
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.SearchResults;
+ import jalview.datamodel.SearchResultsI;
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceI;
  import jalview.gui.SeqPanel.MousePos;
@@@ -40,17 -53,6 +52,8 @@@ import jalview.io.DataSourceType
  import jalview.io.FileLoader;
  import jalview.util.MessageManager;
  import jalview.viewmodel.ViewportRanges;
 +
- import java.awt.EventQueue;
- import java.awt.event.MouseEvent;
- import java.lang.reflect.InvocationTargetException;
- import javax.swing.JLabel;
- import org.testng.annotations.AfterMethod;
- import org.testng.annotations.BeforeClass;
- import org.testng.annotations.Test;
 +
  import junit.extensions.PA;
  
  public class SeqPanelTest
    @AfterMethod(alwaysRun = true)
    public void tearDown()
    {
-     Desktop.getInstance().closeAll_actionPerformed(null);
+     Desktop.instance.closeAll_actionPerformed(null);
    }
  
    @Test(groups = "Functional")
    public void testFindMousePosition_wrapped_annotations()
    {
-     Cache.setPropertyNoSave("SHOW_ANNOTATIONS", "true");
-     Cache.setPropertyNoSave("WRAP_ALIGNMENT", "true");
+     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
+     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
      AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
      AlignViewportI av = alignFrame.getViewport();
    @Test(groups = "Functional")
    public void testFindMousePosition_wrapped_scaleAbove()
    {
-     Cache.setPropertyNoSave("SHOW_ANNOTATIONS", "true");
-     Cache.setPropertyNoSave("WRAP_ALIGNMENT", "true");
+     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "true");
+     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
      AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
      AlignViewportI av = alignFrame.getViewport();
    @Test(groups = "Functional")
    public void testFindMousePosition_wrapped_noAnnotations()
    {
-     Cache.setPropertyNoSave("SHOW_ANNOTATIONS", "false");
-     Cache.setPropertyNoSave("WRAP_ALIGNMENT", "true");
+     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
+     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
+     Cache.applicationProperties.setProperty("FONT_SIZE", "10");
      AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
      AlignViewportI av = alignFrame.getViewport();
    @Test(groups = "Functional")
    public void testFindColumn_unwrapped()
    {
-     Cache.setPropertyNoSave("WRAP_ALIGNMENT", "false");
+     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "false");
      AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
      SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
    @Test(groups = "Functional")
    public void testFindColumn_wrapped()
    {
-     Cache.setPropertyNoSave("WRAP_ALIGNMENT", "true");
+     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
      AlignFrame alignFrame = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
      AlignViewport av = alignFrame.getViewport();
        e.printStackTrace();
      }
    }
+   @Test(groups = "Functional")
+   public void testFindMousePosition_wrapped_scales_longSequence()
+   {
+     Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", "false");
+     Cache.applicationProperties.setProperty("WRAP_ALIGNMENT", "true");
+     Cache.applicationProperties.setProperty("FONT_SIZE", "14");
+     Cache.applicationProperties.setProperty("FONT_NAME", "SansSerif");
+     Cache.applicationProperties.setProperty("FONT_STYLE", "0");
+     // sequence of 50 bases, doubled 10 times, = 51200 bases
+     String dna = "ATGGCCATTGGGCCCAAATTTCCCAAAGGGTTTCCCTGAGGTCAGTCAGA";
+     for (int i = 0 ; i < 10 ; i++)
+     {
+       dna += dna;
+     }
+     assertEquals(dna.length(), 51200);
+     AlignFrame alignFrame = new FileLoader()
+             .LoadFileWaitTillLoaded(dna, DataSourceType.PASTE);
+     SeqPanel testee = alignFrame.alignPanel.getSeqPanel();
+     AlignViewport av = alignFrame.getViewport();
+     av.setScaleAboveWrapped(true);
+     av.setScaleLeftWrapped(true);
+     av.setScaleRightWrapped(true);
+     alignFrame.alignPanel.updateLayout();
+     try
+     {
+       Thread.sleep(200);
+     } catch (InterruptedException e)
+     {
+     }
+   
+     final int charHeight = av.getCharHeight();
+     final int charWidth = av.getCharWidth();
+     assertEquals(charHeight, 17);
+     assertEquals(charWidth, 12);
+     
+     FontMetrics fm = testee.getFontMetrics(av.getFont());
+     int labelWidth = fm.stringWidth("00000") + charWidth;
+     assertEquals(labelWidth, 57); // 5 x 9 + charWidth
+     assertEquals(testee.seqCanvas.getLabelWidthWest(), labelWidth);
+     int x = 0;
+     int y = 0;
+   
+     /*
+      * mouse at top left of wrapped panel; there is a gap of 2 * charHeight
+      * above the alignment
+      */
+     MouseEvent evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y,
+             0, 0, 0, false, 0);
+     MousePos pos = testee.findMousePosition(evt);
+     assertEquals(pos.column, -1); // over scale left, not an alignment column
+     assertEquals(pos.seqIndex, -1); // above sequences
+     assertEquals(pos.annotationIndex, -1);
+     /*
+      * cursor over scale above first sequence
+      */
+     y += charHeight;
+     x = labelWidth;
+     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
+             false, 0);
+     pos = testee.findMousePosition(evt);
+     assertEquals(pos.seqIndex, -1);
+     assertEquals(pos.column, 0);
+     assertEquals(pos.annotationIndex, -1);
+     
+     /*
+      * cursor over scale left of first sequence
+      */
+     y += charHeight;
+     x = 0;
+     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
+             false, 0);
+     pos = testee.findMousePosition(evt);
+     assertEquals(pos.seqIndex, 0);
+     assertEquals(pos.column, -1);
+     assertEquals(pos.annotationIndex, -1);
+   
+     /*
+      * cursor over start of first sequence
+      */
+     x = labelWidth;
+     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
+             false, 0);
+     pos = testee.findMousePosition(evt);
+     assertEquals(pos.seqIndex, 0);
+     assertEquals(pos.column, 0);
+     assertEquals(pos.annotationIndex, -1);
+   
+     /*
+      * move one character right, to bottom pixel of same row
+      */
+     x += charWidth;
+     y += charHeight - 1;
+     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
+             false, 0);
+     pos = testee.findMousePosition(evt);
+     assertEquals(pos.seqIndex, 0);
+     assertEquals(pos.column, 1);
+     
+     /*
+      * move down one pixel - now in the no man's land between rows
+      */
+     y += 1;
+     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
+             false, 0);
+     pos = testee.findMousePosition(evt);
+     assertEquals(pos.seqIndex, -1);
+     assertEquals(pos.column, 1);
+     
+     /*
+      * move down two char heights less one pixel - still in the no man's land
+      * (scale above + spacer line)
+      */
+     y += (2 * charHeight - 1);
+     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
+             false, 0);
+     pos = testee.findMousePosition(evt);
+     assertEquals(pos.seqIndex, -1);
+     assertEquals(pos.column, 1);
+     
+     /*
+      * move down one more pixel - now on the next row of the sequence
+      */
+     y += 1;
+     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
+             false, 0);
+     pos = testee.findMousePosition(evt);
+     assertEquals(pos.seqIndex, 0);
+     assertEquals(pos.column, 1 + av.getWrappedWidth());
+     
+     /*
+      * scroll to near the end of the sequence
+      */
+     SearchResultsI sr = new SearchResults();
+     int scrollTo = dna.length() - 1000;
+     sr.addResult(av.getAlignment().getSequenceAt(0), scrollTo, scrollTo); 
+     alignFrame.alignPanel.scrollToPosition(sr);
+     
+     /*
+      * place the mouse on the first column of the 6th sequence, and
+      * verify that (computed) findMousePosition matches (actual) ViewportRanges
+      */
+     x = labelWidth;
+     y = 17 * charHeight; // 17 = 6 times two header rows and 5 sequence rows
+     evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
+             false, 0);
+     pos = testee.findMousePosition(evt);
+     assertEquals(pos.seqIndex, 0);
+     int expected = av.getRanges().getStartRes() + 5 * av.getWrappedWidth();
+     assertEquals(pos.column, expected);
+   }
  }
   */
  package jalview.gui;
  
- import static org.testng.AssertJUnit.assertEquals;
+ import static org.testng.Assert.assertEquals;
  import static org.testng.AssertJUnit.assertNotNull;
  import static org.testng.AssertJUnit.assertTrue;
  
++
  import java.util.Collection;
  import java.util.Vector;
  
+ import org.junit.Assert;
  import org.testng.annotations.AfterMethod;
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.BeforeMethod;
  import org.testng.annotations.Test;
  
  import jalview.datamodel.DBRefEntry;
- import jalview.datamodel.DBRefSource;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceI;
  import jalview.fts.api.FTSData;
- import jalview.jbgui.GStructureChooser.FilterOption;
+ import jalview.fts.core.FTSRestClient;
+ import jalview.fts.service.pdb.PDBFTSRestClient;
+ import jalview.fts.service.pdb.PDBFTSRestClientTest;
+ import jalview.fts.service.threedbeacons.TDBeaconsFTSRestClient;
+ import jalview.fts.threedbeacons.TDBeaconsFTSRestClientTest;
+ import jalview.gui.structurechooser.PDBStructureChooserQuerySource;
+ import jalview.jbgui.FilterOption;
  import junit.extensions.PA;
  
+ @Test(singleThreaded = true)
  public class StructureChooserTest
  {
  
@@@ -51,7 -58,7 +59,7 @@@
      JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
    }
  
-   Sequence seq;
+   Sequence seq,upSeq,upSeq_nocanonical;
  
    @BeforeMethod(alwaysRun = true)
    public void setUp() throws Exception
      pdbIds.add(dbRef);
  
      seq.setPDBId(pdbIds);
+     
+     // Uniprot sequence for 3D-Beacons mocks
+     upSeq = new Sequence("P38398", 
+             "MDLSALRVEEVQNVINAMQKILECPICLELIKEPVSTKCDHIFCKFCMLKLLNQKKGPSQCPLCKNDITKRS\n"
+             + "LQESTRFSQLVEELLKIICAFQLDTGLEYANSYNFAKKENNSPEHLKDEVSIIQSMGYRNRAKRLLQSEPEN\n"
+             + "PSLQETSLSVQLSNLGTVRTLRTKQRIQPQKTSVYIELGSDSSEDTVNKATYCSVGDQELLQITPQGTRDEI\n"
+             + "SLDSAKKAACEFSETDVTNTEHHQPSNNDLNTTEKRAAERHPEKYQGSSVSNLHVEPCGTNTHASSLQHENS\n"
+             + "SLLLTKDRMNVEKAEFCNKSKQPGLARSQHNRWAGSKETCNDRRTPSTEKKVDLNADPLCERKEWNKQKLPC\n"
+             + "SENPRDTEDVPWITLNSSIQKVNEWFSRSDELLGSDDSHDGESESNAKVADVLDVLNEVDEYSGSSEKIDLL\n"
+             + "ASDPHEALICKSERVHSKSVESNIEDKIFGKTYRKKASLPNLSHVTENLIIGAFVTEPQIIQERPLTNKLKR\n"
+             + "KRRPTSGLHPEDFIKKADLAVQKTPEMINQGTNQTEQNGQVMNITNSGHENKTKGDSIQNEKNPNPIESLEK\n"
+             + "ESAFKTKAEPISSSISNMELELNIHNSKAPKKNRLRRKSSTRHIHALELVVSRNLSPPNCTELQIDSCSSSE\n"
+             + "EIKKKKYNQMPVRHSRNLQLMEGKEPATGAKKSNKPNEQTSKRHDSDTFPELKLTNAPGSFTKCSNTSELKE\n"
+             + "FVNPSLPREEKEEKLETVKVSNNAEDPKDLMLSGERVLQTERSVESSSISLVPGTDYGTQESISLLEVSTLG\n"
+             + "KAKTEPNKCVSQCAAFENPKGLIHGCSKDNRNDTEGFKYPLGHEVNHSRETSIEMEESELDAQYLQNTFKVS\n"
+             + "KRQSFAPFSNPGNAEEECATFSAHSGSLKKQSPKVTFECEQKEENQGKNESNIKPVQTVNITAGFPVVGQKD\n"
+             + "KPVDNAKCSIKGGSRFCLSSQFRGNETGLITPNKHGLLQNPYRIPPLFPIKSFVKTKCKKNLLEENFEEHSM\n"
+             + "SPEREMGNENIPSTVSTISRNNIRENVFKEASSSNINEVGSSTNEVGSSINEIGSSDENIQAELGRNRGPKL\n"
+             + "NAMLRLGVLQPEVYKQSLPGSNCKHPEIKKQEYEEVVQTVNTDFSPYLISDNLEQPMGSSHASQVCSETPDD\n"
+             + "LLDDGEIKEDTSFAENDIKESSAVFSKSVQKGELSRSPSPFTHTHLAQGYRRGAKKLESSEENLSSEDEELP\n"
+             + "CFQHLLFGKVNNIPSQSTRHSTVATECLSKNTEENLLSLKNSLNDCSNQVILAKASQEHHLSEETKCSASLF\n"
+             + "SSQCSELEDLTANTNTQDPFLIGSSKQMRHQSESQGVGLSDKELVSDDEERGTGLEENNQEEQSMDSNLGEA\n"
+             + "ASGCESETSVSEDCSGLSSQSDILTTQQRDTMQHNLIKLQQEMAELEAVLEQHGSQPSNSYPSIISDSSALE\n"
+             + "DLRNPEQSTSEKAVLTSQKSSEYPISQNPEGLSADKFEVSADSSTSKNKEPGVERSSPSKCPSLDDRWYMHS\n"
+             + "CSGSLQNRNYPSQEELIKVVDVEEQQLEESGPHDLTETSYLPRQDLEGTPYLESGISLFSDDPESDPSEDRA\n"
+             + "PESARVGNIPSSTSALKVPQLKVAESAQSPAAAHTTDTAGYNAMEESVSREKPELTASTERVNKRMSMVVSG\n"
+             + "LTPEEFMLVYKFARKHHITLTNLITEETTHVVMKTDAEFVCERTLKYFLGIAGGKWVVSYFWVTQSIKERKM\n"
+             + "LNEHDFEVRGDVVNGRNHQGPKRARESQDRKIFRGLEICCYGPFTNMPTDQLEWMVQLCGASVVKELSSFTL\n"
+             + "GTGVHPIVVVQPDAWTEDNGFHAIGQMCEAPVVTREWVLDSVALYQCQELDTYLIPQIPHSHY\n"
+             + "", 1,
+ 1863);
+     upSeq.setDescription("Breast cancer type 1 susceptibility protein");
+     upSeq_nocanonical = new Sequence(upSeq);
+     upSeq.createDatasetSequence();
+     upSeq.addDBRef(new DBRefEntry("UNIPROT","0","P38398",null,true));
+     
+     upSeq_nocanonical.createDatasetSequence();
+     // not a canonical reference
+     upSeq_nocanonical.addDBRef(new DBRefEntry("UNIPROT","0","P38398",null,false));
 -
    }
  
    @AfterMethod(alwaysRun = true)
    public void tearDown() throws Exception
    {
      seq = null;
-   }
-   @Test(groups = { "Functional" })
-   public void buildQueryTest()
-   {
-     String query = StructureChooser.buildQuery(seq);
-     assertEquals("pdb_id:1tim", query);
-     System.out.println("seq >>>> " + seq);
-     seq.getAllPDBEntries().clear();
-     query = StructureChooser.buildQuery(seq);
-     assertEquals(
-             "text:XYZ_1 OR text:XYZ_2 OR text:XYZ_3 OR text:XYZ_4 OR text:4kqy",
-             query);
-       seq.setDBRefs(null);
-     query = StructureChooser.buildQuery(seq);
-     assertEquals("text:4kqy", query);
-     DBRefEntry uniprotDBRef = new DBRefEntry();
-     uniprotDBRef.setAccessionId("P12345");
-     uniprotDBRef.setSource(DBRefSource.UNIPROT);
-     seq.addDBRef(uniprotDBRef);
-     DBRefEntry pdbDBRef = new DBRefEntry();
-     pdbDBRef.setAccessionId("1XYZ");
-     pdbDBRef.setSource(DBRefSource.PDB);
-     seq.addDBRef(pdbDBRef);
-     for (int x = 1; x < 5; x++)
-     {
-       DBRefEntry dbRef = new DBRefEntry();
-       dbRef.setAccessionId("XYZ_" + x);
-       seq.addDBRef(dbRef);
-     }
-     query = StructureChooser.buildQuery(seq);
-     assertEquals(
-             "uniprot_accession:P12345 OR uniprot_id:P12345 OR pdb_id:1xyz",
-             query);
+     upSeq=null;
+     upSeq_nocanonical=null;
    }
  
    @Test(groups = { "Functional" })
    public void populateFilterComboBoxTest() throws InterruptedException
    {
+     TDBeaconsFTSRestClientTest.setMock();
+     PDBFTSRestClientTest.setMock();
      SequenceI[] selectedSeqs = new SequenceI[] { seq };
      StructureChooser sc = new StructureChooser(selectedSeqs, seq, null);
+     ThreadwaitFor(200, sc);
+     
+     // if structures are not discovered then don't
+     // populate filter options
      sc.populateFilterComboBox(false, false);
      int optionsSize = sc.getCmbFilterOption().getItemCount();
-     assertEquals(2, optionsSize); // if structures are not discovered then don't
-                                   // populate filter options
+     System.out.println("Items (no data, no cache): ");
+     StringBuilder items = new StringBuilder();
+     for (int p=0;p<optionsSize;p++)
+     {
+       items.append
+       ("- ").append(sc.getCmbFilterOption().getItemAt(p).getName()).append("\n");
+     }
+     // report items when this fails - seems to be a race condition
+     Assert.assertEquals(items.toString(),optionsSize,2); 
  
      sc.populateFilterComboBox(true, false);
      optionsSize = sc.getCmbFilterOption().getItemCount();
      FilterOption filterOpt = (FilterOption) sc.getCmbFilterOption()
              .getSelectedItem();
      assertEquals("Cached Structures", filterOpt.getName());
+     FTSRestClient.unMock((FTSRestClient) TDBeaconsFTSRestClient.getInstance());
+     FTSRestClient.unMock((FTSRestClient) PDBFTSRestClient.getInstance());
+   }
+   @Test(groups = { "Functional" })
+   public void displayTDBQueryTest() throws InterruptedException
+   {
+     TDBeaconsFTSRestClientTest.setMock();
+     PDBFTSRestClientTest.setMock();
+     SequenceI[] selectedSeqs = new SequenceI[] { upSeq_nocanonical };
+     StructureChooser sc = new StructureChooser(selectedSeqs, upSeq_nocanonical, null);
+     // mock so should be quick. Exceptions from mocked PDBFTS are expected too
+     ThreadwaitFor(500, sc);
+     
+     assertTrue(sc.isCanQueryTDB() && sc.isNotQueriedTDBYet());
    }
  
    @Test(groups = { "Network" })
    public void fetchStructuresInfoTest()
    {
+     FTSRestClient.unMock((FTSRestClient) TDBeaconsFTSRestClient.getInstance());
+     PDBFTSRestClient.unMock((FTSRestClient) PDBFTSRestClient.getInstance());
      SequenceI[] selectedSeqs = new SequenceI[] { seq };
      StructureChooser sc = new StructureChooser(selectedSeqs, seq, null);
+     // not mocked, wait for 2s 
+     ThreadwaitFor(2000, sc);
+     
      sc.fetchStructuresMetaData();
      Collection<FTSData> ss = (Collection<FTSData>) PA.getValue(sc,
              "discoveredStructuresSet");
      assertNotNull(ss);
      assertTrue(ss.size() > 0);
+   }
  
+   @Test(groups = { "Functional" })
+   public void fetchStructuresInfoMockedTest()
+   {
+     TDBeaconsFTSRestClientTest.setMock();
+     PDBFTSRestClientTest.setMock();
+     SequenceI[] selectedSeqs = new SequenceI[] { upSeq };
+     StructureChooser sc = new StructureChooser(selectedSeqs, seq, null);
+     ThreadwaitFor(500, sc);
+     
+     sc.fetchStructuresMetaData();
+     Collection<FTSData> ss = (Collection<FTSData>) PA.getValue(sc,
+             "discoveredStructuresSet");
+     assertNotNull(ss);
+     assertTrue(ss.size() > 0);
    }
  
+   private void ThreadwaitFor(int i, StructureChooser sc)
+   {
+     long timeout = i+System.currentTimeMillis();
+     while (!sc.isDialogVisible() && timeout > System.currentTimeMillis())
+     {
+       try {
+         Thread.sleep(50);
+       } catch (InterruptedException x)
+       {
+         
+       }
+     }
+     
+   }
 -
 -
    @Test(groups = { "Functional" })
    public void sanitizeSeqNameTest()
    {
      String name = "ab_cdEF|fwxyz012349";
-     assertEquals(name, StructureChooser.sanitizeSeqName(name));
+     assertEquals(name, PDBStructureChooserQuerySource.sanitizeSeqName(name));
  
      // remove a [nn] substring
      name = "abcde12[345]fg";
-     assertEquals("abcde12fg", StructureChooser.sanitizeSeqName(name));
+     assertEquals("abcde12fg", PDBStructureChooserQuerySource.sanitizeSeqName(name));
  
      // remove characters other than a-zA-Z0-9 | or _
      name = "ab[cd],.\t£$*!- \\\"@:e";
-     assertEquals("abcde", StructureChooser.sanitizeSeqName(name));
+     assertEquals("abcde", PDBStructureChooserQuerySource.sanitizeSeqName(name));
  
      name = "abcde12[345a]fg";
-     assertEquals("abcde12345afg", StructureChooser.sanitizeSeqName(name));
+     assertEquals("abcde12345afg", PDBStructureChooserQuerySource.sanitizeSeqName(name));
    }
  }
@@@ -1,12 -1,5 +1,5 @@@
  package jalview.io;
  
- import jalview.bin.Cache;
- import jalview.datamodel.AlignmentI;
- import jalview.datamodel.Sequence;
- import jalview.datamodel.SequenceI;
- import jalview.gui.AlignFrame;
- import jalview.gui.JvOptionPane;
  import java.io.File;
  import java.io.IOException;
  import java.nio.file.Files;
@@@ -24,6 -17,13 +17,13 @@@ import org.testng.annotations.AfterClas
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
  
+ import jalview.bin.Cache;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.Sequence;
+ import jalview.datamodel.SequenceI;
+ import jalview.gui.AlignFrame;
+ import jalview.gui.JvOptionPane;
  public class BackupFilesTest
  {
    @BeforeClass(alwaysRun = true)
@@@ -43,7 -43,6 +43,6 @@@
  
    private static String testFilename = testBasename + testExt;
  
    private static String testFile = testDir + File.separatorChar
            + testFilename;
  
      Assert.assertTrue(backupFiles.length == 0);
    }
  
+   // save with no numbers in the backup file names
+   @Test(groups = { "Functional" })
+   public void backupsEnabledSingleFileBackupTest() throws Exception
+   {
+     // Enable BackupFiles and set noMax so all backupfiles get kept
+     String mysuffix = "~";
+     BackupFilesPresetEntry bfpe = new BackupFilesPresetEntry(mysuffix, 1,
+             false, true, 1, false);
+     setBackupFilesOptions(true, false, true,
+             "test/jalview/io/testProps_singlefilebackup.jvprops", bfpe);
+     // init the newFile and backups (i.e. make sure newFile exists on its own
+     // and has no backups)
+     initNewFileForTesting();
+     HashMap<Integer, String> correctindexmap = new HashMap<>();
+     correctindexmap.put(0, "backupfilestestTemp.fa~");
+     save();
+     Assert.assertTrue(checkBackupFiles(correctindexmap, newFile, "~", 1));
+     // and a second time -- see JAL-3628
+     save();
+     Assert.assertTrue(checkBackupFiles(correctindexmap, newFile, "~", 1));
+     cleanupTmpFiles(newFile, "~", 1);
+   }
    // save keeping all backup files
    @Test(groups = { "Functional" })
    public void backupsEnabledNoRollMaxTest() throws Exception
    private void setBackupFilesOptions(boolean enabled, boolean reverse,
            boolean noMax)
    {
-     Cache.loadProperties("test/jalview/io/testProps.jvprops");
      BackupFilesPresetEntry bfpe = new BackupFilesPresetEntry(suffix, digits,
              reverse, noMax, rollMax, false);
+     setBackupFilesOptions(enabled, reverse, noMax,
+             "test/jalview/io/testProps.jvprops", bfpe);
+   }
+   private void setBackupFilesOptions(boolean enabled, boolean reverse,
+           boolean noMax, String propsFile, BackupFilesPresetEntry bfpe)
+   {
+     Cache.loadProperties(propsFile);
  
 -    Cache.applicationProperties.setProperty(BackupFiles.ENABLED,
 +    Cache.setPropertyNoSave(BackupFiles.ENABLED,
              Boolean.toString(enabled));
 -    Cache.applicationProperties.setProperty(
 +    Cache.setPropertyNoSave(
              BackupFilesPresetEntry.SAVEDCONFIG, bfpe.toString());
      /*
 -    Cache.applicationProperties.setProperty(BackupFiles.ENABLED,
 +    Cache.setPropertyNoSave(BackupFiles.ENABLED,
              Boolean.toString(enabled));
 -    Cache.applicationProperties.setProperty(BackupFiles.SUFFIX, suffix);
 -    Cache.applicationProperties.setProperty(BackupFiles.SUFFIX_DIGITS,
 +    Cache.setPropertyNoSave(BackupFiles.SUFFIX, suffix);
 +    Cache.setPropertyNoSave(BackupFiles.SUFFIX_DIGITS,
              Integer.toString(digits));
 -    Cache.applicationProperties.setProperty(BackupFiles.REVERSE_ORDER,
 +    Cache.setPropertyNoSave(BackupFiles.REVERSE_ORDER,
              Boolean.toString(reverse));
 -    Cache.applicationProperties.setProperty(BackupFiles.NO_MAX,
 +    Cache.setPropertyNoSave(BackupFiles.NO_MAX,
              Boolean.toString(noMax));
 -    Cache.applicationProperties.setProperty(BackupFiles.ROLL_MAX,
 +    Cache.setPropertyNoSave(BackupFiles.ROLL_MAX,
              Integer.toString(rollMax));
 -    Cache.applicationProperties.setProperty(BackupFiles.CONFIRM_DELETE_OLD,
 +    Cache.setPropertyNoSave(BackupFiles.CONFIRM_DELETE_OLD,
              "false");
              */
    }
    {
      if (af != null)
      {
-     af.saveAlignment(newFile, jalview.io.FileFormat.Fasta);
+       af.saveAlignment(newFile, jalview.io.FileFormat.Fasta);
      }
    }
  
    @AfterClass(alwaysRun = true)
    private void cleanupTmpFiles()
    {
-     File newfile = new File(newFile);
+     cleanupTmpFiles(newFile, suffix, digits);
+   }
+   private void cleanupTmpFiles(String file, String mysuffix, int mydigits)
+   {
+     File newfile = new File(file);
      if (newfile.exists())
      {
        newfile.delete();
      }
-     File[] tmpFiles = getBackupFiles(newFile, suffix, digits);
+     File[] tmpFiles = getBackupFiles(file, mysuffix, mydigits);
      for (int i = 0; i < tmpFiles.length; i++)
      {
        if (actuallyDeleteTmpFiles)
  
    private static File[] getBackupFiles(String f, String s, int i)
    {
-     TreeMap<Integer, File> bfTreeMap = BackupFiles.getBackupFilesAsTreeMap(f,
-             s, i);
+     TreeMap<Integer, File> bfTreeMap = BackupFiles
+             .getBackupFilesAsTreeMap(f, s, i);
      File[] backupFiles = new File[bfTreeMap.size()];
      bfTreeMap.values().toArray(backupFiles);
      return backupFiles;
    private static boolean checkBackupFiles(HashMap<Integer, String> indexmap)
            throws IOException
    {
-     TreeMap<Integer, File> map = BackupFiles.getBackupFilesAsTreeMap(newFile,
-             suffix, digits);
+     return checkBackupFiles(indexmap, newFile, suffix, digits);
+   }
+   private static boolean checkBackupFiles(HashMap<Integer, String> indexmap,
+           String file, String mysuffix, int mydigits) throws IOException
+   {
+     TreeMap<Integer, File> map = BackupFiles.getBackupFilesAsTreeMap(file,
+             mysuffix, mydigits);
      Enumeration<Integer> indexesenum = Collections
              .enumeration(indexmap.keySet());
      while (indexesenum.hasMoreElements())
  
    private static boolean checkBackupFiles(int[] indexes) throws IOException
    {
-     TreeMap<Integer, File> map = BackupFiles.getBackupFilesAsTreeMap(newFile,
-             suffix, digits);
+     TreeMap<Integer, File> map = BackupFiles
+             .getBackupFilesAsTreeMap(newFile, suffix, digits);
      for (int m = 0; m < indexes.length; m++)
      {
        int i = indexes[m];
        {
          return false;
        }
-       // check the filename -- although this uses the same code to forumulate the filename so not much of a test!
+       // check the filename -- although this uses the same code to forumulate
+       // the filename so not much of a test!
        String filename = BackupFilenameParts.getBackupFilename(i,
                newBasename + testExt, suffix, digits);
        if (!filename.equals(f.getName()))
      return filenames;
    }
  
-   public static boolean sequencesEqual(SequenceI s1, SequenceI s2) {
-     if (s1 == null && s2 == null) {
+   public static boolean sequencesEqual(SequenceI s1, SequenceI s2)
+   {
+     if (s1 == null && s2 == null)
+     {
        return true;
-     } else if (s1 == null || s2 == null) {
+     }
+     else if (s1 == null || s2 == null)
+     {
        return false;
      }
      return (s1.getName().equals(s2.getName())
@@@ -1,5 -1,7 +1,5 @@@
  package jalview.io;
  
 -import java.util.Locale;
 -
  import static org.testng.Assert.assertEquals;
  import static org.testng.Assert.assertFalse;
  import static org.testng.Assert.assertNotEquals;
@@@ -31,14 -33,15 +31,14 @@@ public class FileFormatsTes
    public void testIsIdentifiable()
    {
      FileFormats formats = FileFormats.getInstance();
 -    assertTrue(formats
 -            .isIdentifiable(formats.forName(FileFormat.Fasta.getName())));
 -    assertTrue(formats
 -            .isIdentifiable(formats.forName(FileFormat.MMCif.getName())));
 -    assertTrue(formats
 -            .isIdentifiable(formats.forName(FileFormat.Jnet.getName())));
 -    assertTrue(formats
 -            .isIdentifiable(formats.forName(FileFormat.Jalview.getName())));
 -    // GenBank/ENA
 +    assertTrue(formats.isIdentifiable(formats.forName(FileFormat.Fasta
 +            .getName())));
 +    assertTrue(formats.isIdentifiable(formats.forName(FileFormat.MMCif
 +            .getName())));
 +    assertTrue(formats.isIdentifiable(formats.forName(FileFormat.Jnet
 +            .getName())));
 +    assertTrue(formats.isIdentifiable(formats.forName(FileFormat.Jalview
 +            .getName())));
      assertFalse(formats.isIdentifiable(null));
  
      /*
@@@ -55,7 -58,7 +55,7 @@@
    @Test(groups = "Functional")
    public void testGetReadableFormats()
    {
-     String expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML]";
+     String expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GenBank Flatfile, ENA Flatfile, GFF or Jalview features, PDB, mmCIF, Jalview]";
      FileFormats formats = FileFormats.getInstance();
      assertEquals(formats.getReadableFormats().toString(), expected);
    }
    @Test(groups = "Functional")
    public void testGetWritableFormats()
    {
 -    String expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP]";
 +    String expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, HMMER3]";
      FileFormats formats = FileFormats.getInstance();
      assertEquals(formats.getWritableFormats(true).toString(), expected);
 -    expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, Jalview]";
 +    expected = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, Jalview, HMMER3]";
      assertEquals(formats.getWritableFormats(false).toString(), expected);
    }
  
    @Test(groups = "Functional")
    public void testDeregisterFileFormat()
    {
 -    String writable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP]";
 -    String readable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GenBank Flatfile, ENA Flatfile, GFF or Jalview features, PDB, mmCIF, Jalview]";
 +    String writable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, HMMER3]";
-     String readable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML]";
++    String readable = "[Fasta, PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GenBank Flatfile, ENA Flatfile, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML]";
      FileFormats formats = FileFormats.getInstance();
 +    System.out.println(formats.getReadableFormats().toString());
      assertEquals(formats.getWritableFormats(true).toString(), writable);
      assertEquals(formats.getReadableFormats().toString(), readable);
  
      formats.deregisterFileFormat(FileFormat.Fasta.getName());
 -    writable = "[PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP]";
 -    readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GenBank Flatfile, ENA Flatfile, GFF or Jalview features, PDB, mmCIF, Jalview]";
 +    writable = "[PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, HMMER3]";
-     readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML]";
++    readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GenBank Flatfile, ENA Flatfile, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML]";
      assertEquals(formats.getWritableFormats(true).toString(), writable);
      assertEquals(formats.getReadableFormats().toString(), readable);
  
@@@ -90,8 -92,8 +90,8 @@@
       * re-register the format: it gets added to the end of the list
       */
      formats.registerFileFormat(FileFormat.Fasta);
 -    writable = "[PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, Fasta]";
 -    readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GenBank Flatfile, ENA Flatfile, GFF or Jalview features, PDB, mmCIF, Jalview, Fasta]";
 +    writable = "[PFAM, Stockholm, PIR, BLC, AMSA, JSON, PileUp, MSF, Clustal, PHYLIP, HMMER3, Fasta]";
-     readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML, Fasta]";
++    readable = "[PFAM, Stockholm, PIR, BLC, AMSA, HTML, RNAML, JSON, PileUp, MSF, Clustal, PHYLIP, GenBank Flatfile, ENA Flatfile, GFF or Jalview features, PDB, mmCIF, Jalview, HMMER3, BSML, Fasta]";
      assertEquals(formats.getWritableFormats(true).toString(), writable);
      assertEquals(formats.getReadableFormats().toString(), readable);
    }
      for (FileFormatI ff : FileFormat.values())
      {
        assertSame(ff, formats.forName(ff.getName()));
-       assertSame(ff, formats.forName(ff.getName().toUpperCase()));
-       assertSame(ff, formats.forName(ff.getName().toLowerCase()));
+       assertSame(ff, formats.forName(ff.getName().toUpperCase(Locale.ROOT)));
+       assertSame(ff, formats.forName(ff.getName().toLowerCase(Locale.ROOT)));
      }
      assertNull(formats.forName(null));
      assertNull(formats.forName("rubbish"));
       * verify the list of file formats registered matches the enum values
       */
      FileFormats instance = FileFormats.getInstance();
 -    Iterator<FileFormatI> formats = instance.getFormats().iterator();
 +    Iterator<FileFormatI> formats = instance.getFormats()
 +            .iterator();
      FileFormatI[] builtIn = FileFormat.values();
  
      for (FileFormatI ff : builtIn)
@@@ -33,13 -33,13 +33,14 @@@ import java.io.IOException
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
+ import java.util.Locale;
  import java.util.Map;
  
  import javax.swing.JInternalFrame;
  
  import org.testng.Assert;
  import org.testng.AssertJUnit;
 +import org.testng.annotations.AfterMethod;
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
  
@@@ -52,7 -52,6 +53,7 @@@ import jalview.datamodel.AlignmentAnnot
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.GeneLocus;
 +import jalview.datamodel.HiddenMarkovModel;
  import jalview.datamodel.HiddenSequences;
  import jalview.datamodel.Mapping;
  import jalview.datamodel.PDBEntry;
@@@ -66,6 -65,7 +67,6 @@@ import jalview.datamodel.features.Featu
  import jalview.datamodel.features.FeatureMatcherSet;
  import jalview.datamodel.features.FeatureMatcherSetI;
  import jalview.gui.AlignFrame;
 -import jalview.gui.AlignViewport;
  import jalview.gui.AlignmentPanel;
  import jalview.gui.Desktop;
  import jalview.gui.JvOptionPane;
@@@ -92,16 -92,9 +93,16 @@@ import jalview.util.matcher.Condition
  import jalview.viewmodel.AlignmentViewport;
  import jalview.viewmodel.seqfeatures.FeatureRendererModel;
  
 +import junit.extensions.PA;
 +
  @Test(singleThreaded = true)
  public class Jalview2xmlTests extends Jalview2xmlBase
  {
 +  @AfterMethod(alwaysRun = true)
 +  public void tearDown()
 +  {
 +    Desktop.getInstance().closeAll_actionPerformed(null);
 +  }
  
    @Override
    @BeforeClass(alwaysRun = true)
              DataSourceType.FILE);
      assertNotNull(af, "Didn't read input file " + inFile);
      af.loadJalviewDataFile(inAnnot, DataSourceType.FILE, null, null);
 -    AlignViewport viewport = af.getViewport();
 +    AlignViewportI viewport = af.getViewport();
      assertSame(viewport.getGlobalColourScheme().getClass(),
              TCoffeeColourScheme.class, "Didn't set T-coffee colourscheme");
      assertNotNull(
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/exampleFile_2_7.jar", DataSourceType.FILE);
      assertNotNull(af, "Didn't read in the example file correctly.");
 -    assertTrue(Desktop.getAlignFrames().length == 1 + origCount,
 +    assertEquals(Desktop.getAlignFrames().length,
 +            1 + origCount,
              "Didn't gather the views in the example file.");
 -
    }
  
    /**
    @Test(groups = { "Functional" }, enabled = true)
    public void testStoreAndRecoverExpandedviews() throws Exception
    {
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
  
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/exampleFile_2_7.jar", DataSourceType.FILE);
      {
        Assert.fail("Didn't save the expanded view state", e);
      }
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      if (Desktop.getAlignFrames() != null)
      {
        Assert.assertEquals(Desktop.getAlignFrames().length, 0);
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverReferenceSeqSettings() throws Exception
    {
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/exampleFile_2_7.jar", DataSourceType.FILE);
      assertNotNull(af, "Didn't read in the example file correctly.");
      {
        Assert.fail("Didn't save the expanded view state", e);
      }
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      if (Desktop.getAlignFrames() != null)
      {
        Assert.assertEquals(Desktop.getAlignFrames().length, 0);
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverGroupRepSeqs() throws Exception
    {
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
      assertNotNull(af, "Didn't read in the example file correctly.");
      {
        Assert.fail("Didn't save the expanded view state", e);
      }
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      if (Desktop.getAlignFrames() != null)
      {
        Assert.assertEquals(Desktop.getAlignFrames().length, 0);
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverPDBEntry() throws Exception
    {
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      String exampleFile = "examples/3W5V.pdb";
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(exampleFile,
              DataSourceType.FILE);
      {
        Assert.fail("Didn't save the state", e);
      }
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      if (Desktop.getAlignFrames() != null)
      {
        Assert.assertEquals(Desktop.getAlignFrames().length, 0);
                "Mismatch PDBEntry 'Type'");
        Assert.assertNotNull(recov.getFile(),
                "Recovered PDBEntry should have a non-null file entry");
+       Assert.assertEquals(recov.getFile().toLowerCase(Locale.ENGLISH).lastIndexOf("pdb"),recov.getFile().length()-3, "Recovered PDBEntry file should have PDB suffix");
      }
    }
  
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverColourThresholds() throws IOException
    {
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/uniref50.fa", DataSourceType.FILE);
  
 -    AlignViewport av = af.getViewport();
 +    AlignViewportI av = af.getViewport();
      AlignmentI al = av.getAlignment();
  
      /*
              ".jvp");
      tfile.deleteOnExit();
      new Jalview2XML(false).saveState(tfile);
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
              DataSourceType.FILE);
      Assert.assertNotNull(af, "Failed to reload project");
    }
  
    /**
 +   * Load an HMM profile to an alignment, and confirm it is correctly restored
 +   * when reloaded from project
 +   * 
 +   * @throws IOException
 +   */
 +  @Test(groups = { "Functional" })
 +  public void testStoreAndRecoverHmmProfile() throws IOException
 +  {
 +    Desktop.getInstance().closeAll_actionPerformed(null);
 +    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
 +            "examples/uniref50.fa", DataSourceType.FILE);
 +  
 +    AlignViewportI av = af.getViewport();
 +    AlignmentI al = av.getAlignment();
 +
 +    /*
 +     * mimic drag and drop of hmm file on to alignment
 +     */
 +    AlignFrame af2 = new FileLoader().LoadFileWaitTillLoaded(
 +            "examples/uniref50.hmm", DataSourceType.FILE);
 +    al.insertSequenceAt(0,
 +            af2.getViewport().getAlignment().getSequenceAt(0));
 +
 +    /*
 +     * check it loaded in
 +     */
 +    SequenceI hmmSeq = al.getSequenceAt(0);
 +    assertTrue(hmmSeq.hasHMMProfile());
 +    HiddenMarkovModel hmm = hmmSeq.getHMM();
 +    assertSame(hmm.getConsensusSequence(), hmmSeq);
 +
 +    /*
 +     * save project, close windows, reload project, verify
 +     */
 +    File tfile = File.createTempFile("testStoreAndRecoverHmmProfile",
 +            ".jvp");
 +    tfile.deleteOnExit();
 +    new Jalview2XML(false).saveState(tfile);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
 +    af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
 +            DataSourceType.FILE);
 +    Assert.assertNotNull(af, "Failed to reload project");
 +
 +    hmmSeq = al.getSequenceAt(0);
 +    assertTrue(hmmSeq.hasHMMProfile());
 +    assertSame(hmm.getConsensusSequence(), hmmSeq);
 +    Mapping mapToHmmConsensus = (Mapping) PA.getValue(hmm,
 +            "mapToHmmConsensus");
 +    assertNotNull(mapToHmmConsensus);
 +    assertSame(mapToHmmConsensus.getTo(), hmmSeq.getDatasetSequence());
 +  }
 +
 +  /**
     * pre 2.11 - jalview 2.10 erroneously created new dataset entries for each
     * view (JAL-3171) this test ensures we can import and merge those views
     */
    @Test(groups = { "Functional" })
    public void testMergeDatasetsforManyViews() throws IOException
    {
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
  
      // complex project - one dataset, several views on several alignments
      AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
    @Test(groups = "Functional")
    public void testPcaViewAssociation() throws IOException
    {
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      final String PCAVIEWNAME = "With PCA";
      // create a new tempfile
      File tempfile = File.createTempFile("jvPCAviewAssoc", "jvp");
      }
  
      // load again.
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              tempfile.getCanonicalPath(), DataSourceType.FILE);
 -    JInternalFrame[] frames = Desktop.instance.getAllFrames();
 +    JInternalFrame[] frames = Desktop.getInstance().getAllFrames();
      // PCA and the tabbed alignment view should be the only two windows on the
      // desktop
      assertEquals(frames.length, 2,
    @Test(groups = { "Functional" })
    public void testStoreAndRecoverGeneLocus() throws Exception
    {
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      String seqData = ">P30419\nACDE\n>X1235\nGCCTGTGACGAA";
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
              DataSourceType.PASTE);
      {
        Assert.fail("Didn't save the state", e);
      }
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
    
      new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
              DataSourceType.FILE);
@@@ -80,8 -80,7 +80,8 @@@ public class StructureSelectionManagerT
    public void setUp()
    {
      StructureImportSettings.setShowSeqFeatures(true);
 -    ssm = new StructureSelectionManager();
 +    ssm = StructureSelectionManager.getStructureSelectionManager(null);
 +    ssm.resetAll();
    }
  
    @Test(groups = { "Functional" })
      acf3.addMap(new Sequence("s3", "ttt"), new Sequence("p3", "p"),
              new MapList(new int[] { 1, 3 }, new int[] { 1, 1 }, 1, 1));
  
-     List<AlignedCodonFrame> set1 = new ArrayList<AlignedCodonFrame>();
+     List<AlignedCodonFrame> set1 = new ArrayList<>();
      set1.add(acf1);
      set1.add(acf2);
-     List<AlignedCodonFrame> set2 = new ArrayList<AlignedCodonFrame>();
+     List<AlignedCodonFrame> set2 = new ArrayList<>();
      set2.add(acf2);
      set2.add(acf3);
  
      ssm.registerMappings(set2);
      ssm.registerMappings(set2);
  
 -    assertEquals(3, ssm.getSequenceMappings().size());
 -    assertTrue(ssm.getSequenceMappings().contains(acf1));
 -    assertTrue(ssm.getSequenceMappings().contains(acf2));
 -    assertTrue(ssm.getSequenceMappings().contains(acf3));
 +    List<AlignedCodonFrame> mappings = ssm.getSequenceMappings();
 +    assertEquals(3, mappings.size());
 +    assertTrue(mappings.contains(acf1));
 +    assertTrue(mappings.contains(acf2));
 +    assertTrue(mappings.contains(acf3));
    }
  
    /**
      SequenceI seq = new Sequence(
              "1GAQ|B",
              "ATYNVKLITPEGEVELQVPDDVYILDQAEEDGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQSYLDDGQIADGWVLTCHAYPTSDVVIETHKEEELTGA");
 -    StructureSelectionManager sm = new StructureSelectionManager();
 +    StructureSelectionManager sm = StructureSelectionManager.getStructureSelectionManager(null);
      sm.setProcessSecondaryStructure(true);
      sm.setAddTempFacAnnot(true);
      StructureFile pmap = sm.setMapping(true, new SequenceI[] { seq },
    {
      // for some reason 'BeforeMethod' (which should be inherited from
      // Jalview2XmlBase isn't always called)...
 -    Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.getInstance().closeAll_actionPerformed(null);
      try { 
        Thread.sleep(200);
      } catch (Exception foo) {}; 
      SequenceI seq = new Sequence("4IM2|A",
              "LDFCIRNIEKTVMGEISDIHTKLLRLSSSQGTIE");
      String P4IM2_MISSING = "examples/testdata/4IM2_missing.pdb";
 -    StructureSelectionManager sm = new StructureSelectionManager();
 +    StructureSelectionManager sm = StructureSelectionManager.getStructureSelectionManager(null);
      sm.setProcessSecondaryStructure(true);
      sm.setAddTempFacAnnot(true);
      StructureFile pmap = sm.setMapping(true, new SequenceI[] { seq },
      assertEquals(1, pmap.getSeqs().size());
      assertEquals("4IM2|A", pmap.getSeqs().get(0).getName());
  
-     List<int[]> structuremap1 = new ArrayList(
+     List<int[]> structuremap1 = new ArrayList<>(
              sm.getMapping(P4IM2_MISSING)[0]
                      .getPDBResNumRanges(seq.getStart(), seq.getEnd()));
  
      // positional mapping to atoms for color by structure is still wrong, even
      // though panel looks correct.
  
-     StructureMappingcommandSet smcr[] = JmolCommands
-             .getColourBySequenceCommand(apssm,
+     String[] smcr = new JmolCommands().colourBySequence(apssm,
              new String[]
              { pdbe.getFile() },
              new SequenceI[][]
                      new SequenceRenderer(alf.alignPanel.getAlignViewport()),
                      alf.alignPanel);
      // Expected - all residues are white
-     for (StructureMappingcommandSet smm : smcr)
+     for (String c : smcr)
      {
-       for (String c : smm.commands)
-       {
-         System.out.println(c);
-       }
+       assertTrue(c.contains("color[255,255,255]"));
+       System.out.println(c);
      }
    }
  
   */
  package jalview.structures.models;
  
+ import static org.testng.Assert.assertEquals;
  import static org.testng.Assert.assertFalse;
- import static org.testng.AssertJUnit.assertEquals;
- import static org.testng.AssertJUnit.assertTrue;
+ import static org.testng.Assert.assertNotNull;
+ import static org.testng.Assert.assertTrue;
+ import java.awt.Color;
+ import java.io.IOException;
+ import java.util.Arrays;
+ import java.util.BitSet;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Map;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
  
  import jalview.api.AlignmentViewPanel;
  import jalview.api.SequenceRenderer;
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentI;
- import jalview.datamodel.HiddenColumns;
+ import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.PDBEntry.Type;
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceI;
+ import jalview.ext.rbvi.chimera.ChimeraCommands;
+ import jalview.gui.AlignFrame;
  import jalview.gui.JvOptionPane;
+ import jalview.gui.StructureViewer.ViewerType;
  import jalview.io.DataSourceType;
  import jalview.io.FileFormats;
- import jalview.schemes.ColourSchemeI;
+ import jalview.io.FileLoader;
+ import jalview.schemes.JalviewColourScheme;
  import jalview.structure.AtomSpec;
- import jalview.structure.StructureMappingcommandSet;
+ import jalview.structure.AtomSpecModel;
+ import jalview.structure.StructureCommandI;
+ import jalview.structure.StructureMapping;
  import jalview.structure.StructureSelectionManager;
- import jalview.structures.models.AAStructureBindingModel.SuperposeData;
- import java.awt.Color;
- import java.io.IOException;
- import java.util.Arrays;
- import java.util.BitSet;
- import java.util.List;
- import org.testng.annotations.BeforeClass;
- import org.testng.annotations.BeforeMethod;
- import org.testng.annotations.Test;
+ import junit.extensions.PA;
  
  /**
   * Unit tests for non-abstract methods of abstract base class
@@@ -130,7 -138,7 +138,7 @@@ public class AAStructureBindingModelTes
      PDBEntry importedPDB = new PDBEntry("3A6S", "", Type.PDB,
              "Paste");
      AAStructureBindingModel binder = new AAStructureBindingModel(
 -            new StructureSelectionManager(), new PDBEntry[]
 +            StructureSelectionManager.getStructureSelectionManager(null), new PDBEntry[]
              { importedPDB },
              new SequenceI[][]
              { importedAl.getSequencesArray() }, null)
        @Override
        public void updateColours(Object source)
        {
-         // TODO Auto-generated method stub
-         
        }
        
        @Override
        public void releaseReferences(Object svl)
        {
-         // TODO Auto-generated method stub
-         
        }
        
        @Override
        public String[] getStructureFiles()
        {
-         // TODO Auto-generated method stub
-         return null;
-       }
-       
-       @Override
-       public String superposeStructures(AlignmentI[] alignments,
-               int[] structureIndices, HiddenColumns[] hiddenCols)
-       {
-         // TODO Auto-generated method stub
          return null;
        }
        
        @Override
-       public void setJalviewColourScheme(ColourSchemeI cs)
-       {
-         // TODO Auto-generated method stub
-         
-       }
-       
-       @Override
-       public void setBackgroundColour(Color col)
-       {
-         // TODO Auto-generated method stub
-         
-       }
-       
-       @Override
        public void highlightAtoms(List<AtomSpec> atoms)
        {
-         // TODO Auto-generated method stub
-         
        }
        
        @Override
        public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
        {
-         // TODO Auto-generated method stub
          return null;
        }
-       
        @Override
-       public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
+       protected List<String> executeCommand(StructureCommandI command,
+               boolean getReply)
        {
-         // TODO Auto-generated method stub
          return null;
        }
-       
        @Override
-       protected StructureMappingcommandSet[] getColourBySequenceCommands(
-               String[] files, SequenceRenderer sr, AlignmentViewPanel avp)
+       protected String getModelIdForFile(String chainId)
        {
-         // TODO Auto-generated method stub
-         return null;
+         return "";
        }
-       
        @Override
-       public List<String> getChainNames()
+       protected ViewerType getViewerType()
        {
-         // TODO Auto-generated method stub
          return null;
        }
-       
-       @Override
-       protected void colourBySequence(
-               StructureMappingcommandSet[] colourBySequenceCommands)
-       {
-         // TODO Auto-generated method stub
-         
-       }
-       
-       @Override
-       public void colourByCharge()
-       {
-         // TODO Auto-generated method stub
-         
-       }
-       
-       @Override
-       public void colourByChain()
-       {
-         // TODO Auto-generated method stub
-         
-       }
      };
      String[][] chains = binder.getChains();
      assertFalse(chains == null || chains[0] == null,
              "No chains discovered by binding");
-     assertEquals(2, chains[0].length);
-     assertEquals("A", chains[0][0]);
-     assertEquals("B", chains[0][1]);
+     assertEquals(chains[0].length, 2);
+     assertEquals(chains[0][0], "A");
+     assertEquals(chains[0][1], "B");
    }
    AAStructureBindingModel testee;
  
      seqs[0] = new SequenceI[] { seq1a, seq1b };
      seqs[1] = new SequenceI[] { seq2 };
      seqs[2] = new SequenceI[] { seq3 };
 -    StructureSelectionManager ssm = new StructureSelectionManager();
 +    StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager(null);
  
      ssm.setMapping(new SequenceI[] { seq1a, seq1b }, null, PDB_1,
              DataSourceType.PASTE, null);
      ssm.setMapping(new SequenceI[] { seq3 }, null, PDB_3,
              DataSourceType.PASTE, null);
  
-     testee = new AAStructureBindingModel(ssm, pdbFiles, seqs, null)
+     testee = newBindingModel(pdbFiles, seqs, ssm, null);
+   }
+   /**
+    * A helper method to construct the test target object
+    * 
+    * @param pdbFiles
+    * @param seqs
+    * @param ssm
+    * @param alignPanel 
+    */
+   protected AAStructureBindingModel newBindingModel(PDBEntry[] pdbFiles,
+           SequenceI[][] seqs,
+           StructureSelectionManager ssm, AlignmentViewPanel avp)
+   {
+     AAStructureBindingModel model = new AAStructureBindingModel(ssm,
+             pdbFiles, seqs, null)
      {
        @Override
        public String[] getStructureFiles()
        {
-         return new String[] { "INLINE1YCS", "INLINE3A6S", "INLINE1OOT" };
+         String[] files = new String[getPdbCount()];
+         for (int i = 0; i < this.getPdbCount(); i++)
+         {
+           files[i] = getPdbEntry(i).getFile();
+         }
+         return files;
        }
  
        @Override
        }
  
        @Override
-       public List<String> getChainNames()
-       {
-         return null;
-       }
-       @Override
-       public void setJalviewColourScheme(ColourSchemeI cs)
-       {
-       }
-       @Override
-       public String superposeStructures(AlignmentI[] als, int[] alm,
-               HiddenColumns[] alc)
-       {
-         return null;
-       }
-       @Override
-       public void setBackgroundColour(Color col)
-       {
-       }
-       @Override
-       protected StructureMappingcommandSet[] getColourBySequenceCommands(
-               String[] files, SequenceRenderer sr, AlignmentViewPanel avp)
-       {
-         return null;
-       }
-       @Override
        public SequenceRenderer getSequenceRenderer(
-               AlignmentViewPanel alignment)
+               AlignmentViewPanel avp)
        {
-         return null;
+         return avp == null ? null
+                 : new jalview.gui.SequenceRenderer(
+                         avp.getAlignViewport());
        }
  
        @Override
-       protected void colourBySequence(
-               StructureMappingcommandSet[] colourBySequenceCommands)
-       {
-       }
-       @Override
-       public void colourByChain()
+       protected List<String> executeCommand(StructureCommandI command,
+               boolean getReply)
        {
+         return null;
        }
  
+       /*
+        * for this test, let structure model ids be 0, 1, ...
+        * corresponding to first, second etc pdbfile
+        */
        @Override
-       public void colourByCharge()
+       protected String getModelIdForFile(String pdbfile)
        {
+         for (int i = 0; i < this.getPdbCount(); i++)
+         {
+           if (pdbfile.equals(this.getPdbEntry(i).getFile()))
+           {
+             return String.valueOf(i);
+           }
+         }
+         return "";
        }
  
        @Override
-       public FeatureRenderer getFeatureRenderer(
-               AlignmentViewPanel alignment)
+       protected ViewerType getViewerType()
        {
          return null;
        }
      };
+     PA.setValue(model, "commandGenerator", new ChimeraCommands());
+     return model;
    }
  
    /**
      /*
       * create a data bean to hold data per structure file
       */
-     SuperposeData[] structs = new SuperposeData[testee.getStructureFiles().length];
+     AAStructureBindingModel.SuperposeData[] structs = new AAStructureBindingModel.SuperposeData[testee.getStructureFiles().length];
      for (int i = 0; i < structs.length; i++)
      {
-       structs[i] = testee.new SuperposeData(al.getWidth());
+       structs[i] = new AAStructureBindingModel.SuperposeData(al.getWidth(), "0");
      }
      /*
       * initialise BitSet of 'superposable columns' to true (would be false for
      int refStructure = testee
              .findSuperposableResidues(al, matched, structs);
  
-     assertEquals(0, refStructure);
+     assertEquals(refStructure, 0);
  
      /*
       * only ungapped, structure-mapped columns are superposable
      assertTrue(matched.get(4));
      assertTrue(matched.get(5)); // gap in second sequence
  
-     assertEquals("1YCS", structs[0].pdbId);
-     assertEquals("3A6S", structs[1].pdbId);
-     assertEquals("1OOT", structs[2].pdbId);
-     assertEquals("A", structs[0].chain); // ? struct has chains A _and_ B
-     assertEquals("B", structs[1].chain);
-     assertEquals("A", structs[2].chain);
+     assertEquals(structs[0].pdbId, "1YCS");
+     assertEquals(structs[1].pdbId, "3A6S");
+     assertEquals(structs[2].pdbId, "1OOT");
+     assertEquals(structs[0].chain, "A"); // ? struct has chains A _and_ B
+     assertEquals(structs[1].chain, "B");
+     assertEquals(structs[2].chain, "A");
      // the 0's for unsuperposable positions propagate down the columns:
-     assertEquals("[0, 97, 98, 99, 100, 102]",
-             Arrays.toString(structs[0].pdbResNo));
-     assertEquals("[0, 2, 0, 3, 4, 5]", Arrays.toString(structs[1].pdbResNo));
-     assertEquals("[0, 8, 0, 0, 10, 12]",
-             Arrays.toString(structs[2].pdbResNo));
+     assertEquals(Arrays.toString(structs[0].pdbResNo),
+             "[0, 97, 98, 99, 100, 102]");
+     assertEquals(Arrays.toString(structs[1].pdbResNo),
+             "[0, 2, 0, 3, 4, 5]");
+     assertEquals(Arrays.toString(structs[2].pdbResNo),
+             "[0, 8, 0, 0, 10, 12]");
    }
  
    @Test(groups = { "Functional" })
    public void testFindSuperposableResidues_hiddenColumn()
    {
-     SuperposeData[] structs = new SuperposeData[al.getHeight()];
+     AAStructureBindingModel.SuperposeData[] structs = new AAStructureBindingModel.SuperposeData[al.getHeight()];
      for (int i = 0; i < structs.length; i++)
      {
-       structs[i] = testee.new SuperposeData(al.getWidth());
+       structs[i] = new AAStructureBindingModel.SuperposeData(al.getWidth(), "0");
      }
      /*
       * initialise BitSet of 'superposable columns' to true (would be false for
      int refStructure = testee
              .findSuperposableResidues(al, matched, structs);
  
-     assertEquals(0, refStructure);
+     assertEquals(refStructure, 0);
  
      // only ungapped, structure-mapped columns are not superposable
      assertFalse(matched.get(0));
      assertFalse(matched.get(4)); // superposable, but hidden, column
      assertTrue(matched.get(5));
    }
- }
+   @Test(groups = { "Functional" })
+   public void testBuildColoursMap()
+   {
+     /*
+      * load these sequences, coloured by Strand propensity,
+      * with columns 2-4 hidden
+      */
+     String fasta = ">seq1\nMHRSQSSSGG\n>seq2\nMVRSNGGSSS";
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(fasta,
+             DataSourceType.PASTE);
+     AlignmentI al = af.getViewport().getAlignment();
+     af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
+     ColumnSelection cs = new ColumnSelection();
+     cs.addElement(2);
+     cs.addElement(3);
+     cs.addElement(4);
+     af.getViewport().setColumnSelection(cs);
+     af.hideSelColumns_actionPerformed(null);
+     SequenceI seq1 = al.getSequenceAt(0);
+     SequenceI seq2 = al.getSequenceAt(1);
+     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
+     PDBEntry[] pdbFiles = new PDBEntry[2];
+     pdbFiles[0] = new PDBEntry("PDB1", "A", Type.PDB, "seq1.pdb");
+     pdbFiles[1] = new PDBEntry("PDB2", "B", Type.PDB, "seq2.pdb");
+     StructureSelectionManager ssm = new StructureSelectionManager();
+   
+     /*
+      * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
+      */
+     HashMap<Integer, int[]> map = new HashMap<>();
+     for (int pos = 1; pos <= seq1.getLength(); pos++)
+     {
+       map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
+     }
+     StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
+             "A", map, null);
+     ssm.addStructureMapping(sm1);
+     StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
+             "B", map, null);
+     ssm.addStructureMapping(sm2);
+     AAStructureBindingModel binding = newBindingModel(pdbFiles, seqs, ssm,
+             af.alignPanel);
+     /*
+      * method under test builds a map of structures residues by colour
+      * verify the map holds what it should
+      */
+     Map<Object, AtomSpecModel> colours = binding.buildColoursMap(ssm, seqs,
+             af.alignPanel);
+     ChimeraCommands helper = new ChimeraCommands();
+     
+     /*
+      * M colour is #82827d (see strand.html help page)
+      * sequence residue 1 mapped to structure residue 21
+      */
+     Color mColor = new Color(0x82827d);
+     AtomSpecModel atomSpec = colours.get(mColor);
+     assertNotNull(atomSpec);
+     assertEquals(helper.getAtomSpec(atomSpec, false), "#0:21.A|#1:21.B");
+     /*
+      * H colour is #60609f, seq1.2 mapped to structure 0 residue 22
+      */
+     Color hColor = new Color(0x60609f);
+     atomSpec = colours.get(hColor);
+     assertNotNull(atomSpec);
+     assertEquals(helper.getAtomSpec(atomSpec, false), "#0:22.A");
+     /*
+      * V colour is #ffff00, seq2.2 mapped to structure 1 residue 22
+      */
+     Color vColor = new Color(0xffff00);
+     atomSpec = colours.get(vColor);
+     assertNotNull(atomSpec);
+     assertEquals(helper.getAtomSpec(atomSpec, false), "#1:22.B");
+     /*
+      * hidden columns are Gray (128, 128, 128)
+      * sequence positions 3-5 mapped to structure residues 23-25
+      */
+     Color gray = new Color(128, 128, 128);
+     atomSpec = colours.get(gray);
+     assertNotNull(atomSpec);
+     assertEquals(helper.getAtomSpec(atomSpec, false), "#0:23-25.A|#1:23-25.B");
+     /*
+      * S and G are both coloured #4949b6, structure residues 26-30
+      */
+     Color sgColour = new Color(0x4949b6);
+     atomSpec = colours.get(sgColour);
+     assertNotNull(atomSpec);
+     assertEquals(helper.getAtomSpec(atomSpec, false),
+             "#0:26-30.A|#1:26-30.B");
+   }
+ }
@@@ -25,19 -25,27 +25,27 @@@ import static org.testng.AssertJUnit.as
  import static org.testng.AssertJUnit.assertNull;
  import static org.testng.AssertJUnit.assertSame;
  import static org.testng.AssertJUnit.assertTrue;
+ import static org.testng.AssertJUnit.fail;
  import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
  
- import jalview.gui.JvOptionPane;
  import java.util.ArrayList;
  import java.util.Arrays;
+ import java.util.BitSet;
  import java.util.List;
  
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
  
+ import jalview.bin.Cache;
+ import jalview.gui.JvOptionPane;
  public class MapListTest
  {
+   @BeforeClass(alwaysRun = true)
+   public void setUp()
+   {
+     Cache.initLogger();
+   }
  
    @BeforeClass(alwaysRun = true)
    public void setUpJvOptionPane()
      JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
    }
  
-   @Test(groups = { "Functional" })
+   @Test(groups = { "Functional" }, enabled = false)
    public void testSomething()
    {
-     MapList ml = new MapList(new int[] { 1, 5, 10, 15, 25, 20 }, new int[] {
-         51, 1 }, 1, 3);
+     MapList ml = new MapList(new int[] { 1, 5, 10, 15, 25, 20 },
+             new int[]
+             { 51, 1 }, 1, 3);
      MapList ml1 = new MapList(new int[] { 1, 3, 17, 4 },
-             new int[] { 51, 1 }, 1, 3);
+             new int[]
+             { 51, 1 }, 1, 3);
      MapList ml2 = new MapList(new int[] { 1, 60 }, new int[] { 1, 20 }, 3,
              1);
      // test internal consistency
      int to[] = new int[51];
      testMap(ml, 1, 60);
-     MapList mldna = new MapList(new int[] { 2, 2, 6, 8, 12, 16 }, new int[]
-     { 1, 3 }, 3, 1);
+     MapList mldna = new MapList(new int[] { 2, 2, 6, 8, 12, 16 },
+             new int[]
+             { 1, 3 }, 3, 1);
      int[] frm = mldna.locateInFrom(1, 1);
      testLocateFrom(mldna, 1, 1, new int[] { 2, 2, 6, 7 });
      testMap(mldna, 1, 3);
      assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
      assertEquals("[1, 6]", Arrays.toString(ml.locateInFrom(1, 2)));
      assertEquals("[1, 9]", Arrays.toString(ml.locateInFrom(1, 3)));
+     // reversed range treated as if forwards:
+     assertEquals("[1, 9]", Arrays.toString(ml.locateInFrom(3, 1)));
      assertEquals("[1, 12]", Arrays.toString(ml.locateInFrom(1, 4)));
      assertEquals("[4, 9]", Arrays.toString(ml.locateInFrom(2, 3)));
      assertEquals("[4, 12]", Arrays.toString(ml.locateInFrom(2, 4)));
      assertEquals("[7, 12]", Arrays.toString(ml.locateInFrom(3, 4)));
      assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
  
+     /*
+      * partial overlap
+      */
+     assertEquals("[1, 12]", Arrays.toString(ml.locateInFrom(1, 5)));
+     assertEquals("[1, 3]", Arrays.toString(ml.locateInFrom(-1, 1)));
+     /*
+      * no overlap
+      */
      assertNull(ml.locateInFrom(0, 0));
-     assertNull(ml.locateInFrom(1, 5));
-     assertNull(ml.locateInFrom(-1, 1));
+     
    }
  
    /**
      assertEquals("[10, 10, 12, 12, 14, 14]",
              Arrays.toString(ml.locateInFrom(3, 3)));
      assertEquals("[16, 18]", Arrays.toString(ml.locateInFrom(4, 4)));
+     
+     /*
+      * codons at 11-16, 21-26, 31-36 mapped to peptide positions 1, 3-4, 6-8
+      */
+     ml = new MapList(new int[] { 11, 16, 21, 26, 31, 36 },
+             new int[]
+             { 1, 1, 3, 4, 6, 8 }, 3, 1);
+     assertArrayEquals(new int[] { 11, 13 }, ml.locateInFrom(1, 1));
+     assertArrayEquals(new int[] { 11, 16 }, ml.locateInFrom(1, 3));
+     assertArrayEquals(new int[] { 11, 16, 21, 23 }, ml.locateInFrom(1, 4));
+     assertArrayEquals(new int[] { 14, 16, 21, 23 }, ml.locateInFrom(3, 4));
+   }
+   @Test(groups = { "Functional" })
+   public void testLocateInFrom_reverseStrand()
+   {
+     int[] codons = new int[] { 12, 1 };
+     int[] protein = new int[] { 1, 4 };
+     MapList ml = new MapList(codons, protein, 3, 1);
+     assertEquals("[12, 10]", Arrays.toString(ml.locateInFrom(1, 1)));
+     assertEquals("[9, 4]", Arrays.toString(ml.locateInFrom(2, 3)));
    }
  
    /**
      assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(1, 12)));
      assertEquals("[2, 2]", Arrays.toString(ml.locateInTo(4, 6)));
      assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(4, 12)));
+     // reverse range treated as if forwards:
+     assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(12, 4)));
  
      /*
       * A part codon is treated as if a whole one.
      assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(3, 11)));
      assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(5, 11)));
  
+     /*
+      * partial overlap
+      */
+     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(1, 13)));
+     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(-1, 2)));
+     
+     /*
+      * no overlap
+      */
      assertNull(ml.locateInTo(0, 0));
-     assertNull(ml.locateInTo(1, 13));
-     assertNull(ml.locateInTo(-1, 1));
    }
  
    /**
      MapList ml = new MapList(codons, protein, 3, 1);
  
      /*
-      * Can't map from an unmapped position
-      */
-     assertNull(ml.locateInTo(1, 2));
-     assertNull(ml.locateInTo(2, 4));
-     assertNull(ml.locateInTo(4, 4));
-     /*
-      * Valid range or subrange of codon1 maps to protein1.
+      * Valid range or subrange of codon1 maps to protein1
       */
      assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 2)));
      assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(3, 3)));
      // codon positions 7 to 17 (part) cover proteins 2/3/4 at positions 3/4/6
      assertEquals("[3, 4, 6, 6]", Arrays.toString(ml.locateInTo(7, 17)));
  
+     /*
+      * partial overlap
+      */
+     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 2)));
+     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 4)));
+     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 4)));
+     
+     /*
+      * no overlap
+      */
+     assertNull(ml.locateInTo(4, 4));
    }
  
    /**
      List<int[]> ranges = new ArrayList<>();
      ranges.add(new int[] { 2, 3 });
      ranges.add(new int[] { 5, 6 });
-     assertEquals("[2, 3, 5, 6]", Arrays.toString(MapList.getRanges(ranges)));
+     assertEquals("[2, 3, 5, 6]",
+             Arrays.toString(MapList.getRanges(ranges)));
    }
  
    /**
      assertEquals(6, ml2.getToHighest());
      assertEquals("{[2, 3], [5, 7], [9, 10], [12, 12], [14, 14], [16, 18]}",
              prettyPrint(ml2.getFromRanges()));
-     assertEquals("{[1, 1], [3, 4], [6, 6]}", prettyPrint(ml2.getToRanges()));
+     assertEquals("{[1, 1], [3, 4], [6, 6]}",
+             prettyPrint(ml2.getToRanges()));
  
      /*
       * reverse direction
    }
  
    /**
-    * Test constructor can merge consecutive ranges
+    * Test constructor used to merge consecutive ranges but now just leaves them
+    * as supplied (JAL-3751)
     */
    @Test(groups = { "Functional" })
    public void testConstructor_mergeRanges()
    {
-     int[] codons = { 2, 3, 3, 7, 9, 10, 12, 12, 14, 14, 16, 17 };
-     int[] protein = { 1, 1, 1, 3, 6, 6 };
+     int[] codons = { 2, 3, 3, 7, 9, 10, 12, 12, 13, 14, 16, 17 };
+     int[] protein = { 1, 1, 2, 3, 6, 6 };
      MapList ml = new MapList(codons, protein, 3, 1);
      assertEquals(3, ml.getFromRatio());
      assertEquals(2, ml.getFromLowest());
      assertEquals(17, ml.getFromHighest());
      assertEquals(1, ml.getToLowest());
      assertEquals(6, ml.getToHighest());
-     assertEquals("{[2, 7], [9, 10], [12, 12], [14, 14], [16, 17]}",
+     assertEquals("{[2, 3], [3, 7], [9, 10], [12, 12], [13, 14], [16, 17]}",
              prettyPrint(ml.getFromRanges()));
-     assertEquals("{[1, 3], [6, 6]}", prettyPrint(ml.getToRanges()));
+     assertEquals("{[1, 1], [2, 3], [6, 6]}", prettyPrint(ml.getToRanges()));
    }
  
    /**
    @Test(groups = { "Functional" })
    public void testToString()
    {
-     MapList ml = new MapList(new int[] { 1, 5, 10, 15, 25, 20 }, new int[] {
-         51, 1 }, 1, 3);
+     MapList ml = new MapList(new int[] { 1, 5, 10, 15, 25, 20 },
+             new int[]
+             { 51, 1 }, 1, 3);
      String s = ml.toString();
      assertEquals("[ [1, 5] [10, 15] [25, 20] ] 1:3 to [ [51, 1] ]", s);
    }
    public void testAddMapList()
    {
      MapList ml = new MapList(new int[] { 11, 15, 20, 25, 35, 30 },
-             new int[] { 72, 22 }, 1, 3);
+             new int[]
+             { 72, 22 }, 1, 3);
      assertEquals(11, ml.getFromLowest());
      assertEquals(35, ml.getFromHighest());
      assertEquals(22, ml.getToLowest());
      assertEquals(72, ml.getToHighest());
  
-     MapList ml2 = new MapList(new int[] { 2, 4, 37, 40 }, new int[] { 12,
-         17, 78, 83, 88, 96 }, 1, 3);
+     MapList ml2 = new MapList(new int[] { 2, 4, 37, 40 },
+             new int[]
+             { 12, 17, 78, 83, 88, 96 }, 1, 3);
      ml.addMapList(ml2);
      assertEquals(2, ml.getFromLowest());
      assertEquals(40, ml.getFromHighest());
    public void testAddMapList_sameMap()
    {
      MapList ml = new MapList(new int[] { 11, 15, 20, 25, 35, 30 },
-             new int[] { 72, 22 }, 1, 3);
+             new int[]
+             { 72, 22 }, 1, 3);
      String before = ml.toString();
      ml.addMapList(ml);
      assertEquals(before, ml.toString());
      MapList ml = new MapList(new int[] { 11, 15 }, new int[] { 72, 58 }, 1,
              3);
  
-     MapList ml2 = new MapList(new int[] { 15, 16 }, new int[] { 58, 53 },
-             1, 3);
+     MapList ml2 = new MapList(new int[] { 15, 16 }, new int[] { 58, 53 }, 1,
+             3);
      ml.addMapList(ml2);
      assertEquals("[ [11, 16] ] 1:3 to [ [72, 53] ]", ml.toString());
    }
  
 -  @Test(groups = "Functional")
 -  public void testAddRange()
 -  {
 -    int[] range = { 1, 5 };
 -    List<int[]> ranges = new ArrayList<>();
 -
 -    // add to empty list:
 -    MapList.addRange(range, ranges);
 -    assertEquals(1, ranges.size());
 -    assertSame(range, ranges.get(0));
 -
 -    // extend contiguous (same position):
 -    MapList.addRange(new int[] { 5, 10 }, ranges);
 -    assertEquals(1, ranges.size());
 -    assertEquals(1, ranges.get(0)[0]);
 -    assertEquals(10, ranges.get(0)[1]);
 -
 -    // extend contiguous (next position):
 -    MapList.addRange(new int[] { 11, 15 }, ranges);
 -    assertEquals(1, ranges.size());
 -    assertEquals(1, ranges.get(0)[0]);
 -    assertEquals(15, ranges.get(0)[1]);
 -
 -    // change direction: range is not merged:
 -    MapList.addRange(new int[] { 16, 10 }, ranges);
 -    assertEquals(2, ranges.size());
 -    assertEquals(16, ranges.get(1)[0]);
 -    assertEquals(10, ranges.get(1)[1]);
 -
 -    // extend reverse contiguous (same position):
 -    MapList.addRange(new int[] { 10, 8 }, ranges);
 -    assertEquals(2, ranges.size());
 -    assertEquals(16, ranges.get(1)[0]);
 -    assertEquals(8, ranges.get(1)[1]);
 -
 -    // extend reverse contiguous (next position):
 -    MapList.addRange(new int[] { 7, 6 }, ranges);
 -    assertEquals(2, ranges.size());
 -    assertEquals(16, ranges.get(1)[0]);
 -    assertEquals(6, ranges.get(1)[1]);
 -
 -    // change direction: range is not merged:
 -    MapList.addRange(new int[] { 6, 9 }, ranges);
 -    assertEquals(3, ranges.size());
 -    assertEquals(6, ranges.get(2)[0]);
 -    assertEquals(9, ranges.get(2)[1]);
 -
 -    // not contiguous: not merged
 -    MapList.addRange(new int[] { 11, 12 }, ranges);
 -    assertEquals(4, ranges.size());
 -    assertEquals(11, ranges.get(3)[0]);
 -    assertEquals(12, ranges.get(3)[1]);
 -  }
 -
    /**
     * Check state after construction
     */
    public void testIsFromForwardStrand()
    {
      // [3-9] declares forward strand
-     MapList ml = new MapList(new int[] { 2, 2, 3, 9, 12, 11 }, new int[] {
-         20, 11 }, 1, 1);
+     MapList ml = new MapList(new int[] { 2, 2, 3, 9, 12, 11 },
+             new int[]
+             { 20, 11 }, 1, 1);
      assertTrue(ml.isFromForwardStrand());
  
      // [11-5] declares reverse strand ([13-14] is ignored)
      ml = new MapList(new int[] { 2, 2, 11, 5, 13, 14 },
-             new int[] { 20, 11 }, 1, 1);
+             new int[]
+             { 20, 11 }, 1, 1);
      assertFalse(ml.isFromForwardStrand());
  
      // all single position ranges - defaults to forward strand
    }
  
    /**
-    * Test the method that merges a list of ranges where possible
+    * Test the method that merges contiguous ranges
     */
    @Test(groups = { "Functional" })
    public void testCoalesceRanges()
      // merging in forward direction:
      ranges.clear();
      ranges.add(new int[] { 1, 3 });
-     ranges.add(new int[] { 4, 5 });
-     ranges.add(new int[] { 5, 5 });
-     ranges.add(new int[] { 5, 7 });
+     ranges.add(new int[] { 4, 5 }); // contiguous
+     ranges.add(new int[] { 5, 5 }); // overlap!
+     ranges.add(new int[] { 6, 7 }); // contiguous
      List<int[]> merged = MapList.coalesceRanges(ranges);
-     assertEquals(1, merged.size());
-     assertArrayEquals(new int[] { 1, 7 }, merged.get(0));
+     assertEquals(2, merged.size());
+     assertArrayEquals(new int[] { 1, 5 }, merged.get(0));
+     assertArrayEquals(new int[] { 5, 7 }, merged.get(1));
      // verify input list is unchanged
      assertEquals(4, ranges.size());
      assertArrayEquals(new int[] { 1, 3 }, ranges.get(0));
      assertArrayEquals(new int[] { 4, 5 }, ranges.get(1));
      assertArrayEquals(new int[] { 5, 5 }, ranges.get(2));
-     assertArrayEquals(new int[] { 5, 7 }, ranges.get(3));
+     assertArrayEquals(new int[] { 6, 7 }, ranges.get(3));
  
      // merging in reverse direction:
      ranges.clear();
      ranges.add(new int[] { 7, 5 });
-     ranges.add(new int[] { 5, 4 });
-     ranges.add(new int[] { 4, 4 });
-     ranges.add(new int[] { 3, 1 });
+     ranges.add(new int[] { 5, 4 }); // overlap
+     ranges.add(new int[] { 4, 4 }); // overlap
+     ranges.add(new int[] { 3, 1 }); // contiguous
      merged = MapList.coalesceRanges(ranges);
-     assertEquals(1, merged.size());
-     assertArrayEquals(new int[] { 7, 1 }, merged.get(0));
+     assertEquals(3, merged.size());
+     assertArrayEquals(new int[] { 7, 5 }, merged.get(0));
+     assertArrayEquals(new int[] { 5, 4 }, merged.get(1));
+     assertArrayEquals(new int[] { 4, 1 }, merged.get(2));
  
      // merging with switches of direction:
      ranges.clear();
      ranges.add(new int[] { 1, 3 });
-     ranges.add(new int[] { 4, 5 });
-     ranges.add(new int[] { 5, 5 });
-     ranges.add(new int[] { 6, 6 });
+     ranges.add(new int[] { 4, 5 }); // contiguous
+     ranges.add(new int[] { 5, 5 }); // overlap
+     ranges.add(new int[] { 6, 6 }); // contiguous
      ranges.add(new int[] { 12, 10 });
-     ranges.add(new int[] { 9, 8 });
-     ranges.add(new int[] { 8, 8 });
-     ranges.add(new int[] { 7, 7 });
+     ranges.add(new int[] { 9, 8 }); // contiguous
+     ranges.add(new int[] { 8, 8 }); // overlap
+     ranges.add(new int[] { 7, 7 }); // contiguous
      merged = MapList.coalesceRanges(ranges);
-     assertEquals(2, merged.size());
-     assertArrayEquals(new int[] { 1, 6 }, merged.get(0));
-     assertArrayEquals(new int[] { 12, 7 }, merged.get(1));
-   }
-   /**
-    * Test the method that merges a list of ranges where possible
-    */
-   @Test(groups = { "Functional" })
-   public void testCoalesceRanges_withOverlap()
-   {
-     List<int[]> ranges = new ArrayList<>();
-     ranges.add(new int[] { 1, 3 });
-     ranges.add(new int[] { 2, 5 });
-     /*
-      * [2, 5] should extend [1, 3]
-      */
-     List<int[]> merged = MapList.coalesceRanges(ranges);
-     assertEquals(1, merged.size());
+     assertEquals(4, merged.size());
      assertArrayEquals(new int[] { 1, 5 }, merged.get(0));
+     assertArrayEquals(new int[] { 5, 6 }, merged.get(1));
+     assertArrayEquals(new int[] { 12, 8 }, merged.get(2));
+     assertArrayEquals(new int[] { 8, 7 }, merged.get(3));
  
-     /*
-      * a subsumed interval should be dropped
-      */
-     ranges.clear();
-     ranges.add(new int[] { 1, 6 });
-     ranges.add(new int[] { 2, 4 });
-     merged = MapList.coalesceRanges(ranges);
-     assertEquals(1, merged.size());
-     assertArrayEquals(new int[] { 1, 6 }, merged.get(0));
-     ranges.clear();
-     ranges.add(new int[] { 1, 5 });
-     ranges.add(new int[] { 1, 6 });
-     merged = MapList.coalesceRanges(ranges);
-     assertEquals(1, merged.size());
-     assertArrayEquals(new int[] { 1, 6 }, merged.get(0));
-     /*
-      * merge duplicate ranges
-      */
-     ranges.clear();
-     ranges.add(new int[] { 1, 3 });
-     ranges.add(new int[] { 1, 3 });
-     merged = MapList.coalesceRanges(ranges);
-     assertEquals(1, merged.size());
-     assertArrayEquals(new int[] { 1, 3 }, merged.get(0));
-     /*
-      * reverse direction
-      */
+     // 'subsumed' ranges are preserved
      ranges.clear();
-     ranges.add(new int[] { 9, 5 });
-     ranges.add(new int[] { 9, 4 });
-     ranges.add(new int[] { 8, 3 });
-     ranges.add(new int[] { 3, 2 });
-     ranges.add(new int[] { 1, 0 });
+     ranges.add(new int[] { 10, 30 });
+     ranges.add(new int[] { 15, 25 });
      merged = MapList.coalesceRanges(ranges);
-     assertEquals(1, merged.size());
-     assertArrayEquals(new int[] { 9, 0 }, merged.get(0));
+     assertEquals(2, merged.size());
+     assertArrayEquals(new int[] { 10, 30 }, merged.get(0));
+     assertArrayEquals(new int[] { 15, 25 }, merged.get(1));
    }
  
    /**
      /*
       * simple 1:1 plus 1:1 forwards
       */
-     MapList ml1 = new MapList(new int[] { 3, 4, 8, 12 }, new int[] { 5, 8,
-         11, 13 }, 1, 1);
+     MapList ml1 = new MapList(new int[] { 3, 4, 8, 12 },
+             new int[]
+             { 5, 8, 11, 13 }, 1, 1);
      assertEquals("{[3, 4], [8, 12]}", prettyPrint(ml1.getFromRanges()));
      assertEquals("{[5, 8], [11, 13]}", prettyPrint(ml1.getToRanges()));
  
-     MapList ml2 = new MapList(new int[] { 1, 50 }, new int[] { 40, 45, 70,
-         75, 90, 127 }, 1, 1);
+     MapList ml2 = new MapList(new int[] { 1, 50 },
+             new int[]
+             { 40, 45, 70, 75, 90, 127 }, 1, 1);
      assertEquals("{[1, 50]}", prettyPrint(ml2.getFromRanges()));
      assertEquals("{[40, 45], [70, 75], [90, 127]}",
              prettyPrint(ml2.getToRanges()));
       */
      ml1 = new MapList(new int[] { 1, 50 }, new int[] { 70, 119 }, 1, 1);
      ml2 = new MapList(new int[] { 1, 500 },
-             new int[] { 1000, 901, 600, 201 }, 1, 1);
+             new int[]
+             { 1000, 901, 600, 201 }, 1, 1);
      compound = ml1.traverse(ml2);
  
      assertEquals(1, compound.getFromRatio());
      toRanges = compound.getToRanges();
      assertEquals(2, toRanges.size());
      assertArrayEquals(new int[] { 931, 901 }, toRanges.get(0));
-     assertArrayEquals(new int[] { 600, 582 }, toRanges.get(1));
+     assertArrayEquals(new int[] { 600, 582}, toRanges.get(1));
  
      /*
       * 1:1 plus 1:3 should result in 1:3
       */
      ml1 = new MapList(new int[] { 1, 30 }, new int[] { 11, 40 }, 1, 1);
-     ml2 = new MapList(new int[] { 1, 100 }, new int[] { 1, 50, 91, 340 },
-             1, 3);
+     ml2 = new MapList(new int[] { 1, 100 }, new int[] { 1, 50, 91, 340 }, 1,
+             3);
      compound = ml1.traverse(ml2);
  
      assertEquals(1, compound.getFromRatio());
       * 3:1 plus 1:1 should result in 3:1
       */
      ml1 = new MapList(new int[] { 1, 30 }, new int[] { 11, 20 }, 3, 1);
-     ml2 = new MapList(new int[] { 1, 100 }, new int[] { 1, 15, 91, 175 },
-             1, 1);
+     ml2 = new MapList(new int[] { 1, 100 }, new int[] { 1, 15, 91, 175 }, 1,
+             1);
      compound = ml1.traverse(ml2);
  
      assertEquals(3, compound.getFromRatio());
      assertArrayEquals(new int[] { 71, 126 }, toRanges.get(1));
  
      /*
-      * method returns null if not all regions are mapped through
+      * if not all regions are mapped through, returns what is
       */
      ml1 = new MapList(new int[] { 1, 50 }, new int[] { 101, 150 }, 1, 1);
-     ml2 = new MapList(new int[] { 131, 180 }, new int[] { 201, 250 }, 1, 3);
+     ml2 = new MapList(new int[] { 131, 180 }, new int[] { 201, 250 }, 1, 1);
      compound = ml1.traverse(ml2);
      assertNull(compound);
    }
  
    /**
-    * Test that method that inspects for the (first) forward or reverse 'to' range.
-    * Single position ranges are ignored.
+    * Test that method that inspects for the (first) forward or reverse 'to'
+    * range. Single position ranges are ignored.
     */
    @Test(groups = { "Functional" })
    public void testIsToForwardsStrand()
              1);
      assertTrue(ml.isToForwardStrand());
    }
+   /**
+    * Test for mapping with overlapping ranges
+    */
+   @Test(groups = { "Functional" })
+   public void testLocateInFrom_withOverlap()
+   {
+     /*
+      * gene to protein...
+      */
+     int[] codons = new int[] { 1, 12, 12, 17 };
+     int[] protein = new int[] { 1, 6 };
+     MapList ml = new MapList(codons, protein, 3, 1);
+     assertEquals("[1, 3]", Arrays.toString(ml.locateInFrom(1, 1)));
+     assertEquals("[4, 6]", Arrays.toString(ml.locateInFrom(2, 2)));
+     assertEquals("[7, 9]", Arrays.toString(ml.locateInFrom(3, 3)));
+     assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
+     assertEquals("[12, 14]", Arrays.toString(ml.locateInFrom(5, 5)));
+     assertEquals("[15, 17]", Arrays.toString(ml.locateInFrom(6, 6)));
+     assertEquals("[1, 6]", Arrays.toString(ml.locateInFrom(1, 2)));
+     assertEquals("[1, 9]", Arrays.toString(ml.locateInFrom(1, 3)));
+     assertEquals("[1, 12]", Arrays.toString(ml.locateInFrom(1, 4)));
+     assertEquals("[1, 12, 12, 14]", Arrays.toString(ml.locateInFrom(1, 5)));
+     assertEquals("[1, 12, 12, 17]", Arrays.toString(ml.locateInFrom(1, 6)));
+     assertEquals("[4, 9]", Arrays.toString(ml.locateInFrom(2, 3)));
+     assertEquals("[7, 12, 12, 17]", Arrays.toString(ml.locateInFrom(3, 6)));
+     /*
+      * partial overlap of range
+      */
+     assertEquals("[4, 12, 12, 17]", Arrays.toString(ml.locateInFrom(2, 7)));
+     assertEquals("[1, 3]", Arrays.toString(ml.locateInFrom(-1, 1)));
+     /*
+      * no overlap in range
+      */
+     assertNull(ml.locateInFrom(0, 0));
+     /*
+      * gene to CDS...from EMBL:MN908947
+      */
+     int[] gene = new int[] { 266, 13468, 13468, 21555 };
+     int[] cds = new int[] { 1, 21291 };
+     ml = new MapList(gene, cds, 1, 1);
+     assertEquals("[13468, 13468]",
+             Arrays.toString(ml.locateInFrom(13203, 13203)));
+     assertEquals("[13468, 13468]",
+             Arrays.toString(ml.locateInFrom(13204, 13204)));
+     assertEquals("[13468, 13468, 13468, 13468]",
+             Arrays.toString(ml.locateInFrom(13203, 13204)));
+   }
+   /**
+    * Test for mapping with overlapping ranges
+    */
+   @Test(groups = { "Functional" })
+   public void testLocateInTo_withOverlap()
+   {
+     /*
+      * gene to protein...
+      */
+     int[] codons = new int[] { 1, 12, 12, 17 };
+     int[] protein = new int[] { 1, 6 };
+     MapList ml = new MapList(codons, protein, 3, 1);
+     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 1)));
+     assertEquals("[1, 3]", Arrays.toString(ml.locateInTo(3, 8)));
+     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(2, 11)));
+     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(3, 11)));
+     // we want base 12 to map to both of the amino acids it codes for
+     assertEquals("[4, 5]", Arrays.toString(ml.locateInTo(12, 12)));
+     assertEquals("[4, 5]", Arrays.toString(ml.locateInTo(11, 12)));
+     assertEquals("[4, 6]", Arrays.toString(ml.locateInTo(11, 15)));
+     assertEquals("[6, 6]", Arrays.toString(ml.locateInTo(15, 17)));
+     /*
+      * no overlap
+      */
+     assertNull(ml.locateInTo(0, 0));
+     
+     /*
+      * partial overlap
+      */
+     assertEquals("[1, 6]", Arrays.toString(ml.locateInTo(1, 18)));
+     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(-1, 1)));
+     /*
+      * gene to CDS...from EMBL:MN908947
+      * the base at 13468 is used twice in transcription
+      */
+     int[] gene = new int[] { 266, 13468, 13468, 21555 };
+     int[] cds = new int[] { 1, 21291 };
+     ml = new MapList(gene, cds, 1, 1);
+     assertEquals("[13203, 13204]",
+             Arrays.toString(ml.locateInTo(13468, 13468)));
+     
+     /*
+      * gene to protein
+      * the base at 13468 is in the codon for 4401N and also 4402R
+      */
+     gene = new int[] { 266, 13468, 13468, 21552 }; // stop codon excluded
+     protein = new int[] { 1, 7096 };
+     ml = new MapList(gene, protein, 3, 1);
+     assertEquals("[4401, 4402]",
+             Arrays.toString(ml.locateInTo(13468, 13468)));
+   }
+   @Test(groups = { "Functional" })
+   public void testTraverseToPosition()
+   {
+     List<int[]> ranges = new ArrayList<>();
+     assertNull(MapList.traverseToPosition(ranges, 0));
+     ranges.add(new int[] { 3, 6 });
+     assertNull(MapList.traverseToPosition(ranges, 0));
+   }
+   @Test(groups = { "Functional" })
+   public void testCountPositions()
+   {
+     try
+     {
+       MapList.countPositions(null, 1);
+       fail("expected exception");
+     } catch (NullPointerException e)
+     {
+       // expected
+     }
+     List<int[]> intervals = new ArrayList<>();
+     assertNull(MapList.countPositions(intervals, 1));
+     /*
+      * forward strand
+      */
+     intervals.add(new int[] { 10, 20 });
+     assertNull(MapList.countPositions(intervals, 9));
+     assertNull(MapList.countPositions(intervals, 21));
+     assertArrayEquals(new int[] { 1, 1 },
+             MapList.countPositions(intervals, 10));
+     assertArrayEquals(new int[] { 6, 1 },
+             MapList.countPositions(intervals, 15));
+     assertArrayEquals(new int[] { 11, 1 },
+             MapList.countPositions(intervals, 20));
+     intervals.add(new int[] { 25, 25 });
+     assertArrayEquals(new int[] { 12, 1 },
+             MapList.countPositions(intervals, 25));
+     // next interval repeats position 25 - which should be counted twice if
+     // traversed
+     intervals.add(new int[] { 25, 26 });
+     assertArrayEquals(new int[] { 12, 1 },
+             MapList.countPositions(intervals, 25));
+     assertArrayEquals(new int[] { 14, 1 },
+             MapList.countPositions(intervals, 26));
+     /*
+      * reverse strand
+      */
+     intervals.clear();
+     intervals.add(new int[] { 5, -5 });
+     assertNull(MapList.countPositions(intervals, 6));
+     assertNull(MapList.countPositions(intervals, -6));
+     assertArrayEquals(new int[] { 1, -1 },
+             MapList.countPositions(intervals, 5));
+     assertArrayEquals(new int[] { 7, -1 },
+             MapList.countPositions(intervals, -1));
+     assertArrayEquals(new int[] { 11, -1 },
+             MapList.countPositions(intervals, -5));
+     /*
+      * reverse then forward
+      */
+     intervals.add(new int[] { 5, 10 });
+     assertArrayEquals(new int[] { 13, 1 },
+             MapList.countPositions(intervals, 6));
+     /*
+      * reverse then forward then reverse
+      */
+     intervals.add(new int[] { -10, -20 });
+     assertArrayEquals(new int[] { 20, -1 },
+             MapList.countPositions(intervals, -12));
+     /*
+      * an interval [x, x] is treated as forward
+      */
+     intervals.add(new int[] { 30, 30 });
+     assertArrayEquals(new int[] { 29, 1 },
+             MapList.countPositions(intervals, 30));
+     /*
+      * it is the first matched occurrence that is returned
+      */
+     intervals.clear();
+     intervals.add(new int[] { 1, 2 });
+     intervals.add(new int[] { 2, 3 });
+     assertArrayEquals(new int[] { 2, 1 },
+             MapList.countPositions(intervals, 2));
+     intervals.add(new int[] { -1, -2 });
+     intervals.add(new int[] { -2, -3 });
+     assertArrayEquals(new int[] { 6, -1 },
+             MapList.countPositions(intervals, -2));
+   }
+   /**
+    * Tests for helper method that adds any overlap (plus offset) to a set of
+    * overlaps
+    */
+   @Test(groups = { "Functional" })
+   public void testAddOffsetPositions()
+   {
+     List<int[]> mapped = new ArrayList<>();
+     int[] range = new int[] {10, 20};
+     BitSet offsets = new BitSet();
+     MapList.addOffsetPositions(mapped, 0, range, offsets);
+     assertTrue(mapped.isEmpty()); // nothing marked for overlap
+     offsets.set(11);
+     MapList.addOffsetPositions(mapped, 0, range, offsets);
+     assertTrue(mapped.isEmpty()); // no offset 11 in range
+     offsets.set(4, 6); // this sets bits 4 and 5
+     MapList.addOffsetPositions(mapped, 0, range, offsets);
+     assertEquals(1, mapped.size());
+     assertArrayEquals(new int[] { 14, 15 }, mapped.get(0));
+     mapped.clear();
+     offsets.set(10);
+     MapList.addOffsetPositions(mapped, 0, range, offsets);
+     assertEquals(2, mapped.size());
+     assertArrayEquals(new int[] { 14, 15 }, mapped.get(0));
+     assertArrayEquals(new int[] { 20, 20 }, mapped.get(1));
+     /*
+      * reverse range
+      */
+     range = new int[] { 20, 10 };
+     mapped.clear();
+     offsets.clear();
+     MapList.addOffsetPositions(mapped, 0, range, offsets);
+     assertTrue(mapped.isEmpty()); // nothing marked for overlap
+     offsets.set(11);
+     MapList.addOffsetPositions(mapped, 0, range, offsets);
+     assertTrue(mapped.isEmpty()); // no offset 11 in range
+     offsets.set(0);
+     offsets.set(10);
+     offsets.set(6, 8); // sets bits 6 and 7
+     MapList.addOffsetPositions(mapped, 0, range, offsets);
+     assertEquals(3, mapped.size());
+     assertArrayEquals(new int[] { 20, 20 }, mapped.get(0));
+     assertArrayEquals(new int[] { 14, 13 }, mapped.get(1));
+     assertArrayEquals(new int[] { 10, 10 }, mapped.get(2));
+   }
+   
+   @Test(groups = { "Functional" })
+   public void testGetPositionsForOffsets()
+   {
+     List<int[]> ranges = new ArrayList<>();
+     BitSet offsets = new BitSet();
+     List<int[]> mapped = MapList.getPositionsForOffsets(ranges, offsets);
+     assertTrue(mapped.isEmpty()); // no ranges and no offsets!
+     
+     offsets.set(5, 1000);
+     mapped = MapList.getPositionsForOffsets(ranges, offsets);
+     assertTrue(mapped.isEmpty()); // no ranges
+     
+     /*
+      * one range with overlap of offsets
+      */
+     ranges.add(new int[] {15, 25});
+     mapped = MapList.getPositionsForOffsets(ranges, offsets);
+     assertEquals(1, mapped.size());
+     assertArrayEquals(new int[] {20,  25}, mapped.get(0));
+     
+     /*
+      * two ranges
+      */
+     ranges.add(new int[] {300, 320});
+     mapped = MapList.getPositionsForOffsets(ranges, offsets);
+     assertEquals(2, mapped.size());
+     assertArrayEquals(new int[] {20,  25}, mapped.get(0));
+     assertArrayEquals(new int[] {300, 320}, mapped.get(1));
+     
+     /*
+      * boundary case - right end of first range overlaps
+      */
+     offsets.clear();
+     offsets.set(10);
+     mapped = MapList.getPositionsForOffsets(ranges, offsets);
+     assertEquals(1, mapped.size());
+     assertArrayEquals(new int[] {25,  25}, mapped.get(0));
+     
+     /*
+      * boundary case - left end of second range overlaps
+      */
+     offsets.set(11);
+     mapped = MapList.getPositionsForOffsets(ranges, offsets);
+     assertEquals(2, mapped.size());
+     assertArrayEquals(new int[] {25,  25}, mapped.get(0));
+     assertArrayEquals(new int[] {300, 300}, mapped.get(1));
+     
+     /*
+      * offsets into a circular range are reported in
+      * the order in which they are traversed
+      */
+     ranges.clear();
+     ranges.add(new int[] {100, 150});
+     ranges.add(new int[] {60, 80});
+     offsets.clear();
+     offsets.set(45, 55); // sets bits 45 to 54
+     mapped = MapList.getPositionsForOffsets(ranges, offsets);
+     assertEquals(2, mapped.size());
+     assertArrayEquals(new int[] {145, 150}, mapped.get(0)); // offsets 45-50
+     assertArrayEquals(new int[] {60, 63}, mapped.get(1)); // offsets 51-54
+     /*
+      * reverse range overlap is reported with start < end
+      */
+     ranges.clear();
+     ranges.add(new int[] {4321, 4000});
+     offsets.clear();
+     offsets.set(20, 22); // sets bits 20 and 21
+     offsets.set(30);
+     mapped = MapList.getPositionsForOffsets(ranges, offsets);
+     assertEquals(2, mapped.size());
+     assertArrayEquals(new int[] {4301, 4300}, mapped.get(0));
+     assertArrayEquals(new int[] {4291, 4291}, mapped.get(1));
+   }
+   
+   @Test(groups = { "Functional" })
+   public void testGetMappedOffsetsForPositions()
+   {
+     /*
+      * start by verifying the examples in the method's Javadoc!
+      */
+     List<int[]> ranges = new ArrayList<>();
+     ranges.add(new int[] {10, 20});
+     ranges.add(new int[] {31, 40});
+     BitSet overlaps = MapList.getMappedOffsetsForPositions(1, 9, ranges, 1, 1);
+     assertTrue(overlaps.isEmpty());
+     overlaps = MapList.getMappedOffsetsForPositions(1, 11, ranges, 1, 1);
+     assertEquals(2, overlaps.cardinality());
+     assertTrue(overlaps.get(0));
+     assertTrue(overlaps.get(1));
+     overlaps = MapList.getMappedOffsetsForPositions(15, 35, ranges, 1, 1);
+     assertEquals(11, overlaps.cardinality());
+     for (int i = 5 ; i <= 11 ; i++)
+     {
+       assertTrue(overlaps.get(i));
+     }
+     
+     ranges.clear();
+     ranges.add(new int[] {1, 200});
+     overlaps = MapList.getMappedOffsetsForPositions(9, 9, ranges, 1, 3);
+     assertEquals(3, overlaps.cardinality());
+     assertTrue(overlaps.get(24));
+     assertTrue(overlaps.get(25));
+     assertTrue(overlaps.get(26));
+     
+     ranges.clear();
+     ranges.add(new int[] {101, 150});
+     ranges.add(new int[] {171, 180});
+     overlaps = MapList.getMappedOffsetsForPositions(101, 102, ranges, 3, 1);
+     assertEquals(1, overlaps.cardinality());
+     assertTrue(overlaps.get(0));
+     overlaps = MapList.getMappedOffsetsForPositions(150, 171, ranges, 3, 1);
+     assertEquals(1, overlaps.cardinality());
+     assertTrue(overlaps.get(16));
+     
+     ranges.clear();
+     ranges.add(new int[] {101, 150});
+     ranges.add(new int[] {21, 30});
+     overlaps = MapList.getMappedOffsetsForPositions(24, 40, ranges, 3, 1);
+     assertEquals(3, overlaps.cardinality());
+     assertTrue(overlaps.get(17));
+     assertTrue(overlaps.get(18));
+     assertTrue(overlaps.get(19));
+     
+     /*
+      * reverse range 1:1 (e.g. reverse strand gene to transcript)
+      */
+     ranges.clear();
+     ranges.add(new int[] {20, 10});
+     overlaps = MapList.getMappedOffsetsForPositions(12, 13, ranges, 1, 1);
+     assertEquals(2, overlaps.cardinality());
+     assertTrue(overlaps.get(7));
+     assertTrue(overlaps.get(8));
+     
+     /*
+      * reverse range 3:1 (e.g. reverse strand gene to peptide)
+      * from EMBL:J03321 to P0CE20
+      */
+     ranges.clear();
+     ranges.add(new int[] {1480, 488});
+     overlaps = MapList.getMappedOffsetsForPositions(1460, 1460, ranges, 3, 1);
+     // 1460 is the end of the 7th codon
+     assertEquals(1, overlaps.cardinality());
+     assertTrue(overlaps.get(6));
+     // add one base (part codon)
+     overlaps = MapList.getMappedOffsetsForPositions(1459, 1460, ranges, 3, 1);
+     assertEquals(2, overlaps.cardinality());
+     assertTrue(overlaps.get(6));
+     assertTrue(overlaps.get(7));
+     // add second base (part codon)
+     overlaps = MapList.getMappedOffsetsForPositions(1458, 1460, ranges, 3, 1);
+     assertEquals(2, overlaps.cardinality());
+     assertTrue(overlaps.get(6));
+     assertTrue(overlaps.get(7));
+     // add third base (whole codon)
+     overlaps = MapList.getMappedOffsetsForPositions(1457, 1460, ranges, 3, 1);
+     assertEquals(2, overlaps.cardinality());
+     assertTrue(overlaps.get(6));
+     assertTrue(overlaps.get(7));
+     // add one more base (part codon)
+     overlaps = MapList.getMappedOffsetsForPositions(1456, 1460, ranges, 3, 1);
+     assertEquals(3, overlaps.cardinality());
+     assertTrue(overlaps.get(6));
+     assertTrue(overlaps.get(7));
+     assertTrue(overlaps.get(8));
+   }
  }
@@@ -24,8 -24,20 +24,20 @@@ import static org.testng.AssertJUnit.as
  import static org.testng.AssertJUnit.assertFalse;
  import static org.testng.AssertJUnit.assertSame;
  import static org.testng.AssertJUnit.assertTrue;
+ import static org.testng.AssertJUnit.fail;
+ import java.awt.Color;
+ import java.io.IOException;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.Iterator;
+ import java.util.List;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.Test;
  
  import jalview.api.AlignViewportI;
+ import jalview.bin.Cache;
  import jalview.commands.EditCommand;
  import jalview.commands.EditCommand.Action;
  import jalview.commands.EditCommand.Edit;
@@@ -46,18 -58,13 +58,14 @@@ import jalview.io.FileFormat
  import jalview.io.FileFormatI;
  import jalview.io.FormatAdapter;
  
- import java.awt.Color;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Iterator;
- import java.util.List;
- import org.testng.annotations.BeforeClass;
- import org.testng.annotations.Test;
 +
  public class MappingUtilsTest
  {
+   @BeforeClass(alwaysRun = true)
+   public void setUp()
+   {
+     Cache.initLogger();
+   }
  
    @BeforeClass(alwaysRun = true)
    public void setUpJvOptionPane()
@@@ -89,8 -96,9 +97,9 @@@
      MapList map = new MapList(new int[] { 5, 10 }, new int[] { 12, 13 }, 3,
              1);
      acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
-     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
-     { acf });
+     List<AlignedCodonFrame> acfList = Arrays
+             .asList(new AlignedCodonFrame[]
+             { acf });
  
      /*
       * Check protein residue 12 maps to codon 5-7, 13 to codon 8-10
       * Map dna bases [6, 8, 9], [11, 13, 115] to protein residues 8 and 9
       */
      AlignedCodonFrame acf = new AlignedCodonFrame();
-     MapList map = new MapList(new int[] { 6, 6, 8, 9, 11, 11, 13, 13, 15,
-         15 }, new int[] { 8, 9 }, 3, 1);
+     MapList map = new MapList(
+             new int[]
+             { 6, 6, 8, 9, 11, 11, 13, 13, 15, 15 }, new int[] { 8, 9 }, 3,
+             1);
      acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
-     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
-     { acf });
+     List<AlignedCodonFrame> acfList = Arrays
+             .asList(new AlignedCodonFrame[]
+             { acf });
  
      /*
       * Check protein residue 8 maps to [6, 8, 9]
      for (int i = 5; i < 18; i++)
      {
        sr = MappingUtils.buildSearchResults(seq1, i, acfList);
-       int residue = (i == 6 || i == 8 || i == 9) ? 8 : (i == 11 || i == 13
-               || i == 15 ? 9 : 0);
+       int residue = (i == 6 || i == 8 || i == 9) ? 8
+               : (i == 11 || i == 13 || i == 15 ? 9 : 0);
        if (residue == 0)
        {
          assertEquals(0, sr.getResults().size());
      MapList map = new MapList(new int[] { 1, 3 }, new int[] { 1, 1 }, 3, 1);
      for (int seq = 0; seq < 3; seq++)
      {
-       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein
-               .getSequenceAt(seq).getDatasetSequence(), map);
+       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(),
+               protein.getSequenceAt(seq).getDatasetSequence(), map);
      }
-     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
-     { acf });
+     List<AlignedCodonFrame> acfList = Arrays
+             .asList(new AlignedCodonFrame[]
+             { acf });
  
      AlignViewportI dnaView = new AlignViewport(cdna);
      AlignViewportI proteinView = new AlignViewport(protein);
    protected AlignmentI loadAlignment(final String data, FileFormatI format)
            throws IOException
    {
-     AlignmentI a = new FormatAdapter().readFile(data,
-             DataSourceType.PASTE, format);
+     AlignmentI a = new FormatAdapter().readFile(data, DataSourceType.PASTE,
+             format);
      a.setDataset(null);
      return a;
    }
      cs.clear();
      colsel.clear();
      colsel.addElement(2);
-     MappingUtils.mapColumnSelection(colsel, hidden, proteinView,
-             dnaView, cs, hs);
+     MappingUtils.mapColumnSelection(colsel, hidden, proteinView, dnaView,
+             cs, hs);
      assertEquals("[]", cs.getSelected().toString());
  
      /*
      cs.clear();
      colsel.clear();
      colsel.addElement(3);
-     MappingUtils.mapColumnSelection(colsel, hidden, proteinView,
-             dnaView, cs, hs);
+     MappingUtils.mapColumnSelection(colsel, hidden, proteinView, dnaView,
+             cs, hs);
      assertEquals("[5, 6, 7, 8, 9, 10]", cs.getSelected().toString());
  
      /*
      colsel.clear();
      colsel.addElement(1);
      colsel.addElement(3);
-     MappingUtils.mapColumnSelection(colsel, hidden, proteinView,
-             dnaView, cs, hs);
-     assertEquals("[0, 1, 2, 3, 5, 6, 7, 8, 9, 10]", cs.getSelected()
-             .toString());
+     MappingUtils.mapColumnSelection(colsel, hidden, proteinView, dnaView,
+             cs, hs);
+     assertEquals("[0, 1, 2, 3, 5, 6, 7, 8, 9, 10]",
+             cs.getSelected().toString());
    }
  
    /**
      // map first dna to first protein seq
      AlignedCodonFrame acf = new AlignedCodonFrame();
      MapList map = new MapList(new int[] { 10, 12, 15, 15, 17, 18 },
-             new int[] { 40, 41 }, 3, 1);
-     acf.addMap(cdna.getSequenceAt(0).getDatasetSequence(), protein
-             .getSequenceAt(0).getDatasetSequence(), map);
+             new int[]
+             { 40, 41 }, 3, 1);
+     acf.addMap(cdna.getSequenceAt(0).getDatasetSequence(),
+             protein.getSequenceAt(0).getDatasetSequence(), map);
  
      // map second dna to second protein seq
-     map = new MapList(new int[] { 20, 20, 22, 23, 24, 26 }, new int[] { 50,
-         51 }, 3, 1);
-     acf.addMap(cdna.getSequenceAt(1).getDatasetSequence(), protein
-             .getSequenceAt(1).getDatasetSequence(), map);
+     map = new MapList(new int[] { 20, 20, 22, 23, 24, 26 },
+             new int[]
+             { 50, 51 }, 3, 1);
+     acf.addMap(cdna.getSequenceAt(1).getDatasetSequence(),
+             protein.getSequenceAt(1).getDatasetSequence(), map);
  
      // map third dna to third protein seq
-     map = new MapList(new int[] { 30, 30, 32, 34, 36, 37 }, new int[] { 60,
-         61 }, 3, 1);
-     acf.addMap(cdna.getSequenceAt(2).getDatasetSequence(), protein
-             .getSequenceAt(2).getDatasetSequence(), map);
-     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
-     { acf });
+     map = new MapList(new int[] { 30, 30, 32, 34, 36, 37 },
+             new int[]
+             { 60, 61 }, 3, 1);
+     acf.addMap(cdna.getSequenceAt(2).getDatasetSequence(),
+             protein.getSequenceAt(2).getDatasetSequence(), map);
+     List<AlignedCodonFrame> acfList = Arrays
+             .asList(new AlignedCodonFrame[]
+             { acf });
  
      dnaView = new AlignViewport(cdna);
      proteinView = new AlignViewport(protein);
    public void testFlattenRanges()
    {
      assertEquals("[1, 2, 3, 4]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 4 })));
-     assertEquals(
-             "[1, 2, 3, 4]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 2, 3,
-                 4 })));
-     assertEquals(
-             "[1, 2, 3, 4]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 1, 2,
-                 2, 3, 3, 4, 4 })));
-     assertEquals(
-             "[1, 2, 3, 4, 7, 8, 9, 12]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 4, 7,
-                 9, 12, 12 })));
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 1, 4 })));
+     assertEquals("[1, 2, 3, 4]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 1, 2, 3, 4 })));
+     assertEquals("[1, 2, 3, 4]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 1, 1, 2, 2, 3, 3, 4, 4 })));
+     assertEquals("[1, 2, 3, 4, 7, 8, 9, 12]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 1, 4, 7, 9, 12, 12 })));
      // trailing unpaired start position is ignored:
-     assertEquals(
-             "[1, 2, 3, 4, 7, 8, 9, 12]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 4, 7,
-                 9, 12, 12, 15 })));
+     assertEquals("[1, 2, 3, 4, 7, 8, 9, 12]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 1, 4, 7, 9, 12, 12, 15 })));
    }
  
    /**
      MapList map = new MapList(new int[] { 1, 6 }, new int[] { 1, 2 }, 3, 1);
      for (int seq = 0; seq < 3; seq++)
      {
-       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein
-               .getSequenceAt(seq).getDatasetSequence(), map);
+       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(),
+               protein.getSequenceAt(seq).getDatasetSequence(), map);
      }
-     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
-     { acf });
+     List<AlignedCodonFrame> acfList = Arrays
+             .asList(new AlignedCodonFrame[]
+             { acf });
  
      AlignViewportI dnaView = new AlignViewport(cdna);
      AlignViewportI proteinView = new AlignViewport(protein);
              FileFormat.Fasta);
      cdna.setDataset(null);
      AlignmentI protein = loadAlignment(
-             ">Seq1\n-KA-S\n>Seq2\n--L-QY\n>Seq3\nQ-V-M\n", FileFormat.Fasta);
+             ">Seq1\n-KA-S\n>Seq2\n--L-QY\n>Seq3\nQ-V-M\n",
+             FileFormat.Fasta);
      protein.setDataset(null);
      AlignedCodonFrame acf = new AlignedCodonFrame();
      MapList map = new MapList(new int[] { 1, 9 }, new int[] { 1, 3 }, 3, 1);
      for (int seq = 0; seq < 3; seq++)
      {
-       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein
-               .getSequenceAt(seq).getDatasetSequence(), map);
+       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(),
+               protein.getSequenceAt(seq).getDatasetSequence(), map);
      }
-     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
-     { acf });
+     List<AlignedCodonFrame> acfList = Arrays
+             .asList(new AlignedCodonFrame[]
+             { acf });
  
      AlignViewportI dnaView = new AlignViewport(cdna);
      AlignViewportI proteinView = new AlignViewport(protein);
      /*
       * Seq1 has three mappings
       */
-     List<AlignedCodonFrame> result = MappingUtils.findMappingsForSequence(
-             seq1, mappings);
+     List<AlignedCodonFrame> result = MappingUtils
+             .findMappingsForSequence(seq1, mappings);
      assertEquals(3, result.size());
      assertTrue(result.contains(acf1));
      assertTrue(result.contains(acf2));
       */
      List<AlignedCodonFrame> result = MappingUtils
              .findMappingsForSequenceAndOthers(null, mappings,
-                     Arrays.asList(new SequenceI[] { seq1, seq2 }));
+                     Arrays.asList(new SequenceI[]
+                     { seq1, seq2 }));
      assertTrue(result.isEmpty());
  
      result = MappingUtils.findMappingsForSequenceAndOthers(seq1, null,
-             Arrays.asList(new SequenceI[] { seq1, seq2 }));
+             Arrays.asList(new SequenceI[]
+             { seq1, seq2 }));
      assertTrue(result.isEmpty());
  
      /*
       * Seq1 has three mappings, but filter argument will only accept
       * those to seq2
       */
-     result = MappingUtils.findMappingsForSequenceAndOthers(
-             seq1,
-             mappings,
-             Arrays.asList(new SequenceI[] { seq1, seq2,
-                 seq1.getDatasetSequence() }));
+     result = MappingUtils.findMappingsForSequenceAndOthers(seq1, mappings,
+             Arrays.asList(new SequenceI[]
+             { seq1, seq2, seq1.getDatasetSequence() }));
      assertEquals(2, result.size());
      assertTrue(result.contains(acf1));
      assertTrue(result.contains(acf2));
      dna.createDatasetSequence();
      protein.createDatasetSequence();
      AlignedCodonFrame acf = new AlignedCodonFrame();
-     MapList map = new MapList(new int[] { 8, 16 }, new int[] { 5, 7 }, 3, 1);
+     MapList map = new MapList(new int[] { 8, 16 }, new int[] { 5, 7 }, 3,
+             1);
      acf.addMap(dna.getDatasetSequence(), protein.getDatasetSequence(), map);
      List<AlignedCodonFrame> mappings = new ArrayList<>();
      mappings.add(acf);
       */
      EditCommand ec = new EditCommand();
      final Edit edit = ec.new Edit(Action.INSERT_GAP,
-             new SequenceI[] { protein }, 4, 2, '-');
+             new SequenceI[]
+             { protein }, 4, 2, '-');
      ec.appendEdit(edit, prot, true, null);
  
      /*
    public void testFlattenRanges_reverseStrand()
    {
      assertEquals("[4, 3, 2, 1]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 1 })));
-     assertEquals(
-             "[4, 3, 2, 1]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 3, 2,
-                 1 })));
-     assertEquals(
-             "[4, 3, 2, 1]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 4, 3,
-                 3, 2, 2, 1, 1 })));
-     assertEquals(
-             "[12, 9, 8, 7, 4, 3, 2, 1]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 12, 12,
-                 9, 7, 4, 1 })));
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 4, 1 })));
+     assertEquals("[4, 3, 2, 1]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 4, 3, 2, 1 })));
+     assertEquals("[4, 3, 2, 1]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 4, 4, 3, 3, 2, 2, 1, 1 })));
+     assertEquals("[12, 9, 8, 7, 4, 3, 2, 1]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 12, 12, 9, 7, 4, 1 })));
      // forwards and backwards anyone?
-     assertEquals(
-             "[4, 5, 6, 3, 2, 1]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 6, 3,
-                 1 })));
+     assertEquals("[4, 5, 6, 3, 2, 1]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 4, 6, 3, 1 })));
      // backwards and forwards
-     assertEquals(
-             "[3, 2, 1, 4, 5, 6]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 3, 1, 4,
-                 6 })));
+     assertEquals("[3, 2, 1, 4, 5, 6]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 3, 1, 4, 6 })));
      // trailing unpaired start position is ignored:
-     assertEquals(
-             "[12, 9, 8, 7, 4, 3, 2]",
-             Arrays.toString(MappingUtils.flattenRanges(new int[] { 12, 12,
-                 9, 7, 4, 2, 1 })));
+     assertEquals("[12, 9, 8, 7, 4, 3, 2]",
+             Arrays.toString(MappingUtils.flattenRanges(new int[]
+             { 12, 12, 9, 7, 4, 2, 1 })));
    }
  
    /**
      /*
       * both forward ranges
       */
-     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         1, 10 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         2, 10 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         1, 9 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         4, 5 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         0, 9 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         -10, -9 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         1, 11 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         11, 12 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 1, 10 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 2, 10 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 1, 9 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 4, 5 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 0, 9 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { -10, -9 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 1, 11 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 11, 12 }));
  
      /*
       * forward range, reverse query
       */
-     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         10, 1 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         9, 1 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         10, 2 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         5, 5 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         11, 1 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
-         10, 0 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 10, 1 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 9, 1 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 10, 2 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 5, 5 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 11, 1 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 10, 0 }));
  
      /*
       * reverse range, forward query
       */
-     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         1, 10 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         1, 9 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         2, 10 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         6, 6 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         6, 11 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         11, 20 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         -3, -2 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 1, 10 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 1, 9 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 2, 10 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 6, 6 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 6, 11 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 11, 20 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { -3, -2 }));
  
      /*
       * both reverse
       */
-     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         10, 1 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         9, 1 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         10, 2 }));
-     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         3, 3 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         11, 1 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         10, 0 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         12, 11 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
-         -5, -8 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 10, 1 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 9, 1 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 10, 2 }));
+     assertTrue(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 3, 3 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 11, 1 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 10, 0 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { 12, 11 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 10, 1 }, new int[] { -5, -8 }));
  
      /*
       * bad arguments
       */
-     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10, 12 },
-             new int[] {
-         1, 10 }));
-     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 },
-             new int[] { 1 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10, 12 }, new int[] { 1, 10 }));
+     assertFalse(
+             MappingUtils.rangeContains(new int[]
+             { 1, 10 }, new int[] { 1 }));
      assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, null));
      assertFalse(MappingUtils.rangeContains(null, new int[] { 1, 10 }));
    }
    }
  
    @Test(groups = "Functional")
+   public void testListToArray()
+   {
+     List<int[]> ranges = new ArrayList<>();
+     int[] result = MappingUtils.rangeListToArray(ranges);
+     assertEquals(result.length, 0);
+     ranges.add(new int[] { 24, 12 });
+     result = MappingUtils.rangeListToArray(ranges);
+     assertEquals(result.length, 2);
+     assertEquals(result[0], 24);
+     assertEquals(result[1], 12);
+     ranges.add(new int[] { -7, 30 });
+     result = MappingUtils.rangeListToArray(ranges);
+     assertEquals(result.length, 4);
+     assertEquals(result[0], 24);
+     assertEquals(result[1], 12);
+     assertEquals(result[2], -7);
+     assertEquals(result[3], 30);
+     try
+     {
+       MappingUtils.rangeListToArray(null);
+       fail("Expected exception");
+     } catch (NullPointerException e)
+     {
+       // expected
+     }
+   }
++
++  // new for 2.12
++  @Test(groups = "Functional")
 +  public void testAddRange()
 +  {
 +    int[] range = { 1, 5 };
 +    List<int[]> ranges = new ArrayList<>();
 +  
 +    // add to empty list:
 +    MappingUtils.addRange(range, ranges);
 +    assertEquals(1, ranges.size());
 +    assertSame(range, ranges.get(0));
 +  
 +    // extend contiguous (same position):
 +    MappingUtils.addRange(new int[] { 5, 10 }, ranges);
 +    assertEquals(1, ranges.size());
 +    assertEquals(1, ranges.get(0)[0]);
 +    assertEquals(10, ranges.get(0)[1]);
 +  
 +    // extend contiguous (next position):
 +    MappingUtils.addRange(new int[] { 11, 15 }, ranges);
 +    assertEquals(1, ranges.size());
 +    assertEquals(1, ranges.get(0)[0]);
 +    assertEquals(15, ranges.get(0)[1]);
 +  
 +    // change direction: range is not merged:
 +    MappingUtils.addRange(new int[] { 16, 10 }, ranges);
 +    assertEquals(2, ranges.size());
 +    assertEquals(16, ranges.get(1)[0]);
 +    assertEquals(10, ranges.get(1)[1]);
 +  
 +    // extend reverse contiguous (same position):
 +    MappingUtils.addRange(new int[] { 10, 8 }, ranges);
 +    assertEquals(2, ranges.size());
 +    assertEquals(16, ranges.get(1)[0]);
 +    assertEquals(8, ranges.get(1)[1]);
 +  
 +    // extend reverse contiguous (next position):
 +    MappingUtils.addRange(new int[] { 7, 6 }, ranges);
 +    assertEquals(2, ranges.size());
 +    assertEquals(16, ranges.get(1)[0]);
 +    assertEquals(6, ranges.get(1)[1]);
 +  
 +    // change direction: range is not merged:
 +    MappingUtils.addRange(new int[] { 6, 9 }, ranges);
 +    assertEquals(3, ranges.size());
 +    assertEquals(6, ranges.get(2)[0]);
 +    assertEquals(9, ranges.get(2)[1]);
 +  
 +    // not contiguous: not merged
 +    MappingUtils.addRange(new int[] { 11, 12 }, ranges);
 +    assertEquals(4, ranges.size());
 +    assertEquals(11, ranges.get(3)[0]);
 +    assertEquals(12, ranges.get(3)[1]);
 +  }
  }
   */
  package jalview.ws.gui;
  
+ import java.util.Locale;
 -
  import jalview.bin.Cache;
  import jalview.gui.JvOptionPane;
  import jalview.gui.WsJobParameters;
  import jalview.util.MessageManager;
 +import jalview.ws.api.ServiceWithParameters;
  import jalview.ws.jabaws.JalviewJabawsTestUtils;
  import jalview.ws.jws2.JabaPreset;
  import jalview.ws.jws2.Jws2Discoverer;
  import jalview.ws.jws2.jabaws2.Jws2Instance;
 +import jalview.ws.params.ArgumentI;
 +import jalview.ws.params.ParamDatastoreI;
  
  import java.awt.BorderLayout;
  import java.awt.event.WindowAdapter;
@@@ -61,15 -60,15 +62,15 @@@ public class Jws2ParamVie
    /**
     * which services to test
     */
 -  public static List<String> serviceTests = new ArrayList<String>();
 +  public static List<String> serviceTests = new ArrayList<>();
  
    /**
     * which presets to test for services
     */
 -  public static List<String> presetTests = new ArrayList<String>();
 +  public static List<String> presetTests = new ArrayList<>();
    static
    {
-     serviceTests.add("AAConWS".toLowerCase());
+     serviceTests.add("AAConWS".toLowerCase(Locale.ROOT));
    }
  
    public static Jws2Discoverer disc = null;
    public void testJws2Gui()
    {
      Iterator<String> presetEnum = presetTests.iterator();
 -    for (Jws2Instance service : disc.getServices())
 +    for (ServiceWithParameters _service : disc.getServices())
      {
 +      // This will fail for non-jabaws services
 +      Jws2Instance service = (Jws2Instance) _service;
        if (serviceTests.size() == 0
-               || serviceTests.contains(service.getName().toLowerCase()))
+               || serviceTests.contains(service.serviceType.toLowerCase(Locale.ROOT)))
        {
          List<Preset> prl = null;
          Preset pr = null;
              }
              pr = en.next();
            }
 -          WsJobParameters pgui = new WsJobParameters(service,
 -                  new JabaPreset(service, pr));
 -          JFrame jf = new JFrame(MessageManager.formatMessage(
 -                  "label.ws_parameters_for",
 -                  new String[] { service.getActionText() }));
 +          WsJobParameters pgui = new WsJobParameters((ParamDatastoreI) null,
 +                  service, new JabaPreset(service, pr),
 +                  (List<ArgumentI>) null);
 +          JFrame jf = new JFrame(MessageManager
 +                  .formatMessage("label.ws_parameters_for", new String[]
 +                  { service.getActionText() }));
            jf.setSize(700, 800);
            JPanel cont = new JPanel(new BorderLayout());
            pgui.validate();
@@@ -20,6 -20,8 +20,7 @@@
   */
  package jalview.ws.jabaws;
  
+ import java.util.Locale;
 -
  import static org.testng.AssertJUnit.assertNotNull;
  import static org.testng.AssertJUnit.assertTrue;
  
@@@ -32,10 -34,9 +33,10 @@@ import jalview.io.DataSourceType
  import jalview.io.FileFormat;
  import jalview.io.FormatAdapter;
  import jalview.io.StockholmFileTest;
 -import jalview.ws.jws2.AADisorderClient;
 +import jalview.ws.api.ServiceWithParameters;
  import jalview.ws.jws2.Jws2Discoverer;
 -import jalview.ws.jws2.jabaws2.Jws2Instance;
 +import jalview.ws.jws2.SeqAnnotationServiceCalcWorker;
 +import jalview.ws.slivkaws.SlivkaWSDiscoverer;
  
  import java.util.ArrayList;
  import java.util.List;
@@@ -64,9 -65,9 +65,9 @@@ public class DisorderAnnotExportImpor
  
    public static Jws2Discoverer disc;
  
 -  public static List<Jws2Instance> iupreds;
 +  public static List<ServiceWithParameters> iupreds;
  
 -  jalview.ws.jws2.AADisorderClient disorderClient;
 +  jalview.ws.jws2.SeqAnnotationServiceCalcWorker disorderClient;
  
    public static jalview.gui.AlignFrame af = null;
  
        // don't get services until discoverer has finished
        Thread.sleep(100);
      }
 -    iupreds = new ArrayList<Jws2Instance>();
 -    for (Jws2Instance svc : disc.getServices())
 +    SlivkaWSDiscoverer disc2 = SlivkaWSDiscoverer.getInstance();
 +    disc2.startDiscoverer();
 +    while (disc2.isRunning())
      {
-       Thread.sleep(100);
-     }
-     iupreds = new ArrayList<>();
-     for (ServiceWithParameters svc : disc2.getServices())
-     {
-       if (svc.getNameURI().toLowerCase().contains("iupred"))
+       if (svc.getServiceTypeURI().toLowerCase(Locale.ROOT).contains("iupredws"))
        {
          iupreds.add(svc);
        }
    @Test(groups = { "External", "Network" })
    public void testDisorderAnnotExport()
    {
 -    disorderClient = new AADisorderClient(iupreds.get(0), af, null, null);
 +    disorderClient = new SeqAnnotationServiceCalcWorker(iupreds.get(0), af, null,
 +            null);
      af.getViewport().getCalcManager().startWorker(disorderClient);
      do
      {
      AlignmentI orig_alig = af.getViewport().getAlignment();
      // NOTE: Consensus annotation row cannot be exported and reimported
      // faithfully - so we remove them
 -    List<AlignmentAnnotation> toremove = new ArrayList<AlignmentAnnotation>();
 +    List<AlignmentAnnotation> toremove = new ArrayList<>();
      for (AlignmentAnnotation aa : orig_alig.getAlignmentAnnotation())
      {
        if (aa.autoCalculated)
@@@ -20,6 -20,8 +20,7 @@@
   */
  package jalview.ws.jabaws;
  
+ import java.util.Locale;
 -
  import static org.testng.AssertJUnit.assertNotNull;
  import static org.testng.AssertJUnit.assertTrue;
  
@@@ -33,10 -35,8 +34,10 @@@ import jalview.io.FileFormat
  import jalview.io.FormatAdapter;
  import jalview.io.StockholmFileTest;
  import jalview.project.Jalview2XML;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.jws2.JabaParamStore;
  import jalview.ws.jws2.Jws2Discoverer;
 -import jalview.ws.jws2.RNAalifoldClient;
 +import jalview.ws.jws2.SeqAnnotationServiceCalcWorker;
  import jalview.ws.jws2.SequenceAnnotationWSClient;
  import jalview.ws.jws2.jabaws2.Jws2Instance;
  import jalview.ws.params.AutoCalcSetting;
@@@ -80,7 -80,7 +81,7 @@@ public class RNAStructExportImpor
  
    public static Jws2Instance rnaalifoldws;
  
 -  jalview.ws.jws2.RNAalifoldClient alifoldClient;
 +  SeqAnnotationServiceCalcWorker alifoldClient;
  
    public static jalview.gui.AlignFrame af = null;
  
        Thread.sleep(100);
      }
  
 -    for (Jws2Instance svc : disc.getServices())
 +    for (ServiceWithParameters svc : disc.getServices())
      {
  
-       if (svc.getNameURI().toLowerCase().contains("rnaalifoldws"))
+       if (svc.getServiceTypeURI().toLowerCase(Locale.ROOT).contains("rnaalifoldws"))
        {
 -        rnaalifoldws = svc;
 +        rnaalifoldws = (Jws2Instance) svc;
        }
      }
  
    public void testRNAAliFoldValidStructure()
    {
  
 -    alifoldClient = new RNAalifoldClient(rnaalifoldws, af, null, null);
 +    alifoldClient = new SeqAnnotationServiceCalcWorker(rnaalifoldws, af, null,
 +            null);
  
      af.getViewport().getCalcManager().startWorker(alifoldClient);
  
    public void testRNAStructExport()
    {
  
 -    alifoldClient = new RNAalifoldClient(rnaalifoldws, af, null, null);
 +    alifoldClient = new SeqAnnotationServiceCalcWorker(rnaalifoldws, af, null,
 +            null);
  
      af.getViewport().getCalcManager().startWorker(alifoldClient);
  
      AlignmentI orig_alig = af.getViewport().getAlignment();
      // JBPNote: this assert fails (2.10.2) because the 'Reference Positions'
      // annotation is mistakenly recognised as an RNA annotation row when read in
 -    // as an annotation file.
 +    // as an annotation file. bug is JAL-3122
      verifyAnnotationFileIO("Testing RNAalifold Annotation IO", orig_alig);
  
    }
          opts.add(rg);
        }
      }
 -    alifoldClient = new RNAalifoldClient(rnaalifoldws, af, null, opts);
 +    alifoldClient = new SeqAnnotationServiceCalcWorker(rnaalifoldws, af, null,
 +            JabaParamStore.getJwsArgsfromJaba(opts));
  
      af.getViewport().getCalcManager().startWorker(alifoldClient);
  
   */
  package jalview.ws.jws2;
  
+ import java.util.Locale;
 -
  import static org.testng.AssertJUnit.assertEquals;
  import static org.testng.AssertJUnit.assertFalse;
  import static org.testng.AssertJUnit.assertTrue;
  
  import jalview.bin.Cache;
  import jalview.gui.JvOptionPane;
 +import jalview.ws.api.ServiceWithParameters;
 +import jalview.ws.api.UIinfo;
  import jalview.ws.jabaws.JalviewJabawsTestUtils;
  import jalview.ws.jws2.jabaws2.Jws2Instance;
  
@@@ -63,14 -63,14 +64,14 @@@ public class ParameterUtilsTes
     * To limit tests to specify services, add them to this list; leave list empty
     * to test all
     */
 -  private static List<String> serviceTests = new ArrayList<String>();
 +  private static List<String> serviceTests = new ArrayList<>();
  
    private static Jws2Discoverer disc = null;
  
    @BeforeClass(alwaysRun = true)
    public static void setUpBeforeClass() throws Exception
    {
-     serviceTests.add("AAConWS".toLowerCase());
+     serviceTests.add("AAConWS".toLowerCase(Locale.ROOT));
      Cache.loadProperties("test/jalview/io/testProps.jvprops");
      Cache.initLogger();
      disc = JalviewJabawsTestUtils.getJabawsDiscoverer();
    @Test(groups = { "Network" })
    public void testWriteParameterSet() throws WrongParameterException
    {
 -    for (Jws2Instance service : disc.getServices())
 +    for (ServiceWithParameters _service : disc.getServices())
      {
 -      if (isForTesting(service))
 +      if (isForTesting(_service))
        {
 +        Jws2Instance service = (Jws2Instance) _service;
  
          List<Preset> prl = null;
          PresetManager prman = service.getPresets();
     * @param service
     * @return
     */
 -  public boolean isForTesting(Jws2Instance service)
 +  public boolean isForTesting(UIinfo service)
    {
      return serviceTests.size() == 0
-             || serviceTests.contains(service.getName().toLowerCase());
+             || serviceTests.contains(service.serviceType.toLowerCase(Locale.ROOT));
    }
  
    @Test(groups = { "Network" })
    public void testCopyOption()
    {
 -    for (Jws2Instance service : disc.getServices())
 +    for (ServiceWithParameters _service : disc.getServices())
      {
 -      if (isForTesting(service))
 +      if (isForTesting(_service))
        {
 +        Jws2Instance service = (Jws2Instance) _service;
          List<Option<?>> options = service.getRunnerConfig().getOptions();
          for (Option<?> o : options)
          {
    @Test(groups = { "Network" })
    public void testCopyParameter()
    {
 -    for (Jws2Instance service : disc.getServices())
 +    for (ServiceWithParameters _service : disc.getServices())
      {
 -      if (isForTesting(service))
 +      if (isForTesting(_service))
        {
 +        Jws2Instance service = (Jws2Instance) _service;
          List<Parameter> parameters = service.getRunnerConfig()
                  .getParameters();
          for (Parameter o : parameters)